From d6f00d7adf4b57671d3381edf21d136042446793 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 6 Jan 2022 20:23:15 +0800 Subject: [PATCH 001/498] feat: use garbageCollection to replace HostClass. --- bridge/CMakeLists.txt | 3 + bridge/bindings/qjs/bom/location.cc | 23 ++- bridge/bindings/qjs/bom/location.h | 33 +++- bridge/bindings/qjs/bom/window.cc | 131 ++++++++------ bridge/bindings/qjs/bom/window.h | 61 +++---- bridge/bindings/qjs/dom/event_listener_map.cc | 2 +- bridge/bindings/qjs/dom/event_listener_map.h | 2 +- bridge/bindings/qjs/dom/event_target.cc | 170 +++++++----------- bridge/bindings/qjs/dom/event_target.h | 95 +++++----- bridge/bindings/qjs/dom/node.h | 88 ++++----- bridge/bindings/qjs/dom/style_declaration.h | 1 - bridge/bindings/qjs/executing_context.cc | 54 +++++- bridge/bindings/qjs/executing_context.h | 83 +-------- bridge/bindings/qjs/executing_context_data.cc | 74 ++++++++ bridge/bindings/qjs/executing_context_data.h | 39 ++++ bridge/bindings/qjs/garbage_collected.h | 25 ++- bridge/bindings/qjs/host_class.h | 2 +- bridge/bindings/qjs/js_context_macros.h | 25 +-- bridge/bindings/qjs/wrapper_type_info.h | 42 +++++ bridge/page.cc | 2 + 20 files changed, 549 insertions(+), 406 deletions(-) create mode 100644 bridge/bindings/qjs/executing_context_data.cc create mode 100644 bridge/bindings/qjs/executing_context_data.h create mode 100644 bridge/bindings/qjs/wrapper_type_info.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 47e6720566..8a40e69787 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -186,6 +186,9 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/garbage_collected.h bindings/qjs/executing_context.cc bindings/qjs/executing_context.h + bindings/qjs/executing_context_data.cc + bindings/qjs/executing_context_data.h + bindings/qjs/wrapper_type_info.h bindings/qjs/heap_hashmap.h bindings/qjs/native_value.cc bindings/qjs/native_value.h diff --git a/bridge/bindings/qjs/bom/location.cc b/bridge/bindings/qjs/bom/location.cc index f58fc0a44c..40e4f77847 100644 --- a/bridge/bindings/qjs/bom/location.cc +++ b/bridge/bindings/qjs/bom/location.cc @@ -9,6 +9,23 @@ namespace kraken::binding::qjs { +void bindLocation(std::unique_ptr& context) { + auto* contextData = context->contextData(); + JSValue classObject = contextData->constructorForType(&locationTypeInfo); + JSValue prototypeObject = contextData->prototypeForType(&locationTypeInfo); + + // Install methods + INSTALL_FUNCTION(Location, prototypeObject, reload, 0); + + context->defineGlobalProperty("Location", classObject); +} + +JSClassID Location::classId{0}; + +Location* Location::create(JSContext* ctx) { + return makeGarbageCollected()->initialize(ctx, &classId); +} + 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) { @@ -16,9 +33,13 @@ JSValue Location::reload(JSContext* ctx, JSValue this_val, int argc, JSValue* ar } getDartMethod()->flushUICommand(); - getDartMethod()->reloadApp(location->m_context->getContextId()); + getDartMethod()->reloadApp(location->context()->getContextId()); return JS_NULL; } +void Location::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const {} + +void Location::dispose() const {} + } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/location.h b/bridge/bindings/qjs/bom/location.h index f3f87c7300..50e1a99890 100644 --- a/bridge/bindings/qjs/bom/location.h +++ b/bridge/bindings/qjs/bom/location.h @@ -7,19 +7,38 @@ #define KRAKENBRIDGE_LOCATION_H #include "bindings/qjs/executing_context.h" -#include "bindings/qjs/host_object.h" +#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/wrapper_type_info.h" namespace kraken::binding::qjs { -class Location : public HostObject { - public: - Location() = delete; - explicit Location(ExecutionContext* context) : HostObject(context, "Location") {} +void bindLocation(std::unique_ptr& context); +class Location : public GarbageCollected { + public: + static JSClassID classId; + static Location* create(JSContext* ctx); static JSValue reload(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - private: - DEFINE_FUNCTION(reload, 0); + void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; + void dispose() const override; +}; + +auto locationCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + auto* type = static_cast(JS_GetOpaque(func_obj, JSValueGetClassId(func_obj))); + auto* location = Location::create(ctx); + auto* context = static_cast(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(type); + + // Let eventTarget instance inherit EventTarget prototype methods. + JS_SetPrototype(ctx, location->toQuickJS(), prototype); + return location->toQuickJS(); +}; + +const WrapperTypeInfo locationTypeInfo = { + "Location", + nullptr, + locationCreator }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/window.cc b/bridge/bindings/qjs/bom/window.cc index 7c032a7f7c..d83d6af16f 100644 --- a/bridge/bindings/qjs/bom/window.cc +++ b/bridge/bindings/qjs/bom/window.cc @@ -12,36 +12,50 @@ namespace kraken::binding::qjs { -std::once_flag kWindowInitOnceFlag; - void bindWindow(std::unique_ptr& context) { + auto* contextData = context->contextData(); + JSValue classObject = contextData->constructorForType(&windowTypeInfo); + JSValue prototypeObject = contextData->prototypeForType(&windowTypeInfo); + + // Install methods. + INSTALL_FUNCTION(Window, prototypeObject, open, 1); + installFunctionProperty(context.get(), prototypeObject, "scroll", Window::m_scrollTo_, 2); + INSTALL_FUNCTION(Window, prototypeObject, scrollTo, 2); + INSTALL_FUNCTION(Window, prototypeObject, scrollBy, 2); + INSTALL_FUNCTION(Window, prototypeObject, postMessage, 3); + INSTALL_FUNCTION(Window, prototypeObject, requestAnimationFrame, 1); + INSTALL_FUNCTION(Window, prototypeObject, cancelAnimationFrame, 1); + + // Install Getter and Setter properties. + INSTALL_READONLY_PROPERTY(Window, prototypeObject, devicePixelRatio); + INSTALL_READONLY_PROPERTY(Window, prototypeObject, colorScheme); + INSTALL_READONLY_PROPERTY(Window, prototypeObject, __location__); + INSTALL_READONLY_PROPERTY(Window, prototypeObject, location); + INSTALL_READONLY_PROPERTY(Window, prototypeObject, window); + INSTALL_READONLY_PROPERTY(Window, prototypeObject, parent); + INSTALL_READONLY_PROPERTY(Window, prototypeObject, scrollX); + INSTALL_READONLY_PROPERTY(Window, prototypeObject, scrollY); + INSTALL_READONLY_PROPERTY(Window, prototypeObject, self); + + INSTALL_PROPERTY(Window, prototypeObject, onerror); + // Set globalThis and Window's prototype to EventTarget's prototype to support EventTarget methods in global. - auto* windowConstructor = new Window(context.get()); - JS_SetPrototype(context->ctx(), context->global(), windowConstructor->prototype()); - context->defineGlobalProperty("Window", windowConstructor->jsObject); + JS_SetPrototype(context->ctx(), context->global(), prototypeObject); + context->defineGlobalProperty("Window", classObject); - auto* window = new WindowInstance(windowConstructor); + // Hide window instance to global object, to get access to window when get property on globalObject. + auto* window = makeGarbageCollected()->initialize(context->ctx(), &Window::classId); JS_SetOpaque(context->global(), window); - context->defineGlobalProperty("__window__", window->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()); + context->defineGlobalProperty("__window__", window->toQuickJS()); } -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())); +IMPL_FUNCTION(Window, open)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto window = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0])}; return window->callNativeMethods("open", 1, arguments); } -JSValue Window::scrollTo(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +IMPL_FUNCTION(Window, scrollTo)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { #if FLUTTER_BACKEND auto window = static_cast(JS_GetOpaque(this_val, Window::classId())); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0]), jsValueToNativeValue(ctx, argv[1])}; @@ -50,23 +64,24 @@ JSValue Window::scrollTo(JSContext* ctx, JSValue this_val, int argc, JSValue* ar return JS_UNDEFINED; #endif } -JSValue Window::scrollBy(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto window = static_cast(JS_GetOpaque(this_val, Window::classId())); + +IMPL_FUNCTION(Window, scrollBy)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto window = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0]), jsValueToNativeValue(ctx, argv[1])}; return window->callNativeMethods("scrollBy", 2, arguments); } -JSValue Window::postMessage(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(Window, postMessage)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { JSValue messageValue = argv[0]; JSValue originValue = argv[1]; JSValue globalObjectValue = JS_GetGlobalObject(ctx); - auto* window = static_cast(JS_GetOpaque(globalObjectValue, Window::classId())); + auto* window = static_cast(JS_GetOpaque(globalObjectValue, JSValueGetClassId(this_val))); JSValue messageEventInitValue = JS_NewObject(ctx); JS_SetPropertyStr(ctx, messageEventInitValue, "data", JS_DupValue(ctx, messageValue)); JS_SetPropertyStr(ctx, originValue, "origin", JS_DupValue(ctx, originValue)); - JSValue messageEventValue = JS_CallConstructor(ctx, MessageEvent::instance(window->m_context)->jsObject, 1, &messageEventInitValue); + JSValue messageEventValue = JS_CallConstructor(ctx, MessageEvent::instance(window->context())->jsObject, 1, &messageEventInitValue); auto* event = static_cast(JS_GetOpaque(messageEventValue, Event::kEventClassID)); window->dispatchEvent(event); @@ -76,13 +91,13 @@ JSValue Window::postMessage(JSContext* ctx, JSValue this_val, int argc, JSValue* return JS_NULL; } -JSValue Window::requestAnimationFrame(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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())); + auto window = static_cast(JS_GetOpaque(context->global(), JSValueGetClassId(this_val))); JSValue callbackValue = argv[0]; @@ -107,7 +122,7 @@ JSValue Window::requestAnimationFrame(JSContext* ctx, JSValue this_val, int argc } #endif - auto* frameCallback = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(ctx, &FrameCallback::classId); + auto* frameCallback = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(ctx, &FrameCallback::classId); int32_t requestId = window->document()->requestAnimationFrame(frameCallback); @@ -121,13 +136,13 @@ JSValue Window::requestAnimationFrame(JSContext* ctx, JSValue this_val, int argc return JS_NewUint32(ctx, requestId); } -JSValue Window::cancelAnimationFrame(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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())); + auto window = static_cast(JS_GetOpaque(context->global(), JSValueGetClassId(this_val))); JSValue requestIdValue = argv[0]; if (!JS_IsNumber(requestIdValue)) { @@ -146,6 +161,27 @@ JSValue Window::cancelAnimationFrame(JSContext* ctx, JSValue this_val, int argc, return JS_NULL; } +Window* Window::create(JSContext* ctx) { + return makeGarbageCollected()->initialize(ctx, &classId, nullptr); +} + +DocumentInstance* Window::document() { + return context()->document(); +} + +Window::Window() { + if (getDartMethod()->initWindow != nullptr) { + getDartMethod()->initWindow(context()->getContextId(), nativeEventTarget); + } + + m_location = makeGarbageCollected()->initialize(m_ctx, &Location::classId); +} + +void Window::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + EventTarget::trace(rt, val, mark_func); + JS_MarkValue(rt, onerror, mark_func); +} + IMPL_PROPERTY_GETTER(Window, devicePixelRatio)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (getDartMethod()->devicePixelRatio == nullptr) { return JS_ThrowTypeError(ctx, "Failed to read devicePixelRatio: dart method (devicePixelRatio) is not register."); @@ -167,15 +203,15 @@ IMPL_PROPERTY_GETTER(Window, colorScheme)(JSContext* ctx, JSValue this_val, int } IMPL_PROPERTY_GETTER(Window, __location__)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); + auto* window = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); if (window == nullptr) return JS_UNDEFINED; - return JS_DupValue(ctx, window->m_location.value()); + return JS_DupValue(ctx, window->m_location->toQuickJS()); } 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"); + auto* window = static_cast(JS_GetOpaque(this_val, 1)); + return JS_GetPropertyStr(ctx, window->context()->global(), "location"); } IMPL_PROPERTY_GETTER(Window, window)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { @@ -187,21 +223,21 @@ IMPL_PROPERTY_GETTER(Window, parent)(JSContext* ctx, JSValue this_val, int argc, } IMPL_PROPERTY_GETTER(Window, scrollX)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); + auto* window = static_cast(JS_GetOpaque(this_val, 1)); return window->callNativeMethods("scrollX", 0, nullptr); } IMPL_PROPERTY_GETTER(Window, scrollY)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); + auto* window = static_cast(JS_GetOpaque(this_val, 1)); return window->callNativeMethods("scrollY", 0, nullptr); } IMPL_PROPERTY_GETTER(Window, onerror)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast(JS_GetOpaque(this_val, 1)); + 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)); + auto* window = static_cast(JS_GetOpaque(this_val, 1)); JSValue eventString = JS_NewString(ctx, "onerror"); JSString* p = JS_VALUE_GET_STRING(eventString); JSValue onerrorHandler = argv[0]; @@ -220,21 +256,4 @@ IMPL_PROPERTY_GETTER(Window, self)(JSContext* ctx, JSValue this_val, int argc, J return JS_GetGlobalObject(ctx); } -WindowInstance::WindowInstance(Window* window) : EventTargetInstance(window, Window::kWindowClassId, "window", WINDOW_TARGET_ID) { - if (getDartMethod()->initWindow != nullptr) { - getDartMethod()->initWindow(context()->getContextId(), nativeEventTarget); - } - m_context->m_window = this; -} - -void WindowInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - EventTargetInstance::trace(rt, val, mark_func); - - JS_MarkValue(rt, onerror, mark_func); -} - -DocumentInstance* WindowInstance::document() { - return m_context->m_document; -} - } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/window.h b/bridge/bindings/qjs/bom/window.h index 4a1cbccfbd..7c65081c00 100644 --- a/bridge/bindings/qjs/bom/window.h +++ b/bridge/bindings/qjs/bom/window.h @@ -9,32 +9,27 @@ #include "bindings/qjs/bom/location.h" #include "bindings/qjs/dom/event_target.h" #include "bindings/qjs/executing_context.h" +#include "bindings/qjs/wrapper_type_info.h" namespace kraken::binding::qjs { void bindWindow(std::unique_ptr& 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(); - Window() = delete; - explicit Window(ExecutionContext* context); + static JSClassID classId; + static Window* create(JSContext* ctx); - OBJECT_INSTANCE(Window); + DEFINE_FUNCTION(open); + DEFINE_FUNCTION(scrollTo); + DEFINE_FUNCTION(scrollBy); + DEFINE_FUNCTION(postMessage); + DEFINE_FUNCTION(requestAnimationFrame); + DEFINE_FUNCTION(cancelAnimationFrame); - private: DEFINE_PROTOTYPE_READONLY_PROPERTY(devicePixelRatio); DEFINE_PROTOTYPE_READONLY_PROPERTY(colorScheme); DEFINE_PROTOTYPE_READONLY_PROPERTY(__location__); @@ -47,34 +42,30 @@ class Window : public EventTarget { 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; -}; - -class WindowInstance : public EventTargetInstance { - public: - WindowInstance() = delete; - explicit WindowInstance(Window* window); - ~WindowInstance() {} + void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; 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}; + Location* m_location{nullptr}; JSValue onerror{JS_NULL}; - friend Window; friend ExecutionContext; }; +auto windowCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + + auto* type = static_cast(JS_GetOpaque(func_obj, JSValueGetClassId(func_obj))); + auto* window = Window::create(ctx); + auto* context = static_cast(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(type); + + // Let eventTarget instance inherit EventTarget prototype methods. + JS_SetPrototype(ctx, window->toQuickJS(), prototype); + return window->toQuickJS(); +}; + +const WrapperTypeInfo windowTypeInfo = {"Window", &eventTargetTypeInfo, windowCreator}; + } // namespace kraken::binding::qjs #endif // KRAKENBRIDGE_WINDOW_H diff --git a/bridge/bindings/qjs/dom/event_listener_map.cc b/bridge/bindings/qjs/dom/event_listener_map.cc index a31cc79692..bfb1bc89d0 100644 --- a/bridge/bindings/qjs/dom/event_listener_map.cc +++ b/bridge/bindings/qjs/dom/event_listener_map.cc @@ -78,7 +78,7 @@ const EventListenerVector* EventListenerMap::find(JSAtom eventType) { return nullptr; } -void EventListenerMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { +void EventListenerMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { for (const auto& entry : m_entries) { for (const auto& vector : entry.second) { JS_MarkValue(rt, vector, mark_func); diff --git a/bridge/bindings/qjs/dom/event_listener_map.h b/bridge/bindings/qjs/dom/event_listener_map.h index edfc165729..e9eddfb5cb 100644 --- a/bridge/bindings/qjs/dom/event_listener_map.h +++ b/bridge/bindings/qjs/dom/event_listener_map.h @@ -26,7 +26,7 @@ class EventListenerMap final { bool remove(JSAtom eventType, JSValue callback); const EventListenerVector* find(JSAtom eventType); - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const; private: // EventListener handlers registered with addEventListener API. diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/bindings/qjs/dom/event_target.cc index c305f35a5f..4e31c96b0b 100644 --- a/bridge/bindings/qjs/dom/event_target.cc +++ b/bridge/bindings/qjs/dom/event_target.cc @@ -17,36 +17,24 @@ namespace kraken::binding::qjs { static std::atomic globalEventTargetId{0}; -std::once_flag kEventTargetInitFlag; #define GetPropertyCallPreFix "_getProperty_" void bindEventTarget(std::unique_ptr& context) { - auto* constructor = EventTarget::instance(context.get()); - // 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); -} + auto* contextData = context->contextData(); + JSValue classObject = contextData->constructorForType(&eventTargetTypeInfo); + JSValue prototypeObject = contextData->prototypeForType(&eventTargetTypeInfo); -JSClassID EventTarget::kEventTargetClassId{0}; + installFunctionProperty(context.get(), prototypeObject, "addEventListener", EventTarget::addEventListener, 3); + installFunctionProperty(context.get(), prototypeObject, "removeEventListener", EventTarget::removeEventListener, 2); + installFunctionProperty(context.get(), prototypeObject, "dispatchEvent", EventTarget::dispatchEvent, 1); -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; + // Set globalThis and Window's prototype to EventTarget's prototype to support EventTarget methods in global. + JS_SetPrototype(context->ctx(), context->global(), classObject); + context->defineGlobalProperty("EventTarget", classObject); } -JSClassID EventTarget::classId(JSValue& value) { - JSClassID classId = JSValueGetClassId(value); - return classId; +EventTarget::EventTarget() : GarbageCollected() { + m_eventTargetId = globalEventTargetId++; } JSValue EventTarget::addEventListener(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { @@ -54,8 +42,8 @@ JSValue EventTarget::addEventListener(JSContext* ctx, JSValue this_val, int argc 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) { + auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + if (eventTarget == nullptr) { return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); } @@ -70,16 +58,14 @@ JSValue EventTarget::addEventListener(JSContext* ctx, JSValue this_val, int argc 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(); - + if (!eventTarget->m_eventListenerMap.contains(eventType) || eventTarget->m_eventHandlerMap.contains(eventType)) { NativeString args_01{}; buildUICommandArgs(ctx, eventTypeValue, args_01); - eventTargetInstance->m_context->uiCommandBuffer()->addCommand(eventTargetInstance->m_eventTargetId, UICommand::addEvent, args_01, nullptr); + eventTarget->context()->uiCommandBuffer()->addCommand(eventTarget->m_eventTargetId, UICommand::addEvent, args_01, nullptr); } - bool success = eventTargetInstance->m_eventListenerMap.add(eventType, JS_DupValue(ctx, callback)); + bool success = eventTarget->m_eventListenerMap.add(eventType, JS_DupValue(ctx, callback)); // Callback didn't saved to eventListenerMap. if (!success) { JS_FreeAtom(ctx, eventType); @@ -94,8 +80,8 @@ JSValue EventTarget::removeEventListener(JSContext* ctx, JSValue this_val, int a 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) { + auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + if (eventTarget == nullptr) { return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); } @@ -107,9 +93,9 @@ JSValue EventTarget::removeEventListener(JSContext* ctx, JSValue this_val, int a } JSAtom eventType = JS_ValueToAtom(ctx, eventTypeValue); - auto& eventHandlers = eventTargetInstance->m_eventListenerMap; + auto& eventHandlers = eventTarget->m_eventListenerMap; - if (!eventTargetInstance->m_eventListenerMap.contains(eventType)) { + if (!eventTarget->m_eventListenerMap.contains(eventType)) { JS_FreeAtom(ctx, eventType); return JS_UNDEFINED; } @@ -119,14 +105,11 @@ JSValue EventTarget::removeEventListener(JSContext* ctx, JSValue this_val, int a JS_FreeValue(ctx, callback); } - if (eventHandlers.empty() && eventTargetInstance->m_eventHandlerMap.contains(eventType)) { - // Dart needs to be notified for handles is empty. - int32_t contextId = eventTargetInstance->prototype()->contextId(); - + if (eventHandlers.empty() && eventTarget->m_eventHandlerMap.contains(eventType)) { NativeString args_01{}; buildUICommandArgs(ctx, eventTypeValue, args_01); - eventTargetInstance->m_context->uiCommandBuffer()->addCommand(eventTargetInstance->m_eventTargetId, UICommand::removeEvent, args_01, nullptr); + eventTarget->context()->uiCommandBuffer()->addCommand(eventTarget->m_eventTargetId, UICommand::removeEvent, args_01, nullptr); } JS_FreeAtom(ctx, eventType); @@ -138,17 +121,21 @@ JSValue EventTarget::dispatchEvent(JSContext* ctx, JSValue this_val, int argc, J 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) { + auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + if (eventTarget == 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))); - return JS_NewBool(ctx, eventTargetInstance->dispatchEvent(eventInstance)); + auto eventInstance = reinterpret_cast(JS_GetOpaque(eventValue, JSValueGetClassId(eventValue))); + return JS_NewBool(ctx, eventTarget->dispatchEvent(eventInstance)); +} + +EventTarget* EventTarget::create(JSContext* ctx) { + return makeGarbageCollected()->initialize(ctx, &EventTarget::classId, nullptr); } -bool EventTargetInstance::dispatchEvent(EventInstance* event) { +bool EventTarget::dispatchEvent(EventInstance* event) { std::u16string u16EventType = std::u16string(reinterpret_cast(event->nativeEvent->type->string), event->nativeEvent->type->length); std::string eventType = toUTF8(u16EventType); @@ -159,15 +146,15 @@ bool EventTargetInstance::dispatchEvent(EventInstance* event) { // Bubble event to root event target. if (event->nativeEvent->bubbles == 1 && !event->propagationStopped()) { - auto node = reinterpret_cast(this); - auto* parent = static_cast(JS_GetOpaque(node->parentNode, Node::classId(node->parentNode))); + auto node = reinterpret_cast(this); + auto* parent = static_cast(JS_GetOpaque(node->parentNode, Node::classId(node->parentNode))); if (parent != nullptr) { parent->dispatchEvent(event); } else { // Window does not inherit from Node, so it is not in the Node tree and needs to continue passing to the Window when it bubbles to Document. - JSValue globalObjectValue = JS_GetGlobalObject(m_context->ctx()); - auto* window = static_cast(JS_GetOpaque(globalObjectValue, Window::classId())); + JSValue globalObjectValue = JS_GetGlobalObject(m_ctx); + auto* window = static_cast(JS_GetOpaque(globalObjectValue, Window::classId())); window->internalDispatchEvent(event); JS_FreeValue(m_ctx, globalObjectValue); } @@ -178,7 +165,7 @@ bool EventTargetInstance::dispatchEvent(EventInstance* event) { return event->cancelled(); } -bool EventTargetInstance::internalDispatchEvent(EventInstance* eventInstance) { +bool EventTarget::internalDispatchEvent(EventInstance* eventInstance) { std::u16string u16EventType = std::u16string(reinterpret_cast(eventInstance->nativeEvent->type->string), eventInstance->nativeEvent->type->length); std::string eventTypeStr = toUTF8(u16EventType); JSAtom eventType = JS_NewAtom(m_ctx, eventTypeStr.c_str()); @@ -202,8 +189,8 @@ bool EventTargetInstance::internalDispatchEvent(EventInstance* eventInstance) { JSValue returnedValue = JS_Call(m_ctx, handler, JS_NULL, 1, &eventInstance->jsObject); JS_FreeValue(m_ctx, handler); - m_context->handleException(&returnedValue); - m_context->drainPendingPromiseJobs(); + context()->handleException(&returnedValue); + context()->drainPendingPromiseJobs(); JS_FreeValue(m_ctx, returnedValue); }; @@ -229,7 +216,7 @@ bool EventTargetInstance::internalDispatchEvent(EventInstance* eventInstance) { JSValue args[]{messageValue, fileNameValue, lineNumberValue, columnValue, error}; JS_Call(m_ctx, handler, eventInstance->jsObject, 5, args); - m_context->drainPendingPromiseJobs(); + context()->drainPendingPromiseJobs(); JS_FreeValue(m_ctx, error); JS_FreeValue(m_ctx, messageValue); @@ -250,34 +237,11 @@ bool EventTargetInstance::internalDispatchEvent(EventInstance* eventInstance) { return eventInstance->cancelled(); } -EventTargetInstance::EventTargetInstance(EventTarget* eventTarget, JSClassID classId, JSClassExoticMethods& exoticMethods, std::string name) - : Instance(eventTarget, name, &exoticMethods, classId, finalize) { - m_eventTargetId = globalEventTargetId++; -} +int EventTarget::hasProperty(JSContext* ctx, JSValue obj, JSAtom atom) { + auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + JSValue prototype = eventTarget->context()->contextData()->prototypeForType(&eventTargetTypeInfo); -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() { - 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)) + if (JS_HasProperty(ctx, prototype, atom)) return true; JSValue atomString = JS_AtomToString(ctx, atom); @@ -292,8 +256,8 @@ int EventTargetInstance::hasProperty(JSContext* ctx, JSValue obj, JSAtom atom) { 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 EventTarget::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); @@ -331,8 +295,8 @@ JSValue EventTargetInstance::getProperty(JSContext* ctx, JSValue obj, JSAtom ato 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))); +int EventTarget::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. @@ -365,7 +329,7 @@ int EventTargetInstance::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, J if (isJavaScriptExtensionElementInstance(eventTarget->context(), eventTarget->jsObject) && !p->is_wide_char && p->u.str8[0] != '_') { std::unique_ptr args_01 = atomToNativeString(ctx, atom); std::unique_ptr args_02 = jsValueToNativeString(ctx, value); - eventTarget->m_context->uiCommandBuffer()->addCommand(eventTarget->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); + eventTarget->context()->uiCommandBuffer()->addCommand(eventTarget->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); } } @@ -374,11 +338,11 @@ int EventTargetInstance::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, J return 0; } -int EventTargetInstance::deleteProperty(JSContext* ctx, JSValue obj, JSAtom prop) { +int EventTarget::deleteProperty(JSContext* ctx, JSValue obj, JSAtom prop) { return 0; } -JSValue EventTargetInstance::callNativeMethods(const char* method, int32_t argc, NativeValue* argv) { +JSValue EventTarget::callNativeMethods(const char* method, int32_t argc, NativeValue* argv) { if (nativeEventTarget->callNativeMethods == nullptr) { return JS_ThrowTypeError(m_ctx, "Failed to call native dart methods: callNativeMethods not initialized."); } @@ -390,11 +354,11 @@ JSValue EventTargetInstance::callNativeMethods(const char* method, int32_t argc, NativeValue nativeValue{}; nativeEventTarget->callNativeMethods(nativeEventTarget, &nativeValue, &m, argc, argv); - JSValue returnValue = nativeValueToJSValue(m_context, nativeValue); + JSValue returnValue = nativeValueToJSValue(context(), nativeValue); return returnValue; } -void EventTargetInstance::setAttributesEventHandler(JSString* p, JSValue value) { +void EventTarget::setAttributesEventHandler(JSString* p, JSValue value) { char eventType[p->len + 1 - 2]; memcpy(eventType, &p->u.str8[2], p->len + 1 - 2); JSAtom atom = JS_NewAtom(m_ctx, eventType); @@ -409,14 +373,13 @@ void EventTargetInstance::setAttributesEventHandler(JSString* p, JSValue value) m_eventHandlerMap.setProperty(atom, JS_DupValue(m_ctx, value)); if (JS_IsFunction(m_ctx, value) && m_eventListenerMap.empty()) { - int32_t contextId = m_context->getContextId(); std::unique_ptr args_01 = atomToNativeString(m_ctx, atom); int32_t type = JS_IsFunction(m_ctx, value) ? UICommand::addEvent : UICommand::removeEvent; - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, type, *args_01, nullptr); + context()->uiCommandBuffer()->addCommand(m_eventTargetId, type, *args_01, nullptr); } } -JSValue EventTargetInstance::getAttributesEventHandler(JSString* p) { +JSValue EventTarget::getAttributesEventHandler(JSString* p) { char eventType[p->len + 1 - 2]; memcpy(eventType, &p->u.str8[2], p->len + 1 - 2); JSAtom atom = JS_NewAtom(m_ctx, eventType); @@ -430,12 +393,7 @@ JSValue EventTargetInstance::getAttributesEventHandler(JSString* p) { return handler; } -void EventTargetInstance::finalize(JSRuntime* rt, JSValue val) { - auto* eventTarget = static_cast(JS_GetOpaque(val, EventTarget::classId(val))); - delete eventTarget; -} - -JSValue EventTargetInstance::getNativeProperty(const char* prop) { +JSValue EventTarget::getNativeProperty(const char* prop) { std::string method = GetPropertyCallPreFix + std::string(prop); getDartMethod()->flushUICommand(); JSValue result = callNativeMethods(method.c_str(), 0, nullptr); @@ -444,7 +402,7 @@ JSValue EventTargetInstance::getNativeProperty(const char* prop) { // 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) { +void EventTarget::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { // Trace m_eventListeners. m_eventListenerMap.trace(rt, JS_UNDEFINED, mark_func); @@ -455,14 +413,20 @@ void EventTargetInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_fu m_properties.trace(rt, JS_UNDEFINED, mark_func); } -void EventTargetInstance::copyNodeProperties(EventTargetInstance* newNode, EventTargetInstance* referenceNode) { +void EventTarget::dispose() const { + context()->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::disposeEventTarget, nullptr, false); + getDartMethod()->flushUICommand(); + delete nativeEventTarget; +} + +void EventTarget::copyNodeProperties(EventTarget* newNode, EventTarget* referenceNode) { referenceNode->m_properties.copyWith(&newNode->m_properties); } void NativeEventTarget::dispatchEventImpl(NativeEventTarget* nativeEventTarget, NativeString* nativeEventType, void* rawEvent, int32_t isCustomEvent) { assert_m(nativeEventTarget->instance != nullptr, "NativeEventTarget should have owner"); - EventTargetInstance* eventTargetInstance = nativeEventTarget->instance; - ExecutionContext* context = eventTargetInstance->context(); + EventTarget* eventTarget = nativeEventTarget->instance; + ExecutionContext* context = eventTarget->context(); std::u16string u16EventType = std::u16string(reinterpret_cast(nativeEventType->string), nativeEventType->length); std::string eventType = toUTF8(u16EventType); auto* raw = static_cast(rawEvent); @@ -470,8 +434,8 @@ void NativeEventTarget::dispatchEventImpl(NativeEventTarget* nativeEventTarget, // 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); - eventInstance->nativeEvent->target = eventTargetInstance; - eventTargetInstance->dispatchEvent(eventInstance); + eventInstance->nativeEvent->target = eventTarget; + eventTarget->dispatchEvent(eventInstance); JS_FreeValue(context->ctx(), eventInstance->jsObject); } diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/bindings/qjs/dom/event_target.h index f35fc0acbe..fd6064c816 100644 --- a/bridge/bindings/qjs/dom/event_target.h +++ b/bridge/bindings/qjs/dom/event_target.h @@ -8,9 +8,8 @@ #include "bindings/qjs/dom/event.h" #include "bindings/qjs/executing_context.h" +#include "bindings/qjs/garbage_collected.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" @@ -21,46 +20,22 @@ void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int namespace kraken::binding::qjs { -class EventTargetInstance; +class EventTarget; class NativeEventTarget; class CSSStyleDeclaration; class StyleDeclarationInstance; void bindEventTarget(std::unique_ptr& 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 = void (*)(NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); using CallNativeMethods = void (*)(void* nativePtr, NativeValue* returnValue, NativeString* method, int32_t argc, NativeValue* argv); struct NativeEventTarget { NativeEventTarget() = delete; - explicit NativeEventTarget(EventTargetInstance* _instance) : instance(_instance), dispatchEvent(NativeEventTarget::dispatchEventImpl){}; + explicit NativeEventTarget(EventTarget* _instance) : instance(_instance), dispatchEvent(NativeEventTarget::dispatchEventImpl){}; static void dispatchEventImpl(NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); - EventTargetInstance* instance{nullptr}; + EventTarget* instance{nullptr}; NativeDispatchEvent dispatchEvent{nullptr}; #if UNIT_TEST CallNativeMethods callNativeMethods{reinterpret_cast(TEST_callNativeMethod)}; @@ -79,57 +54,69 @@ class EventHandlerMap : public HeapHashMap { EventHandlerMap(JSContext* ctx) : HeapHashMap(ctx){}; }; -class EventTargetInstance : public Instance { +class EventTarget : public GarbageCollected { 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(); + EventTarget(); + static JSClassID classId; + static EventTarget* create(JSContext* ctx); + 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); + + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void dispose() const override; virtual bool dispatchEvent(EventInstance* event); - static inline JSClassID classId(); inline int32_t eventTargetId() const { return m_eventTargetId; } + protected: JSValue callNativeMethods(const char* method, int32_t argc, NativeValue* argv); JSValue getNativeProperty(const char* prop); + // Used for legacy "onEvent" attribute APIs. + void setAttributesEventHandler(JSString* p, JSValue value); + JSValue getAttributesEventHandler(JSString* p); + static void copyNodeProperties(EventTarget* newNode, EventTarget* referenceNode); + NativeEventTarget* nativeEventTarget{new NativeEventTarget(this)}; +private: + 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); + + bool internalDispatchEvent(EventInstance* eventInstance); - protected: int32_t m_eventTargetId; // EventListener handlers registered with addEventListener API. // https://dom.spec.whatwg.org/#concept-event-listener - EventListenerMap m_eventListenerMap{m_ctx}; + EventListenerMap m_eventListenerMap{this->m_ctx}; // EventListener handlers registered with DOM attributes API. // https://html.spec.whatwg.org/C/#event-handler-attributes - EventHandlerMap m_eventHandlerMap{m_ctx}; + EventHandlerMap m_eventHandlerMap{this->m_ctx}; // When javascript code set a property on EventTarget instance, EventTarget::setProperty callback will be called when // property are not defined by Object.defineProperty or setProperty. // We store there values in here. - EventTargetProperties m_properties{m_ctx}; + EventTargetProperties m_properties{this->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); +auto eventTargetCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + auto* type = static_cast(JS_GetOpaque(func_obj, JSValueGetClassId(func_obj))); + auto* eventTarget = EventTarget::create(ctx); + auto* context = static_cast(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(type); - // Used for legacy "onEvent" attribute APIs. - void setAttributesEventHandler(JSString* p, JSValue value); - JSValue getAttributesEventHandler(JSString* p); + // Let eventTarget instance inherit EventTarget prototype methods. + JS_SetPrototype(ctx, eventTarget->toQuickJS(), prototype); - private: - bool internalDispatchEvent(EventInstance* eventInstance); - static void finalize(JSRuntime* rt, JSValue val); - friend EventTarget; - friend StyleDeclarationInstance; + return eventTarget->toQuickJS(); }; +const WrapperTypeInfo eventTargetTypeInfo = {"EventTarget", nullptr, eventTargetCreator}; + } // namespace kraken::binding::qjs #endif // KRAKENBRIDGE_EVENT_TARGET_H diff --git a/bridge/bindings/qjs/dom/node.h b/bridge/bindings/qjs/dom/node.h index ab708d882b..9f388f04fc 100644 --- a/bridge/bindings/qjs/dom/node.h +++ b/bridge/bindings/qjs/dom/node.h @@ -17,21 +17,18 @@ void bindNode(std::unique_ptr& 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 Node; class ElementInstance; class DocumentInstance; class TextNodeInstance; +struct NodeJob { + Node* nodeInstance; + list_head link; +}; + 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); @@ -43,50 +40,12 @@ class Node : public EventTarget { 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(); @@ -102,12 +61,15 @@ class NodeInstance : public EventTargetInstance { 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)}; + +protected: NodeJob nodeLink{this}; void refer(); @@ -117,16 +79,36 @@ class NodeInstance : public EventTargetInstance { virtual void _notifyNodeRemoved(NodeInstance* node); virtual void _notifyNodeInsert(NodeInstance* node); - protected: - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + + +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); - private: DocumentInstance* m_document{nullptr}; - ObjectProperty m_childNodes{m_context, jsObject, "childNodes", childNodes}; + ObjectProperty m_childNodes{context(), jsObject, "childNodes", childNodes}; void ensureDetached(NodeInstance* node); - friend DocumentInstance; - friend Node; + + static void traverseCloneNode(JSContext* ctx, NodeInstance* baseNode, NodeInstance* targetNode); + static JSValue copyNodeValue(JSContext* ctx, NodeInstance* node); friend ElementInstance; + friend TextNodeInstance; }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/style_declaration.h b/bridge/bindings/qjs/dom/style_declaration.h index 56f90c9118..bb575679db 100644 --- a/bridge/bindings/qjs/dom/style_declaration.h +++ b/bridge/bindings/qjs/dom/style_declaration.h @@ -10,7 +10,6 @@ namespace kraken::binding::qjs { -class EventTargetInstance; void bindCSSStyleDeclaration(std::unique_ptr& context); template diff --git a/bridge/bindings/qjs/executing_context.cc b/bridge/bindings/qjs/executing_context.cc index fea22013e0..d5f1dcdaf7 100644 --- a/bridge/bindings/qjs/executing_context.cc +++ b/bridge/bindings/qjs/executing_context.cc @@ -75,7 +75,7 @@ ExecutionContext::ExecutionContext(int32_t contextId, const JSExceptionHandler& JS_SetContextOpaque(m_ctx, this); JS_SetHostPromiseRejectionTracker(m_runtime, promiseRejectTracker, nullptr); - m_gcTracker = makeGarbageCollected()->initialize(m_ctx, &ExecutionContextGCTracker::contextGcTrackerClassId); + m_gcTracker = makeGarbageCollected()->initialize(m_ctx, &ExecutionContextGCTracker::contextGcTrackerClassId); JS_DefinePropertyValueStr(m_ctx, globalObject, "_gc_tracker_", m_gcTracker->toQuickJS(), JS_PROP_NORMAL); runningContexts++; @@ -278,6 +278,10 @@ void ExecutionContext::defineGlobalProperty(const char* prop, JSValue value) { JS_FreeAtom(m_ctx, atom); } +ExecutionContextData* ExecutionContext::contextData() { + return &m_data; +} + 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); @@ -317,6 +321,54 @@ void ExecutionContext::promiseRejectTracker(JSContext* ctx, JSValue promise, JSV context->dispatchGlobalPromiseRejectionEvent(promise, reason); } +void installFunctionProperty(ExecutionContext* context, JSValue 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); +} + +void installPropertyGetterSetter(ExecutionContext* context, JSValue thisObject, const char* 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); + 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); +} + +void installPropertyGetter(ExecutionContext* context, JSValue thisObject, const char* 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); + 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); +} + + DOMTimerCoordinator* ExecutionContext::timers() { return &m_timers; } diff --git a/bridge/bindings/qjs/executing_context.h b/bridge/bindings/qjs/executing_context.h index a31223455b..4afaecc98a 100644 --- a/bridge/bindings/qjs/executing_context.h +++ b/bridge/bindings/qjs/executing_context.h @@ -21,7 +21,9 @@ #include "garbage_collected.h" #include "js_context_macros.h" #include "kraken_foundation.h" +#include "executing_context_data.h" #include "qjs_patch.h" +#include "wrapper_type_info.h" using JSExceptionHandler = std::function; @@ -29,7 +31,6 @@ namespace kraken::binding::qjs { static std::once_flag kinitJSClassIDFlag; -class WindowInstance; class DocumentInstance; class ExecutionContext; struct DOMTimerCallbackContext; @@ -86,6 +87,7 @@ class ExecutionContext { bool handleException(JSValue* exc); 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); // Gets the DOMTimerCoordinator which maintains the "active timer @@ -125,13 +127,12 @@ class ExecutionContext { JSValue globalObject{JS_NULL}; bool ctxInvalid_{false}; JSContext* m_ctx{nullptr}; - friend WindowInstance; - friend DocumentInstance; - WindowInstance* m_window{nullptr}; DocumentInstance* m_document{nullptr}; DOMTimerCoordinator m_timers; ExecutionContextGCTracker* m_gcTracker{nullptr}; + ExecutionContextData m_data{this}; foundation::UICommandBuffer m_commandBuffer{contextId}; + friend DocumentInstance; }; // The read object's method or properties via Proxy, we should redirect this_val from Proxy into target property of @@ -154,76 +155,10 @@ static JSValue handleCallThisOnProxy(JSContext* ctx, JSValueConst this_val, int return result; } -class ObjectProperty { - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectProperty); - - public: - ObjectProperty() = delete; - - // Define a property on object with a getter and setter function. - explicit ObjectProperty(ExecutionContext* context, JSValueConst thisObject, const std::string& property, JSCFunction getterFunction, JSCFunction setterFunction) { - // Getter on jsObject works well with all conditions. - // We create an getter function and define to jsObject directly. - JSAtom propertyKeyAtom = JS_NewAtom(context->ctx(), property.c_str()); - JSValue getter = JS_NewCFunction(context->ctx(), getterFunction, "getter", 0); - JSValue getterProxy = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 0, 0, 1, &getter); - - // Getter on jsObject works well with all conditions. - // We create an getter function and define to jsObject directly. - JSValue setter = JS_NewCFunction(context->ctx(), setterFunction, "setter", 0); - JSValue setterProxy = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 1, 0, 1, &setter); - - // Define getter and setter property. - JS_DefinePropertyGetSet(context->ctx(), thisObject, propertyKeyAtom, getterProxy, setterProxy, JS_PROP_NORMAL | JS_PROP_ENUMERABLE); - - JS_FreeAtom(context->ctx(), propertyKeyAtom); - JS_FreeValue(context->ctx(), getter); - JS_FreeValue(context->ctx(), setter); - }; - - explicit ObjectProperty(ExecutionContext* context, JSValueConst thisObject, const std::string& property, JSCFunction getterFunction) { - // Getter on jsObject works well with all conditions. - // We create an getter function and define to jsObject directly. - JSAtom propertyKeyAtom = JS_NewAtom(context->ctx(), property.c_str()); - JSValue getter = JS_NewCFunction(context->ctx(), getterFunction, "getter", 0); - JSValue getterProxy = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, 0, 0, 1, &getter); - JS_DefinePropertyGetSet(context->ctx(), thisObject, propertyKeyAtom, getterProxy, JS_UNDEFINED, JS_PROP_NORMAL | JS_PROP_ENUMERABLE); - JS_FreeAtom(context->ctx(), propertyKeyAtom); - JS_FreeValue(context->ctx(), getter); - }; - - // Define an property on object with a JSValue. - explicit ObjectProperty(ExecutionContext* context, JSValueConst thisObject, const char* property, JSValue value) : m_value(value) { - JS_DefinePropertyValueStr(context->ctx(), thisObject, property, value, JS_PROP_ENUMERABLE); - } - - JSValue value() const { return m_value; } - - private: - JSValue m_value{JS_NULL}; -}; - -class ObjectFunction { - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectFunction); - - public: - ObjectFunction() = delete; - explicit ObjectFunction(ExecutionContext* context, JSValueConst thisObject, const char* functionName, JSCFunction function, int argc) { - JSValue f = JS_NewCFunction(context->ctx(), function, functionName, argc); - JSValue pf = JS_NewCFunctionData(context->ctx(), handleCallThisOnProxy, argc, 0, 1, &f); - JSAtom key = JS_NewAtom(context->ctx(), functionName); - - JS_FreeValue(context->ctx(), f); - -// We should avoid overwrite exist property functions. -#ifdef DEBUG - assert_m(JS_HasProperty(context->ctx(), thisObject, key) == 0, (std::string("Found exist function property: ") + std::string(functionName)).c_str()); -#endif - - JS_DefinePropertyValue(context->ctx(), thisObject, key, pf, JS_PROP_ENUMERABLE); - JS_FreeAtom(context->ctx(), key); - }; -}; +// Property define helpers +void installFunctionProperty(ExecutionContext* context, JSValueConst thisObject, const char* functionName, JSCFunction function, int argc); +void installPropertyGetterSetter(ExecutionContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction, JSCFunction setterFunction); +void installPropertyGetter(ExecutionContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction); class JSValueHolder { public: diff --git a/bridge/bindings/qjs/executing_context_data.cc b/bridge/bindings/qjs/executing_context_data.cc new file mode 100644 index 0000000000..0007e650e3 --- /dev/null +++ b/bridge/bindings/qjs/executing_context_data.cc @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + + +#include "executing_context_data.h" + +namespace kraken::binding::qjs { + +JSValue ExecutionContextData::constructorForType(const WrapperTypeInfo* type) { + auto it = m_constructorMap.find(type); + return it != m_constructorMap.end() ? it->second : constructorForIdSlowCase(type); +} + +JSValue ExecutionContextData::prototypeForType(const WrapperTypeInfo* type) { + auto it = m_prototypeMap.find(type); + + // Constructor not initialized, create it. + if (it == m_prototypeMap.end()) { + constructorForIdSlowCase(type); + it = m_prototypeMap.find(type); + } + + return it != m_prototypeMap.end() ? it->second : JS_NULL; +} + +JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* type) { + JSRuntime* runtime = m_context.runtime(); + JSContext* ctx = m_context.ctx(); + + assert(type->classId == 0 || !JS_HasClassId(runtime, type->classId)); + + // Allocate a new unique classID from QuickJS. + JS_NewClassID(const_cast(&type->classId)); + + // Create class template for behavior. + JSClassDef def{}; + def.class_name = type->className; + def.call = type->callFunc; + JS_NewClass(m_context.runtime(), type->classId, &def); + + // Create class object and prototype object. + JSValue classObject = m_constructorMap[type] = JS_NewObjectClass(m_context.ctx(), type->classId); + JSValue prototypeObject = m_prototypeMap[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(m_prototypeMap.count(type->parent_class) > 0); + JS_SetPrototype(m_context.ctx(), prototypeObject, m_prototypeMap[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; +} + +} diff --git a/bridge/bindings/qjs/executing_context_data.h b/bridge/bindings/qjs/executing_context_data.h new file mode 100644 index 0000000000..16e2c8171f --- /dev/null +++ b/bridge/bindings/qjs/executing_context_data.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CONTEXT_DATA_H +#define KRAKENBRIDGE_CONTEXT_DATA_H + +#include +#include "executing_context.h" +#include "wrapper_type_info.h" + +namespace kraken::binding::qjs { + +// 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(ExecutionContext& 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); + + private: + JSValue constructorForIdSlowCase(const WrapperTypeInfo* type); + std::unordered_map m_constructorMap; + std::unordered_map m_prototypeMap; + + ExecutionContext& m_context; +}; + + +} + +#endif // KRAKENBRIDGE_CONTEXT_DATA_H diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index 768adfbfa8..6fce87e47d 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -34,7 +34,8 @@ class GarbageCollected { public: using ParentMostGarbageCollectedType = T; - virtual T* initialize(JSContext* ctx, JSClassID* classId); + template P* initialize(JSContext* ctx, JSClassID* classId); + template P* initialize(JSContext* ctx, JSClassID* classId, JSClassExoticMethods* exoticMethods); // Must use MakeGarbageCollected. void* operator new(size_t) = delete; @@ -67,9 +68,7 @@ class GarbageCollected { 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; + FORCE_INLINE ExecutionContext* context() const { return static_cast(JS_GetContextOpaque(m_ctx)); }; protected: JSValue jsObject{JS_NULL}; @@ -92,7 +91,7 @@ class MakeGarbageCollectedTrait { }; template -T* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId) { +template P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId, JSClassExoticMethods* exoticMethods) { 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. @@ -109,14 +108,19 @@ T* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId) { /// 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))); + auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); object->trace(rt, val, mark_func); }; + /// Define custom behavior when call getProperty, setProperty on object. + if (exoticMethods != nullptr) { + def.exotic = exoticMethods; + } + /// 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))); + auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); object->dispose(); free(object); }; @@ -133,7 +137,12 @@ T* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId) { m_ctx = ctx; m_runtime = JS_GetRuntime(m_ctx); - return static_cast(this); + return static_cast(this); +} + +template +template P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId) { + return initialize

(ctx, classId, nullptr); } template diff --git a/bridge/bindings/qjs/host_class.h b/bridge/bindings/qjs/host_class.h index 3e07173ced..32d43349c6 100644 --- a/bridge/bindings/qjs/host_class.h +++ b/bridge/bindings/qjs/host_class.h @@ -25,7 +25,7 @@ class HostClass { /// 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. + /// 3. Use JS_NewClass() to initializeAsJSObject your template and you can use your unique JSClassID to create JSObjects. /// 4. Use JS_NewObjectClass() to create your JSObjects. /// Example: /// JSClassID sampleId; diff --git a/bridge/bindings/qjs/js_context_macros.h b/bridge/bindings/qjs/js_context_macros.h index 3701708db4..4351a14b2d 100644 --- a/bridge/bindings/qjs/js_context_macros.h +++ b/bridge/bindings/qjs/js_context_macros.h @@ -23,18 +23,26 @@ #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 { \ @@ -42,14 +50,12 @@ 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 { \ @@ -57,6 +63,5 @@ static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ }; \ - ObjectProperty __##PROPERTY##__ { m_context, jsObject, #PROPERTY, PROPERTY##PropertyDescriptor::getter, PROPERTY##PropertyDescriptor::setter } #endif // KRAKENBRIDGE_JS_CONTEXT_MACROS_H diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h new file mode 100644 index 0000000000..a7bc005cd0 --- /dev/null +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_WRAPPER_TYPE_INFO_H +#define KRAKENBRIDGE_WRAPPER_TYPE_INFO_H + +#include +#include +#include "bindings/qjs/qjs_patch.h" +#include "include/kraken_foundation.h" + +namespace kraken::binding::qjs { + +// 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; + } + + const char* className; + const WrapperTypeInfo* parent_class; + JSClassCall* callFunc; + + JSClassID classId{0}; +}; + +} + +#endif // KRAKENBRIDGE_WRAPPER_TYPE_INFO_H diff --git a/bridge/page.cc b/bridge/page.cc index 81c94dda6c..c24d439888 100644 --- a/bridge/page.cc +++ b/bridge/page.cc @@ -15,6 +15,7 @@ #include "bindings/qjs/bom/performance.h" #include "bindings/qjs/bom/screen.h" #include "bindings/qjs/bom/timer.h" +#include "bindings/qjs/bom/location.h" #include "bindings/qjs/bom/window.h" #include "bindings/qjs/dom/comment_node.h" #include "bindings/qjs/dom/custom_event.h" @@ -69,6 +70,7 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c bindModuleManager(m_context); bindEventTarget(m_context); bindBlob(m_context); + bindLocation(m_context); bindWindow(m_context); bindEvent(m_context); bindCustomEvent(m_context); From 3449e36ded6a30db4267465e9645a08de81aa3dd Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Tue, 11 Jan 2022 21:35:12 +0800 Subject: [PATCH 002/498] refactor: refactor node, element and document. --- bridge/bindings/qjs/bom/window.cc | 12 +- bridge/bindings/qjs/bom/window.h | 9 +- bridge/bindings/qjs/dom/document.cc | 263 +++++++------- bridge/bindings/qjs/dom/document.h | 121 +++---- bridge/bindings/qjs/dom/document_fragment.cc | 31 +- bridge/bindings/qjs/dom/document_fragment.h | 28 +- bridge/bindings/qjs/dom/element.cc | 228 +++++++----- bridge/bindings/qjs/dom/element.h | 121 +++---- bridge/bindings/qjs/dom/event_target.cc | 27 +- bridge/bindings/qjs/dom/event_target.h | 17 +- bridge/bindings/qjs/dom/node.cc | 344 ++++++++++--------- bridge/bindings/qjs/dom/node.h | 117 +++---- bridge/bindings/qjs/dom/text_node.cc | 61 ++-- bridge/bindings/qjs/dom/text_node.h | 45 +-- bridge/bindings/qjs/executing_context.h | 17 +- bridge/bindings/qjs/js_context_macros.h | 8 - 16 files changed, 750 insertions(+), 699 deletions(-) diff --git a/bridge/bindings/qjs/bom/window.cc b/bridge/bindings/qjs/bom/window.cc index d83d6af16f..9e79e35950 100644 --- a/bridge/bindings/qjs/bom/window.cc +++ b/bridge/bindings/qjs/bom/window.cc @@ -162,10 +162,18 @@ IMPL_FUNCTION(Window, cancelAnimationFrame)(JSContext* ctx, JSValue this_val, in } Window* Window::create(JSContext* ctx) { - return makeGarbageCollected()->initialize(ctx, &classId, nullptr); + auto* context = static_cast(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(&windowTypeInfo); + + auto* window = makeGarbageCollected()->initialize(ctx, &classId, nullptr); + + // Let window inherit Window prototype methods. + JS_SetPrototype(ctx, window->toQuickJS(), prototype); + + return window; } -DocumentInstance* Window::document() { +Document* Window::document() { return context()->document(); } diff --git a/bridge/bindings/qjs/bom/window.h b/bridge/bindings/qjs/bom/window.h index 7c65081c00..2d5fe54e7e 100644 --- a/bridge/bindings/qjs/bom/window.h +++ b/bridge/bindings/qjs/bom/window.h @@ -45,7 +45,7 @@ class Window : public EventTarget { void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; private: - DocumentInstance* document(); + Document* document(); Location* m_location{nullptr}; JSValue onerror{JS_NULL}; @@ -53,14 +53,7 @@ class Window : public EventTarget { }; auto windowCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { - - auto* type = static_cast(JS_GetOpaque(func_obj, JSValueGetClassId(func_obj))); auto* window = Window::create(ctx); - auto* context = static_cast(JS_GetContextOpaque(ctx)); - JSValue prototype = context->contextData()->prototypeForType(type); - - // Let eventTarget instance inherit EventTarget prototype methods. - JS_SetPrototype(ctx, window->toQuickJS(), prototype); return window->toQuickJS(); }; diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc index abebe9d9e9..ad55cc3e68 100644 --- a/bridge/bindings/qjs/dom/document.cc +++ b/bridge/bindings/qjs/dom/document.cc @@ -34,21 +34,21 @@ namespace kraken::binding::qjs { -void traverseNode(NodeInstance* node, TraverseHandler handler) { +void traverseNode(Node* node, TraverseHandler handler) { bool shouldExit = handler(node); if (shouldExit) return; - JSContext* ctx = node->context()->ctx(); + JSContext* ctx = node->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))); + auto* nextNode = static_cast(JS_GetOpaque(n, JSValueGetClassId(n))); traverseNode(nextNode, handler); - JS_FreeValue(node->context()->ctx(), n); + JS_FreeValue(node->ctx(), n); } } } @@ -56,88 +56,88 @@ void traverseNode(NodeInstance* node, TraverseHandler handler) { std::once_flag kDocumentInitOnceFlag; void bindDocument(std::unique_ptr& context) { - auto* documentConstructor = Document::instance(context.get()); - context->defineGlobalProperty("Document", documentConstructor->jsObject); - JSValue documentInstance = JS_CallConstructor(context->ctx(), documentConstructor->jsObject, 0, nullptr); - context->defineGlobalProperty("document", documentInstance); + +// auto* documentConstructor = Document::instance(context.get()); +// context->defineGlobalProperty("Document", documentConstructor->jsObject); +// JSValue documentInstance = JS_CallConstructor(context->ctx(), documentConstructor->jsObject, 0, nullptr); +// context->defineGlobalProperty("document", documentInstance); } -JSClassID Document::kDocumentClassID{0}; +JSClassID Document::classId{0}; + +Document* Document::create(JSContext* ctx) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(&eventTargetTypeInfo); + auto* document = makeGarbageCollected()->initialize(ctx, &Document::classId, nullptr); + + JS_SetPrototype(ctx, document->toQuickJS(), prototype); -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()); + return document; +} + +Document::Document() : Node() { 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("object", ObjectElement::instance(m_context)); - defineElement("script", ScriptElement::instance(m_context)); - defineElement("template", TemplateElement::instance(m_context)); +// 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("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)); - }); +// 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) { +IMPL_FUNCTION(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."); } @@ -153,7 +153,7 @@ JSValue Document::createEvent(JSContext* ctx, JSValue this_val, int argc, JSValu std::unique_ptr nativeEventType = jsValueToNativeString(ctx, eventTypeValue); auto nativeEvent = new NativeEvent{nativeEventType.release()}; - auto document = static_cast(JS_GetOpaque(this_val, Document::classId())); + auto document = static_cast(JS_GetOpaque(this_val, Document::classId)); auto e = Event::buildEventInstance(eventType, document->context(), nativeEvent, false); return e->jsObject; } else { @@ -161,7 +161,7 @@ JSValue Document::createEvent(JSContext* ctx, JSValue this_val, int argc, JSValu } } -JSValue Document::createElement(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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."); } @@ -171,7 +171,7 @@ JSValue Document::createElement(JSContext* ctx, JSValue this_val, int argc, JSVa return JS_ThrowTypeError(ctx, "Failed to createElement: tagName should be a string."); } - auto document = static_cast(JS_GetOpaque(this_val, Document::classId())); + 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); @@ -180,33 +180,33 @@ JSValue Document::createElement(JSContext* ctx, JSValue this_val, int argc, JSVa return element; } -JSValue Document::createTextNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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())); + 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())); +IMPL_FUNCTION(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())); +IMPL_FUNCTION(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) { +IMPL_FUNCTION(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())); + auto* document = static_cast(JS_GetOpaque(this_val, Document::classId)); JSValue idValue = argv[0]; if (!JS_IsString(idValue)) @@ -240,14 +240,14 @@ JSValue Document::getElementsByTagName(JSContext* ctx, JSValue this_val, int arg "but only 0 present."); } - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); + 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) { + traverseNode(document, [tagName, &elements](Node* node) { if (node->nodeType == NodeType::ELEMENT_NODE) { auto* element = static_cast(node); if (element->tagName() == tagName || tagName == "*") { @@ -274,11 +274,11 @@ JSValue Document::getElementsByClassName(JSContext* ctx, JSValue this_val, int a 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())); + 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) { + traverseNode(document, [ctx, className, &elements](Node* node) { if (node->nodeType == NodeType::ELEMENT_NODE) { auto element = reinterpret_cast(node); if (element->classNames()->containsAll(className)) { @@ -319,10 +319,10 @@ IMPL_PROPERTY_GETTER(Document, nodeName)(JSContext* ctx, JSValue this_val, int a } 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* document = static_cast(JS_GetOpaque(this_val, Document::classId)); auto all = new AllCollection(document->m_context); - traverseNode(document, [&all](NodeInstance* node) { + traverseNode(document, [&all](Node* node) { all->internalAdd(node, nullptr); return false; }); @@ -332,23 +332,23 @@ IMPL_PROPERTY_GETTER(Document, all)(JSContext* ctx, JSValue this_val, int argc, // 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(); + auto* document = static_cast(JS_GetOpaque(this_val, Document::classId)); + Element* 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(); + auto* document = static_cast(JS_GetOpaque(this_val, Document::classId)); + Element* 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))); + auto* nodeInstance = static_cast(JS_GetOpaque(v, Node::classId(v))); if (nodeInstance->nodeType == NodeType::ELEMENT_NODE) { - auto* elementInstance = static_cast(nodeInstance); + auto* elementInstance = static_cast(nodeInstance); if (elementInstance->tagName() == "HEAD") { head = elementInstance->jsObject; break; @@ -365,8 +365,8 @@ IMPL_PROPERTY_GETTER(Document, head)(JSContext* ctx, JSValue this_val, int argc, // 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(); + auto* document = static_cast(JS_GetOpaque(this_val, Document::classId)); + Element* documentElement = document->getDocumentElement(); JSValue body = JS_NULL; if (documentElement != nullptr) { @@ -375,9 +375,9 @@ IMPL_PROPERTY_GETTER(Document, body)(JSContext* ctx, JSValue this_val, int argc, // 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))); + auto* nodeInstance = static_cast(JS_GetOpaque(v, Node::classId(v))); if (nodeInstance->nodeType == NodeType::ELEMENT_NODE) { - auto* elementInstance = static_cast(nodeInstance); + auto* elementInstance = static_cast(nodeInstance); if (elementInstance->tagName() == "BODY") { body = elementInstance->jsObject; break; @@ -393,8 +393,8 @@ IMPL_PROPERTY_GETTER(Document, body)(JSContext* ctx, JSValue this_val, int argc, // 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(); + auto* document = static_cast(JS_GetOpaque(this_val, Document::classId)); + Element* documentElement = document->getDocumentElement(); // If there is no document element, throw a Exception. if (documentElement == nullptr) { return JS_ThrowInternalError(ctx, "No document element exists"); @@ -403,7 +403,7 @@ IMPL_PROPERTY_SETTER(Document, body)(JSContext* ctx, JSValue this_val, int argc, 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())); + 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"); @@ -414,7 +414,7 @@ IMPL_PROPERTY_SETTER(Document, body)(JSContext* ctx, JSValue this_val, int argc, 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())); + auto* oldElementInstance = static_cast(JS_GetOpaque(oldBody, Element::classId())); documentElement->internalReplaceChild(newElementInstance, oldElementInstance); } } @@ -433,14 +433,14 @@ IMPL_PROPERTY_SETTER(Document, body)(JSContext* ctx, JSValue this_val, int argc, // 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())); + 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))); + 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); @@ -453,12 +453,12 @@ IMPL_PROPERTY_GETTER(Document, children)(JSContext* ctx, JSValue this_val, int a } IMPL_PROPERTY_GETTER(Document, cookie)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); + 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())); + 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; @@ -516,7 +516,7 @@ void DocumentCookie::setCookie(std::string& cookieStr) { cookiePairs[key] = value; } -DocumentInstance::DocumentInstance(Document* document) : NodeInstance(document, NodeType::DOCUMENT_NODE, Document::classId(), "document") { +Document::Document(Document* document) : Node(document, NodeType::DOCUMENT_NODE, Document::classId, "document") { m_context->m_document = this; m_document = this; m_cookie = std::make_unique(); @@ -529,16 +529,9 @@ DocumentInstance::DocumentInstance(Document* document) : NodeInstance(document, #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. - } +Document::~Document() { } -void DocumentInstance::removeElementById(JSAtom id, ElementInstance* element) { +void Document::removeElementById(JSAtom id, Element* element) { if (m_elementMapById.count(id) > 0) { auto& list = m_elementMapById[id]; auto idx = std::find(list.begin(), list.end(), element); @@ -547,9 +540,9 @@ void DocumentInstance::removeElementById(JSAtom id, ElementInstance* element) { JS_FreeValue(m_ctx, element->jsObject); } } -void DocumentInstance::addElementById(JSAtom id, ElementInstance* element) { +void Document::addElementById(JSAtom id, Element* element) { if (m_elementMapById.count(id) == 0) { - m_elementMapById[id] = std::vector(); + m_elementMapById[id] = std::vector(); JS_DupAtom(m_ctx, id); } @@ -562,14 +555,14 @@ void DocumentInstance::addElementById(JSAtom id, ElementInstance* element) { } } -ElementInstance* DocumentInstance::getDocumentElement() { +Element* Document::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))); + auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); if (instance->nodeType == NodeType::ELEMENT_NODE) { - return static_cast(instance); + return static_cast(instance); } JS_FreeValue(m_ctx, v); } @@ -577,16 +570,16 @@ ElementInstance* DocumentInstance::getDocumentElement() { return nullptr; } -int32_t DocumentInstance::requestAnimationFrame(FrameCallback* frameCallback) { +int32_t Document::requestAnimationFrame(FrameCallback* frameCallback) { return m_scriptAnimationController->registerFrameCallback(frameCallback); } -void DocumentInstance::cancelAnimationFrame(uint32_t callbackId) { +void Document::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); +void Document::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + Node::trace(rt, val, mark_func); // Trace scriptAnimationController if (m_scriptAnimationController != nullptr) { JS_MarkValue(rt, m_scriptAnimationController->toQuickJS(), mark_func); @@ -594,9 +587,21 @@ void DocumentInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) // Trace elementByIdMaps for (auto& entry : m_elementMapById) { for (auto& value : entry.second) { - JS_MarkValue(rt, value->jsObject, mark_func); + JS_MarkValue(rt, value->toQuickJS(), mark_func); } } } +void Document::dispose() const { + Node::dispose(); + + // 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_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. + } +} + } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/bindings/qjs/dom/document.h index 161ec71502..b83cc42b17 100644 --- a/bridge/bindings/qjs/dom/document.h +++ b/bridge/bindings/qjs/dom/document.h @@ -15,36 +15,37 @@ namespace kraken::binding::qjs { void bindDocument(std::unique_ptr& context); -using TraverseHandler = std::function; +using TraverseHandler = std::function; -void traverseNode(NodeInstance* node, TraverseHandler handler); +void traverseNode(Node* node, TraverseHandler handler); -class Document : public Node { - public: - static JSClassID kDocumentClassID; - - Document() = delete; - Document(ExecutionContext* context); - - static JSClassID classId(); +class DocumentCookie { +public: + DocumentCookie() = default; - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + std::string getCookie(); + void setCookie(std::string& str); - OBJECT_INSTANCE(Document); +private: + std::unordered_map cookiePairs; +}; - static JSValue createEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createElement(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createTextNode(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createDocumentFragment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue createComment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getElementById(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getElementsByTagName(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue getElementsByClassName(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - JSValue getElementConstructor(ExecutionContext* context, const std::string& tagName); - bool isCustomElement(const std::string& tagName); +class Document : public Node { + public: + static JSClassID classId; + Document* create(JSContext* ctx); + explicit Document(); + + DEFINE_FUNCTION(createEvent); + DEFINE_FUNCTION(createElement); + DEFINE_FUNCTION(createTextNode); + DEFINE_FUNCTION(createDocumentFragment); + DEFINE_FUNCTION(createComment); + DEFINE_FUNCTION(getElementById); + DEFINE_FUNCTION(getElementsByTagName); + DEFINE_FUNCTION(getElementsByClassName); - private: DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); DEFINE_PROTOTYPE_READONLY_PROPERTY(all); DEFINE_PROTOTYPE_READONLY_PROPERTY(documentElement); @@ -54,58 +55,48 @@ class Document : public Node { 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); + JSValue getElementConstructor(ExecutionContext* context, const std::string& tagName); + bool isCustomElement(const std::string& tagName); - void defineElement(const std::string& tagName, Element* constructor); + int32_t requestAnimationFrame(FrameCallback* frameCallback); + void cancelAnimationFrame(uint32_t callbackId); + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void dispose() const override; + + private: + + void removeElementById(JSAtom id, Element* element); + void addElementById(JSAtom id, Element* element); + Element* getDocumentElement(); + std::unordered_map> m_elementMapById; + Element* m_documentElement{nullptr}; + std::unique_ptr m_cookie; + + ScriptAnimationController* m_scriptAnimationController; +// 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); - friend DocumentInstance; + void defineElement(const std::string& tagName, Element* constructor); 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; +auto documentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + return JS_ThrowTypeError(ctx, "Illegal constructor"); }; -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; - - private: - void removeElementById(JSAtom id, ElementInstance* element); - void addElementById(JSAtom id, ElementInstance* element); - ElementInstance* getDocumentElement(); - std::unordered_map> m_elementMapById; - ElementInstance* m_documentElement{nullptr}; - std::unique_ptr m_cookie; - - ScriptAnimationController* m_scriptAnimationController; - - friend Document; - friend ElementInstance; - friend ExecutionContext; +const WrapperTypeInfo documentTypeInfo = { + "Document", + &nodeTypeInfo, + documentCreator }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document_fragment.cc b/bridge/bindings/qjs/dom/document_fragment.cc index 1ebe5a0754..6b14777bba 100644 --- a/bridge/bindings/qjs/dom/document_fragment.cc +++ b/bridge/bindings/qjs/dom/document_fragment.cc @@ -10,29 +10,28 @@ namespace kraken::binding::qjs { void bindDocumentFragment(std::unique_ptr& context) { - auto* constructor = DocumentFragment::instance(context.get()); - context->defineGlobalProperty("DocumentFragment", constructor->jsObject); + JSValue classObject = context->contextData()->constructorForType(&documentFragmentInfo); + context->defineGlobalProperty("DocumentFragment", classObject); } -std::once_flag kDocumentFragmentFlag; +JSValue DocumentFragment::constructor(ExecutionContext* context) { + return context->contextData()->constructorForType(&documentFragmentInfo); +} -JSClassID DocumentFragment::kDocumentFragmentID{0}; +DocumentFragment * DocumentFragment::create(JSContext* ctx) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(&documentFragmentInfo); + auto* documentFragment = makeGarbageCollected()->initialize(ctx, &classId); -DocumentFragment::DocumentFragment(ExecutionContext* context) : Node(context) { - std::call_once(kDocumentFragmentFlag, []() { JS_NewClassID(&kDocumentFragmentID); }); - JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); -} + // Let documentFragment instance inherit Document prototype methods. + JS_SetPrototype(ctx, documentFragment->toQuickJS(), prototype); -JSClassID DocumentFragment::classId() { - return kDocumentFragmentID; + return documentFragment; } -JSValue DocumentFragment::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return (new DocumentFragmentInstance(this))->jsObject; +DocumentFragment::DocumentFragment() { + setNodeFlag(DocumentFragment::NodeFlag::IsDocumentFragment); + context()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::createDocumentFragment, nativeEventTarget); } -DocumentFragmentInstance::DocumentFragmentInstance(DocumentFragment* fragment) : NodeInstance(fragment, NodeType::DOCUMENT_FRAGMENT_NODE, DocumentFragment::classId(), "DocumentFragment") { - setNodeFlag(DocumentFragmentInstance::NodeFlag::IsDocumentFragment); - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createDocumentFragment, nativeEventTarget); -} } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document_fragment.h b/bridge/bindings/qjs/dom/document_fragment.h index 95f74303d5..a2bdcf8791 100644 --- a/bridge/bindings/qjs/dom/document_fragment.h +++ b/bridge/bindings/qjs/dom/document_fragment.h @@ -14,21 +14,25 @@ void bindDocumentFragment(std::unique_ptr& 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; + static JSClassID classId; + // Return the constructor class object of DocumentFragment. + static JSValue constructor(ExecutionContext* context); + DocumentFragment* create(JSContext* ctx); + DocumentFragment(); + + private: + friend Node; +}; - OBJECT_INSTANCE(DocumentFragment); +auto documentFragmentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + auto* eventTarget = EventTarget::create(ctx); + return eventTarget->toQuickJS(); }; -class DocumentFragmentInstance : public NodeInstance { - public: - DocumentFragmentInstance() = delete; - DocumentFragmentInstance(DocumentFragment* fragment); +const WrapperTypeInfo documentFragmentInfo = { + "DocumentFragment", + &nodeTypeInfo, + documentFragmentCreator }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc index cfe5376cbb..3957b66b91 100644 --- a/bridge/bindings/qjs/dom/element.cc +++ b/bridge/bindings/qjs/dom/element.cc @@ -17,18 +17,58 @@ namespace kraken::binding::qjs { -std::once_flag kElementInitOnceFlag; - void bindElement(std::unique_ptr& context) { - auto* constructor = Element::instance(context.get()); - // auto* domRectConstructor = BoundingClientRect - context->defineGlobalProperty("Element", constructor->jsObject); - context->defineGlobalProperty("HTMLElement", JS_DupValue(context->ctx(), constructor->jsObject)); + auto* contextData = context->contextData(); + JSValue classObject = contextData->constructorForType(&elementTypeInfo); + JSValue prototypeObject = contextData->prototypeForType(&elementTypeInfo); + + // Install methods on prototype. + INSTALL_FUNCTION(Element, prototypeObject, getBoundingClientRect, 0); + INSTALL_FUNCTION(Element, prototypeObject, hasAttribute, 1); + INSTALL_FUNCTION(Element, prototypeObject, setAttribute, 2); + INSTALL_FUNCTION(Element, prototypeObject, getAttribute, 2); + INSTALL_FUNCTION(Element, prototypeObject, removeAttribute, 1); + INSTALL_FUNCTION(Element, prototypeObject, toBlob, 0); + INSTALL_FUNCTION(Element, prototypeObject, click, 2); + INSTALL_FUNCTION(Element, prototypeObject, scroll, 2); + // ScrollTo is same as scroll which reuse scroll functions. Macro expand is not support here. + installFunctionProperty(context.get(), prototypeObject, "scrollTo", Element::m_scroll_, 1); + INSTALL_FUNCTION(Element, prototypeObject, scrollBy, 2); + + // Install Getter and Setter properties. + // Install readonly properties. + INSTALL_READONLY_PROPERTY(Element, prototypeObject, nodeName); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, tagName); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, offsetLeft); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, offsetTop); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, offsetWidth); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, offsetHeight); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, clientWidth); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, clientHeight); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, clientTop); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, clientLeft); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, scrollHeight); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, scrollWidth); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, firstElementChild); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, lastElementChild); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, children); + INSTALL_READONLY_PROPERTY(Element, prototypeObject, attributes); + + // Install properties. + INSTALL_PROPERTY(Element, prototypeObject, className); + INSTALL_PROPERTY(Element, prototypeObject, innerHTML); + INSTALL_PROPERTY(Element, prototypeObject, outerHTML); + INSTALL_PROPERTY(Element, prototypeObject, scrollTop); + INSTALL_PROPERTY(Element, prototypeObject, scrollLeft); + + context->defineGlobalProperty("Element", classObject); + context->defineGlobalProperty("HTMLElement", JS_DupValue(context->ctx(), classObject)); } 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())); + JSValue classObject = context->contextData()->constructorForType(&elementTypeInfo); + if (JS_IsInstanceOf(context->ctx(), instance, classObject)) { + auto* elementInstance = static_cast(JS_GetOpaque(instance, Element::classId())); std::string tagName = elementInstance->getRegisteredTagName(); // Special case for kraken official plugins. @@ -44,17 +84,6 @@ bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue ins 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); @@ -138,29 +167,40 @@ void ElementAttributes::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* 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"); - } +JSClassID Element::classId{0}; +Element* Element::create(JSContext* ctx) { 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 prototype = context->contextData()->prototypeForType(&elementTypeInfo); + auto* element = makeGarbageCollected()->initialize(ctx, &classId); + // Let eventTarget instance inherit EventTarget prototype methods. + JS_SetPrototype(ctx, element->toQuickJS(), prototype); + return element; +} + +//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 Element(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())); + auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); getDartMethod()->flushUICommand(); return element->callNativeMethods("getBoundingClientRect", 0, nullptr); } @@ -176,7 +216,7 @@ JSValue Element::hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu 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* element = static_cast(JS_GetOpaque(this_val, Element::classId())); auto* attributes = element->m_attributes; const char* cname = JS_ToCString(ctx, nameValue); @@ -200,7 +240,7 @@ JSValue Element::setAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu 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* element = static_cast(JS_GetOpaque(this_val, Element::classId())); std::string name = jsValueToStdString(ctx, nameValue); std::transform(name.begin(), name.end(), name.begin(), ::tolower); @@ -241,7 +281,7 @@ JSValue Element::getAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu 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* element = static_cast(JS_GetOpaque(this_val, Element::classId())); std::string name = jsValueToStdString(ctx, nameValue); auto* attributes = element->m_attributes; @@ -264,7 +304,7 @@ JSValue Element::removeAttribute(JSContext* ctx, JSValue this_val, int argc, JSV 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())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); std::string name = jsValueToStdString(ctx, nameValue); auto* attributes = element->m_attributes; @@ -298,7 +338,7 @@ JSValue Element::toBlob(JSContext* ctx, JSValue this_val, int argc, JSValue* arg return JS_ThrowTypeError(ctx, "Failed to export blob: dart method (toBlob) is not registered."); } - auto* element = reinterpret_cast(JS_GetOpaque(this_val, Element::classId())); + 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) { @@ -360,10 +400,10 @@ JSValue Element::toBlob(JSContext* ctx, JSValue this_val, int argc, JSValue* arg 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())); + auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); return element->callNativeMethods("click", 0, nullptr); #elif UNIT_TEST - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); TEST_dispatchEvent(element, "click"); return JS_UNDEFINED; #else @@ -373,36 +413,36 @@ JSValue Element::click(JSContext* ctx, JSValue this_val, int argc, JSValue* argv JSValue Element::scroll(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0]), jsValueToNativeValue(ctx, argv[1])}; return element->callNativeMethods("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())); + auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0]), jsValueToNativeValue(ctx, argv[1])}; return element->callNativeMethods("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())); + 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())); + 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) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); return element->m_attributes->getAttribute("class"); } IMPL_PROPERTY_SETTER(Element, className)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); element->m_attributes->setAttribute("class", argv[0]); std::unique_ptr args_01 = stringToNativeString("class"); std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); @@ -414,103 +454,103 @@ enum class ViewModuleProperty { offsetTop, offsetLeft, offsetWidth, offsetHeight IMPL_PROPERTY_GETTER(Element, offsetLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetLeft))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, offsetTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetTop))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, offsetWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetWidth))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, offsetHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetHeight))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, clientWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientWidth))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, clientHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientHeight))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, clientTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientTop))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, clientLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientLeft))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollTop))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_SETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollTop)), jsValueToNativeValue(ctx, argv[0])}; return element->callNativeMethods("setViewModuleProperty", 2, args); } IMPL_PROPERTY_GETTER(Element, scrollLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollLeft))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_SETTER(Element, scrollLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollLeft)), jsValueToNativeValue(ctx, argv[0])}; return element->callNativeMethods("setViewModuleProperty", 2, args); } IMPL_PROPERTY_GETTER(Element, scrollHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollHeight))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, scrollWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollWidth))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } // 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())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); int32_t len = arrayGetLength(ctx, element->childNodes); for (int i = 0; i < len; i++) { @@ -527,7 +567,7 @@ IMPL_PROPERTY_GETTER(Element, firstElementChild)(JSContext* ctx, JSValue this_va // 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())); + 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--) { @@ -543,7 +583,7 @@ IMPL_PROPERTY_GETTER(Element, lastElementChild)(JSContext* ctx, JSValue this_val } IMPL_PROPERTY_GETTER(Element, children)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); JSValue array = JS_NewArray(ctx); JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); @@ -565,16 +605,16 @@ IMPL_PROPERTY_GETTER(Element, children)(JSContext* ctx, JSValue this_val, int ar } IMPL_PROPERTY_GETTER(Element, attributes)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + 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())); + 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())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); const char* chtml = JS_ToCString(ctx, argv[0]); if (element->hasNodeFlag(NodeInstance::NodeFlag::IsTemplateElement)) { @@ -589,20 +629,20 @@ IMPL_PROPERTY_SETTER(Element, innerHTML)(JSContext* ctx, JSValue this_val, int a } IMPL_PROPERTY_GETTER(Element, outerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + 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() { +JSClassID Element::classID() { return Element::classId(); } -ElementInstance::~ElementInstance() {} +Element::~Element() {} -JSValue ElementInstance::internalGetTextContent() { +JSValue Element::internalGetTextContent() { JSValue array = JS_NewArray(m_ctx); JSValue pushMethod = JS_GetPropertyStr(m_ctx, array, "push"); @@ -629,7 +669,7 @@ JSValue ElementInstance::internalGetTextContent() { return returnValue; } -void ElementInstance::internalSetTextContent(JSValue content) { +void Element::internalSetTextContent(JSValue content) { internalClearChild(); JSValue textNodeValue = JS_CallConstructor(m_ctx, TextNode::instance(m_context)->jsObject, 1, &content); @@ -638,7 +678,7 @@ void ElementInstance::internalSetTextContent(JSValue content) { JS_FreeValue(m_ctx, textNodeValue); } -std::shared_ptr ElementInstance::classNames() { +std::shared_ptr Element::classNames() { return m_attributes->className(); } @@ -692,17 +732,17 @@ bool SpaceSplitString::containsAll(std::string s) { return flag; } -std::string ElementInstance::tagName() { +std::string Element::tagName() { std::string tagName = std::string(m_tagName); std::transform(tagName.begin(), tagName.end(), tagName.begin(), ::toupper); return tagName; } -std::string ElementInstance::getRegisteredTagName() { +std::string Element::getRegisteredTagName() { return m_tagName; } -std::string ElementInstance::outerHTML() { +std::string Element::outerHTML() { std::string s = "<" + getRegisteredTagName(); // Read attributes @@ -726,7 +766,7 @@ std::string ElementInstance::outerHTML() { return s; } -std::string ElementInstance::innerHTML() { +std::string Element::innerHTML() { std::string s; // If Element is TemplateElement, the innerHTML content is the content of documentFragment. @@ -745,7 +785,7 @@ std::string ElementInstance::innerHTML() { 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(); + s += reinterpret_cast(node)->outerHTML(); } else if (node->nodeType == NodeType::TEXT_NODE) { s += reinterpret_cast(node)->toString(); } @@ -755,12 +795,12 @@ std::string ElementInstance::innerHTML() { return s; } -void ElementInstance::_notifyNodeRemoved(NodeInstance* insertionNode) { +void Element::_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); + auto element = reinterpret_cast(node); element->_notifyChildRemoved(); } @@ -769,7 +809,7 @@ void ElementInstance::_notifyNodeRemoved(NodeInstance* insertionNode) { } } -void ElementInstance::_notifyChildRemoved() { +void Element::_notifyChildRemoved() { std::string prop = "id"; if (m_attributes->hasAttribute(prop)) { JSValue idValue = m_attributes->getAttribute(prop); @@ -780,12 +820,12 @@ void ElementInstance::_notifyChildRemoved() { } } -void ElementInstance::_notifyNodeInsert(NodeInstance* insertNode) { +void Element::_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); + auto element = reinterpret_cast(node); element->_notifyChildInsert(); } @@ -794,7 +834,7 @@ void ElementInstance::_notifyNodeInsert(NodeInstance* insertNode) { } } -void ElementInstance::_notifyChildInsert() { +void Element::_notifyChildInsert() { std::string prop = "id"; if (m_attributes->hasAttribute(prop)) { JSValue idValue = m_attributes->getAttribute(prop); @@ -805,13 +845,13 @@ void ElementInstance::_notifyChildInsert() { } } -void ElementInstance::_didModifyAttribute(std::string& name, JSValue oldId, JSValue newId) { +void Element::_didModifyAttribute(std::string& name, JSValue oldId, JSValue newId) { if (name == "id") { _beforeUpdateId(oldId, newId); } } -void ElementInstance::_beforeUpdateId(JSValue oldIdValue, JSValue newIdValue) { +void Element::_beforeUpdateId(JSValue oldIdValue, JSValue newIdValue) { JSAtom oldId = JS_ValueToAtom(m_ctx, oldIdValue); JSAtom newId = JS_ValueToAtom(m_ctx, newIdValue); @@ -833,14 +873,14 @@ void ElementInstance::_beforeUpdateId(JSValue oldIdValue, JSValue newIdValue) { JS_FreeAtom(m_ctx, newId); } -void ElementInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { +void Element::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) +Element::Element(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}; @@ -855,9 +895,9 @@ ElementInstance::ElementInstance(Element* element, std::string tagName, bool sho } } -JSClassExoticMethods ElementInstance::exoticMethods{nullptr, nullptr, nullptr, nullptr, hasProperty, getProperty, setProperty}; +JSClassExoticMethods Element::exoticMethods{nullptr, nullptr, nullptr, nullptr, hasProperty, getProperty, setProperty}; -StyleDeclarationInstance* ElementInstance::style() { +StyleDeclarationInstance* Element::style() { return m_style; } diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h index e94295155d..28577ee00b 100644 --- a/bridge/bindings/qjs/dom/element.h +++ b/bridge/bindings/qjs/dom/element.h @@ -16,11 +16,9 @@ namespace kraken::binding::qjs { void bindElement(std::unique_ptr& context); -class ElementInstance; - class Element; -using ElementCreator = ElementInstance* (*)(Element* element, std::string tagName); +using ElementCreator = Element* (*)(Element* element, std::string tagName); struct NativeBoundingClientRect { double x; @@ -74,27 +72,19 @@ bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue ins 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); + static JSClassID classId; + static Element* create(JSContext* ctx); + + DEFINE_FUNCTION(getBoundingClientRect); + DEFINE_FUNCTION(hasAttribute); + DEFINE_FUNCTION(setAttribute); + DEFINE_FUNCTION(getAttribute); + DEFINE_FUNCTION(removeAttribute); + DEFINE_FUNCTION(toBlob); + DEFINE_FUNCTION(click); + DEFINE_FUNCTION(scroll); + DEFINE_FUNCTION(scrollBy); - private: DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); DEFINE_PROTOTYPE_READONLY_PROPERTY(tagName); DEFINE_PROTOTYPE_READONLY_PROPERTY(offsetLeft); @@ -118,29 +108,6 @@ class Element : public Node { 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; @@ -151,37 +118,63 @@ class ElementInstance : public NodeInstance { std::string innerHTML(); StyleDeclarationInstance* style(); - static inline JSClassID classID(); + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void dispose() const override; protected: - explicit ElementInstance(Element* element, std::string tagName, bool shouldAddUICommand); - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; - + StyleDeclarationInstance* m_style{nullptr}; + ElementAttributes* m_attributes{nullptr}; private: - void _notifyNodeRemoved(NodeInstance* node) override; + std::string m_tagName; + void _notifyNodeRemoved(Node* node) override; void _notifyChildRemoved(); - void _notifyNodeInsert(NodeInstance* insertNode) override; + void _notifyNodeInsert(Node* 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; + friend class Node; }; -class BoundingClientRect : public HostObject { +struct PersistElement { + Element* element; + list_head link; +}; + +auto elementCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + 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); + + Element* element = Element::create(ctx); + + auto* document = context->document(); +// auto* Document = Document::instance(context); +// if (Document->isCustomElement(name)) { +// return JS_CallConstructor(ctx, Document->getElementConstructor(context, name), argc, argv); +// } +// +// auto* element = new Element(this, name, true); +// return element->jsObject; +}; + +const WrapperTypeInfo elementTypeInfo = {"Element", &nodeTypeInfo, elementCreator}; + +class BoundingClientRect : public GarbageCollected { public: BoundingClientRect() = delete; - explicit BoundingClientRect(ExecutionContext* context, NativeBoundingClientRect* nativeBoundingClientRect) - : HostObject(context, "BoundingClientRect"), m_nativeBoundingClientRect(nativeBoundingClientRect){}; + explicit BoundingClientRect(ExecutionContext* context, NativeBoundingClientRect* nativeBoundingClientRect) : GarbageCollected(), m_nativeBoundingClientRect(nativeBoundingClientRect){}; + + const char* getHumanReadableName() const override { return "BoundingClientRect"; } private: DEFINE_READONLY_PROPERTY(x); diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/bindings/qjs/dom/event_target.cc index 4e31c96b0b..b5958c687b 100644 --- a/bridge/bindings/qjs/dom/event_target.cc +++ b/bridge/bindings/qjs/dom/event_target.cc @@ -21,23 +21,23 @@ static std::atomic globalEventTargetId{0}; void bindEventTarget(std::unique_ptr& context) { auto* contextData = context->contextData(); - JSValue classObject = contextData->constructorForType(&eventTargetTypeInfo); + JSValue constructor = contextData->constructorForType(&eventTargetTypeInfo); JSValue prototypeObject = contextData->prototypeForType(&eventTargetTypeInfo); - installFunctionProperty(context.get(), prototypeObject, "addEventListener", EventTarget::addEventListener, 3); - installFunctionProperty(context.get(), prototypeObject, "removeEventListener", EventTarget::removeEventListener, 2); - installFunctionProperty(context.get(), prototypeObject, "dispatchEvent", EventTarget::dispatchEvent, 1); + INSTALL_FUNCTION(EventTarget, prototypeObject, addEventListener, 3); + INSTALL_FUNCTION(EventTarget, prototypeObject, removeEventListener, 2); + INSTALL_FUNCTION(EventTarget, prototypeObject, dispatchEvent, 1); // Set globalThis and Window's prototype to EventTarget's prototype to support EventTarget methods in global. - JS_SetPrototype(context->ctx(), context->global(), classObject); - context->defineGlobalProperty("EventTarget", classObject); + JS_SetPrototype(context->ctx(), context->global(), constructor); + context->defineGlobalProperty("EventTarget", constructor); } EventTarget::EventTarget() : GarbageCollected() { m_eventTargetId = globalEventTargetId++; } -JSValue EventTarget::addEventListener(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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."); } @@ -75,7 +75,7 @@ JSValue EventTarget::addEventListener(JSContext* ctx, JSValue this_val, int argc return JS_UNDEFINED; } -JSValue EventTarget::removeEventListener(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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."); } @@ -116,7 +116,7 @@ JSValue EventTarget::removeEventListener(JSContext* ctx, JSValue this_val, int a return JS_UNDEFINED; } -JSValue EventTarget::dispatchEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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"); } @@ -132,7 +132,14 @@ JSValue EventTarget::dispatchEvent(JSContext* ctx, JSValue this_val, int argc, J } EventTarget* EventTarget::create(JSContext* ctx) { - return makeGarbageCollected()->initialize(ctx, &EventTarget::classId, nullptr); + auto* context = static_cast(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(&eventTargetTypeInfo); + auto* eventTarget = makeGarbageCollected()->initialize(ctx, &EventTarget::classId, nullptr); + + // Let eventTarget instance inherit EventTarget prototype methods. + JS_SetPrototype(ctx, eventTarget->toQuickJS(), prototype); + + return eventTarget; } bool EventTarget::dispatchEvent(EventInstance* event) { diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/bindings/qjs/dom/event_target.h index fd6064c816..f293bfb42a 100644 --- a/bridge/bindings/qjs/dom/event_target.h +++ b/bridge/bindings/qjs/dom/event_target.h @@ -59,15 +59,16 @@ class EventTarget : public GarbageCollected { EventTarget(); static JSClassID classId; static EventTarget* create(JSContext* ctx); - 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_FUNCTION(addEventListener); + DEFINE_FUNCTION(removeEventListener); + DEFINE_FUNCTION(dispatchEvent); void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; void dispose() const override; virtual bool dispatchEvent(EventInstance* event); - inline int32_t eventTargetId() const { return m_eventTargetId; } + FORCE_INLINE int32_t eventTargetId() const { return m_eventTargetId; } protected: JSValue callNativeMethods(const char* method, int32_t argc, NativeValue* argv); @@ -100,18 +101,10 @@ class EventTarget : public GarbageCollected { // property are not defined by Object.defineProperty or setProperty. // We store there values in here. EventTargetProperties m_properties{this->m_ctx}; - }; auto eventTargetCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { - auto* type = static_cast(JS_GetOpaque(func_obj, JSValueGetClassId(func_obj))); auto* eventTarget = EventTarget::create(ctx); - auto* context = static_cast(JS_GetContextOpaque(ctx)); - JSValue prototype = context->contextData()->prototypeForType(type); - - // Let eventTarget instance inherit EventTarget prototype methods. - JS_SetPrototype(ctx, eventTarget->toQuickJS(), prototype); - return eventTarget->toQuickJS(); }; diff --git a/bridge/bindings/qjs/dom/node.cc b/bridge/bindings/qjs/dom/node.cc index 63cfc06efb..2f3c5fc468 100644 --- a/bridge/bindings/qjs/dom/node.cc +++ b/bridge/bindings/qjs/dom/node.cc @@ -15,30 +15,37 @@ namespace kraken::binding::qjs { void bindNode(std::unique_ptr& context) { - auto* constructor = Node::instance(context.get()); - context->defineGlobalProperty("Node", constructor->jsObject); -} + auto* contextData = context->contextData(); + JSValue constructor = Node::constructor(context.get()); + JSValue prototype = Node::prototype(context.get()); + + // Install methods to Node.prototype. + INSTALL_FUNCTION(Node, prototype, cloneNode, 1); + INSTALL_FUNCTION(Node, prototype, appendChild, 1); + INSTALL_FUNCTION(Node, prototype, remove, 0); + INSTALL_FUNCTION(Node, prototype, removeChild, 1); + INSTALL_FUNCTION(Node, prototype, insertBefore, 2); + INSTALL_FUNCTION(Node, prototype, replaceChild, 2); -JSValue Node::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return JS_ThrowTypeError(ctx, "Illegal constructor"); + context->defineGlobalProperty("Node", constructor); } -JSClassID Node::classId() { - assert_m(false, "classId is not implemented"); - return 0; +JSValue Node::constructor(ExecutionContext* context) { + return context->contextData()->constructorForType(&nodeTypeInfo); } -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; - } +JSValue Node::prototype(ExecutionContext* context) { + return context->contextData()->prototypeForType(&nodeTypeInfo); +} - return 0; +Node* Node::create(JSContext* ctx) { + return nullptr; } -JSValue Node::cloneNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto selfInstance = static_cast(JS_GetOpaque(this_val, Node::classId(this_val))); +JSClassID Node::classId{0}; + +IMPL_FUNCTION(Node, cloneNode)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); JSValue deepValue; if (argc < 1) { @@ -52,38 +59,38 @@ JSValue Node::cloneNode(JSContext* ctx, JSValue this_val, int argc, JSValue* arg } 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 (self->nodeType == NodeType::ELEMENT_NODE) { + JSValue newElementValue = copyNodeValue(ctx, self); + auto* newElement = static_cast(JS_GetOpaque(newElementValue, JSValueGetClassId(newElementValue))); if (deep) { - traverseCloneNode(ctx, selfInstance, newElementInstance); + traverseCloneNode(ctx, self, newElement); } - return newElementInstance->jsObject; - } else if (selfInstance->nodeType == NodeType::TEXT_NODE) { - auto textNode = static_cast(selfInstance); - JSValue newTextNode = copyNodeValue(ctx, static_cast(textNode)); + return newElement->jsObject; + } else if (self->nodeType == NodeType::TEXT_NODE) { + auto textNode = static_cast(self); + 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))); + } else if (self->nodeType == NodeType::DOCUMENT_FRAGMENT_NODE) { + JSValue newFragmentValue = JS_CallConstructor(ctx, DocumentFragment::constructor(self->context()), 0, nullptr); + auto* newFragment = static_cast(JS_GetOpaque(newFragmentValue, JSValueGetClassId(newFragmentValue))); if (deep) { - traverseCloneNode(ctx, selfInstance, newFragmentInstance); + traverseCloneNode(ctx, self, newFragment); } - return newFragment; + return newFragmentValue; } return JS_NULL; } -JSValue Node::appendChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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) + auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + if (self == nullptr) return JS_ThrowTypeError(ctx, "this object is not a instance of Node."); JSValue nodeValue = argv[0]; @@ -91,39 +98,38 @@ JSValue Node::appendChild(JSContext* ctx, JSValue this_val, int argc, JSValue* a 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))); + auto* node = static_cast(JS_GetOpaque(nodeValue, JSValueGetClassId(nodeValue))); - if (nodeInstance == nullptr || nodeInstance->document() != selfInstance->document()) { + if (node == nullptr || node->ownerDocument() != self->ownerDocument()) { return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': first arguments should be an Node type."); } - if (nodeInstance == selfInstance) { + if (node == self) { 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); + if (node->hasNodeFlag(Node::NodeFlag::IsDocumentFragment)) { + size_t len = arrayGetLength(ctx, node->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); + JSValue n = JS_GetPropertyUint32(ctx, node->childNodes, i); + self->internalAppendChild(static_cast(JS_GetOpaque(n, JSValueGetClassId(n)))); JS_FreeValue(ctx, n); } - JS_SetPropertyStr(ctx, nodeInstance->childNodes, "length", JS_NewUint32(ctx, 0)); + JS_SetPropertyStr(ctx, node->childNodes, "length", JS_NewUint32(ctx, 0)); } else { - selfInstance->ensureDetached(nodeInstance); - selfInstance->internalAppendChild(nodeInstance); + self->ensureDetached(node); + self->internalAppendChild(node); } - return JS_DupValue(ctx, nodeInstance->jsObject); + return JS_DupValue(ctx, node->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(); +IMPL_FUNCTION(Node, remove)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + self->internalRemove(); return JS_UNDEFINED; } -JSValue Node::removeChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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"); } @@ -134,18 +140,18 @@ JSValue Node::removeChild(JSContext* ctx, JSValue this_val, int argc, JSValue* a 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))); + auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + auto node = static_cast(JS_GetOpaque(nodeValue, JSValueGetClassId(nodeValue))); - if (nodeInstance == nullptr || nodeInstance->document() != selfInstance->document()) { + if (node == nullptr || node->ownerDocument() != self->ownerDocument()) { return JS_ThrowTypeError(ctx, "Failed to execute 'removeChild' on 'Node': 1st arguments is not a Node object."); } - auto removedNode = selfInstance->internalRemoveChild(nodeInstance); + auto removedNode = self->internalRemoveChild(node); return JS_DupValue(ctx, removedNode->jsObject); } -JSValue Node::insertBefore(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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."); } @@ -157,41 +163,40 @@ JSValue Node::insertBefore(JSContext* ctx, JSValue this_val, int argc, JSValue* return JS_ThrowTypeError(ctx, "Failed to execute 'insertBefore' on 'Node': the node element is not object."); } - NodeInstance* referenceInstance = nullptr; + Node* reference = nullptr; if (JS_IsObject(referenceNodeValue)) { - referenceInstance = static_cast(JS_GetOpaque(referenceNodeValue, Node::classId(referenceNodeValue))); + reference = static_cast(JS_GetOpaque(referenceNodeValue, JSValueGetClassId(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))); + auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + auto node = static_cast(JS_GetOpaque(nodeValue, JSValueGetClassId(nodeValue))); - if (nodeInstance == nullptr || nodeInstance->document() != selfInstance->document()) { + if (node == nullptr || node->ownerDocument() != self->ownerDocument()) { 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); + if (node->hasNodeFlag(Node::NodeFlag::IsDocumentFragment)) { + size_t len = arrayGetLength(ctx, node->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); + JSValue n = JS_GetPropertyUint32(ctx, node->childNodes, i); + self->internalInsertBefore(static_cast(JS_GetOpaque(n, JSValueGetClassId(n))), reference); JS_FreeValue(ctx, n); } // Clear fragment childNodes reference. - JS_SetPropertyStr(ctx, nodeInstance->childNodes, "length", JS_NewUint32(ctx, 0)); + JS_SetPropertyStr(ctx, node->childNodes, "length", JS_NewUint32(ctx, 0)); } else { - selfInstance->ensureDetached(nodeInstance); - selfInstance->internalInsertBefore(nodeInstance, referenceInstance); + self->ensureDetached(node); + self->internalInsertBefore(node, reference); } return JS_NULL; } -JSValue Node::replaceChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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"); } @@ -207,66 +212,66 @@ JSValue Node::replaceChild(JSContext* ctx, JSValue this_val, int argc, JSValue* 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))); + auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + auto newChild = static_cast(JS_GetOpaque(newChildValue, JSValueGetClassId(newChildValue))); + auto oldChild = static_cast(JS_GetOpaque(oldChildValue, JSValueGetClassId(oldChildValue))); - if (oldChildInstance == nullptr || JS_VALUE_GET_PTR(oldChildInstance->parentNode) != JS_VALUE_GET_PTR(selfInstance->jsObject) || oldChildInstance->document() != selfInstance->document()) { + if (oldChild == nullptr || JS_VALUE_GET_PTR(oldChild->parentNode) != JS_VALUE_GET_PTR(self->jsObject) || oldChild->ownerDocument() != self->ownerDocument()) { 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()) { + if (newChild == nullptr || newChild->ownerDocument() != self->ownerDocument()) { 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); + if (newChild->hasNodeFlag(Node::NodeFlag::IsDocumentFragment)) { + size_t len = arrayGetLength(ctx, newChild->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); + JSValue n = JS_GetPropertyUint32(ctx, newChild->childNodes, i); + auto* node = static_cast(JS_GetOpaque(n, JSValueGetClassId(n))); + self->internalInsertBefore(node, oldChild); JS_FreeValue(ctx, n); } - selfInstance->internalRemoveChild(oldChildInstance); + self->internalRemoveChild(oldChild); // Clear fragment childNodes reference. - JS_SetPropertyStr(ctx, newChildInstance->childNodes, "length", JS_NewUint32(ctx, 0)); + JS_SetPropertyStr(ctx, newChild->childNodes, "length", JS_NewUint32(ctx, 0)); } else { - selfInstance->ensureDetached(newChildInstance); - selfInstance->internalReplaceChild(newChildInstance, oldChildInstance); + self->ensureDetached(newChild); + self->internalReplaceChild(newChild, oldChild); } - return JS_DupValue(ctx, oldChildInstance->jsObject); + return JS_DupValue(ctx, oldChild->jsObject); } -void Node::traverseCloneNode(JSContext* ctx, NodeInstance* baseNode, NodeInstance* targetNode) { +void Node::traverseCloneNode(JSContext* ctx, Node* baseNode, Node* 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); + auto* node = static_cast(JS_GetOpaque(n, JSValueGetClassId(n))); + JSValue newNodeValue = copyNodeValue(ctx, node); + auto newNode = static_cast(JS_GetOpaque(newNodeValue, JSValueGetClassId(newNodeValue))); + targetNode->ensureDetached(newNode); + targetNode->internalAppendChild(newNode); // element node needs recursive child nodes. if (node->nodeType == NodeType::ELEMENT_NODE) { - traverseCloneNode(ctx, node, newNodeInstance); + traverseCloneNode(ctx, node, newNode); } - JS_FreeValue(ctx, newNode); + JS_FreeValue(ctx, newNodeValue); JS_FreeValue(ctx, n); } } -JSValue Node::copyNodeValue(JSContext* ctx, NodeInstance* node) { +JSValue Node::copyNodeValue(JSContext* ctx, Node* node) { if (node->nodeType == NodeType::ELEMENT_NODE) { - auto* element = reinterpret_cast(node); + auto* element = reinterpret_cast(node); /* createElement */ std::string tagName = element->getRegisteredTagName(); - JSValue tagNameValue = JS_NewString(element->m_ctx, tagName.c_str()); + JSValue tagNameValue = JS_NewString(element->ctx(), tagName.c_str()); JSValue arguments[] = {tagNameValue}; - JSValue newElementValue = JS_CallConstructor(element->context()->ctx(), Element::instance(element->context())->jsObject, 1, arguments); + JSValue newElementValue = JS_CallConstructor(element->context()->ctx(), element->context()->contextData()->constructorForType(&elementTypeInfo), 1, arguments); JS_FreeValue(ctx, tagNameValue); - auto* newElement = static_cast(JS_GetOpaque(newElementValue, Node::classId(newElementValue))); + auto* newElement = static_cast(JS_GetOpaque(newElementValue, JSValueGetClassId(newElementValue))); /* copy attributes */ newElement->m_attributes->copyWith(element->m_attributes); @@ -275,18 +280,18 @@ JSValue Node::copyNodeValue(JSContext* ctx, NodeInstance* node) { newElement->m_style->copyWith(element->m_style); /* copy properties */ - ElementInstance::copyNodeProperties(newElement, element); + EventTarget::copyNodeProperties(newElement, element); - std::string newNodeEventTargetId = std::to_string(newElement->m_eventTargetId); + std::string newNodeEventTargetId = std::to_string(newElement->eventTargetId()); std::unique_ptr args_01 = stringToNativeString(newNodeEventTargetId); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::cloneNode, *args_01, nullptr); + element->context()->uiCommandBuffer()->addCommand(element->eventTargetId(), UICommand::cloneNode, *args_01, nullptr); return newElement->jsObject; } else if (node->nodeType == TEXT_NODE) { - auto* textNode = reinterpret_cast(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); + JSValue result = JS_CallConstructor(ctx, TextNode::constructor(textNode->context()), 1, arguments); JS_FreeValue(ctx, textContent); return result; } @@ -294,172 +299,172 @@ JSValue Node::copyNodeValue(JSContext* ctx, NodeInstance* node) { } 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()); + auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + return JS_NewBool(ctx, node->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); + auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + return JS_DupValue(ctx, node->ownerDocument()->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(); + auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + auto* instance = node->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(); + auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + auto* instance = node->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); + auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + return JS_DupValue(ctx, node->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(); + auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + auto* instance = node->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(); + auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + auto* instance = node->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); + auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + return JS_NewUint32(ctx, node->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(); + auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + return node->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]); + auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + node->internalSetTextContent(argv[0]); return JS_NULL; } -bool NodeInstance::isConnected() { - bool _isConnected = this == document(); - auto parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); +bool Node::isConnected() { + bool _isConnected = this == ownerDocument(); + auto parent = static_cast(JS_GetOpaque(parentNode, JSValueGetClassId(parentNode))); while (parent != nullptr && !_isConnected) { - _isConnected = parent == document(); + _isConnected = parent == ownerDocument(); JSValue parentParentNode = parent->parentNode; - parent = static_cast(JS_GetOpaque(parentParentNode, Node::classId(parentParentNode))); + parent = static_cast(JS_GetOpaque(parentParentNode, JSValueGetClassId(parentParentNode))); } return _isConnected; } -DocumentInstance* NodeInstance::ownerDocument() { +Document* Node::ownerDocument() { if (nodeType == NodeType::DOCUMENT_NODE) { return nullptr; } - return document(); + return context()->document(); } -NodeInstance* NodeInstance::firstChild() { +Node* Node::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))); + return static_cast(JS_GetOpaque(result, JSValueGetClassId(result))); } -NodeInstance* NodeInstance::lastChild() { +Node* Node::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))); + return static_cast(JS_GetOpaque(result, JSValueGetClassId(result))); } -NodeInstance* NodeInstance::previousSibling() { +Node* Node::previousSibling() { if (JS_IsNull(parentNode)) return nullptr; - auto* parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); + auto* parent = static_cast(JS_GetOpaque(parentNode, JSValueGetClassId(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 static_cast(JS_GetOpaque(result, JSValueGetClassId(result))); } return nullptr; } -NodeInstance* NodeInstance::nextSibling() { +Node* Node::nextSibling() { if (JS_IsNull(parentNode)) return nullptr; - auto* parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); + auto* parent = static_cast(JS_GetOpaque(parentNode, JSValueGetClassId(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 static_cast(JS_GetOpaque(result, JSValueGetClassId(result))); } return nullptr; } -void NodeInstance::internalAppendChild(NodeInstance* node) { +void Node::internalAppendChild(Node* node) { arrayPushValue(m_ctx, childNodes, node->jsObject); node->setParentNode(this); node->_notifyNodeInsert(this); - std::string nodeEventTargetId = std::to_string(node->m_eventTargetId); + std::string nodeEventTargetId = std::to_string(node->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); + context()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); } -void NodeInstance::internalRemove() { +void Node::internalRemove() { if (JS_IsNull(parentNode)) return; - auto* parent = static_cast(JS_GetOpaque(parentNode, Node::classId(parentNode))); + auto* parent = static_cast(JS_GetOpaque(parentNode, JSValueGetClassId(parentNode))); parent->internalRemoveChild(this); } -void NodeInstance::internalClearChild() { +void Node::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))); + auto* node = static_cast(JS_GetOpaque(v, JSValueGetClassId(v))); node->removeParentNode(); node->_notifyNodeRemoved(this); - node->m_context->uiCommandBuffer()->addCommand(node->m_eventTargetId, UICommand::removeNode, nullptr); + node->context()->uiCommandBuffer()->addCommand(node->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) { +Node* Node::internalRemoveChild(Node* 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); + node->context()->uiCommandBuffer()->addCommand(node->eventTargetId(), UICommand::removeNode, nullptr); } return node; } -JSValue NodeInstance::internalInsertBefore(NodeInstance* node, NodeInstance* referenceNode) { +JSValue Node::internalInsertBefore(Node* node, Node* referenceNode) { if (referenceNode == nullptr) { internalAppendChild(node); } else { @@ -468,7 +473,7 @@ JSValue NodeInstance::internalInsertBefore(NodeInstance* node, NodeInstance* ref } auto parentNodeValue = referenceNode->parentNode; - auto* parent = static_cast(JS_GetOpaque(parentNodeValue, Node::classId(parentNodeValue))); + auto* parent = static_cast(JS_GetOpaque(parentNodeValue, JSValueGetClassId(parentNodeValue))); if (parent != nullptr) { JSValue parentChildNodes = parent->childNodes; int32_t idx = arrayFindIdx(m_ctx, parentChildNodes, referenceNode->jsObject); @@ -481,23 +486,23 @@ JSValue NodeInstance::internalInsertBefore(NodeInstance* node, NodeInstance* ref node->setParentNode(parent); node->_notifyNodeInsert(parent); - std::string nodeEventTargetId = std::to_string(node->m_eventTargetId); + std::string nodeEventTargetId = std::to_string(node->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); + context()->uiCommandBuffer()->addCommand(referenceNode->eventTargetId(), UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); } } return JS_NULL; } -JSValue NodeInstance::internalGetTextContent() { +JSValue Node::internalGetTextContent() { return JS_NULL; } -void NodeInstance::internalSetTextContent(JSValue content) {} -JSValue NodeInstance::internalReplaceChild(NodeInstance* newChild, NodeInstance* oldChild) { +void Node::internalSetTextContent(JSValue content) {} +JSValue Node::internalReplaceChild(Node* newChild, Node* oldChild) { assert_m(JS_IsNull(newChild->parentNode), "ReplaceChild Error: newChild was not detached."); oldChild->removeParentNode(); @@ -513,20 +518,20 @@ JSValue NodeInstance::internalReplaceChild(NodeInstance* newChild, NodeInstance* oldChild->_notifyNodeRemoved(this); newChild->_notifyNodeInsert(this); - std::string newChildEventTargetId = std::to_string(newChild->m_eventTargetId); + std::string newChildEventTargetId = std::to_string(newChild->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); + context()->uiCommandBuffer()->addCommand(oldChild->eventTargetId(), UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); - m_context->uiCommandBuffer()->addCommand(oldChild->m_eventTargetId, UICommand::removeNode, nullptr); + context()->uiCommandBuffer()->addCommand(oldChild->eventTargetId(), UICommand::removeNode, nullptr); return oldChild->jsObject; } -void NodeInstance::setParentNode(NodeInstance* parent) { +void Node::setParentNode(Node* parent) { if (!JS_IsNull(parentNode)) { JS_FreeValue(m_ctx, parentNode); } @@ -534,7 +539,7 @@ void NodeInstance::setParentNode(NodeInstance* parent) { parentNode = JS_DupValue(m_ctx, parent->jsObject); } -void NodeInstance::removeParentNode() { +void Node::removeParentNode() { if (!JS_IsNull(parentNode)) { JS_FreeValue(m_ctx, parentNode); } @@ -542,19 +547,18 @@ void NodeInstance::removeParentNode() { parentNode = JS_NULL; } -NodeInstance::~NodeInstance() {} -void NodeInstance::refer() { +void Node::refer() { JS_DupValue(m_ctx, jsObject); - list_add_tail(&nodeLink.link, &m_context->node_job_list); + list_add_tail(&nodeLink.link, &context()->node_job_list); } -void NodeInstance::unrefer() { +void Node::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))); +void Node::_notifyNodeRemoved(Node* node) {} +void Node::_notifyNodeInsert(Node* node) {} +void Node::ensureDetached(Node* node) { + auto* nodeParent = static_cast(JS_GetOpaque(node->parentNode, JSValueGetClassId(node->parentNode))); if (nodeParent != nullptr) { int32_t idx = arrayFindIdx(m_ctx, nodeParent->childNodes, node->jsObject); @@ -566,12 +570,14 @@ void NodeInstance::ensureDetached(NodeInstance* node) { } } -void NodeInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - EventTargetInstance::trace(rt, val, mark_func); +void Node::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + EventTarget::trace(rt, val, mark_func); // Should check object is already inited before gc mark. if (JS_IsObject(parentNode)) JS_MarkValue(rt, parentNode, mark_func); } +void Node::dispose() const {} + } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/node.h b/bridge/bindings/qjs/dom/node.h index 9f388f04fc..ddc1b43a71 100644 --- a/bridge/bindings/qjs/dom/node.h +++ b/bridge/bindings/qjs/dom/node.h @@ -13,14 +13,17 @@ namespace kraken::binding::qjs { +class Element; +class Document; +class DocumentFragment; + void bindNode(std::unique_ptr& context); enum NodeType { ELEMENT_NODE = 1, TEXT_NODE = 3, COMMENT_NODE = 8, DOCUMENT_NODE = 9, DOCUMENT_TYPE_NODE = 10, DOCUMENT_FRAGMENT_NODE = 11 }; class Node; -class ElementInstance; -class DocumentInstance; -class TextNodeInstance; +class TextNode; +class Document; struct NodeJob { Node* nodeInstance; @@ -29,16 +32,28 @@ struct NodeJob { class Node : public EventTarget { public: - 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); + static JSClassID classId; + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); + static Node* create(JSContext* ctx); + + DEFINE_FUNCTION(cloneNode); + DEFINE_FUNCTION(appendChild); + DEFINE_FUNCTION(remove); + DEFINE_FUNCTION(removeChild); + DEFINE_FUNCTION(insertBefore); + DEFINE_FUNCTION(replaceChild); + + 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); enum class NodeFlag : uint32_t { IsDocumentFragment = 1 << 0, IsTemplateElement = 1 << 1 }; mutable std::set m_nodeFlags; @@ -47,68 +62,54 @@ class Node : public EventTarget { void removeNodeFlag(NodeFlag flag) const { m_nodeFlags.erase(flag); } 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); - + Document* ownerDocument(); + Node* firstChild(); + Node* lastChild(); + Node* previousSibling(); + Node* nextSibling(); - void setParentNode(NodeInstance* parent); + void setParentNode(Node* parent); void removeParentNode(); NodeType nodeType; JSValue parentNode{JS_NULL}; JSValue childNodes{JS_NewArray(m_ctx)}; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void dispose() const override; protected: NodeJob nodeLink{this}; void refer(); void unrefer(); - inline DocumentInstance* document() { return m_document; } - - virtual void _notifyNodeRemoved(NodeInstance* node); - virtual void _notifyNodeInsert(NodeInstance* node); - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void internalAppendChild(Node* node); + void internalRemove(); + void internalClearChild(); + Node* internalRemoveChild(Node* node); + JSValue internalInsertBefore(Node* node, Node* referenceNode); + virtual JSValue internalGetTextContent(); + virtual void internalSetTextContent(JSValue content); + JSValue internalReplaceChild(Node* newChild, Node* oldChild); + virtual void _notifyNodeRemoved(Node* node); + virtual void _notifyNodeInsert(Node* node); 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); - - DocumentInstance* m_document{nullptr}; ObjectProperty m_childNodes{context(), jsObject, "childNodes", childNodes}; - void ensureDetached(NodeInstance* node); + void ensureDetached(Node* node); + + static void traverseCloneNode(JSContext* ctx, Node* baseNode, Node* targetNode); + static JSValue copyNodeValue(JSContext* ctx, Node* node); +}; + +auto nodeCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + return JS_ThrowTypeError(ctx, "Illegal constructor"); +}; - static void traverseCloneNode(JSContext* ctx, NodeInstance* baseNode, NodeInstance* targetNode); - static JSValue copyNodeValue(JSContext* ctx, NodeInstance* node); - friend ElementInstance; - friend TextNodeInstance; +const WrapperTypeInfo nodeTypeInfo = { + "Node", + &eventTargetTypeInfo, + nodeCreator }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/text_node.cc b/bridge/bindings/qjs/dom/text_node.cc index 29c162b37e..0ee6065437 100644 --- a/bridge/bindings/qjs/dom/text_node.cc +++ b/bridge/bindings/qjs/dom/text_node.cc @@ -12,46 +12,55 @@ namespace kraken::binding::qjs { std::once_flag kTextNodeInitFlag; void bindTextNode(std::unique_ptr& context) { - auto* constructor = TextNode::instance(context.get()); - context->defineGlobalProperty("Text", constructor->jsObject); + JSValue constructor = TextNode::constructor(context.get()); + JSValue prototype = TextNode::prototype(context.get()); + + // Install readonly properties. + INSTALL_READONLY_PROPERTY(TextNode, prototype, nodeName); + + // Install properties. + INSTALL_PROPERTY(TextNode, prototype, data); + INSTALL_PROPERTY(TextNode, prototype, nodeValue); + + context->defineGlobalProperty("Text", constructor); } -JSClassID TextNode::kTextNodeClassId{0}; +JSClassID TextNode::classId{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::constructor(ExecutionContext* context) { + return context->contextData()->constructorForType(&textNodeType); } -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]; - } +JSValue TextNode::prototype(ExecutionContext* context) { + return context->contextData()->prototypeForType(&textNodeType); +} - return (new TextNodeInstance(this, textContent))->jsObject; +TextNode* TextNode::create(JSContext* ctx, JSValue textContent) { + return makeGarbageCollected(textContent)->initialize(ctx, &classId); } -JSClassID TextNode::classId() { - return kTextNodeClassId; +TextNode::TextNode(JSValueConst textContent) { + m_data = jsValueToStdString(m_ctx, textContent); + std::unique_ptr args_01 = stringToNativeString(m_data); + context()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::createTextNode, *args_01, nativeEventTarget); } IMPL_PROPERTY_GETTER(TextNode, data)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* textNode = static_cast(JS_GetOpaque(this_val, TextNode::classId())); + 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())); + 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())); + 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())); + auto* textNode = static_cast(JS_GetOpaque(this_val, TextNode::classId)); textNode->internalSetTextContent(argv[0]); return JS_NULL; } @@ -60,27 +69,19 @@ IMPL_PROPERTY_GETTER(TextNode, nodeName)(JSContext* ctx, JSValue this_val, int a 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() { +std::string TextNode::toString() { return m_data; } -JSValue TextNodeInstance::internalGetTextContent() { +JSValue TextNode::internalGetTextContent() { return JS_NewString(m_ctx, m_data.c_str()); } -void TextNodeInstance::internalSetTextContent(JSValue content) { +void TextNode::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::setProperty, *args_01, *args_02, nullptr); + context()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::setProperty, *args_01, *args_02, nullptr); } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/text_node.h b/bridge/bindings/qjs/dom/text_node.h index 1591831e96..2dea9ab6d4 100644 --- a/bridge/bindings/qjs/dom/text_node.h +++ b/bridge/bindings/qjs/dom/text_node.h @@ -16,40 +16,43 @@ void bindTextNode(std::unique_ptr& context); class TextNode : public Node { public: - static JSClassID kTextNodeClassId; - static JSClassID classId(); - TextNode() = delete; - explicit TextNode(ExecutionContext* context); + static JSClassID classId; + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); + static TextNode* create(JSContext* ctx, JSValue textContent); - OBJECT_INSTANCE(TextNode); + TextNode() = delete; + explicit TextNode(JSValueConst textContent); - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + std::string toString(); - 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: + protected: JSValue internalGetTextContent() override; void internalSetTextContent(JSValue content) override; - friend TextNode; - friend Node; - std::string m_data; }; +auto textNodeCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + JSValue textContent = JS_NULL; + if (argc == 1) { + textContent = argv[0]; + } + + TextNode* textNode = TextNode::create(ctx, textContent); + return textNode->toQuickJS(); +}; + +const WrapperTypeInfo textNodeType = { + "TextNode", + &nodeTypeInfo, + textNodeCreator +}; + } // namespace kraken::binding::qjs #endif // KRAKENBRIDGE_TEXT_NODE_H diff --git a/bridge/bindings/qjs/executing_context.h b/bridge/bindings/qjs/executing_context.h index 4afaecc98a..936f9025d6 100644 --- a/bridge/bindings/qjs/executing_context.h +++ b/bridge/bindings/qjs/executing_context.h @@ -31,7 +31,6 @@ namespace kraken::binding::qjs { static std::once_flag kinitJSClassIDFlag; -class DocumentInstance; class ExecutionContext; struct DOMTimerCallbackContext; @@ -155,6 +154,22 @@ static JSValue handleCallThisOnProxy(JSContext* ctx, JSValueConst this_val, int return result; } +class ObjectProperty { + KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectProperty); +public: + ObjectProperty() = delete; + + // 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}; +}; + // Property define helpers void installFunctionProperty(ExecutionContext* context, JSValueConst thisObject, const char* functionName, JSCFunction function, int argc); void installPropertyGetterSetter(ExecutionContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction, JSCFunction setterFunction); diff --git a/bridge/bindings/qjs/js_context_macros.h b/bridge/bindings/qjs/js_context_macros.h index 4351a14b2d..e514f28cbe 100644 --- a/bridge/bindings/qjs/js_context_macros.h +++ b/bridge/bindings/qjs/js_context_macros.h @@ -6,14 +6,6 @@ #ifndef KRAKENBRIDGE_JS_CONTEXT_MACROS_H #define KRAKENBRIDGE_JS_CONTEXT_MACROS_H -#define OBJECT_INSTANCE(NAME) \ - static NAME* instance(ExecutionContext* context) { \ - if (context->constructorMap.count(#NAME) == 0) { \ - context->constructorMap[#NAME] = static_cast(new NAME(context)); \ - } \ - return static_cast(context->constructorMap[#NAME]); \ - } - #define QJS_GLOBAL_BINDING_FUNCTION(context, function, name, argc) \ { \ JSValue f = JS_NewCFunction(context->ctx(), function, name, argc); \ From d80b5287c88d302e14d33ac0c526d732d4eeec26 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Mon, 24 Jan 2022 18:07:00 +0800 Subject: [PATCH 003/498] refactor: refactor event and document. --- bridge/bindings/qjs/bom/blob.cc | 142 ++++++------ bridge/bindings/qjs/bom/blob.h | 108 ++++++--- .../bindings/qjs/bom/dom_timer_coordinator.h | 2 +- bridge/bindings/qjs/dom/comment_node.cc | 31 ++- bridge/bindings/qjs/dom/comment_node.h | 38 +++- bridge/bindings/qjs/dom/custom_event.cc | 63 ++++-- bridge/bindings/qjs/dom/custom_event.h | 62 +++-- bridge/bindings/qjs/dom/document.cc | 56 +++-- bridge/bindings/qjs/dom/document.h | 12 +- bridge/bindings/qjs/dom/element.cc | 211 ++++++++++-------- bridge/bindings/qjs/dom/element.h | 9 +- bridge/bindings/qjs/dom/event.cc | 198 ++++++++-------- bridge/bindings/qjs/dom/event.h | 106 ++++----- bridge/bindings/qjs/dom/event_target.cc | 66 +++--- bridge/bindings/qjs/dom/event_target.h | 6 +- bridge/bindings/qjs/dom/node.h | 1 - bridge/bindings/qjs/garbage_collected.h | 6 +- bridge/bindings/qjs/html_parser.cc | 2 +- bridge/bindings/qjs/html_parser.h | 8 +- bridge/polyfill/scripts/js_to_c.js | 8 +- bridge/test/kraken_test_env.cc | 2 +- bridge/test/kraken_test_env.h | 2 +- 22 files changed, 646 insertions(+), 493 deletions(-) diff --git a/bridge/bindings/qjs/bom/blob.cc b/bridge/bindings/qjs/bom/blob.cc index c797e945a5..d83c449739 100644 --- a/bridge/bindings/qjs/bom/blob.cc +++ b/bridge/bindings/qjs/bom/blob.cc @@ -8,90 +8,74 @@ namespace kraken::binding::qjs { -std::once_flag kBlobInitOnceFlag; - void bindBlob(std::unique_ptr& context) { - auto* constructor = Blob::instance(context.get()); - 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 constructor = context->contextData()->constructorForType(&blobTypeInfo); + JSValue prototype = context->contextData()->prototypeForType(&blobTypeInfo); - JSValue arrayValue = argv[0]; - JSValue optionValue = JS_UNDEFINED; + // Install methods on prototype. + INSTALL_FUNCTION(Blob, prototype, arrayBuffer, 0); + INSTALL_FUNCTION(Blob, prototype, slice, 3); + INSTALL_FUNCTION(Blob, prototype, text, 0); - if (argc > 1) { - optionValue = argv[1]; - } + // Install readonly properties. + INSTALL_READONLY_PROPERTY(Blob, prototype, type); + INSTALL_READONLY_PROPERTY(Blob, prototype, size); - if (!JS_IsArray(ctx, arrayValue)) { - return JS_ThrowTypeError(ctx, "Failed to construct 'Blob': The provided value cannot be converted to a sequence"); - } + context->defineGlobalProperty("Blob", constructor); +} - if (argc == 1 || JS_IsUndefined(optionValue)) { - builder.append(*constructor->m_context, arrayValue); - auto blob = new BlobInstance(constructor, builder.finalize()); - return blob->jsObject; - } +JSClassID Blob::classID{0}; - if (!JS_IsObject(optionValue)) { - return JS_ThrowTypeError(ctx, - "Failed to construct 'Blob': parameter 2 ('options') " - "is not an object"); - } +Blob* Blob::create(JSContext* ctx) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* blob = makeGarbageCollected()->initialize(ctx, &classID); - JSAtom mimeTypeKey = JS_NewAtom(ctx, "type"); + JSValue prototype = context->contextData()->prototypeForType(&blobTypeInfo); - 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); + // Let eventTarget instance inherit EventTarget prototype methods. + JS_SetPrototype(ctx, blob->toQuickJS(), prototype); + return blob; - auto* blob = new BlobInstance(constructor, builder.finalize(), mimeType); +} +Blob* Blob::create(JSContext* ctx, std::vector&& data) { + return create(ctx); +} +Blob* Blob::create(JSContext* ctx, std::vector&& data, std::string& mime) { + return create(ctx); +} - JS_FreeValue(ctx, mimeTypeValue); - JS_FreeCString(ctx, mimeType.c_str()); - JS_FreeAtom(ctx, mimeTypeKey); +JSValue Blob::constructor(ExecutionContext* context) { + return context->contextData()->constructorForType(&blobTypeInfo); +} - return blob->jsObject; +JSValue Blob::prototype(ExecutionContext* context) { + return context->contextData()->prototypeForType(&blobTypeInfo); } 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()); + auto* blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); + return JS_NewString(blob->m_ctx, blob->mimeType.empty() ? "" : blob->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); + auto* blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); + return JS_NewFloat64(blob->m_ctx, blob->_size); } -JSValue Blob::arrayBuffer(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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)); + auto blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); JS_DupValue(ctx, blob->jsObject); - auto* promiseContext = new PromiseContext{blob, blob->m_context, resolving_funcs[0], resolving_funcs[1], promise}; + auto* promiseContext = new PromiseContext{blob, blob->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); + auto* blob = static_cast(promiseContext->data); JSContext* ctx = blob->m_ctx; JSValue arrayBuffer = JS_NewArrayBuffer( @@ -114,7 +98,7 @@ JSValue Blob::arrayBuffer(JSContext* ctx, JSValue this_val, int argc, JSValue* a list_del(&promiseContext->link); delete promiseContext; }; - list_add_tail(&promiseContext->link, &blob->m_context->promise_job_list); + list_add_tail(&promiseContext->link, &blob->context()->promise_job_list); // TODO: remove setTimeout getDartMethod()->setTimeout(promiseContext, blob->context()->getContextId(), callback, 0); @@ -122,12 +106,12 @@ JSValue Blob::arrayBuffer(JSContext* ctx, JSValue this_val, int argc, JSValue* a return promise; } -JSValue Blob::slice(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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)); + auto* blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); int32_t start = 0; int32_t end = blob->_data.size(); std::string mimeType = blob->mimeType; @@ -147,31 +131,31 @@ JSValue Blob::slice(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { } if (start == 0 && end == blob->_data.size()) { - auto newBlob = new BlobInstance(reinterpret_cast(blob->m_hostClass), std::move(blob->_data), mimeType); - return newBlob->jsObject; + auto* newBlob = Blob::create(ctx, std::move(blob->_data), mimeType); + return newBlob->toQuickJS(); } 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; + auto* newBlob = Blob::create(ctx, std::move(newData), mimeType); + return newBlob->toQuickJS(); } -JSValue Blob::text(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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)); + auto blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); JS_DupValue(ctx, blob->jsObject); - auto* promiseContext = new PromiseContext{blob, blob->m_context, resolving_funcs[0], resolving_funcs[1], promise}; + auto* promiseContext = new PromiseContext{blob, blob->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); + auto* blob = static_cast(promiseContext->data); JSContext* ctx = blob->m_ctx; JSValue text = JS_NewStringLen(ctx, reinterpret_cast(blob->bytes()), blob->size()); @@ -193,19 +177,14 @@ JSValue Blob::text(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { list_del(&promiseContext->link); delete promiseContext; }; - list_add_tail(&promiseContext->link, &blob->m_context->promise_job_list); + list_add_tail(&promiseContext->link, &blob->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) { +void BlobBuilder::append(ExecutionContext& context, Blob* blob) { std::vector blobData = blob->_data; _data.reserve(_data.size() + blobData.size()); _data.insert(_data.end(), blobData.begin(), blobData.end()); @@ -234,11 +213,11 @@ void BlobBuilder::append(ExecutionContext& context, JSValue& value) { 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 (JS_IsInstanceOf(context.ctx(), value, Blob::constructor(&context))) { + auto blob = static_cast(JS_GetOpaque(value, Blob::classID)); if (blob == nullptr) return; - if (std::string(blob->m_name) == "Blob") { + if (std::string(blob->getHumanReadableName()) == "Blob") { std::vector blobData = blob->_data; _data.reserve(_data.size() + blobData.size()); _data.insert(_data.end(), blobData.begin(), blobData.end()); @@ -271,11 +250,18 @@ std::vector BlobBuilder::finalize() { return std::move(_data); } -int32_t BlobInstance::size() { +int32_t Blob::size() { return _data.size(); } -uint8_t* BlobInstance::bytes() { +uint8_t* Blob::bytes() { return _data.data(); } + +void Blob::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const {} +void Blob::dispose() const { + +} + + } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/blob.h b/bridge/bindings/qjs/bom/blob.h index 54734508cb..e34fafe7fc 100644 --- a/bridge/bindings/qjs/bom/blob.h +++ b/bridge/bindings/qjs/bom/blob.h @@ -15,57 +15,45 @@ class BlobInstance; void bindBlob(std::unique_ptr& context); -class Blob : public HostClass { +class Blob : public GarbageCollected { public: - static JSClassID kBlobClassID; - OBJECT_INSTANCE(Blob); + static JSClassID classID; + static Blob* create(JSContext* ctx); + static Blob* create(JSContext* ctx, std::vector&& data); + static Blob* create(JSContext* ctx, std::vector&& data, std::string& mime); + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); - Blob() = delete; - explicit Blob(ExecutionContext* context); + Blob() {}; + Blob(std::vector&& data): _size(data.size()), _data(std::move(data)) {}; + Blob(std::vector&& data, std::string& mime): mimeType(mime), _size(data.size()), _data(std::move(data)) {}; - 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){}; + DEFINE_FUNCTION(arrayBuffer); + DEFINE_FUNCTION(slice); + DEFINE_FUNCTION(text); /// get an pointer of bytes data from JSBlob uint8_t* bytes(); /// get bytes data's length int32_t size(); + DEFINE_PROTOTYPE_READONLY_PROPERTY(type); + DEFINE_PROTOTYPE_READONLY_PROPERTY(size); + + void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; + void dispose() const override; + private: size_t _size; - std::string mimeType{""}; + 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); + void append(ExecutionContext& context, Blob* blob); std::vector finalize(); @@ -74,6 +62,60 @@ class BlobBuilder { std::vector _data; }; +auto blobCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + if (argc == 0) { + auto* blob = Blob::create(ctx); + return blob->toQuickJS(); + } + + 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"); + } + + auto* context = static_cast(JS_GetContextOpaque(ctx)); + BlobBuilder builder; + + if (argc == 1 || JS_IsUndefined(optionValue)) { + builder.append(*context, arrayValue); + auto* blob = Blob::create(ctx, builder.finalize()); + return blob->toQuickJS(); + } + + 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(*context, mimeTypeValue); + const char* cMineType = JS_ToCString(ctx, mimeTypeValue); + std::string mimeType = std::string(cMineType); + + auto* blob = Blob::create(ctx, builder.finalize(), mimeType); + + JS_FreeValue(ctx, mimeTypeValue); + JS_FreeCString(ctx, mimeType.c_str()); + JS_FreeAtom(ctx, mimeTypeKey); + + return blob->toQuickJS(); +}; + +const WrapperTypeInfo blobTypeInfo = { + "Blob", + nullptr, + blobCreator +}; + } // namespace kraken::binding::qjs #endif // KRAKENBRIDGE_BLOB_H diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.h b/bridge/bindings/qjs/bom/dom_timer_coordinator.h index d496693d89..e736fcb38c 100644 --- a/bridge/bindings/qjs/bom/dom_timer_coordinator.h +++ b/bridge/bindings/qjs/bom/dom_timer_coordinator.h @@ -8,11 +8,11 @@ #include #include +#include "bindings/qjs/executing_context.h" #include namespace kraken::binding::qjs { -class ExecutionContext; class DOMTimer; // Maintains a set of DOMTimers for a given page diff --git a/bridge/bindings/qjs/dom/comment_node.cc b/bridge/bindings/qjs/dom/comment_node.cc index 6b3ac811af..c2c7cf26f3 100644 --- a/bridge/bindings/qjs/dom/comment_node.cc +++ b/bridge/bindings/qjs/dom/comment_node.cc @@ -9,24 +9,31 @@ namespace kraken::binding::qjs { -std::once_flag kCommentInitFlag; - -JSClassID Comment::kCommentClassId{0}; - void bindCommentNode(std::unique_ptr& context) { - auto* constructor = Comment::instance(context.get()); - context->defineGlobalProperty("Comment", constructor->jsObject); +// auto* constructor = Comment::instance(context.get()); +// context->defineGlobalProperty("Comment", constructor->jsObject); } -JSClassID Comment::classId() { - return kCommentClassId; -} +JSClassID Comment::classId{0}; + +Comment* Comment::create(JSContext* ctx) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* comment = makeGarbageCollected()->initialize(ctx, &classId); + + JSValue prototype = context->contextData()->prototypeForType(&commentTypeInfo); + + // Let eventTarget instance inherit EventTarget prototype methods. + JS_SetPrototype(ctx, comment->toQuickJS(), prototype); + + return comment; -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()); } +//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; } diff --git a/bridge/bindings/qjs/dom/comment_node.h b/bridge/bindings/qjs/dom/comment_node.h index 61777b04ca..7be7e31737 100644 --- a/bridge/bindings/qjs/dom/comment_node.h +++ b/bridge/bindings/qjs/dom/comment_node.h @@ -16,14 +16,17 @@ class CommentInstance; class Comment : public Node { public: - static JSClassID kCommentClassId; - static JSClassID classId(); - Comment() = delete; - explicit Comment(ExecutionContext* context); + static JSClassID classId; + static Comment* create(JSContext* ctx); + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); - OBJECT_INSTANCE(Comment); +// static JSClassID kCommentClassId; +// static JSClassID classId(); +// Comment() = delete; +// explicit Comment(ExecutionContext* context); - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; +// JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; private: DEFINE_PROTOTYPE_READONLY_PROPERTY(data); @@ -33,15 +36,26 @@ class Comment : public Node { friend CommentInstance; }; -class CommentInstance : public NodeInstance { - public: - CommentInstance() = delete; - explicit CommentInstance(Comment* comment); +auto commentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { +}; - private: - friend Comment; + +const WrapperTypeInfo commentTypeInfo = { + "Comment", + &nodeTypeInfo, + commentCreator }; +// +//class CommentInstance : public NodeInstance { +// public: +// CommentInstance() = delete; +// explicit CommentInstance(Comment* comment); +// +// private: +// friend Comment; +//}; + } // namespace kraken::binding::qjs #endif // KRAKENBRIDGE_COMMENT_NODE_H diff --git a/bridge/bindings/qjs/dom/custom_event.cc b/bridge/bindings/qjs/dom/custom_event.cc index b7f5e03d7b..705e7cccdb 100644 --- a/bridge/bindings/qjs/dom/custom_event.cc +++ b/bridge/bindings/qjs/dom/custom_event.cc @@ -12,8 +12,52 @@ namespace kraken::binding::qjs { void bindCustomEvent(std::unique_ptr& context) { - auto* constructor = CustomEvent::instance(context.get()); - context->defineGlobalProperty("CustomEvent", constructor->jsObject); +// auto* constructor = CustomEvent::instance(context.get()); +// context->defineGlobalProperty("CustomEvent", constructor->jsObject); +} + +JSClassID CustomEvent::classId{0}; + +CustomEvent* CustomEvent::create(JSContext* ctx, JSValue eventType, JSValue init) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(&eventTypeInfo); + + auto* event = makeGarbageCollected()->initialize(ctx, &classId); + + if (!JS_IsNull(init)) { + JSAtom detailKey = JS_NewAtom(ctx, "detail"); + if (JS_HasProperty(ctx, init, detailKey)) { + JSValue detailValue = JS_GetProperty(ctx, init, detailKey); + event->m_detail = JS_DupValue(ctx, detailValue); + JS_FreeValue(ctx, detailValue); + } + JS_FreeAtom(ctx, detailKey); + } + + // Let eventTarget instance inherit EventTarget prototype methods. + JS_SetPrototype(ctx, event->toQuickJS(), prototype); + + return event; +} + +JSValue CustomEvent::constructor(ExecutionContext* context) { + return context->contextData()->constructorForType(&customEventTypeInfo); +} + +JSValue CustomEvent::prototype(ExecutionContext* context) { + return context->contextData()->prototypeForType(&customEventTypeInfo); +} + +CustomEvent::CustomEvent(JSValue eventType, JSValue eventInit) { + 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); + } } JSValue CustomEvent::initCustomEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { @@ -46,22 +90,7 @@ JSValue CustomEvent::initCustomEvent(JSContext* ctx, JSValue this_val, int argc, } 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) { diff --git a/bridge/bindings/qjs/dom/custom_event.h b/bridge/bindings/qjs/dom/custom_event.h index b14eeecf01..fda15db447 100644 --- a/bridge/bindings/qjs/dom/custom_event.h +++ b/bridge/bindings/qjs/dom/custom_event.h @@ -17,33 +17,59 @@ struct NativeCustomEvent { NativeString* detail{nullptr}; }; -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 JSClassID classId; + static CustomEvent* create(JSContext* ctx, JSValue eventType, JSValue init); + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); - static JSValue initCustomEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - OBJECT_INSTANCE(CustomEvent); + void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; + void dispose() const override; + +// CustomEvent() = delete; +// explicit CustomEvent(JSValue eventType, JSValue eventInit); - private: DEFINE_PROTOTYPE_READONLY_PROPERTY(detail); + DEFINE_FUNCTION(initCustomEvent); - DEFINE_PROTOTYPE_FUNCTION(initCustomEvent, 4); - friend CustomEventInstance; + private: + JSValue m_detail{JS_NULL}; }; -class CustomEventInstance : public EventInstance { - public: - explicit CustomEventInstance(CustomEvent* jsCustomEvent, JSAtom CustomEventType, JSValue eventInit); - explicit CustomEventInstance(CustomEvent* jsCustomEvent, NativeCustomEvent* nativeCustomEvent); +//class CustomEventInstance : public EventInstance { +// public: +// explicit CustomEventInstance(CustomEvent* jsCustomEvent, JSAtom CustomEventType, JSValue eventInit); +// explicit CustomEventInstance(CustomEvent* jsCustomEvent, NativeCustomEvent* nativeCustomEvent); +// +// private: - private: - JSValueHolder m_detail{m_ctx, JS_NULL}; - NativeCustomEvent* nativeCustomEvent{nullptr}; - friend CustomEvent; +// NativeCustomEvent* nativeCustomEvent{nullptr}; +// friend CustomEvent; +//}; + +auto customEventCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + if (argc < 1) { + return JS_ThrowTypeError(ctx, "Failed to construct 'CustomEvent': 1 argument required, but only 0 present."); + } + + JSValue typeValue = argv[0]; + JSValue customEventInit = JS_NULL; + + if (argc == 2) { + customEventInit = argv[1]; + } + + auto* customEvent = CustomEvent::create(ctx, typeValue, customEventInit); + // auto* customEvent = new CustomEventInstance(CustomEvent::instance(context()), typeAtom, customEventInit); + // JS_FreeAtom(m_ctx, typeAtom); + return customEvent->toQuickJS(); +}; + +const WrapperTypeInfo customEventTypeInfo = { + "CustomEvent", + &eventTypeInfo, + customEventCreator }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc index ad55cc3e68..1fc1d243e1 100644 --- a/bridge/bindings/qjs/dom/document.cc +++ b/bridge/bindings/qjs/dom/document.cc @@ -53,14 +53,32 @@ void traverseNode(Node* node, TraverseHandler handler) { } } -std::once_flag kDocumentInitOnceFlag; - void bindDocument(std::unique_ptr& context) { + JSValue classObject = Document::constructor(context.get()); + JSValue prototype = Document::prototype(context.get()); + + // Install methods on prototype. + INSTALL_FUNCTION(Document, prototype, createEvent, 1); + INSTALL_FUNCTION(Document, prototype, createElement, 1); + INSTALL_FUNCTION(Document, prototype, createDocumentFragment, 0); + INSTALL_FUNCTION(Document, prototype, createTextNode, 1); + INSTALL_FUNCTION(Document, prototype, createComment, 1); + INSTALL_FUNCTION(Document, prototype, getElementById, 1); + INSTALL_FUNCTION(Document, prototype, getElementsByTagName, 1); + INSTALL_FUNCTION(Document, prototype, getElementsByClassName, 1); + + // Install readonly properties on prototype. + INSTALL_READONLY_PROPERTY(Document, prototype, nodeName); + INSTALL_READONLY_PROPERTY(Document, prototype, all); + INSTALL_READONLY_PROPERTY(Document, prototype, documentElement); + INSTALL_READONLY_PROPERTY(Document, prototype, children); + INSTALL_READONLY_PROPERTY(Document, prototype, head); -// auto* documentConstructor = Document::instance(context.get()); -// context->defineGlobalProperty("Document", documentConstructor->jsObject); -// JSValue documentInstance = JS_CallConstructor(context->ctx(), documentConstructor->jsObject, 0, nullptr); -// context->defineGlobalProperty("document", documentInstance); + // Install properties on prototype. + INSTALL_PROPERTY(Document, prototype, cookie); + INSTALL_PROPERTY(Document, prototype, body); + + context->defineGlobalProperty("Document", classObject); } JSClassID Document::classId{0}; @@ -75,6 +93,14 @@ Document* Document::create(JSContext* ctx) { return document; } +JSValue Document::constructor(ExecutionContext* context) { + return context->contextData()->constructorForType(&documentTypeInfo); +} + +JSValue Document::prototype(ExecutionContext* context) { + return context->contextData()->prototypeForType(&documentTypeInfo); +} + Document::Document() : Node() { if (!document_registered) { // defineElement("img", ImageElement::instance(m_context)); @@ -154,8 +180,8 @@ IMPL_FUNCTION(Document, createEvent)(JSContext* ctx, JSValue this_val, int argc, auto nativeEvent = new NativeEvent{nativeEventType.release()}; auto document = static_cast(JS_GetOpaque(this_val, Document::classId)); - auto e = Event::buildEventInstance(eventType, document->context(), nativeEvent, false); - return e->jsObject; + Event* event = Event::create(ctx, nativeEvent); + return event->toQuickJS(); } else { return JS_NULL; } @@ -172,12 +198,12 @@ IMPL_FUNCTION(Document, createElement)(JSContext* ctx, JSValue this_val, int arg } 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; +// auto* context = static_cast(JS_GetContextOpaque(ctx)); +// std::string tagName = jsValueToStdString(ctx, tagNameValue); +// JSValue constructor = static_cast(document->prototype())->getElementConstructor(document->context(), tagName); +// +// JSValue element = JS_CallConstructor(ctx, constructor, argc, argv); +// return element; } IMPL_FUNCTION(Document, createTextNode)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { @@ -186,7 +212,7 @@ IMPL_FUNCTION(Document, createTextNode)(JSContext* ctx, JSValue this_val, int ar } auto* document = static_cast(JS_GetOpaque(this_val, Document::classId)); - JSValue textNode = JS_CallConstructor(ctx, TextNode::instance(document->m_context)->jsObject, argc, argv); + JSValue textNode = JS_CallConstructor(ctx, TextNode::constructor(document->context()), argc, argv); return textNode; } diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/bindings/qjs/dom/document.h index b83cc42b17..feb7c44ed1 100644 --- a/bridge/bindings/qjs/dom/document.h +++ b/bridge/bindings/qjs/dom/document.h @@ -34,7 +34,9 @@ class DocumentCookie { class Document : public Node { public: static JSClassID classId; - Document* create(JSContext* ctx); + static Document* create(JSContext* ctx); + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); explicit Document(); DEFINE_FUNCTION(createEvent); @@ -73,14 +75,6 @@ class Document : public Node { std::unique_ptr m_cookie; ScriptAnimationController* m_scriptAnimationController; -// DEFINE_PROTOTYPE_FUNCTION(createEvent, 1); -// DEFINE_PROTOTYPE_FUNCTION(createElement, 1); -// DEFINE_PROTOTYPE_FUNCTION(createDocumentFragment, 0); -// DEFINE_PROTOTYPE_FUNCTION(createTextNode, 1); -// DEFINE_PROTOTYPE_FUNCTION(createComment, 1); -// DEFINE_PROTOTYPE_FUNCTION(getElementById, 1); -// DEFINE_PROTOTYPE_FUNCTION(getElementsByTagName, 1); -// DEFINE_PROTOTYPE_FUNCTION(getElementsByClassName, 1); void defineElement(const std::string& tagName, Element* constructor); diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc index 3957b66b91..6a082a7bd3 100644 --- a/bridge/bindings/qjs/dom/element.cc +++ b/bridge/bindings/qjs/dom/element.cc @@ -18,48 +18,47 @@ namespace kraken::binding::qjs { void bindElement(std::unique_ptr& context) { - auto* contextData = context->contextData(); - JSValue classObject = contextData->constructorForType(&elementTypeInfo); - JSValue prototypeObject = contextData->prototypeForType(&elementTypeInfo); + JSValue classObject = Element::constructor(context.get()); + JSValue prototype = Element::prototype(context.get()); // Install methods on prototype. - INSTALL_FUNCTION(Element, prototypeObject, getBoundingClientRect, 0); - INSTALL_FUNCTION(Element, prototypeObject, hasAttribute, 1); - INSTALL_FUNCTION(Element, prototypeObject, setAttribute, 2); - INSTALL_FUNCTION(Element, prototypeObject, getAttribute, 2); - INSTALL_FUNCTION(Element, prototypeObject, removeAttribute, 1); - INSTALL_FUNCTION(Element, prototypeObject, toBlob, 0); - INSTALL_FUNCTION(Element, prototypeObject, click, 2); - INSTALL_FUNCTION(Element, prototypeObject, scroll, 2); + INSTALL_FUNCTION(Element, prototype, getBoundingClientRect, 0); + INSTALL_FUNCTION(Element, prototype, hasAttribute, 1); + INSTALL_FUNCTION(Element, prototype, setAttribute, 2); + INSTALL_FUNCTION(Element, prototype, getAttribute, 2); + INSTALL_FUNCTION(Element, prototype, removeAttribute, 1); + INSTALL_FUNCTION(Element, prototype, toBlob, 0); + INSTALL_FUNCTION(Element, prototype, click, 2); + INSTALL_FUNCTION(Element, prototype, scroll, 2); // ScrollTo is same as scroll which reuse scroll functions. Macro expand is not support here. - installFunctionProperty(context.get(), prototypeObject, "scrollTo", Element::m_scroll_, 1); - INSTALL_FUNCTION(Element, prototypeObject, scrollBy, 2); + installFunctionProperty(context.get(), prototype, "scrollTo", Element::m_scroll_, 1); + INSTALL_FUNCTION(Element, prototype, scrollBy, 2); // Install Getter and Setter properties. // Install readonly properties. - INSTALL_READONLY_PROPERTY(Element, prototypeObject, nodeName); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, tagName); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, offsetLeft); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, offsetTop); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, offsetWidth); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, offsetHeight); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, clientWidth); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, clientHeight); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, clientTop); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, clientLeft); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, scrollHeight); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, scrollWidth); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, firstElementChild); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, lastElementChild); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, children); - INSTALL_READONLY_PROPERTY(Element, prototypeObject, attributes); + INSTALL_READONLY_PROPERTY(Element, prototype, nodeName); + INSTALL_READONLY_PROPERTY(Element, prototype, tagName); + INSTALL_READONLY_PROPERTY(Element, prototype, offsetLeft); + INSTALL_READONLY_PROPERTY(Element, prototype, offsetTop); + INSTALL_READONLY_PROPERTY(Element, prototype, offsetWidth); + INSTALL_READONLY_PROPERTY(Element, prototype, offsetHeight); + INSTALL_READONLY_PROPERTY(Element, prototype, clientWidth); + INSTALL_READONLY_PROPERTY(Element, prototype, clientHeight); + INSTALL_READONLY_PROPERTY(Element, prototype, clientTop); + INSTALL_READONLY_PROPERTY(Element, prototype, clientLeft); + INSTALL_READONLY_PROPERTY(Element, prototype, scrollHeight); + INSTALL_READONLY_PROPERTY(Element, prototype, scrollWidth); + INSTALL_READONLY_PROPERTY(Element, prototype, firstElementChild); + INSTALL_READONLY_PROPERTY(Element, prototype, lastElementChild); + INSTALL_READONLY_PROPERTY(Element, prototype, children); + INSTALL_READONLY_PROPERTY(Element, prototype, attributes); // Install properties. - INSTALL_PROPERTY(Element, prototypeObject, className); - INSTALL_PROPERTY(Element, prototypeObject, innerHTML); - INSTALL_PROPERTY(Element, prototypeObject, outerHTML); - INSTALL_PROPERTY(Element, prototypeObject, scrollTop); - INSTALL_PROPERTY(Element, prototypeObject, scrollLeft); + INSTALL_PROPERTY(Element, prototype, className); + INSTALL_PROPERTY(Element, prototype, innerHTML); + INSTALL_PROPERTY(Element, prototype, outerHTML); + INSTALL_PROPERTY(Element, prototype, scrollTop); + INSTALL_PROPERTY(Element, prototype, scrollLeft); context->defineGlobalProperty("Element", classObject); context->defineGlobalProperty("HTMLElement", JS_DupValue(context->ctx(), classObject)); @@ -68,7 +67,7 @@ void bindElement(std::unique_ptr& context) { bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance) { JSValue classObject = context->contextData()->constructorForType(&elementTypeInfo); if (JS_IsInstanceOf(context->ctx(), instance, classObject)) { - auto* elementInstance = static_cast(JS_GetOpaque(instance, Element::classId())); + auto* elementInstance = static_cast(JS_GetOpaque(instance, Element::classId)); std::string tagName = elementInstance->getRegisteredTagName(); // Special case for kraken official plugins. @@ -178,6 +177,14 @@ Element* Element::create(JSContext* ctx) { return element; } +JSValue Element::constructor(ExecutionContext* context) { + return context->contextData()->constructorForType(&elementTypeInfo); +} + +JSValue Element::prototype(ExecutionContext* context) { + return context->contextData()->prototypeForType(&elementTypeInfo); +} + //JSValue Element::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { // if (argc == 0) // return JS_ThrowTypeError(ctx, "Illegal constructor"); @@ -199,13 +206,13 @@ Element* Element::create(JSContext* ctx) { // 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())); +IMPL_FUNCTION(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->callNativeMethods("getBoundingClientRect", 0, nullptr); } -JSValue Element::hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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"); } @@ -216,7 +223,7 @@ JSValue Element::hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu 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* element = static_cast(JS_GetOpaque(this_val, Element::classId)); auto* attributes = element->m_attributes; const char* cname = JS_ToCString(ctx, nameValue); @@ -228,7 +235,7 @@ JSValue Element::hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu return result; } -JSValue Element::setAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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); } @@ -240,7 +247,7 @@ JSValue Element::setAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu 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* element = static_cast(JS_GetOpaque(this_val, Element::classId)); std::string name = jsValueToStdString(ctx, nameValue); std::transform(name.begin(), name.end(), name.begin(), ::tolower); @@ -263,14 +270,14 @@ JSValue Element::setAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu std::unique_ptr args_01 = stringToNativeString(name); std::unique_ptr args_02 = jsValueToNativeString(ctx, attributeValue); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); + element->context()->uiCommandBuffer()->addCommand(element->eventTargetId(), UICommand::setProperty, *args_01, *args_02, nullptr); JS_FreeValue(ctx, attributeValue); return JS_NULL; } -JSValue Element::getAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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"); } @@ -281,7 +288,7 @@ JSValue Element::getAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu 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* element = static_cast(JS_GetOpaque(this_val, Element::classId)); std::string name = jsValueToStdString(ctx, nameValue); auto* attributes = element->m_attributes; @@ -293,7 +300,7 @@ JSValue Element::getAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu return JS_NULL; } -JSValue Element::removeAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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"); } @@ -304,7 +311,7 @@ JSValue Element::removeAttribute(JSContext* ctx, JSValue this_val, int argc, JSV 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())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); std::string name = jsValueToStdString(ctx, nameValue); auto* attributes = element->m_attributes; @@ -315,13 +322,13 @@ JSValue Element::removeAttribute(JSContext* ctx, JSValue this_val, int argc, JSV JS_FreeValue(ctx, targetValue); std::unique_ptr args_01 = stringToNativeString(name); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::removeProperty, *args_01, nullptr); + element->context()->uiCommandBuffer()->addCommand(element->eventTargetId(), UICommand::removeProperty, *args_01, nullptr); } return JS_NULL; } -JSValue Element::toBlob(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(Element, toBlob)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { double devicePixelRatio = 1.0; if (argc > 0) { @@ -338,7 +345,7 @@ JSValue Element::toBlob(JSContext* ctx, JSValue this_val, int argc, JSValue* arg return JS_ThrowTypeError(ctx, "Failed to export blob: dart method (toBlob) is not registered."); } - auto* element = reinterpret_cast(JS_GetOpaque(this_val, Element::classId())); + 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) { @@ -397,13 +404,13 @@ JSValue Element::toBlob(JSContext* ctx, JSValue this_val, int argc, JSValue* arg return promise; } -JSValue Element::click(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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())); + auto element = static_cast(JS_GetOpaque(this_val, Element::classId)); return element->callNativeMethods("click", 0, nullptr); #elif UNIT_TEST - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto element = static_cast(JS_GetOpaque(this_val, Element::classId)); TEST_dispatchEvent(element, "click"); return JS_UNDEFINED; #else @@ -411,38 +418,38 @@ JSValue Element::click(JSContext* ctx, JSValue this_val, int argc, JSValue* argv #endif } -JSValue Element::scroll(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(Element, scroll)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0]), jsValueToNativeValue(ctx, argv[1])}; return element->callNativeMethods("scroll", 2, arguments); } -JSValue Element::scrollBy(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(Element, scrollBy)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0]), jsValueToNativeValue(ctx, argv[1])}; return element->callNativeMethods("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())); + 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())); + 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) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); return element->m_attributes->getAttribute("class"); } IMPL_PROPERTY_SETTER(Element, className)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); element->m_attributes->setAttribute("class", argv[0]); std::unique_ptr args_01 = stringToNativeString("class"); std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); @@ -454,103 +461,103 @@ enum class ViewModuleProperty { offsetTop, offsetLeft, offsetWidth, offsetHeight IMPL_PROPERTY_GETTER(Element, offsetLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetLeft))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, offsetTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetTop))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, offsetWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetWidth))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, offsetHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::offsetHeight))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, clientWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientWidth))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, clientHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientHeight))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, clientTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientTop))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, clientLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::clientLeft))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollTop))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_SETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollTop)), jsValueToNativeValue(ctx, argv[0])}; return element->callNativeMethods("setViewModuleProperty", 2, args); } IMPL_PROPERTY_GETTER(Element, scrollLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollLeft))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_SETTER(Element, scrollLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollLeft)), jsValueToNativeValue(ctx, argv[0])}; return element->callNativeMethods("setViewModuleProperty", 2, args); } IMPL_PROPERTY_GETTER(Element, scrollHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollHeight))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } IMPL_PROPERTY_GETTER(Element, scrollWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollWidth))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } // 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())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); int32_t len = arrayGetLength(ctx, element->childNodes); for (int i = 0; i < len; i++) { @@ -567,7 +574,7 @@ IMPL_PROPERTY_GETTER(Element, firstElementChild)(JSContext* ctx, JSValue this_va // 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())); + 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--) { @@ -583,7 +590,7 @@ IMPL_PROPERTY_GETTER(Element, lastElementChild)(JSContext* ctx, JSValue this_val } IMPL_PROPERTY_GETTER(Element, children)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); JSValue array = JS_NewArray(ctx); JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); @@ -605,16 +612,16 @@ IMPL_PROPERTY_GETTER(Element, children)(JSContext* ctx, JSValue this_val, int ar } IMPL_PROPERTY_GETTER(Element, attributes)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + 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())); + 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())); + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); const char* chtml = JS_ToCString(ctx, argv[0]); if (element->hasNodeFlag(NodeInstance::NodeFlag::IsTemplateElement)) { @@ -629,18 +636,18 @@ IMPL_PROPERTY_SETTER(Element, innerHTML)(JSContext* ctx, JSValue this_val, int a } IMPL_PROPERTY_GETTER(Element, outerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); + 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 Element::classID() { - return Element::classId(); -} +//JSClassID Element::classId { +// return Element::classId; +//} -Element::~Element() {} +//Element::~Element() {} JSValue Element::internalGetTextContent() { JSValue array = JS_NewArray(m_ctx); @@ -880,20 +887,19 @@ void Element::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { NodeInstance::trace(rt, val, mark_func); } -Element::Element(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); - } -} +//Element::Element(Element* element, std::string tagName, bool shouldAddUICommand): Node() { +// 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 Element::exoticMethods{nullptr, nullptr, nullptr, nullptr, hasProperty, getProperty, setProperty}; @@ -901,6 +907,13 @@ StyleDeclarationInstance* Element::style() { return m_style; } +void Element::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + +} +void Element::dispose() const { + +} + 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); diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h index 28577ee00b..126bd0b305 100644 --- a/bridge/bindings/qjs/dom/element.h +++ b/bridge/bindings/qjs/dom/element.h @@ -8,7 +8,7 @@ #include #include "bindings/qjs/garbage_collected.h" -#include "bindings/qjs/host_object.h" +#include "bindings/qjs/executing_context.h" #include "node.h" #include "style_declaration.h" @@ -74,6 +74,8 @@ class Element : public Node { public: static JSClassID classId; static Element* create(JSContext* ctx); + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); DEFINE_FUNCTION(getBoundingClientRect); DEFINE_FUNCTION(hasAttribute); @@ -137,11 +139,6 @@ class Element : public Node { friend class Node; }; -struct PersistElement { - Element* element; - list_head link; -}; - auto elementCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { if (argc == 0) { return JS_ThrowTypeError(ctx, "Illegal constructor"); diff --git a/bridge/bindings/qjs/dom/event.cc b/bridge/bindings/qjs/dom/event.cc index 6f6156c683..416a4c203b 100644 --- a/bridge/bindings/qjs/dom/event.cc +++ b/bridge/bindings/qjs/dom/event.cc @@ -11,135 +11,170 @@ namespace kraken::binding::qjs { -std::once_flag kEventInitOnceFlag; - void bindEvent(std::unique_ptr& context) { - auto* constructor = Event::instance(context.get()); - context->defineGlobalProperty("Event", constructor->jsObject); + JSValue constructor = Event::constructor(context.get()); + JSValue prototype = Event::prototype(context.get()); + + // Install readonly properties. + INSTALL_READONLY_PROPERTY(Event, prototype, type); + INSTALL_READONLY_PROPERTY(Event, prototype, bubbles); + INSTALL_READONLY_PROPERTY(Event, prototype, cancelable); + INSTALL_READONLY_PROPERTY(Event, prototype, timestamp); + INSTALL_READONLY_PROPERTY(Event, prototype, bubbles); + INSTALL_READONLY_PROPERTY(Event, prototype, defaultPrevented); + INSTALL_READONLY_PROPERTY(Event, prototype, target); + INSTALL_READONLY_PROPERTY(Event, prototype, srcElement); + INSTALL_READONLY_PROPERTY(Event, prototype, currentTarget); + INSTALL_READONLY_PROPERTY(Event, prototype, returnValue); + INSTALL_READONLY_PROPERTY(Event, prototype, cancelBubble); + + // Install functions + INSTALL_FUNCTION(Event, prototype, stopPropagation, 0); + INSTALL_FUNCTION(Event, prototype, stopImmediatePropagation, 0); + INSTALL_FUNCTION(Event, prototype, preventDefault, 1); + INSTALL_FUNCTION(Event, prototype, initEvent, 3); + + context->defineGlobalProperty("Event", constructor); } -JSClassID Event::kEventClassID{0}; +JSClassID Event::classId{0}; + +Event* Event::create(JSContext* ctx) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(&eventTypeInfo); + + auto* event = makeGarbageCollected()->initialize(ctx, &classId); + + // Let eventTarget instance inherit EventTarget prototype methods. + JS_SetPrototype(ctx, event->toQuickJS(), prototype); -Event::Event(ExecutionContext* context) : HostClass(context, "Event") { - std::call_once(kEventInitOnceFlag, []() { JS_NewClassID(&kEventClassID); }); + return event; } -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."); - } +Event* Event::create(JSContext* ctx, NativeEvent* nativeEvent) { + auto* event = create(ctx); + event->nativeEvent = nativeEvent; + return event; +} + +Event::Event() { - JSValue eventTypeValue = argv[0]; - std::string eventType = jsValueToStdString(ctx, eventTypeValue); +} +Event::Event(NativeEvent* nativeEvent): nativeEvent(nativeEvent) {} +Event::Event(JSValue eventType, JSValue eventInit) { + +} - auto* nativeEvent = new NativeEvent{stringToNativeString(eventType).release()}; - auto* event = Event::buildEventInstance(eventType, m_context, nativeEvent, false); - return event->jsObject; +JSValue Event::constructor(ExecutionContext* context) { + return context->contextData()->constructorForType(&eventTypeInfo); +} + +JSValue Event::prototype(ExecutionContext* context) { + return context->contextData()->prototypeForType(&eventTypeInfo); } 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* eventInstance = static_cast(JS_GetOpaque(this_val, Event::classId)); return JS_NewUnicodeString(eventInstance->context()->runtime(), eventInstance->context()->ctx(), eventInstance->nativeEvent->type->string, eventInstance->nativeEvent->type->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); + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); + return JS_NewBool(ctx, event->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); + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); + return JS_NewBool(ctx, event->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); + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); + return JS_NewInt64(ctx, event->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()); + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); + return JS_NewBool(ctx, event->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->nativeEvent->target != nullptr) { - auto instance = reinterpret_cast(eventInstance->nativeEvent->target); - return JS_DupValue(ctx, instance->jsObject); + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); + if (event->nativeEvent->target != nullptr) { + auto eventTarget = reinterpret_cast(event->nativeEvent->target); + return JS_DupValue(ctx, eventTarget->toQuickJS()); } 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->nativeEvent->target != nullptr) { - auto instance = reinterpret_cast(eventInstance->nativeEvent->target); - return JS_DupValue(ctx, instance->jsObject); + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); + if (event->nativeEvent->target != nullptr) { + auto eventTarget = reinterpret_cast(event->nativeEvent->target); + return JS_DupValue(ctx, eventTarget->toQuickJS()); } 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->nativeEvent->currentTarget != nullptr) { - auto instance = reinterpret_cast(eventInstance->nativeEvent->currentTarget); - return JS_DupValue(ctx, instance->jsObject); + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); + if (event->nativeEvent->currentTarget != nullptr) { + auto eventTarget = reinterpret_cast(event->nativeEvent->currentTarget); + return JS_DupValue(ctx, eventTarget->toQuickJS()); } 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()); + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); + return JS_NewBool(ctx, !event->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; -} + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); + return JS_NewBool(ctx, event->cancelled()); +} + +//Event* Event::buildEvent(JSValue eventType, JSContext* ctx, void* nativeEvent, bool isCustomEvent) { +// Event* event; +// if (isCustomEvent) { +// event = CustomEvent::create(ctx, reinterpret_cast(nativeEvent), eventType); +// } else if (m_eventCreatorMap.count(eventType) > 0) { +// event = m_eventCreatorMap[eventType](ctx, nativeEvent); +// } else { +// event = Event::create(ctx, static_cast(nativeEvent)); +// } +// return event; +//} 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)); +IMPL_FUNCTION(Event, stopPropagation)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); 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)); +IMPL_FUNCTION(Event, stopImmediatePropagation)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); 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)); +IMPL_FUNCTION(Event, preventDefault)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); if (event->nativeEvent->cancelable) { event->m_cancelled = true; } return JS_NULL; } -JSValue Event::initEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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."); } @@ -159,7 +194,7 @@ JSValue Event::initEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* ar return JS_ThrowTypeError(ctx, "Failed to initEvent: type should be a string."); } - auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); + auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); event->nativeEvent->type = jsValueToNativeString(ctx, typeValue).release(); if (!JS_IsNull(bubblesValue)) { @@ -171,41 +206,10 @@ JSValue Event::initEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* ar return JS_NULL; } -EventInstance* EventInstance::fromNativeEvent(Event* event, NativeEvent* nativeEvent) { - return new EventInstance(event, nativeEvent); -} - -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); - nativeEvent = new NativeEvent{jsValueToNativeString(m_ctx, v).release()}; - JS_FreeValue(m_ctx, v); +void Event::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const {} - 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)); - if (event->context()->isValid()) { - JS_FreeValue(event->m_ctx, event->jsObject); - } - delete event; +void Event::dispose() const { + delete nativeEvent; } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event.h b/bridge/bindings/qjs/dom/event.h index e161ed623d..35bc07fa46 100644 --- a/bridge/bindings/qjs/dom/event.h +++ b/bridge/bindings/qjs/dom/event.h @@ -52,49 +52,9 @@ namespace kraken::binding::qjs { void bindEvent(std::unique_ptr& context); -class EventInstance; +class Event; -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; -}; +using EventCreator = Event* (*)(JSContext* ctx, void* nativeEvent); struct NativeEvent { NativeString* type{nullptr}; @@ -113,29 +73,69 @@ struct RawEvent { int64_t length; }; -class EventInstance : public Instance { +class Event : public GarbageCollected { public: - EventInstance() = delete; - ~EventInstance() override { delete nativeEvent; } + static JSClassID classId; + static Event* create(JSContext* ctx); + static Event* create(JSContext* ctx, NativeEvent* nativeEvent); + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); - static EventInstance* fromNativeEvent(Event* event, NativeEvent* nativeEvent); - NativeEvent* nativeEvent{nullptr}; + explicit Event(); + explicit Event(NativeEvent* nativeEvent); + explicit Event(JSValue eventType, JSValue eventInit); + + static void defineEvent(const std::string& eventType, EventCreator creator); + + 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_FUNCTION(stopPropagation); + DEFINE_FUNCTION(stopImmediatePropagation); + DEFINE_FUNCTION(preventDefault); + DEFINE_FUNCTION(initEvent); inline const bool propagationStopped() { return m_propagationStopped; } inline const bool cancelled() { return m_cancelled; } inline void cancelled(bool v) { m_cancelled = v; } inline const bool propagationImmediatelyStopped() { return m_propagationImmediatelyStopped; } - protected: - explicit EventInstance(Event* jsEvent, JSAtom eventType, JSValue eventInit); - explicit EventInstance(Event* jsEvent, NativeEvent* nativeEvent); + void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; + void dispose() const override; + + NativeEvent* nativeEvent{nullptr}; + private: + static std::unordered_map m_eventCreatorMap; bool m_cancelled{false}; bool m_propagationStopped{false}; bool m_propagationImmediatelyStopped{false}; +}; - private: - static void finalizer(JSRuntime* rt, JSValue val); - friend Event; +auto eventCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { + if (argc < 1) { + return JS_ThrowTypeError(ctx, "Failed to construct 'Event': 1 argument required, but only 0 present."); + } + + JSValue eventTypeValue = argv[0]; + auto nativeEventType = jsValueToNativeString(ctx, eventTypeValue); + auto* nativeEvent = new NativeEvent{nativeEventType.release()}; + + auto* event = Event::create(ctx, nativeEvent); + return event->toQuickJS(); +}; + +const WrapperTypeInfo eventTypeInfo = { + "Event", + nullptr, + eventCreator }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/bindings/qjs/dom/event_target.cc index b5958c687b..c823ab9b04 100644 --- a/bridge/bindings/qjs/dom/event_target.cc +++ b/bridge/bindings/qjs/dom/event_target.cc @@ -20,9 +20,8 @@ static std::atomic globalEventTargetId{0}; #define GetPropertyCallPreFix "_getProperty_" void bindEventTarget(std::unique_ptr& context) { - auto* contextData = context->contextData(); - JSValue constructor = contextData->constructorForType(&eventTargetTypeInfo); - JSValue prototypeObject = contextData->prototypeForType(&eventTargetTypeInfo); + JSValue constructor = EventTarget::constructor(context.get()); + JSValue prototypeObject = EventTarget::prototype(context.get()); INSTALL_FUNCTION(EventTarget, prototypeObject, addEventListener, 3); INSTALL_FUNCTION(EventTarget, prototypeObject, removeEventListener, 2); @@ -42,7 +41,7 @@ IMPL_FUNCTION(EventTarget, addEventListener)(JSContext* ctx, JSValue this_val, i return JS_ThrowTypeError(ctx, "Failed to addEventListener: type and listener are required."); } - auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + auto* eventTarget = static_cast(JS_GetOpaque(this_val, EventTarget::classId)); if (eventTarget == nullptr) { return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); } @@ -80,7 +79,7 @@ IMPL_FUNCTION(EventTarget, removeEventListener)(JSContext* ctx, JSValue this_val return JS_ThrowTypeError(ctx, "Failed to removeEventListener: at least type and listener are required."); } - auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + auto* eventTarget = static_cast(JS_GetOpaque(this_val, EventTarget::classId)); if (eventTarget == nullptr) { return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); } @@ -121,14 +120,14 @@ IMPL_FUNCTION(EventTarget, dispatchEvent)(JSContext* ctx, JSValue this_val, int return JS_ThrowTypeError(ctx, "Failed to dispatchEvent: first arguments should be an event object"); } - auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); + auto* eventTarget = static_cast(JS_GetOpaque(this_val, EventTarget::classId)); if (eventTarget == 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, JSValueGetClassId(eventValue))); - return JS_NewBool(ctx, eventTarget->dispatchEvent(eventInstance)); + auto event = reinterpret_cast(JS_GetOpaque(eventValue, EventTarget::classId)); + return JS_NewBool(ctx, eventTarget->dispatchEvent(event)); } EventTarget* EventTarget::create(JSContext* ctx) { @@ -142,7 +141,15 @@ EventTarget* EventTarget::create(JSContext* ctx) { return eventTarget; } -bool EventTarget::dispatchEvent(EventInstance* event) { +JSValue EventTarget::constructor(ExecutionContext* context) { + return context->contextData()->constructorForType(&eventTargetTypeInfo); +} + +JSValue EventTarget::prototype(ExecutionContext* context) { + return context->contextData()->prototypeForType(&eventTargetTypeInfo); +} + +bool EventTarget::dispatchEvent(Event* event) { std::u16string u16EventType = std::u16string(reinterpret_cast(event->nativeEvent->type->string), event->nativeEvent->type->length); std::string eventType = toUTF8(u16EventType); @@ -154,14 +161,14 @@ bool EventTarget::dispatchEvent(EventInstance* event) { // Bubble event to root event target. if (event->nativeEvent->bubbles == 1 && !event->propagationStopped()) { auto node = reinterpret_cast(this); - auto* parent = static_cast(JS_GetOpaque(node->parentNode, Node::classId(node->parentNode))); + auto* parent = static_cast(JS_GetOpaque(node->parentNode, JSValueGetClassId(node->parentNode))); if (parent != nullptr) { parent->dispatchEvent(event); } else { // Window does not inherit from Node, so it is not in the Node tree and needs to continue passing to the Window when it bubbles to Document. JSValue globalObjectValue = JS_GetGlobalObject(m_ctx); - auto* window = static_cast(JS_GetOpaque(globalObjectValue, Window::classId())); + auto* window = static_cast(JS_GetOpaque(globalObjectValue, JSValueGetClassId(globalObjectValue))); window->internalDispatchEvent(event); JS_FreeValue(m_ctx, globalObjectValue); } @@ -172,28 +179,32 @@ bool EventTarget::dispatchEvent(EventInstance* event) { return event->cancelled(); } -bool EventTarget::internalDispatchEvent(EventInstance* eventInstance) { - std::u16string u16EventType = std::u16string(reinterpret_cast(eventInstance->nativeEvent->type->string), eventInstance->nativeEvent->type->length); +bool EventTarget::internalDispatchEvent(Event* event) { + std::u16string u16EventType = std::u16string(reinterpret_cast(event->nativeEvent->type->string), event->nativeEvent->type->length); std::string eventTypeStr = toUTF8(u16EventType); JSAtom eventType = JS_NewAtom(m_ctx, eventTypeStr.c_str()); // Modify the currentTarget to this. - eventInstance->nativeEvent->currentTarget = this; + event->nativeEvent->currentTarget = this; // Dispatch event listeners writen by addEventListener - auto _dispatchEvent = [&eventInstance, this](JSValue handler) { + auto _dispatchEvent = [&event, this](JSValue handler) { if (!JS_IsFunction(m_ctx, handler)) return; - if (eventInstance->propagationImmediatelyStopped()) + if (event->propagationImmediatelyStopped()) return; /* 'handler' might be destroyed when calling itself (if it frees the handler), so must take extra care */ JS_DupValue(m_ctx, handler); + JSValue arguments[] = { + event->toQuickJS() + }; + // The third params `thisObject` to null equals global object. - JSValue returnedValue = JS_Call(m_ctx, handler, JS_NULL, 1, &eventInstance->jsObject); + JSValue returnedValue = JS_Call(m_ctx, handler, JS_NULL, 1, arguments); JS_FreeValue(m_ctx, handler); context()->handleException(&returnedValue); @@ -214,15 +225,15 @@ bool EventTarget::internalDispatchEvent(EventInstance* eventInstance) { bool specialErrorEventHanding = eventTypeStr == "error"; if (specialErrorEventHanding) { - auto _dispatchErrorEvent = [&eventInstance, this, eventTypeStr](JSValue handler) { - JSValue error = JS_GetPropertyStr(m_ctx, eventInstance->jsObject, "error"); + auto _dispatchErrorEvent = [&event, this, eventTypeStr](JSValue handler) { + JSValue error = JS_GetPropertyStr(m_ctx, event->toQuickJS(), "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}; - JS_Call(m_ctx, handler, eventInstance->jsObject, 5, args); + JS_Call(m_ctx, handler, event->toQuickJS(), 5, args); context()->drainPendingPromiseJobs(); JS_FreeValue(m_ctx, error); @@ -241,11 +252,11 @@ bool EventTarget::internalDispatchEvent(EventInstance* eventInstance) { // do not dispatch event when event has been canceled // true is prevented. - return eventInstance->cancelled(); + return event->cancelled(); } int EventTarget::hasProperty(JSContext* ctx, JSValue obj, JSAtom atom) { - auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* eventTarget = static_cast(JS_GetOpaque(obj, EventTarget::classId)); JSValue prototype = eventTarget->context()->contextData()->prototypeForType(&eventTargetTypeInfo); if (JS_HasProperty(ctx, prototype, atom)) @@ -303,7 +314,7 @@ JSValue EventTarget::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSVal } int EventTarget::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { - auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* eventTarget = static_cast(JS_GetOpaque(obj, EventTarget::classId)); JSValue prototype = JS_GetPrototype(ctx, eventTarget->jsObject); // Check there are setter functions on prototype. @@ -440,10 +451,11 @@ void NativeEventTarget::dispatchEventImpl(NativeEventTarget* nativeEventTarget, // 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); - eventInstance->nativeEvent->target = eventTarget; - eventTarget->dispatchEvent(eventInstance); - JS_FreeValue(context->ctx(), eventInstance->jsObject); + Event* event = Event::create(); +// Event* event = Event::buildEventInstance(eventType, context, nativeEvent, isCustomEvent == 1); +// eventInstance->nativeEvent->target = eventTarget; +// eventTarget->dispatchEvent(eventInstance); +// JS_FreeValue(context->ctx(), eventInstance->jsObject); } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/bindings/qjs/dom/event_target.h index f293bfb42a..5e1f80f991 100644 --- a/bridge/bindings/qjs/dom/event_target.h +++ b/bridge/bindings/qjs/dom/event_target.h @@ -59,6 +59,8 @@ class EventTarget : public GarbageCollected { EventTarget(); static JSClassID classId; static EventTarget* create(JSContext* ctx); + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); DEFINE_FUNCTION(addEventListener); DEFINE_FUNCTION(removeEventListener); @@ -67,7 +69,7 @@ class EventTarget : public GarbageCollected { void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; void dispose() const override; - virtual bool dispatchEvent(EventInstance* event); + virtual bool dispatchEvent(Event* event); FORCE_INLINE int32_t eventTargetId() const { return m_eventTargetId; } protected: @@ -86,7 +88,7 @@ class EventTarget : public GarbageCollected { static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); static int deleteProperty(JSContext* ctx, JSValueConst obj, JSAtom prop); - bool internalDispatchEvent(EventInstance* eventInstance); + bool internalDispatchEvent(Event* event); int32_t m_eventTargetId; // EventListener handlers registered with addEventListener API. diff --git a/bridge/bindings/qjs/dom/node.h b/bridge/bindings/qjs/dom/node.h index ddc1b43a71..8b6795777c 100644 --- a/bridge/bindings/qjs/dom/node.h +++ b/bridge/bindings/qjs/dom/node.h @@ -91,7 +91,6 @@ class Node : public EventTarget { virtual void internalSetTextContent(JSValue content); JSValue internalReplaceChild(Node* newChild, Node* oldChild); - virtual void _notifyNodeRemoved(Node* node); virtual void _notifyNodeInsert(Node* node); private: diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index 6fce87e47d..aebb018d2e 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -15,6 +15,8 @@ namespace kraken::binding::qjs { template class MakeGarbageCollectedTrait; +class ExecutionContext; + /** * Base class for GC managed objects. Only descendent types of `GarbageCollected` * can be constructed using `MakeGarbageCollected()`. Must be inherited from as @@ -67,8 +69,8 @@ class GarbageCollected { FORCE_INLINE JSValue toQuickJS() { return jsObject; }; - FORCE_INLINE JSContext* ctx() { return m_ctx; } - FORCE_INLINE ExecutionContext* context() const { return static_cast(JS_GetContextOpaque(m_ctx)); }; + FORCE_INLINE JSContext* ctx() { return m_ctx; }; + FORCE_INLINE ExecutionContext* context() { return static_cast(JS_GetContextOpaque(m_ctx)); }; protected: JSValue jsObject{JS_NULL}; diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/bindings/qjs/html_parser.cc index 4dc56ec44e..4694424196 100644 --- a/bridge/bindings/qjs/html_parser.cc +++ b/bridge/bindings/qjs/html_parser.cc @@ -18,7 +18,7 @@ inline std::string trim(std::string& str) { return str; } -void HTMLParser::traverseHTML(NodeInstance* root, GumboNode* node) { +void HTMLParser::traverseHTML(Node* root, GumboNode* node) { ExecutionContext* context = root->context(); JSContext* ctx = context->ctx(); diff --git a/bridge/bindings/qjs/html_parser.h b/bridge/bindings/qjs/html_parser.h index 1b29853287..7e01625e8f 100644 --- a/bridge/bindings/qjs/html_parser.h +++ b/bridge/bindings/qjs/html_parser.h @@ -15,13 +15,13 @@ namespace kraken::binding::qjs { class HTMLParser { public: - static bool parseHTML(const char* code, size_t codeLength, NodeInstance* rootNode); - static bool parseHTML(std::string html, NodeInstance* rootNode); + static bool parseHTML(const char* code, size_t codeLength, Node* rootNode); + static bool parseHTML(std::string html, Node* rootNode); private: ExecutionContext* m_context; - static void traverseHTML(NodeInstance* root, GumboNode* node); - static void parseProperty(ElementInstance* element, GumboElement* gumboElement); + static void traverseHTML(Node* root, GumboNode* node); + static void parseProperty(Element* element, GumboElement* gumboElement); }; } // namespace kraken::binding::qjs diff --git a/bridge/polyfill/scripts/js_to_c.js b/bridge/polyfill/scripts/js_to_c.js index 6f3fa43b71..764dbb9100 100644 --- a/bridge/polyfill/scripts/js_to_c.js +++ b/bridge/polyfill/scripts/js_to_c.js @@ -68,11 +68,11 @@ ${getPolyFillJavaScriptSource(source)} void initKraken${outputName}(kraken::KrakenPage *page) { ${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/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index eed50920a9..af3a001315 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -275,7 +275,7 @@ void TEST_runLoop(ExecutionContext* context) { } } -void TEST_dispatchEvent(EventTargetInstance* eventTarget, const std::string type) { +void TEST_dispatchEvent(EventTarget* eventTarget, const std::string type) { NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); auto nativeEventType = stringToNativeString(type); NativeEvent* nativeEvent = new NativeEvent(); diff --git a/bridge/test/kraken_test_env.h b/bridge/test/kraken_test_env.h index 9085aa8717..9484d027e0 100644 --- a/bridge/test/kraken_test_env.h +++ b/bridge/test/kraken_test_env.h @@ -21,7 +21,7 @@ std::unique_ptr TEST_init(OnJSError onJsError); std::unique_ptr TEST_init(); std::unique_ptr TEST_allocateNewPage(); void TEST_runLoop(ExecutionContext* context); -void TEST_dispatchEvent(EventTargetInstance* eventTarget, const std::string type); +void TEST_dispatchEvent(EventTarget* eventTarget, const std::string type); void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); #endif // KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ From 16bdee7d59ae53caf162e19cc12d81a25095848b Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Mon, 24 Jan 2022 21:29:32 +0800 Subject: [PATCH 004/498] refactor: remove host class and host object files. --- bridge/CMakeLists.txt | 3 - bridge/bindings/qjs/bom/blob.h | 3 +- bridge/bindings/qjs/bom/console_test.cc | 2 - .../bindings/qjs/bom/dom_timer_coordinator.h | 2 +- bridge/bindings/qjs/bom/location.cc | 2 +- bridge/bindings/qjs/bom/location.h | 3 +- bridge/bindings/qjs/bom/screen.h | 5 + .../{js_context_macros.h => context_macros.h} | 6 +- bridge/bindings/qjs/dom/custom_event.cc | 83 +-- bridge/bindings/qjs/dom/custom_event.h | 9 +- bridge/bindings/qjs/dom/event.cc | 3 - bridge/bindings/qjs/dom/event.h | 4 +- bridge/bindings/qjs/dom/event_target.cc | 10 +- bridge/bindings/qjs/dom/event_target.h | 8 +- bridge/bindings/qjs/dom/style_declaration.h | 52 +- bridge/bindings/qjs/executing_context.h | 12 +- bridge/bindings/qjs/executing_context_data.cc | 15 +- bridge/bindings/qjs/executing_context_data.h | 7 +- bridge/bindings/qjs/garbage_collected.h | 2 +- bridge/bindings/qjs/garbage_collected_test.cc | 575 ++++++++++++++++++ bridge/bindings/qjs/host_class.h | 148 ----- bridge/bindings/qjs/host_class_test.cc | 413 ------------- bridge/bindings/qjs/host_object.cc | 17 - bridge/bindings/qjs/host_object.h | 88 --- bridge/bindings/qjs/host_object_test.cc | 161 ----- bridge/bindings/qjs/native_value.cc | 15 + bridge/bindings/qjs/native_value.h | 15 +- bridge/foundation/ui_command_buffer.h | 2 - bridge/include/dart_methods.h | 1 - bridge/include/kraken_bridge.h | 14 +- bridge/include/kraken_foundation.h | 1 + bridge/kraken_bridge.cc | 14 - bridge/page.cc | 46 +- bridge/test/test.cmake | 3 +- 34 files changed, 740 insertions(+), 1004 deletions(-) rename bridge/bindings/qjs/{js_context_macros.h => context_macros.h} (96%) create mode 100644 bridge/bindings/qjs/garbage_collected_test.cc delete mode 100644 bridge/bindings/qjs/host_class.h delete mode 100644 bridge/bindings/qjs/host_class_test.cc delete mode 100644 bridge/bindings/qjs/host_object.cc delete mode 100644 bridge/bindings/qjs/host_object.h delete mode 100644 bridge/bindings/qjs/host_object_test.cc diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 8a40e69787..08ff65fd3e 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -192,9 +192,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/heap_hashmap.h bindings/qjs/native_value.cc bindings/qjs/native_value.h - bindings/qjs/host_object.h - bindings/qjs/host_object.cc - bindings/qjs/host_class.h bindings/qjs/qjs_patch.cc bindings/qjs/qjs_patch.h bindings/qjs/module_manager.cc diff --git a/bridge/bindings/qjs/bom/blob.h b/bridge/bindings/qjs/bom/blob.h index e34fafe7fc..5639f0126c 100644 --- a/bridge/bindings/qjs/bom/blob.h +++ b/bridge/bindings/qjs/bom/blob.h @@ -6,7 +6,8 @@ #ifndef KRAKENBRIDGE_BLOB_H #define KRAKENBRIDGE_BLOB_H -#include "bindings/qjs/host_class.h" +#include "bindings/qjs/context_macros.h" +#include "bindings/qjs/garbage_collected.h" namespace kraken::binding::qjs { diff --git a/bridge/bindings/qjs/bom/console_test.cc b/bridge/bindings/qjs/bom/console_test.cc index c64ff2c01b..0e986de4e6 100644 --- a/bridge/bindings/qjs/bom/console_test.cc +++ b/bridge/bindings/qjs/bom/console_test.cc @@ -8,8 +8,6 @@ #include "kraken_test_env.h" #include "page.h" -std::once_flag kGlobalClassIdFlag; - TEST(Console, rawPrintShouldWork) { static bool logExecuted = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.h b/bridge/bindings/qjs/bom/dom_timer_coordinator.h index e736fcb38c..cd3fcbb54b 100644 --- a/bridge/bindings/qjs/bom/dom_timer_coordinator.h +++ b/bridge/bindings/qjs/bom/dom_timer_coordinator.h @@ -8,12 +8,12 @@ #include #include -#include "bindings/qjs/executing_context.h" #include namespace kraken::binding::qjs { class DOMTimer; +class ExecutionContext; // Maintains a set of DOMTimers for a given page // DOMTimerCoordinator assigns IDs to timers; these IDs are diff --git a/bridge/bindings/qjs/bom/location.cc b/bridge/bindings/qjs/bom/location.cc index 40e4f77847..85f8b77aa0 100644 --- a/bridge/bindings/qjs/bom/location.cc +++ b/bridge/bindings/qjs/bom/location.cc @@ -26,7 +26,7 @@ Location* Location::create(JSContext* ctx) { return makeGarbageCollected()->initialize(ctx, &classId); } -JSValue Location::reload(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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."); diff --git a/bridge/bindings/qjs/bom/location.h b/bridge/bindings/qjs/bom/location.h index 50e1a99890..0cbb6054fc 100644 --- a/bridge/bindings/qjs/bom/location.h +++ b/bridge/bindings/qjs/bom/location.h @@ -18,7 +18,8 @@ class Location : public GarbageCollected { public: static JSClassID classId; static Location* create(JSContext* ctx); - static JSValue reload(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); + + DEFINE_FUNCTION(reload); void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; void dispose() const override; diff --git a/bridge/bindings/qjs/bom/screen.h b/bridge/bindings/qjs/bom/screen.h index 69e84b60f2..a5c4a5da24 100644 --- a/bridge/bindings/qjs/bom/screen.h +++ b/bridge/bindings/qjs/bom/screen.h @@ -12,6 +12,11 @@ namespace kraken::binding::qjs { +struct NativeScreen { + double width; + double height; +}; + class Screen : public HostObject { public: explicit Screen(ExecutionContext* context) : HostObject(context, "Screen"){}; diff --git a/bridge/bindings/qjs/js_context_macros.h b/bridge/bindings/qjs/context_macros.h similarity index 96% rename from bridge/bindings/qjs/js_context_macros.h rename to bridge/bindings/qjs/context_macros.h index e514f28cbe..fedcd3ef9a 100644 --- a/bridge/bindings/qjs/js_context_macros.h +++ b/bridge/bindings/qjs/context_macros.h @@ -3,8 +3,8 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_JS_CONTEXT_MACROS_H -#define KRAKENBRIDGE_JS_CONTEXT_MACROS_H +#ifndef KRAKENBRIDGE_CONTEXT_MACROS_H +#define KRAKENBRIDGE_CONTEXT_MACROS_H #define QJS_GLOBAL_BINDING_FUNCTION(context, function, name, argc) \ { \ @@ -56,4 +56,4 @@ static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ }; \ -#endif // KRAKENBRIDGE_JS_CONTEXT_MACROS_H +#endif // KRAKENBRIDGE_CONTEXT_MACROS_H diff --git a/bridge/bindings/qjs/dom/custom_event.cc b/bridge/bindings/qjs/dom/custom_event.cc index 705e7cccdb..7730a466ee 100644 --- a/bridge/bindings/qjs/dom/custom_event.cc +++ b/bridge/bindings/qjs/dom/custom_event.cc @@ -3,8 +3,9 @@ * Author: Kraken Team. */ -#include "custom_event.h" #include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/native_value.h" +#include "custom_event.h" #include "kraken_bridge.h" #include @@ -12,8 +13,16 @@ namespace kraken::binding::qjs { void bindCustomEvent(std::unique_ptr& context) { -// auto* constructor = CustomEvent::instance(context.get()); -// context->defineGlobalProperty("CustomEvent", constructor->jsObject); + JSValue constructor = context->contextData()->constructorForType(&customEventTypeInfo); + JSValue prototype = context->contextData()->prototypeForType(&customEventTypeInfo); + + // Install methods on prototype. + INSTALL_FUNCTION(CustomEvent, prototype, initCustomEvent, 4); + + // Install readonly properties on prototype. + INSTALL_READONLY_PROPERTY(CustomEvent, prototype, detail); + + context->defineGlobalProperty("CustomEvent", constructor); } JSClassID CustomEvent::classId{0}; @@ -22,7 +31,7 @@ CustomEvent* CustomEvent::create(JSContext* ctx, JSValue eventType, JSValue init auto* context = static_cast(JS_GetContextOpaque(ctx)); JSValue prototype = context->contextData()->prototypeForType(&eventTypeInfo); - auto* event = makeGarbageCollected()->initialize(ctx, &classId); + auto* event = makeGarbageCollected(eventType, init)->initialize(ctx, &classId); if (!JS_IsNull(init)) { JSAtom detailKey = JS_NewAtom(ctx, "detail"); @@ -34,7 +43,19 @@ CustomEvent* CustomEvent::create(JSContext* ctx, JSValue eventType, JSValue init JS_FreeAtom(ctx, detailKey); } - // Let eventTarget instance inherit EventTarget prototype methods. + // Let instance inherit prototype methods. + JS_SetPrototype(ctx, event->toQuickJS(), prototype); + + return event; +} + +CustomEvent * CustomEvent::create(JSContext* ctx, NativeCustomEvent* nativeCustomEvent) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + JSValue prototype = context->contextData()->prototypeForType(&eventTypeInfo); + + auto* event = makeGarbageCollected(nativeCustomEvent)->initialize(ctx, &classId); + + // Let instance inherit prototype methods. JS_SetPrototype(ctx, event->toQuickJS(), prototype); return event; @@ -48,24 +69,39 @@ JSValue CustomEvent::prototype(ExecutionContext* context) { return context->contextData()->prototypeForType(&customEventTypeInfo); } -CustomEvent::CustomEvent(JSValue eventType, JSValue eventInit) { +CustomEvent::CustomEvent(JSValue eventType, JSValue eventInit): Event(eventType, eventInit) { if (!JS_IsNull(eventInit)) { JSAtom detailKey = JS_NewAtom(m_ctx, "detail"); if (JS_HasProperty(m_ctx, eventInit, detailKey)) { JSValue detailValue = JS_GetProperty(m_ctx, eventInit, detailKey); - m_detail.value(detailValue); + m_detail = JS_DupValue(m_ctx, detailValue); JS_FreeValue(m_ctx, detailValue); } JS_FreeAtom(m_ctx, detailKey); } } -JSValue CustomEvent::initCustomEvent(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +CustomEvent::CustomEvent(NativeCustomEvent* nativeEvent): m_nativeCustomEvent(nativeEvent), Event(reinterpret_cast(nativeEvent)) { + m_detail = JS_NewUnicodeString(m_runtime, m_ctx, nativeEvent->detail->string, nativeEvent->detail->length); +} + +void CustomEvent::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + Event::trace(rt, val, mark_func); + JS_MarkValue(rt, m_detail, mark_func); +} + +void CustomEvent::dispose() const { + // No needs to free m_nativeCustomEvent, Event::dispose() will handle this. + Event::dispose(); + JS_FreeValueRT(m_runtime, m_detail); +} + +IMPL_FUNCTION(CustomEvent, initCustomEvent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to execute 'initCustomEvent' on 'CustomEvent': 1 argument required, but only 0 present"); } - auto* eventInstance = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); + auto* eventInstance = static_cast(JS_GetOpaque(this_val, CustomEvent::classId)); if (eventInstance == nullptr) { return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); } @@ -84,37 +120,14 @@ JSValue CustomEvent::initCustomEvent(JSContext* ctx, JSValue this_val, int argc, } if (argc <= 4) { - eventInstance->m_detail.value(argv[3]); + eventInstance->m_detail = JS_DupValue(ctx, argv[3]); } return JS_NULL; } -JSValue CustomEvent::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - -} - 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(); + auto* customEventInstance = static_cast(JS_GetOpaque(this_val, CustomEvent::classId)); + return JS_DupValue(ctx, customEventInstance->m_detail); } -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)) { - JSValue newDetail = JS_NewUnicodeString(jsCustomEvent->context()->runtime(), jsCustomEvent->context()->ctx(), nativeCustomEvent->detail->string, nativeCustomEvent->detail->length); - nativeCustomEvent->detail->free(); - m_detail.value(newDetail); - JS_FreeValue(m_ctx, newDetail); -} } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/custom_event.h b/bridge/bindings/qjs/dom/custom_event.h index fda15db447..84522d5620 100644 --- a/bridge/bindings/qjs/dom/custom_event.h +++ b/bridge/bindings/qjs/dom/custom_event.h @@ -21,19 +21,22 @@ class CustomEvent : public Event { public: static JSClassID classId; static CustomEvent* create(JSContext* ctx, JSValue eventType, JSValue init); + static CustomEvent* create(JSContext* ctx, NativeCustomEvent* nativeCustomEvent); static JSValue constructor(ExecutionContext* context); static JSValue prototype(ExecutionContext* context); + CustomEvent(JSValue eventType, JSValue init); + CustomEvent(NativeCustomEvent* nativeEvent); + void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; void dispose() const override; -// CustomEvent() = delete; -// explicit CustomEvent(JSValue eventType, JSValue eventInit); + DEFINE_FUNCTION(initCustomEvent); DEFINE_PROTOTYPE_READONLY_PROPERTY(detail); - DEFINE_FUNCTION(initCustomEvent); private: + NativeCustomEvent* m_nativeCustomEvent{nullptr}; JSValue m_detail{JS_NULL}; }; diff --git a/bridge/bindings/qjs/dom/event.cc b/bridge/bindings/qjs/dom/event.cc index 416a4c203b..0d6ab0d8ac 100644 --- a/bridge/bindings/qjs/dom/event.cc +++ b/bridge/bindings/qjs/dom/event.cc @@ -57,9 +57,6 @@ Event* Event::create(JSContext* ctx, NativeEvent* nativeEvent) { return event; } -Event::Event() { - -} Event::Event(NativeEvent* nativeEvent): nativeEvent(nativeEvent) {} Event::Event(JSValue eventType, JSValue eventInit) { diff --git a/bridge/bindings/qjs/dom/event.h b/bridge/bindings/qjs/dom/event.h index 35bc07fa46..91e60d983d 100644 --- a/bridge/bindings/qjs/dom/event.h +++ b/bridge/bindings/qjs/dom/event.h @@ -6,7 +6,8 @@ #ifndef KRAKENBRIDGE_EVENT_H #define KRAKENBRIDGE_EVENT_H -#include "bindings/qjs/host_class.h" +#include "bindings/qjs/executing_context.h" +#include "bindings/qjs/context_macros.h" namespace kraken::binding::qjs { @@ -81,7 +82,6 @@ class Event : public GarbageCollected { static JSValue constructor(ExecutionContext* context); static JSValue prototype(ExecutionContext* context); - explicit Event(); explicit Event(NativeEvent* nativeEvent); explicit Event(JSValue eventType, JSValue eventInit); diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/bindings/qjs/dom/event_target.cc index c823ab9b04..510eaff282 100644 --- a/bridge/bindings/qjs/dom/event_target.cc +++ b/bridge/bindings/qjs/dom/event_target.cc @@ -12,6 +12,7 @@ #include "document.h" #include "element.h" #include "event.h" +#include "custom_event.h" #include "kraken_bridge.h" namespace kraken::binding::qjs { @@ -451,11 +452,10 @@ void NativeEventTarget::dispatchEventImpl(NativeEventTarget* nativeEventTarget, // 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); - Event* event = Event::create(); -// Event* event = Event::buildEventInstance(eventType, context, nativeEvent, isCustomEvent == 1); -// eventInstance->nativeEvent->target = eventTarget; -// eventTarget->dispatchEvent(eventInstance); -// JS_FreeValue(context->ctx(), eventInstance->jsObject); + Event* event = isCustomEvent == 1 ? CustomEvent::create(context->ctx(), reinterpret_cast(nativeEvent)) : Event::create(context->ctx(), nativeEvent); + event->nativeEvent->target = eventTarget; + eventTarget->dispatchEvent(event); + JS_FreeValue(context->ctx(), event->toQuickJS()); } } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/bindings/qjs/dom/event_target.h index 5e1f80f991..584239ddd6 100644 --- a/bridge/bindings/qjs/dom/event_target.h +++ b/bridge/bindings/qjs/dom/event_target.h @@ -6,12 +6,10 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H -#include "bindings/qjs/dom/event.h" +#include "bindings/qjs/native_value.h" #include "bindings/qjs/executing_context.h" -#include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/heap_hashmap.h" -#include "bindings/qjs/native_value.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/context_macros.h" #include "event_listener_map.h" #if UNIT_TEST @@ -23,7 +21,7 @@ namespace kraken::binding::qjs { class EventTarget; class NativeEventTarget; class CSSStyleDeclaration; -class StyleDeclarationInstance; +class Event; void bindEventTarget(std::unique_ptr& context); diff --git a/bridge/bindings/qjs/dom/style_declaration.h b/bridge/bindings/qjs/dom/style_declaration.h index bb575679db..7d6202f5cd 100644 --- a/bridge/bindings/qjs/dom/style_declaration.h +++ b/bridge/bindings/qjs/dom/style_declaration.h @@ -6,7 +6,9 @@ #ifndef KRAKENBRIDGE_STYLE_DECLARATION_H #define KRAKENBRIDGE_STYLE_DECLARATION_H -#include "bindings/qjs/host_class.h" +#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/context_macros.h" +#include "bindings/qjs/dom/event_target.h" namespace kraken::binding::qjs { @@ -22,58 +24,32 @@ inline CharacterType toASCIIUpper(CharacterType character) { return character & ~(isASCIILower(character) << 5); } -class CSSStyleDeclaration : public HostClass { +class CSSStyleDeclaration : public GarbageCollected { public: - OBJECT_INSTANCE(CSSStyleDeclaration); + static JSClassID classId; + static CSSStyleDeclaration* create(JSContext* ctx); + static JSValue constructor(ExecutionContext* context); + static JSValue prototype(ExecutionContext* context); - static JSClassID kCSSStyleDeclarationClassId; + CSSStyleDeclaration(); - 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 copyWith(CSSStyleDeclaration* instance); - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; - - const EventTargetInstance* ownerEventTarget; + DEFINE_FUNCTION(setProperty); + DEFINE_FUNCTION(removeProperty); + DEFINE_FUNCTION(getPropertyValue); private: + static int hasProperty(JSContext* ctx, JSValueConst obj, JSAtom atom); static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); - static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - static void finalize(JSRuntime* rt, JSValue val) { - auto* instance = static_cast(JS_GetOpaque(val, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - delete instance; - } - - static JSClassExoticMethods m_exoticMethods; - std::unordered_map properties; - friend EventTargetInstance; }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/executing_context.h b/bridge/bindings/qjs/executing_context.h index 936f9025d6..3417d68ac4 100644 --- a/bridge/bindings/qjs/executing_context.h +++ b/bridge/bindings/qjs/executing_context.h @@ -16,14 +16,13 @@ #include #include #include -#include "bindings/qjs/bom/dom_timer_coordinator.h" #include "foundation/ui_command_buffer.h" +#include "executing_context_data.h" #include "garbage_collected.h" -#include "js_context_macros.h" #include "kraken_foundation.h" -#include "executing_context_data.h" #include "qjs_patch.h" #include "wrapper_type_info.h" +#include "bindings/qjs/bom/dom_timer_coordinator.h" using JSExceptionHandler = std::function; @@ -32,7 +31,7 @@ namespace kraken::binding::qjs { static std::once_flag kinitJSClassIDFlag; class ExecutionContext; -struct DOMTimerCallbackContext; +class Document; std::string jsAtomToStdString(JSContext* ctx, JSAtom atom); @@ -95,7 +94,7 @@ class ExecutionContext { // not be used after the ExecutionContext is destroyed. DOMTimerCoordinator* timers(); - FORCE_INLINE DocumentInstance* document() { return m_document; }; + FORCE_INLINE Document* document() { return m_document; }; FORCE_INLINE foundation::UICommandBuffer* uiCommandBuffer() { return &m_commandBuffer; }; void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); @@ -126,12 +125,11 @@ class ExecutionContext { JSValue globalObject{JS_NULL}; bool ctxInvalid_{false}; JSContext* m_ctx{nullptr}; - DocumentInstance* m_document{nullptr}; + Document* m_document{nullptr}; DOMTimerCoordinator m_timers; ExecutionContextGCTracker* m_gcTracker{nullptr}; ExecutionContextData m_data{this}; foundation::UICommandBuffer m_commandBuffer{contextId}; - friend DocumentInstance; }; // The read object's method or properties via Proxy, we should redirect this_val from Proxy into target property of diff --git a/bridge/bindings/qjs/executing_context_data.cc b/bridge/bindings/qjs/executing_context_data.cc index 0007e650e3..0474a379dc 100644 --- a/bridge/bindings/qjs/executing_context_data.cc +++ b/bridge/bindings/qjs/executing_context_data.cc @@ -5,6 +5,7 @@ #include "executing_context_data.h" +#include "executing_context.h" namespace kraken::binding::qjs { @@ -26,8 +27,8 @@ JSValue ExecutionContextData::prototypeForType(const WrapperTypeInfo* type) { } JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* type) { - JSRuntime* runtime = m_context.runtime(); - JSContext* ctx = m_context.ctx(); + JSRuntime* runtime = m_context->runtime(); + JSContext* ctx = m_context->ctx(); assert(type->classId == 0 || !JS_HasClassId(runtime, type->classId)); @@ -38,14 +39,14 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty JSClassDef def{}; def.class_name = type->className; def.call = type->callFunc; - JS_NewClass(m_context.runtime(), type->classId, &def); + JS_NewClass(m_context->runtime(), type->classId, &def); // Create class object and prototype object. - JSValue classObject = m_constructorMap[type] = JS_NewObjectClass(m_context.ctx(), type->classId); - JSValue prototypeObject = m_prototypeMap[type] = JS_NewObject(m_context.ctx()); + JSValue classObject = m_constructorMap[type] = JS_NewObjectClass(m_context->ctx(), type->classId); + JSValue prototypeObject = m_prototypeMap[type] = JS_NewObject(m_context->ctx()); // Make constructor function inherit to Function.prototype - JSValue functionConstructor = JS_GetPropertyStr(ctx, m_context.global(), "Function"); + 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); @@ -59,7 +60,7 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty // Inherit to parentClass. if (type->parent_class != nullptr) { assert(m_prototypeMap.count(type->parent_class) > 0); - JS_SetPrototype(m_context.ctx(), prototypeObject, m_prototypeMap[type->parent_class]); + JS_SetPrototype(m_context->ctx(), prototypeObject, m_prototypeMap[type->parent_class]); } // Configure to be called as a constructor. diff --git a/bridge/bindings/qjs/executing_context_data.h b/bridge/bindings/qjs/executing_context_data.h index 16e2c8171f..d379d0f9a2 100644 --- a/bridge/bindings/qjs/executing_context_data.h +++ b/bridge/bindings/qjs/executing_context_data.h @@ -7,16 +7,17 @@ #define KRAKENBRIDGE_CONTEXT_DATA_H #include -#include "executing_context.h" #include "wrapper_type_info.h" namespace kraken::binding::qjs { +class ExecutionContext; + // 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(ExecutionContext& context): m_context(context) {}; + explicit ExecutionContextData(ExecutionContext* context): m_context(context) {}; ExecutionContextData(const ExecutionContextData&) = delete; ExecutionContextData& operator=(const ExecutionContextData&) = delete; @@ -30,7 +31,7 @@ class ExecutionContextData final { std::unordered_map m_constructorMap; std::unordered_map m_prototypeMap; - ExecutionContext& m_context; + ExecutionContext* m_context; }; diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index aebb018d2e..b79b8eb0d9 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -70,7 +70,7 @@ class GarbageCollected { FORCE_INLINE JSValue toQuickJS() { return jsObject; }; FORCE_INLINE JSContext* ctx() { return m_ctx; }; - FORCE_INLINE ExecutionContext* context() { return static_cast(JS_GetContextOpaque(m_ctx)); }; + FORCE_INLINE ExecutionContext* context() const { return static_cast(JS_GetContextOpaque(m_ctx)); }; protected: JSValue jsObject{JS_NULL}; diff --git a/bridge/bindings/qjs/garbage_collected_test.cc b/bridge/bindings/qjs/garbage_collected_test.cc new file mode 100644 index 0000000000..4e43f1d7d1 --- /dev/null +++ b/bridge/bindings/qjs/garbage_collected_test.cc @@ -0,0 +1,575 @@ +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#include "host_class.h" +//#include +//#include "gtest/gtest.h" +//#include "kraken_test_env.h" +//#include "page.h" +// +//namespace kraken::binding::qjs { +// +//class ParentClass : public HostClass { +// public: +// explicit ParentClass(ExecutionContext* context) : HostClass(context, "ParentClass") {} +// JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValueConst* argv) override { return HostClass::instanceConstructor(ctx, func_obj, this_val, argc, argv); } +// +// OBJECT_INSTANCE(ParentClass); +// +// static JSValue foo(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 20); } +// +// private: +// ObjectFunction m_foo{m_context, m_prototypeObject, "foo", foo, 0}; +//}; +// +//class SampleClass; +//static JSClassID kSampleClassId{0}; +// +//class SampleClassInstance : public Instance { +// public: +// explicit SampleClassInstance(HostClass* sampleClass) : Instance(sampleClass, "SampleClass", nullptr, kSampleClassId, finalizer){}; +// +// private: +// static void finalizer(JSRuntime* rt, JSValue v) { +// auto* instance = static_cast(JS_GetOpaque(v, kSampleClassId)); +// if (instance->context()->isValid()) { +// JS_FreeValue(instance->m_ctx, instance->jsObject); +// } +// delete instance; +// } +//}; +// +//std::once_flag kSampleClassOnceFlag; +//class SampleClass : public ParentClass { +// public: +// explicit SampleClass(ExecutionContext* context) : ParentClass(context) { +// std::call_once(kSampleClassOnceFlag, []() { JS_NewClassID(&kSampleClassId); }); +// JS_SetPrototype(m_ctx, m_prototypeObject, ParentClass::instance(m_context)->prototype()); +// } +// JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override { +// auto* sampleClass = static_cast(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); +// auto* instance = new SampleClassInstance(sampleClass); +// return instance->jsObject; +// } +// ~SampleClass() {} +// +// private: +// static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 10); } +// +// ObjectFunction m_f{m_context, m_prototypeObject, "f", f, 0}; +//}; +// +//TEST(HostClass, newInstance) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "10"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleClass(context.get()); +// auto* parentObject = ParentClass::instance(context.get()); +// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// const char* code = "let obj = new SampleClass(1,2,3,4); console.log(obj.f())"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(HostClass, instanceOf) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "true"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// errorCalled = true; +// KRAKEN_LOG(VERBOSE) << errmsg; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleClass(context.get()); +// auto* parentObject = ParentClass::instance(context.get()); +// // Test for C API +// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// JSValue args[] = {}; +// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); +// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, parentObject->jsObject); +// EXPECT_EQ(isInstanceof, true); +// JS_FreeValue(context->ctx(), object); +// +// // Test with Javascript +// const char* code = "let obj = new SampleClass(1,2,3,4); \n console.log(obj instanceof SampleClass)"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(HostClass, inheritance) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "20"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// errorCalled = true; +// KRAKEN_LOG(VERBOSE) << errmsg; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleClass(context.get()); +// +// auto* parentObject = ParentClass::instance(context.get()); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// +// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); +// +// const char* code = +// "let obj = new SampleClass(1,2,3,4);\n" +// "console.log(obj.foo())"; +// context->evaluateJavaScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(HostClass, inherintanceInJavaScript) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "TEST 10 20"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// errorCalled = true; +// KRAKEN_LOG(VERBOSE) << errmsg; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleClass(context.get()); +// +// auto* parentObject = ParentClass::instance(context.get()); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// +// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); +// +// const char* code = R"( +//class Demo extends SampleClass { +// constructor(name) { +// super(); +// this.name = name; +// } +// +// getName() { +// return this.name.toUpperCase(); +// } +//} +//let demo = new Demo('test'); +//console.log(demo.getName(), demo.f(), demo.foo()); +//)"; +// context->evaluateJavaScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(HostClass, haveFunctionProtoMethods) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "ƒ ()"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// errorCalled = true; +// KRAKEN_LOG(VERBOSE) << errmsg; +// }); +// auto& context = bridge->getContext(); +// auto* parentObject = ParentClass::instance(context.get()); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// +// const char* code = R"( +//class Demo extends ParentClass { +// constructor(name) { +// super(); +// this.name = name; +// } +// +// getName() { +// return this.name.toUpperCase(); +// } +//} +//console.log(Demo.call); +//)"; +// context->evaluateJavaScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(HostClass, multipleInstance) { +// bool static errorCalled = false; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// errorCalled = true; +// KRAKEN_LOG(VERBOSE) << errmsg; +// }); +// auto& context = bridge->getContext(); +// +// auto* parentObject = ParentClass::instance(context.get()); +// context->defineGlobalProperty("ParentClass", parentObject->jsObject); +// +// // Test for C API 1 +// { +// auto* sampleObject = new SampleClass(context.get()); +// context->defineGlobalProperty("SampleClass1", sampleObject->jsObject); +// JSValue args[] = {}; +// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); +// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); +// EXPECT_EQ(isInstanceof, true); +// JS_FreeValue(context->ctx(), object); +// } +// +// // Test for C API 2 +// { +// auto* sampleObject = new SampleClass(context.get()); +// context->defineGlobalProperty("SampleClass2", sampleObject->jsObject); +// JSValue args[] = {}; +// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); +// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); +// EXPECT_EQ(isInstanceof, true); +// JS_FreeValue(context->ctx(), object); +// } +// +// { +// auto* sampleObject = new SampleClass(context.get()); +// context->defineGlobalProperty("SampleClass3", sampleObject->jsObject); +// JSValue args[] = {}; +// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); +// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); +// EXPECT_EQ(isInstanceof, true); +// JS_FreeValue(context->ctx(), object); +// } +// +// { +// auto* sampleObject = new SampleClass(context.get()); +// context->defineGlobalProperty("SampleClass4", sampleObject->jsObject); +// JSValue args[] = {}; +// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); +// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); +// EXPECT_EQ(isInstanceof, true); +// JS_FreeValue(context->ctx(), object); +// } +// +// EXPECT_EQ(errorCalled, false); +//} +// +//std::once_flag kExoticClassOnceFlag; +// +//class ExoticClassInstance; +//class ExoticClass : public HostClass { +// public: +// static JSClassID exoticClassID; +// ExoticClass() = delete; +// explicit ExoticClass(ExecutionContext* context) : HostClass(context, "ExoticClass") { +// std::call_once(kExoticClassOnceFlag, []() { JS_NewClassID(&exoticClassID); }); +// } +// JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv); +// +// private: +// friend ExoticClassInstance; +//}; +// +//JSClassID ExoticClass::exoticClassID{0}; +//static bool exoticClassFreed = false; +// +//class ExoticClassInstance : public Instance { +// public: +// ExoticClassInstance() = delete; +// static JSClassExoticMethods methods; +// +// explicit ExoticClassInstance(ExoticClass* exoticClass) : Instance(exoticClass, "ExoticClass", &methods, ExoticClass::exoticClassID, finalizer){}; +// +// static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { +// auto* instance = static_cast(JS_GetOpaque(obj, ExoticClass::exoticClassID)); +// auto* prototype = static_cast(instance->prototype()); +// if (JS_HasProperty(ctx, prototype->m_prototypeObject, atom)) { +// return JS_GetProperty(ctx, prototype->m_prototypeObject, atom); +// } +// +// if (instance->m_properties.count(atom) > 0) { +// return instance->m_properties[atom]; +// } +// +// return JS_NULL; +// }; +// +// static void finalizer(JSRuntime* rt, JSValue val) { +// auto* instance = static_cast(JS_GetOpaque(val, ExoticClass::exoticClassID)); +// if (instance->context()->isValid()) { +// JS_FreeValue(instance->m_ctx, instance->jsObject); +// } +// delete instance; +// }; +// +// static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { +// auto* instance = static_cast(JS_GetOpaque(obj, ExoticClass::exoticClassID)); +// instance->m_properties[atom] = JS_DupValue(ctx, value); +// return 0; +// } +// ~ExoticClassInstance() { exoticClassFreed = true; } +// friend ExoticClass; +// +// class ClassNamePropertyDescriptor { +// public: +// static JSValue getter(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* instance = static_cast(JS_GetOpaque(this_val, ExoticClass::exoticClassID)); +// return JS_NewFloat64(ctx, instance->classValue); +// }; +// static JSValue setter(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* instance = static_cast(JS_GetOpaque(this_val, ExoticClass::exoticClassID)); +// double v; +// JS_ToFloat64(ctx, &v, argv[0]); +// instance->classValue = v; +// return JS_NULL; +// }; +// }; +// ObjectProperty m_getClassName{m_context, jsObject, "className", ClassNamePropertyDescriptor::getter, ClassNamePropertyDescriptor::setter}; +// +// private: +// std::unordered_map m_properties; +// double classValue{100.0}; +//}; +// +//JSClassExoticMethods ExoticClassInstance::methods{nullptr, nullptr, nullptr, nullptr, nullptr, getProperty, setProperty}; +// +//JSValue ExoticClass::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +// return (new ExoticClassInstance(this))->jsObject; +//} +// +//TEST(HostClass, exoticClass) { +// bool static errorCalled = false; +// bool static logCalled = false; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "10"); +// }; +// +// auto& context = bridge->getContext(); +// auto* constructor = new ExoticClass(context.get()); +// context->defineGlobalProperty("ExoticClass", constructor->jsObject); +// +// std::string code = +// "globalThis.obj = new ExoticClass();" +// "var key = 'onclick'; " +// "var otherKey = 'o' + 'n' + 'c' + 'l' + 'i' + 'c' + 'k';" +// "obj[key] = function() {return 10;};" +// "console.log(obj[otherKey]());"; +// context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(HostClass, setExoticClassProperty) { +// bool static errorCalled = false; +// bool static logCalled = false; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "200"); +// }; +// +// auto& context = bridge->getContext(); +// auto* constructor = new ExoticClass(context.get()); +// context->defineGlobalProperty("ExoticClass", constructor->jsObject); +// +// std::string code = +// "var obj = new ExoticClass();" +// "obj.className = 200.0;" +// "console.log(obj.className);"; +// context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(exoticClassFreed, true); +// EXPECT_EQ(logCalled, true); +//} +// +//} // namespace kraken::binding::qjs + +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#include "host_object.h" +//#include +//#include "executing_context.h" +//#include "kraken_test_env.h" +//#include "page.h" +// +//namespace kraken::binding::qjs { +// +//static bool isSampleFree = false; +// +//class SampleObject : public HostObject { +// public: +// explicit SampleObject(ExecutionContext* context) : HostObject(context, "SampleObject"){}; +// ~SampleObject() { isSampleFree = true; } +// +// private: +// class FooPropertyDescriptor { +// public: +// static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +// auto* sampleObject = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// return JS_NewFloat64(ctx, sampleObject->m_foo); +// } +// static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +// auto* sampleObject = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// double f; +// JS_ToFloat64(ctx, &f, argv[0]); +// sampleObject->m_foo = f; +// return JS_NULL; +// } +// }; +// +// static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +// double v; +// JS_ToFloat64(ctx, &v, argv[0]); +// return JS_NewFloat64(ctx, 10 + v); +// } +// +// double m_foo{0}; +// ObjectProperty m_width{m_context, jsObject, "foo", FooPropertyDescriptor::getter, FooPropertyDescriptor::setter}; +// ObjectFunction m_f{m_context, jsObject, "f", f, 1}; +//}; +// +//TEST(HostObject, defineProperty) { +// bool static logCalled = false; +// bool static errorCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// +// EXPECT_STREQ(message.c_str(), "{f: ƒ (), foo: 1}"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleObject(context.get()); +// JSValue object = sampleObject->jsObject; +// context->defineGlobalProperty("o", object); +// const char* code = "o.foo++; console.log(o);"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(logCalled, true); +// EXPECT_EQ(errorCalled, false); +//} +// +//TEST(ObjectProperty, worksWithProxy) { +// bool static logCalled = false; +// bool static errorCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "0"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleObject(context.get()); +// JSValue object = sampleObject->jsObject; +// context->defineGlobalProperty("o", object); +// std::string code = std::string(R"( +//let p = new Proxy(o, { +// get(target, key, receiver) { +// return Reflect.get(target, key, receiver); +// } +//}); +//console.log(p.foo); +//)"); +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(logCalled, true); +// EXPECT_EQ(errorCalled, false); +//} +// +//TEST(HostObject, defineFunction) { +// bool static logCalled = false; +// bool static errorCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "20"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleObject(context.get()); +// JSValue object = sampleObject->jsObject; +// context->defineGlobalProperty("o", object); +// const char* code = "console.log(o.f(10))"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(logCalled, true); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(isSampleFree, true); +//} +// +//class SampleExoticHostObject : public ExoticHostObject { +// public: +// explicit SampleExoticHostObject(ExecutionContext* context) : ExoticHostObject(context, "SampleObject"){}; +// ~SampleExoticHostObject() { isSampleFree = true; } +// +// JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); +// int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); +// +// private: +//}; +// +//JSValue SampleExoticHostObject::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { +// return JS_NewFloat64(ctx, 100.0); +//} +//int SampleExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { +// return 0; +//} +// +//TEST(ExoticHostObject, overriteGetterSetter) { +// bool static logCalled = false; +// bool static errorCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "100"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto& context = bridge->getContext(); +// auto* sampleObject = new SampleExoticHostObject(context.get()); +// JSValue object = sampleObject->jsObject; +// context->defineGlobalProperty("o", object); +// const char* code = "console.log(o.abc)"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(logCalled, true); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(isSampleFree, true); +//} +// +//} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/host_class.h b/bridge/bindings/qjs/host_class.h deleted file mode 100644 index 32d43349c6..0000000000 --- a/bridge/bindings/qjs/host_class.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_HOST_CLASS_H -#define KRAKENBRIDGE_HOST_CLASS_H - -#include "executing_context.h" -#include "qjs_patch.h" -#include "third_party/quickjs/quickjs.h" - -namespace kraken::binding::qjs { - -class Instance; - -class HostClass { - public: - KRAKEN_DISALLOW_COPY_AND_ASSIGN(HostClass); - - HostClass(ExecutionContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { - /// JavaScript object in QuickJS are created by template, in QuickJS, these template is called JSClassDef. - /// JSClassDef define this JSObject's base behavior like className, property getter and setter, and advanced feature such as run a callback when JSObject had been freed by QuickJS garbage - /// collector. Every JSClassDef must have a unique ID, called JSClassID, you can obtain this ID from JS_NewClassID() API. If your wants to create JSObjects defined by your own template, please - /// follow this steps: - /// 1. Use JS_NewClassID() to allocate new id for your template. - /// 2. Create JSClassDef and set up your customized behavior about your JSObject. - /// 3. Use JS_NewClass() to initializeAsJSObject 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)); - if (hostObject->context()->isValid()) { - JS_FreeValue(hostObject->m_ctx, hostObject->jsObject); - } - delete hostObject; - }; - static JSValue proxyCall(JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) { - // This jsObject is called as a constructor. - if ((flags & JS_CALL_FLAG_CONSTRUCTOR) != 0) { - auto* hostClass = static_cast(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); - JSValue instance = hostClass->instanceConstructor(ctx, func_obj, this_val, argc, argv); - JSValue proto = JS_GetPropertyStr(ctx, this_val, "prototype"); - JS_SetPrototype(ctx, instance, proto); - JS_FreeValue(ctx, proto); - return instance; - } - - return this_val; - } -}; - -class Instance { - public: - explicit Instance(HostClass* hostClass, std::string name, JSClassExoticMethods* exotic, JSClassID classId, JSClassFinalizer finalizer) - : m_context(hostClass->context()), m_hostClass(hostClass), m_name(std::move(name)), m_ctx(m_context->ctx()), m_contextId(hostClass->contextId()) { - JSClassDef def{}; - def.class_name = m_name.c_str(); - def.finalizer = finalizer; - def.exotic = exotic; - def.gc_mark = proxyGCMark; - int32_t success = JS_NewClass(m_context->runtime(), classId, &def); - jsObject = JS_NewObjectProtoClass(m_ctx, hostClass->m_prototypeObject, classId); - JS_SetOpaque(jsObject, this); - }; - JSValue jsObject; - virtual ~Instance() = default; - - inline HostClass* prototype() const { return m_hostClass; } - inline ExecutionContext* context() const { return m_context; } - inline std::string name() const { return m_name; } - - private: - static void proxyGCMark(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) { - auto* instance = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); - instance->trace(rt, val, mark_func); - } - - protected: - // Subclass must to provider a method of void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) - // to tell GC all JSValues are managed by them. - virtual void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func){}; - - ExecutionContext* m_context{nullptr}; - JSContext* m_ctx{nullptr}; - HostClass* m_hostClass{nullptr}; - std::string m_name; - int64_t m_contextId{-1}; - - friend HostClass; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_HOST_CLASS_H diff --git a/bridge/bindings/qjs/host_class_test.cc b/bridge/bindings/qjs/host_class_test.cc deleted file mode 100644 index 61a705720c..0000000000 --- a/bridge/bindings/qjs/host_class_test.cc +++ /dev/null @@ -1,413 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "host_class.h" -#include -#include "gtest/gtest.h" -#include "kraken_test_env.h" -#include "page.h" - -namespace kraken::binding::qjs { - -class ParentClass : public HostClass { - public: - explicit ParentClass(ExecutionContext* context) : HostClass(context, "ParentClass") {} - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValueConst* argv) override { return HostClass::instanceConstructor(ctx, func_obj, this_val, argc, argv); } - - OBJECT_INSTANCE(ParentClass); - - static JSValue foo(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 20); } - - private: - ObjectFunction m_foo{m_context, m_prototypeObject, "foo", foo, 0}; -}; - -class SampleClass; -static JSClassID kSampleClassId{0}; - -class SampleClassInstance : public Instance { - public: - explicit SampleClassInstance(HostClass* sampleClass) : Instance(sampleClass, "SampleClass", nullptr, kSampleClassId, finalizer){}; - - private: - static void finalizer(JSRuntime* rt, JSValue v) { - auto* instance = static_cast(JS_GetOpaque(v, kSampleClassId)); - if (instance->context()->isValid()) { - JS_FreeValue(instance->m_ctx, instance->jsObject); - } - delete instance; - } -}; - -std::once_flag kSampleClassOnceFlag; -class SampleClass : public ParentClass { - public: - explicit SampleClass(ExecutionContext* context) : ParentClass(context) { - std::call_once(kSampleClassOnceFlag, []() { JS_NewClassID(&kSampleClassId); }); - JS_SetPrototype(m_ctx, m_prototypeObject, ParentClass::instance(m_context)->prototype()); - } - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override { - auto* sampleClass = static_cast(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); - auto* instance = new SampleClassInstance(sampleClass); - return instance->jsObject; - } - ~SampleClass() {} - - private: - static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 10); } - - ObjectFunction m_f{m_context, m_prototypeObject, "f", f, 0}; -}; - -TEST(HostClass, newInstance) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "10"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto& context = bridge->getContext(); - auto* sampleObject = new SampleClass(context.get()); - auto* parentObject = ParentClass::instance(context.get()); - context->defineGlobalProperty("SampleClass", sampleObject->jsObject); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - const char* code = "let obj = new SampleClass(1,2,3,4); console.log(obj.f())"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, instanceOf) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "true"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - KRAKEN_LOG(VERBOSE) << errmsg; - }); - auto& context = bridge->getContext(); - auto* sampleObject = new SampleClass(context.get()); - auto* parentObject = ParentClass::instance(context.get()); - // Test for C API - context->defineGlobalProperty("SampleClass", sampleObject->jsObject); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, parentObject->jsObject); - EXPECT_EQ(isInstanceof, true); - JS_FreeValue(context->ctx(), object); - - // Test with Javascript - const char* code = "let obj = new SampleClass(1,2,3,4); \n console.log(obj instanceof SampleClass)"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, inheritance) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "20"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - KRAKEN_LOG(VERBOSE) << errmsg; - }); - auto& context = bridge->getContext(); - auto* sampleObject = new SampleClass(context.get()); - - auto* parentObject = ParentClass::instance(context.get()); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - - context->defineGlobalProperty("SampleClass", sampleObject->jsObject); - - const char* code = - "let obj = new SampleClass(1,2,3,4);\n" - "console.log(obj.foo())"; - context->evaluateJavaScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, inherintanceInJavaScript) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "TEST 10 20"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - KRAKEN_LOG(VERBOSE) << errmsg; - }); - auto& context = bridge->getContext(); - auto* sampleObject = new SampleClass(context.get()); - - auto* parentObject = ParentClass::instance(context.get()); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - - context->defineGlobalProperty("SampleClass", sampleObject->jsObject); - - const char* code = R"( -class Demo extends SampleClass { - constructor(name) { - super(); - this.name = name; - } - - getName() { - return this.name.toUpperCase(); - } -} -let demo = new Demo('test'); -console.log(demo.getName(), demo.f(), demo.foo()); -)"; - context->evaluateJavaScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, haveFunctionProtoMethods) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "ƒ ()"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - KRAKEN_LOG(VERBOSE) << errmsg; - }); - auto& context = bridge->getContext(); - auto* parentObject = ParentClass::instance(context.get()); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - - const char* code = R"( -class Demo extends ParentClass { - constructor(name) { - super(); - this.name = name; - } - - getName() { - return this.name.toUpperCase(); - } -} -console.log(Demo.call); -)"; - context->evaluateJavaScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, multipleInstance) { - bool static errorCalled = false; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - KRAKEN_LOG(VERBOSE) << errmsg; - }); - auto& context = bridge->getContext(); - - auto* parentObject = ParentClass::instance(context.get()); - context->defineGlobalProperty("ParentClass", parentObject->jsObject); - - // Test for C API 1 - { - auto* sampleObject = new SampleClass(context.get()); - context->defineGlobalProperty("SampleClass1", sampleObject->jsObject); - JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); - EXPECT_EQ(isInstanceof, true); - JS_FreeValue(context->ctx(), object); - } - - // Test for C API 2 - { - auto* sampleObject = new SampleClass(context.get()); - context->defineGlobalProperty("SampleClass2", sampleObject->jsObject); - JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); - EXPECT_EQ(isInstanceof, true); - JS_FreeValue(context->ctx(), object); - } - - { - auto* sampleObject = new SampleClass(context.get()); - context->defineGlobalProperty("SampleClass3", sampleObject->jsObject); - JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); - EXPECT_EQ(isInstanceof, true); - JS_FreeValue(context->ctx(), object); - } - - { - auto* sampleObject = new SampleClass(context.get()); - context->defineGlobalProperty("SampleClass4", sampleObject->jsObject); - JSValue args[] = {}; - JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); - bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); - EXPECT_EQ(isInstanceof, true); - JS_FreeValue(context->ctx(), object); - } - - EXPECT_EQ(errorCalled, false); -} - -std::once_flag kExoticClassOnceFlag; - -class ExoticClassInstance; -class ExoticClass : public HostClass { - public: - static JSClassID exoticClassID; - ExoticClass() = delete; - explicit ExoticClass(ExecutionContext* context) : HostClass(context, "ExoticClass") { - std::call_once(kExoticClassOnceFlag, []() { JS_NewClassID(&exoticClassID); }); - } - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv); - - private: - friend ExoticClassInstance; -}; - -JSClassID ExoticClass::exoticClassID{0}; -static bool exoticClassFreed = false; - -class ExoticClassInstance : public Instance { - public: - ExoticClassInstance() = delete; - static JSClassExoticMethods methods; - - explicit ExoticClassInstance(ExoticClass* exoticClass) : Instance(exoticClass, "ExoticClass", &methods, ExoticClass::exoticClassID, finalizer){}; - - static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { - auto* instance = static_cast(JS_GetOpaque(obj, ExoticClass::exoticClassID)); - auto* prototype = static_cast(instance->prototype()); - if (JS_HasProperty(ctx, prototype->m_prototypeObject, atom)) { - return JS_GetProperty(ctx, prototype->m_prototypeObject, atom); - } - - if (instance->m_properties.count(atom) > 0) { - return instance->m_properties[atom]; - } - - return JS_NULL; - }; - - static void finalizer(JSRuntime* rt, JSValue val) { - auto* instance = static_cast(JS_GetOpaque(val, ExoticClass::exoticClassID)); - if (instance->context()->isValid()) { - JS_FreeValue(instance->m_ctx, instance->jsObject); - } - delete instance; - }; - - static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { - auto* instance = static_cast(JS_GetOpaque(obj, ExoticClass::exoticClassID)); - instance->m_properties[atom] = JS_DupValue(ctx, value); - return 0; - } - ~ExoticClassInstance() { exoticClassFreed = true; } - friend ExoticClass; - - class ClassNamePropertyDescriptor { - public: - static JSValue getter(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* instance = static_cast(JS_GetOpaque(this_val, ExoticClass::exoticClassID)); - return JS_NewFloat64(ctx, instance->classValue); - }; - static JSValue setter(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* instance = static_cast(JS_GetOpaque(this_val, ExoticClass::exoticClassID)); - double v; - JS_ToFloat64(ctx, &v, argv[0]); - instance->classValue = v; - return JS_NULL; - }; - }; - ObjectProperty m_getClassName{m_context, jsObject, "className", ClassNamePropertyDescriptor::getter, ClassNamePropertyDescriptor::setter}; - - private: - std::unordered_map m_properties; - double classValue{100.0}; -}; - -JSClassExoticMethods ExoticClassInstance::methods{nullptr, nullptr, nullptr, nullptr, nullptr, getProperty, setProperty}; - -JSValue ExoticClass::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return (new ExoticClassInstance(this))->jsObject; -} - -TEST(HostClass, exoticClass) { - bool static errorCalled = false; - bool static logCalled = false; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "10"); - }; - - auto& context = bridge->getContext(); - auto* constructor = new ExoticClass(context.get()); - context->defineGlobalProperty("ExoticClass", constructor->jsObject); - - std::string code = - "globalThis.obj = new ExoticClass();" - "var key = 'onclick'; " - "var otherKey = 'o' + 'n' + 'c' + 'l' + 'i' + 'c' + 'k';" - "obj[key] = function() {return 10;};" - "console.log(obj[otherKey]());"; - context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(HostClass, setExoticClassProperty) { - bool static errorCalled = false; - bool static logCalled = false; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "200"); - }; - - auto& context = bridge->getContext(); - auto* constructor = new ExoticClass(context.get()); - context->defineGlobalProperty("ExoticClass", constructor->jsObject); - - std::string code = - "var obj = new ExoticClass();" - "obj.className = 200.0;" - "console.log(obj.className);"; - context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(exoticClassFreed, true); - EXPECT_EQ(logCalled, true); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/host_object.cc b/bridge/bindings/qjs/host_object.cc deleted file mode 100644 index ba0f8a2c31..0000000000 --- a/bridge/bindings/qjs/host_object.cc +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "host_object.h" - -namespace kraken::binding::qjs { - -JSValue ExoticHostObject::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { - return JS_NULL; -} -int ExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { - return 0; -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/host_object.h b/bridge/bindings/qjs/host_object.h deleted file mode 100644 index 5ba3f01db4..0000000000 --- a/bridge/bindings/qjs/host_object.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_HOST_OBJECT_H -#define KRAKENBRIDGE_HOST_OBJECT_H - -#include "executing_context.h" - -namespace kraken::binding::qjs { - -class HostObject { - public: - KRAKEN_DISALLOW_COPY_AND_ASSIGN(HostObject); - - HostObject() = delete; - HostObject(ExecutionContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { - JSClassDef def{}; - def.class_name = "HostObject"; - def.finalizer = proxyFinalize; - JS_NewClass(context->runtime(), ExecutionContext::kHostObjectClassId, &def); - jsObject = JS_NewObjectClass(m_ctx, ExecutionContext::kHostObjectClassId); - JS_SetOpaque(jsObject, this); - } - - JSValue jsObject{JS_NULL}; - - protected: - virtual ~HostObject() = default; - std::string m_name; - ExecutionContext* m_context; - int32_t m_contextId; - JSContext* m_ctx; - - private: - static void proxyFinalize(JSRuntime* rt, JSValue val) { - auto hostObject = static_cast(JS_GetOpaque(val, ExecutionContext::kHostObjectClassId)); - delete hostObject; - }; -}; - -class ExoticHostObject { - public: - KRAKEN_DISALLOW_COPY_AND_ASSIGN(ExoticHostObject); - - ExoticHostObject() = delete; - ExoticHostObject(ExecutionContext* context, std::string name) : m_context(context), m_name(std::move(name)), m_ctx(context->ctx()), m_contextId(context->getContextId()) { - JSClassExoticMethods* m_exoticMethods = new JSClassExoticMethods{nullptr, nullptr, nullptr, nullptr, nullptr, proxyGetProperty, proxySetProperty}; - JSClassDef def{}; - def.class_name = m_name.c_str(); - def.finalizer = proxyFinalize; - def.exotic = m_exoticMethods; - JS_NewClass(context->runtime(), ExecutionContext::kHostExoticObjectClassId, &def); - jsObject = JS_NewObjectClass(m_ctx, ExecutionContext::kHostExoticObjectClassId); - JS_SetOpaque(jsObject, this); - } - - JSValue jsObject{JS_NULL}; - - static JSValue proxyGetProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { - auto* object = static_cast(JS_GetOpaque(obj, ExecutionContext::kHostExoticObjectClassId)); - return object->getProperty(ctx, obj, atom, receiver); - }; - static int proxySetProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { - auto* object = static_cast(JS_GetOpaque(obj, ExecutionContext::kHostExoticObjectClassId)); - return object->setProperty(ctx, obj, atom, value, receiver, flags); - }; - - virtual JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - virtual int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); - - protected: - virtual ~ExoticHostObject() = default; - std::string m_name; - ExecutionContext* m_context; - int32_t m_contextId; - JSContext* m_ctx; - - static void proxyFinalize(JSRuntime* rt, JSValue val) { - auto hostObject = static_cast(JS_GetOpaque(val, ExecutionContext::kHostExoticObjectClassId)); - delete hostObject; - }; -}; - -} // namespace kraken::binding::qjs - -#endif // KRAKENBRIDGE_HOST_OBJECT_H diff --git a/bridge/bindings/qjs/host_object_test.cc b/bridge/bindings/qjs/host_object_test.cc deleted file mode 100644 index de7d997945..0000000000 --- a/bridge/bindings/qjs/host_object_test.cc +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "host_object.h" -#include -#include "executing_context.h" -#include "kraken_test_env.h" -#include "page.h" - -namespace kraken::binding::qjs { - -static bool isSampleFree = false; - -class SampleObject : public HostObject { - public: - explicit SampleObject(ExecutionContext* context) : HostObject(context, "SampleObject"){}; - ~SampleObject() { isSampleFree = true; } - - private: - class FooPropertyDescriptor { - public: - static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* sampleObject = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewFloat64(ctx, sampleObject->m_foo); - } - static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* sampleObject = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - double f; - JS_ToFloat64(ctx, &f, argv[0]); - sampleObject->m_foo = f; - return JS_NULL; - } - }; - - static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - double v; - JS_ToFloat64(ctx, &v, argv[0]); - return JS_NewFloat64(ctx, 10 + v); - } - - double m_foo{0}; - ObjectProperty m_width{m_context, jsObject, "foo", FooPropertyDescriptor::getter, FooPropertyDescriptor::setter}; - ObjectFunction m_f{m_context, jsObject, "f", f, 1}; -}; - -TEST(HostObject, defineProperty) { - bool static logCalled = false; - bool static errorCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - - EXPECT_STREQ(message.c_str(), "{f: ƒ (), foo: 1}"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto& context = bridge->getContext(); - auto* sampleObject = new SampleObject(context.get()); - JSValue object = sampleObject->jsObject; - context->defineGlobalProperty("o", object); - const char* code = "o.foo++; console.log(o);"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(logCalled, true); - EXPECT_EQ(errorCalled, false); -} - -TEST(ObjectProperty, worksWithProxy) { - bool static logCalled = false; - bool static errorCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "0"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto& context = bridge->getContext(); - auto* sampleObject = new SampleObject(context.get()); - JSValue object = sampleObject->jsObject; - context->defineGlobalProperty("o", object); - std::string code = std::string(R"( -let p = new Proxy(o, { - get(target, key, receiver) { - return Reflect.get(target, key, receiver); - } -}); -console.log(p.foo); -)"); - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(logCalled, true); - EXPECT_EQ(errorCalled, false); -} - -TEST(HostObject, defineFunction) { - bool static logCalled = false; - bool static errorCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "20"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto& context = bridge->getContext(); - auto* sampleObject = new SampleObject(context.get()); - JSValue object = sampleObject->jsObject; - context->defineGlobalProperty("o", object); - const char* code = "console.log(o.f(10))"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(logCalled, true); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(isSampleFree, true); -} - -class SampleExoticHostObject : public ExoticHostObject { - public: - explicit SampleExoticHostObject(ExecutionContext* context) : ExoticHostObject(context, "SampleObject"){}; - ~SampleExoticHostObject() { isSampleFree = true; } - - JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); - - private: -}; - -JSValue SampleExoticHostObject::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { - return JS_NewFloat64(ctx, 100.0); -} -int SampleExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { - return 0; -} - -TEST(ExoticHostObject, overriteGetterSetter) { - bool static logCalled = false; - bool static errorCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "100"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto& context = bridge->getContext(); - auto* sampleObject = new SampleExoticHostObject(context.get()); - JSValue object = sampleObject->jsObject; - context->defineGlobalProperty("o", object); - const char* code = "console.log(o.abc)"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(logCalled, true); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(isSampleFree, true); -} - -} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/native_value.cc b/bridge/bindings/qjs/native_value.cc index 4d945694a9..85954aef13 100644 --- a/bridge/bindings/qjs/native_value.cc +++ b/bridge/bindings/qjs/native_value.cc @@ -15,6 +15,21 @@ namespace kraken::binding::qjs { #define AnonymousFunctionCallPreFix "_anonymous_fn_" #define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" +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; +} + + NativeValue Native_NewNull() { return (NativeValue){0, .u = {.int64 = 0}, NativeTag::TAG_NULL}; } diff --git a/bridge/bindings/qjs/native_value.h b/bridge/bindings/qjs/native_value.h index 3e7ae0dda0..73549dec8b 100644 --- a/bridge/bindings/qjs/native_value.h +++ b/bridge/bindings/qjs/native_value.h @@ -6,7 +6,10 @@ #ifndef KRAKENBRIDGE_NATIVE_VALUE_H #define KRAKENBRIDGE_NATIVE_VALUE_H -#include "executing_context.h" +#include +#include +#include +#include enum NativeTag { TAG_STRING = 0, @@ -24,6 +27,16 @@ enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, N namespace kraken::binding::qjs { +struct NativeString { + const uint16_t* string; + uint32_t length; + + NativeString* clone(); + void free(); +}; + +class ExecutionContext; + // Exchange data struct between dart and C++ struct NativeValue { double float64; diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index d38776e201..15b10b5bea 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -6,8 +6,6 @@ #ifndef KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ #define KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ -#include "include/kraken_bridge.h" - namespace foundation { class UICommandBuffer { diff --git a/bridge/include/dart_methods.h b/bridge/include/dart_methods.h index d07c37b0ad..47bd2f24df 100644 --- a/bridge/include/dart_methods.h +++ b/bridge/include/dart_methods.h @@ -13,7 +13,6 @@ #define KRAKEN_EXPORT __attribute__((__visibility__("default"))) -struct NativeString; struct NativeScreen; using AsyncCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg); diff --git a/bridge/include/kraken_bridge.h b/bridge/include/kraken_bridge.h index 3a1935b2b2..6f29301a64 100644 --- a/bridge/include/kraken_bridge.h +++ b/bridge/include/kraken_bridge.h @@ -6,7 +6,6 @@ #ifndef KRAKEN_BRIDGE_EXPORT_H #define KRAKEN_BRIDGE_EXPORT_H -#include #include #include "dart_methods.h" @@ -23,13 +22,7 @@ KRAKEN_EXPORT std::thread::id getUIThreadId(); -struct NativeString { - const uint16_t* string; - uint32_t length; - - NativeString* clone(); - void free(); -}; +typedef struct void* NativeString*; struct NativeByteCode { uint8_t* bytes; @@ -45,11 +38,6 @@ struct KrakenInfo { const char* system_name{nullptr}; }; -struct NativeScreen { - double width; - double height; -}; - enum UICommand { createElement, createTextNode, diff --git a/bridge/include/kraken_foundation.h b/bridge/include/kraken_foundation.h index eb85806234..2f8d4b1405 100644 --- a/bridge/include/kraken_foundation.h +++ b/bridge/include/kraken_foundation.h @@ -6,6 +6,7 @@ #ifndef KRAKENBRIDGE_FOUNDATION_H #define KRAKENBRIDGE_FOUNDATION_H +#include #include #include #include diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index b274b8edc2..7c1b5c6ba1 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -257,17 +257,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/page.cc b/bridge/page.cc index c24d439888..f85d6a05c8 100644 --- a/bridge/page.cc +++ b/bridge/page.cc @@ -79,25 +79,25 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c bindTextNode(m_context); bindCommentNode(m_context); bindElement(m_context); - bindAnchorElement(m_context); - bindCanvasElement(m_context); - bindImageElement(m_context); - bindInputElement(m_context); - bindObjectElement(m_context); - bindScriptElement(m_context); - bindTemplateElement(m_context); +// bindAnchorElement(m_context); +// bindCanvasElement(m_context); +// bindImageElement(m_context); +// bindInputElement(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); +// 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); +// bindPerformance(m_context); #if ENABLE_PROFILE nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); @@ -119,22 +119,22 @@ bool KrakenPage::parseHTML(const char* code, size_t length) { if (!m_context->isValid()) return false; JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); - auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); + auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId)); HTMLParser::parseHTML(code, length, body); JS_FreeValue(m_context->ctx(), bodyValue); return true; } -void KrakenPage::invokeModuleEvent(NativeString* moduleName, const char* eventType, void* rawEvent, NativeString* extra) { +void KrakenPage::invokeModuleEvent(NativeString* moduleName, const char* eventType, void* ptr, NativeString* extra) { if (!m_context->isValid()) return; JSValue eventObject = JS_NULL; - if (rawEvent != nullptr) { + if (ptr != nullptr) { std::string type = std::string(eventType); - auto* event = static_cast(rawEvent)->bytes; - EventInstance* eventInstance = Event::buildEventInstance(type, m_context.get(), event, false); - eventObject = eventInstance->jsObject; + auto* rawEvent = static_cast(ptr)->bytes; + Event* event = Event::create(m_context->ctx(), reinterpret_cast(rawEvent)); + eventObject = event->toQuickJS(); } JSValue moduleNameValue = JS_NewUnicodeString(m_context->runtime(), m_context->ctx(), moduleName->string, moduleName->length); diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index a7aa81b38f..e77b1de3bb 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -20,8 +20,7 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./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/garbage_collected_test.cc ./bindings/qjs/dom/event_target_test.cc ./bindings/qjs/module_manager_test.cc ./bindings/qjs/dom/node_test.cc From 13dbcc2004bc9fddd34505e2a092d63c2d044a0b Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Mon, 24 Jan 2022 13:30:33 +0000 Subject: [PATCH 005/498] Committing clang-format changes --- bridge/bindings/qjs/bom/blob.cc | 6 +- bridge/bindings/qjs/bom/blob.h | 14 +- bridge/bindings/qjs/bom/location.h | 8 +- bridge/bindings/qjs/bom/window.h | 3 +- bridge/bindings/qjs/context_macros.h | 18 +-- bridge/bindings/qjs/dom/comment_node.cc | 7 +- bridge/bindings/qjs/dom/comment_node.h | 22 ++-- bridge/bindings/qjs/dom/custom_event.cc | 10 +- bridge/bindings/qjs/dom/custom_event.h | 10 +- bridge/bindings/qjs/dom/document.cc | 122 +++++++++--------- bridge/bindings/qjs/dom/document.h | 16 +-- bridge/bindings/qjs/dom/document_fragment.cc | 2 +- bridge/bindings/qjs/dom/document_fragment.h | 6 +- bridge/bindings/qjs/dom/element.cc | 16 +-- bridge/bindings/qjs/dom/element.h | 17 +-- bridge/bindings/qjs/dom/event.cc | 8 +- bridge/bindings/qjs/dom/event.h | 11 +- bridge/bindings/qjs/dom/event_target.cc | 6 +- bridge/bindings/qjs/dom/event_target.h | 7 +- bridge/bindings/qjs/dom/node.h | 15 +-- bridge/bindings/qjs/dom/style_declaration.h | 3 +- bridge/bindings/qjs/dom/text_node.h | 6 +- bridge/bindings/qjs/executing_context.cc | 1 - bridge/bindings/qjs/executing_context.h | 9 +- bridge/bindings/qjs/executing_context_data.cc | 3 +- bridge/bindings/qjs/executing_context_data.h | 5 +- bridge/bindings/qjs/garbage_collected.h | 12 +- bridge/bindings/qjs/garbage_collected_test.cc | 83 ++++++------ bridge/bindings/qjs/native_value.cc | 1 - bridge/bindings/qjs/native_value.h | 4 +- bridge/bindings/qjs/wrapper_type_info.h | 7 +- bridge/include/kraken_foundation.h | 1 - bridge/page.cc | 36 +++--- 33 files changed, 219 insertions(+), 276 deletions(-) diff --git a/bridge/bindings/qjs/bom/blob.cc b/bridge/bindings/qjs/bom/blob.cc index d83c449739..3fe0fb6e28 100644 --- a/bridge/bindings/qjs/bom/blob.cc +++ b/bridge/bindings/qjs/bom/blob.cc @@ -35,7 +35,6 @@ Blob* Blob::create(JSContext* ctx) { // Let eventTarget instance inherit EventTarget prototype methods. JS_SetPrototype(ctx, blob->toQuickJS(), prototype); return blob; - } Blob* Blob::create(JSContext* ctx, std::vector&& data) { return create(ctx); @@ -259,9 +258,6 @@ uint8_t* Blob::bytes() { } void Blob::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const {} -void Blob::dispose() const { - -} - +void Blob::dispose() const {} } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/blob.h b/bridge/bindings/qjs/bom/blob.h index 5639f0126c..25de28eab1 100644 --- a/bridge/bindings/qjs/bom/blob.h +++ b/bridge/bindings/qjs/bom/blob.h @@ -25,9 +25,9 @@ class Blob : public GarbageCollected { static JSValue constructor(ExecutionContext* context); static JSValue prototype(ExecutionContext* context); - Blob() {}; - Blob(std::vector&& data): _size(data.size()), _data(std::move(data)) {}; - Blob(std::vector&& data, std::string& mime): mimeType(mime), _size(data.size()), _data(std::move(data)) {}; + Blob(){}; + Blob(std::vector&& data) : _size(data.size()), _data(std::move(data)){}; + Blob(std::vector&& data, std::string& mime) : mimeType(mime), _size(data.size()), _data(std::move(data)){}; DEFINE_FUNCTION(arrayBuffer); DEFINE_FUNCTION(slice); @@ -41,7 +41,7 @@ class Blob : public GarbageCollected { DEFINE_PROTOTYPE_READONLY_PROPERTY(type); DEFINE_PROTOTYPE_READONLY_PROPERTY(size); - void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; void dispose() const override; private: @@ -111,11 +111,7 @@ auto blobCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_v return blob->toQuickJS(); }; -const WrapperTypeInfo blobTypeInfo = { - "Blob", - nullptr, - blobCreator -}; +const WrapperTypeInfo blobTypeInfo = {"Blob", nullptr, blobCreator}; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/location.h b/bridge/bindings/qjs/bom/location.h index 0cbb6054fc..8813793201 100644 --- a/bridge/bindings/qjs/bom/location.h +++ b/bridge/bindings/qjs/bom/location.h @@ -21,7 +21,7 @@ class Location : public GarbageCollected { DEFINE_FUNCTION(reload); - void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; void dispose() const override; }; @@ -36,11 +36,7 @@ auto locationCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst th return location->toQuickJS(); }; -const WrapperTypeInfo locationTypeInfo = { - "Location", - nullptr, - locationCreator -}; +const WrapperTypeInfo locationTypeInfo = {"Location", nullptr, locationCreator}; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/bom/window.h b/bridge/bindings/qjs/bom/window.h index 2d5fe54e7e..2e01be49b1 100644 --- a/bridge/bindings/qjs/bom/window.h +++ b/bridge/bindings/qjs/bom/window.h @@ -17,7 +17,6 @@ void bindWindow(std::unique_ptr& context); class Window : public EventTarget { public: - Window(); static JSClassID classId; @@ -42,7 +41,7 @@ class Window : public EventTarget { DEFINE_PROTOTYPE_PROPERTY(onerror); - void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; private: Document* document(); diff --git a/bridge/bindings/qjs/context_macros.h b/bridge/bindings/qjs/context_macros.h index fedcd3ef9a..e50a85484c 100644 --- a/bridge/bindings/qjs/context_macros.h +++ b/bridge/bindings/qjs/context_macros.h @@ -15,45 +15,41 @@ #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_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 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 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); \ - }; \ + }; #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); \ - }; \ + }; #define DEFINE_READONLY_PROPERTY(PROPERTY) \ class PROPERTY##PropertyDescriptor { \ public: \ static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ - }; \ + }; #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); \ - }; \ + }; #endif // KRAKENBRIDGE_CONTEXT_MACROS_H diff --git a/bridge/bindings/qjs/dom/comment_node.cc b/bridge/bindings/qjs/dom/comment_node.cc index c2c7cf26f3..6af1bef852 100644 --- a/bridge/bindings/qjs/dom/comment_node.cc +++ b/bridge/bindings/qjs/dom/comment_node.cc @@ -10,8 +10,8 @@ namespace kraken::binding::qjs { void bindCommentNode(std::unique_ptr& context) { -// auto* constructor = Comment::instance(context.get()); -// context->defineGlobalProperty("Comment", constructor->jsObject); + // auto* constructor = Comment::instance(context.get()); + // context->defineGlobalProperty("Comment", constructor->jsObject); } JSClassID Comment::classId{0}; @@ -26,10 +26,9 @@ Comment* Comment::create(JSContext* ctx) { JS_SetPrototype(ctx, comment->toQuickJS(), prototype); return comment; - } -//Comment::Comment(ExecutionContext* context) : Node(context, "Comment") { +// 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()); //} diff --git a/bridge/bindings/qjs/dom/comment_node.h b/bridge/bindings/qjs/dom/comment_node.h index 7be7e31737..eed74f4e8f 100644 --- a/bridge/bindings/qjs/dom/comment_node.h +++ b/bridge/bindings/qjs/dom/comment_node.h @@ -21,12 +21,12 @@ class Comment : public Node { static JSValue constructor(ExecutionContext* context); static JSValue prototype(ExecutionContext* context); -// static JSClassID kCommentClassId; -// static JSClassID classId(); -// Comment() = delete; -// explicit Comment(ExecutionContext* context); + // static JSClassID kCommentClassId; + // static JSClassID classId(); + // Comment() = delete; + // explicit Comment(ExecutionContext* context); -// JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + // JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; private: DEFINE_PROTOTYPE_READONLY_PROPERTY(data); @@ -36,18 +36,12 @@ class Comment : public Node { friend CommentInstance; }; -auto commentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { -}; - +auto commentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue {}; -const WrapperTypeInfo commentTypeInfo = { - "Comment", - &nodeTypeInfo, - commentCreator -}; +const WrapperTypeInfo commentTypeInfo = {"Comment", &nodeTypeInfo, commentCreator}; // -//class CommentInstance : public NodeInstance { +// class CommentInstance : public NodeInstance { // public: // CommentInstance() = delete; // explicit CommentInstance(Comment* comment); diff --git a/bridge/bindings/qjs/dom/custom_event.cc b/bridge/bindings/qjs/dom/custom_event.cc index 7730a466ee..f92a6a5f18 100644 --- a/bridge/bindings/qjs/dom/custom_event.cc +++ b/bridge/bindings/qjs/dom/custom_event.cc @@ -3,9 +3,9 @@ * Author: Kraken Team. */ -#include "bindings/qjs/qjs_patch.h" -#include "bindings/qjs/native_value.h" #include "custom_event.h" +#include "bindings/qjs/native_value.h" +#include "bindings/qjs/qjs_patch.h" #include "kraken_bridge.h" #include @@ -49,7 +49,7 @@ CustomEvent* CustomEvent::create(JSContext* ctx, JSValue eventType, JSValue init return event; } -CustomEvent * CustomEvent::create(JSContext* ctx, NativeCustomEvent* nativeCustomEvent) { +CustomEvent* CustomEvent::create(JSContext* ctx, NativeCustomEvent* nativeCustomEvent) { auto* context = static_cast(JS_GetContextOpaque(ctx)); JSValue prototype = context->contextData()->prototypeForType(&eventTypeInfo); @@ -69,7 +69,7 @@ JSValue CustomEvent::prototype(ExecutionContext* context) { return context->contextData()->prototypeForType(&customEventTypeInfo); } -CustomEvent::CustomEvent(JSValue eventType, JSValue eventInit): Event(eventType, eventInit) { +CustomEvent::CustomEvent(JSValue eventType, JSValue eventInit) : Event(eventType, eventInit) { if (!JS_IsNull(eventInit)) { JSAtom detailKey = JS_NewAtom(m_ctx, "detail"); if (JS_HasProperty(m_ctx, eventInit, detailKey)) { @@ -81,7 +81,7 @@ CustomEvent::CustomEvent(JSValue eventType, JSValue eventInit): Event(eventType, } } -CustomEvent::CustomEvent(NativeCustomEvent* nativeEvent): m_nativeCustomEvent(nativeEvent), Event(reinterpret_cast(nativeEvent)) { +CustomEvent::CustomEvent(NativeCustomEvent* nativeEvent) : m_nativeCustomEvent(nativeEvent), Event(reinterpret_cast(nativeEvent)) { m_detail = JS_NewUnicodeString(m_runtime, m_ctx, nativeEvent->detail->string, nativeEvent->detail->length); } diff --git a/bridge/bindings/qjs/dom/custom_event.h b/bridge/bindings/qjs/dom/custom_event.h index 84522d5620..bd1c68694b 100644 --- a/bridge/bindings/qjs/dom/custom_event.h +++ b/bridge/bindings/qjs/dom/custom_event.h @@ -28,7 +28,7 @@ class CustomEvent : public Event { CustomEvent(JSValue eventType, JSValue init); CustomEvent(NativeCustomEvent* nativeEvent); - void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; void dispose() const override; DEFINE_FUNCTION(initCustomEvent); @@ -40,7 +40,7 @@ class CustomEvent : public Event { JSValue m_detail{JS_NULL}; }; -//class CustomEventInstance : public EventInstance { +// class CustomEventInstance : public EventInstance { // public: // explicit CustomEventInstance(CustomEvent* jsCustomEvent, JSAtom CustomEventType, JSValue eventInit); // explicit CustomEventInstance(CustomEvent* jsCustomEvent, NativeCustomEvent* nativeCustomEvent); @@ -69,11 +69,7 @@ auto customEventCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst return customEvent->toQuickJS(); }; -const WrapperTypeInfo customEventTypeInfo = { - "CustomEvent", - &eventTypeInfo, - customEventCreator -}; +const WrapperTypeInfo customEventTypeInfo = {"CustomEvent", &eventTypeInfo, customEventCreator}; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc index 1fc1d243e1..4c12abaac3 100644 --- a/bridge/bindings/qjs/dom/document.cc +++ b/bridge/bindings/qjs/dom/document.cc @@ -103,63 +103,66 @@ JSValue Document::prototype(ExecutionContext* context) { Document::Document() : Node() { 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("object", ObjectElement::instance(m_context)); -// defineElement("script", ScriptElement::instance(m_context)); -// defineElement("template", TemplateElement::instance(m_context)); + // 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("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)); -// }); + // 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)); + // }); } } @@ -198,12 +201,12 @@ IMPL_FUNCTION(Document, createElement)(JSContext* ctx, JSValue this_val, int arg } 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->context(), tagName); -// -// JSValue element = JS_CallConstructor(ctx, constructor, argc, argv); -// return element; + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // std::string tagName = jsValueToStdString(ctx, tagNameValue); + // JSValue constructor = static_cast(document->prototype())->getElementConstructor(document->context(), tagName); + // + // JSValue element = JS_CallConstructor(ctx, constructor, argc, argv); + // return element; } IMPL_FUNCTION(Document, createTextNode)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { @@ -555,8 +558,7 @@ Document::Document(Document* document) : Node(document, NodeType::DOCUMENT_NODE, #endif } -Document::~Document() { -} +Document::~Document() {} void Document::removeElementById(JSAtom id, Element* element) { if (m_elementMapById.count(id) > 0) { auto& list = m_elementMapById[id]; diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/bindings/qjs/dom/document.h index feb7c44ed1..1e3ce04bdd 100644 --- a/bridge/bindings/qjs/dom/document.h +++ b/bridge/bindings/qjs/dom/document.h @@ -20,17 +20,16 @@ using TraverseHandler = std::function; void traverseNode(Node* node, TraverseHandler handler); class DocumentCookie { -public: + public: DocumentCookie() = default; std::string getCookie(); void setCookie(std::string& str); -private: + private: std::unordered_map cookiePairs; }; - class Document : public Node { public: static JSClassID classId; @@ -66,7 +65,6 @@ class Document : public Node { void dispose() const override; private: - void removeElementById(JSAtom id, Element* element); void addElementById(JSAtom id, Element* element); Element* getDocumentElement(); @@ -83,15 +81,9 @@ class Document : public Node { std::unordered_map elementConstructorMap; }; -auto documentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { - return JS_ThrowTypeError(ctx, "Illegal constructor"); -}; +auto documentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { return JS_ThrowTypeError(ctx, "Illegal constructor"); }; -const WrapperTypeInfo documentTypeInfo = { - "Document", - &nodeTypeInfo, - documentCreator -}; +const WrapperTypeInfo documentTypeInfo = {"Document", &nodeTypeInfo, documentCreator}; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/document_fragment.cc b/bridge/bindings/qjs/dom/document_fragment.cc index 6b14777bba..2b2c096ad1 100644 --- a/bridge/bindings/qjs/dom/document_fragment.cc +++ b/bridge/bindings/qjs/dom/document_fragment.cc @@ -18,7 +18,7 @@ JSValue DocumentFragment::constructor(ExecutionContext* context) { return context->contextData()->constructorForType(&documentFragmentInfo); } -DocumentFragment * DocumentFragment::create(JSContext* ctx) { +DocumentFragment* DocumentFragment::create(JSContext* ctx) { auto* context = static_cast(JS_GetContextOpaque(ctx)); JSValue prototype = context->contextData()->prototypeForType(&documentFragmentInfo); auto* documentFragment = makeGarbageCollected()->initialize(ctx, &classId); diff --git a/bridge/bindings/qjs/dom/document_fragment.h b/bridge/bindings/qjs/dom/document_fragment.h index a2bdcf8791..b24c57314e 100644 --- a/bridge/bindings/qjs/dom/document_fragment.h +++ b/bridge/bindings/qjs/dom/document_fragment.h @@ -29,11 +29,7 @@ auto documentFragmentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValue return eventTarget->toQuickJS(); }; -const WrapperTypeInfo documentFragmentInfo = { - "DocumentFragment", - &nodeTypeInfo, - documentFragmentCreator -}; +const WrapperTypeInfo documentFragmentInfo = {"DocumentFragment", &nodeTypeInfo, documentFragmentCreator}; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc index 6a082a7bd3..19cfd69963 100644 --- a/bridge/bindings/qjs/dom/element.cc +++ b/bridge/bindings/qjs/dom/element.cc @@ -185,7 +185,7 @@ JSValue Element::prototype(ExecutionContext* context) { return context->contextData()->prototypeForType(&elementTypeInfo); } -//JSValue Element::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +// 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]; @@ -643,11 +643,11 @@ IMPL_PROPERTY_SETTER(Element, outerHTML)(JSContext* ctx, JSValue this_val, int a return JS_NULL; } -//JSClassID Element::classId { +// JSClassID Element::classId { // return Element::classId; //} -//Element::~Element() {} +// Element::~Element() {} JSValue Element::internalGetTextContent() { JSValue array = JS_NewArray(m_ctx); @@ -887,7 +887,7 @@ void Element::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { NodeInstance::trace(rt, val, mark_func); } -//Element::Element(Element* element, std::string tagName, bool shouldAddUICommand): Node() { +// Element::Element(Element* element, std::string tagName, bool shouldAddUICommand): Node() { // m_attributes = makeGarbageCollected()->initialize(m_ctx, &ElementAttributes::classId); // JSValue arguments[] = {jsObject}; // JSValue style = JS_CallConstructor(m_ctx, CSSStyleDeclaration::instance(m_context)->jsObject, 1, arguments); @@ -907,12 +907,8 @@ StyleDeclarationInstance* Element::style() { return m_style; } -void Element::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - -} -void Element::dispose() const { - -} +void Element::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const {} +void Element::dispose() const {} IMPL_PROPERTY_GETTER(BoundingClientRect, x)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* boundingClientRect = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h index 126bd0b305..0442ba416c 100644 --- a/bridge/bindings/qjs/dom/element.h +++ b/bridge/bindings/qjs/dom/element.h @@ -7,8 +7,8 @@ #define KRAKENBRIDGE_ELEMENT_H #include -#include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/executing_context.h" +#include "bindings/qjs/garbage_collected.h" #include "node.h" #include "style_declaration.h" @@ -126,6 +126,7 @@ class Element : public Node { protected: StyleDeclarationInstance* m_style{nullptr}; ElementAttributes* m_attributes{nullptr}; + private: std::string m_tagName; void _notifyNodeRemoved(Node* node) override; @@ -155,13 +156,13 @@ auto elementCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst thi Element* element = Element::create(ctx); auto* document = context->document(); -// auto* Document = Document::instance(context); -// if (Document->isCustomElement(name)) { -// return JS_CallConstructor(ctx, Document->getElementConstructor(context, name), argc, argv); -// } -// -// auto* element = new Element(this, name, true); -// return element->jsObject; + // auto* Document = Document::instance(context); + // if (Document->isCustomElement(name)) { + // return JS_CallConstructor(ctx, Document->getElementConstructor(context, name), argc, argv); + // } + // + // auto* element = new Element(this, name, true); + // return element->jsObject; }; const WrapperTypeInfo elementTypeInfo = {"Element", &nodeTypeInfo, elementCreator}; diff --git a/bridge/bindings/qjs/dom/event.cc b/bridge/bindings/qjs/dom/event.cc index 0d6ab0d8ac..b783b3db5c 100644 --- a/bridge/bindings/qjs/dom/event.cc +++ b/bridge/bindings/qjs/dom/event.cc @@ -57,10 +57,8 @@ Event* Event::create(JSContext* ctx, NativeEvent* nativeEvent) { return event; } -Event::Event(NativeEvent* nativeEvent): nativeEvent(nativeEvent) {} -Event::Event(JSValue eventType, JSValue eventInit) { - -} +Event::Event(NativeEvent* nativeEvent) : nativeEvent(nativeEvent) {} +Event::Event(JSValue eventType, JSValue eventInit) {} JSValue Event::constructor(ExecutionContext* context) { return context->contextData()->constructorForType(&eventTypeInfo); @@ -134,7 +132,7 @@ IMPL_PROPERTY_GETTER(Event, cancelBubble)(JSContext* ctx, JSValue this_val, int return JS_NewBool(ctx, event->cancelled()); } -//Event* Event::buildEvent(JSValue eventType, JSContext* ctx, void* nativeEvent, bool isCustomEvent) { +// Event* Event::buildEvent(JSValue eventType, JSContext* ctx, void* nativeEvent, bool isCustomEvent) { // Event* event; // if (isCustomEvent) { // event = CustomEvent::create(ctx, reinterpret_cast(nativeEvent), eventType); diff --git a/bridge/bindings/qjs/dom/event.h b/bridge/bindings/qjs/dom/event.h index 91e60d983d..54dd6c220f 100644 --- a/bridge/bindings/qjs/dom/event.h +++ b/bridge/bindings/qjs/dom/event.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_EVENT_H #define KRAKENBRIDGE_EVENT_H -#include "bindings/qjs/executing_context.h" #include "bindings/qjs/context_macros.h" +#include "bindings/qjs/executing_context.h" namespace kraken::binding::qjs { @@ -108,10 +108,11 @@ class Event : public GarbageCollected { inline void cancelled(bool v) { m_cancelled = v; } inline const bool propagationImmediatelyStopped() { return m_propagationImmediatelyStopped; } - void trace(JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) const override; + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; void dispose() const override; NativeEvent* nativeEvent{nullptr}; + private: static std::unordered_map m_eventCreatorMap; bool m_cancelled{false}; @@ -132,11 +133,7 @@ auto eventCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_ return event->toQuickJS(); }; -const WrapperTypeInfo eventTypeInfo = { - "Event", - nullptr, - eventCreator -}; +const WrapperTypeInfo eventTypeInfo = {"Event", nullptr, eventCreator}; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/bindings/qjs/dom/event_target.cc index 510eaff282..53e34e9f5d 100644 --- a/bridge/bindings/qjs/dom/event_target.cc +++ b/bridge/bindings/qjs/dom/event_target.cc @@ -9,10 +9,10 @@ #include "bindings/qjs/bom/window.h" #include "bindings/qjs/dom/text_node.h" #include "bindings/qjs/qjs_patch.h" +#include "custom_event.h" #include "document.h" #include "element.h" #include "event.h" -#include "custom_event.h" #include "kraken_bridge.h" namespace kraken::binding::qjs { @@ -200,9 +200,7 @@ bool EventTarget::internalDispatchEvent(Event* event) { handler), so must take extra care */ JS_DupValue(m_ctx, handler); - JSValue arguments[] = { - event->toQuickJS() - }; + JSValue arguments[] = {event->toQuickJS()}; // The third params `thisObject` to null equals global object. JSValue returnedValue = JS_Call(m_ctx, handler, JS_NULL, 1, arguments); diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/bindings/qjs/dom/event_target.h index 584239ddd6..522fec4f00 100644 --- a/bridge/bindings/qjs/dom/event_target.h +++ b/bridge/bindings/qjs/dom/event_target.h @@ -6,10 +6,10 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H -#include "bindings/qjs/native_value.h" +#include "bindings/qjs/context_macros.h" #include "bindings/qjs/executing_context.h" #include "bindings/qjs/heap_hashmap.h" -#include "bindings/qjs/context_macros.h" +#include "bindings/qjs/native_value.h" #include "event_listener_map.h" #if UNIT_TEST @@ -80,7 +80,8 @@ class EventTarget : public GarbageCollected { static void copyNodeProperties(EventTarget* newNode, EventTarget* referenceNode); NativeEventTarget* nativeEventTarget{new NativeEventTarget(this)}; -private: + + private: 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); diff --git a/bridge/bindings/qjs/dom/node.h b/bridge/bindings/qjs/dom/node.h index 8b6795777c..b47fd31090 100644 --- a/bridge/bindings/qjs/dom/node.h +++ b/bridge/bindings/qjs/dom/node.h @@ -77,7 +77,7 @@ class Node : public EventTarget { void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; void dispose() const override; -protected: + protected: NodeJob nodeLink{this}; void refer(); @@ -93,7 +93,8 @@ class Node : public EventTarget { virtual void _notifyNodeRemoved(Node* node); virtual void _notifyNodeInsert(Node* node); -private: + + private: ObjectProperty m_childNodes{context(), jsObject, "childNodes", childNodes}; void ensureDetached(Node* node); @@ -101,15 +102,9 @@ class Node : public EventTarget { static JSValue copyNodeValue(JSContext* ctx, Node* node); }; -auto nodeCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { - return JS_ThrowTypeError(ctx, "Illegal constructor"); -}; +auto nodeCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { return JS_ThrowTypeError(ctx, "Illegal constructor"); }; -const WrapperTypeInfo nodeTypeInfo = { - "Node", - &eventTargetTypeInfo, - nodeCreator -}; +const WrapperTypeInfo nodeTypeInfo = {"Node", &eventTargetTypeInfo, nodeCreator}; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/dom/style_declaration.h b/bridge/bindings/qjs/dom/style_declaration.h index 7d6202f5cd..66e71c947d 100644 --- a/bridge/bindings/qjs/dom/style_declaration.h +++ b/bridge/bindings/qjs/dom/style_declaration.h @@ -6,9 +6,9 @@ #ifndef KRAKENBRIDGE_STYLE_DECLARATION_H #define KRAKENBRIDGE_STYLE_DECLARATION_H -#include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/context_macros.h" #include "bindings/qjs/dom/event_target.h" +#include "bindings/qjs/garbage_collected.h" namespace kraken::binding::qjs { @@ -44,7 +44,6 @@ class CSSStyleDeclaration : public GarbageCollected { DEFINE_FUNCTION(getPropertyValue); 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); diff --git a/bridge/bindings/qjs/dom/text_node.h b/bridge/bindings/qjs/dom/text_node.h index 2dea9ab6d4..955166d9f5 100644 --- a/bridge/bindings/qjs/dom/text_node.h +++ b/bridge/bindings/qjs/dom/text_node.h @@ -47,11 +47,7 @@ auto textNodeCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst th return textNode->toQuickJS(); }; -const WrapperTypeInfo textNodeType = { - "TextNode", - &nodeTypeInfo, - textNodeCreator -}; +const WrapperTypeInfo textNodeType = {"TextNode", &nodeTypeInfo, textNodeCreator}; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/executing_context.cc b/bridge/bindings/qjs/executing_context.cc index d5f1dcdaf7..0129f81de4 100644 --- a/bridge/bindings/qjs/executing_context.cc +++ b/bridge/bindings/qjs/executing_context.cc @@ -368,7 +368,6 @@ void installPropertyGetter(ExecutionContext* context, JSValue thisObject, const JS_FreeValue(context->ctx(), getter); } - DOMTimerCoordinator* ExecutionContext::timers() { return &m_timers; } diff --git a/bridge/bindings/qjs/executing_context.h b/bridge/bindings/qjs/executing_context.h index 3417d68ac4..f13ba24926 100644 --- a/bridge/bindings/qjs/executing_context.h +++ b/bridge/bindings/qjs/executing_context.h @@ -16,13 +16,13 @@ #include #include #include -#include "foundation/ui_command_buffer.h" +#include "bindings/qjs/bom/dom_timer_coordinator.h" #include "executing_context_data.h" +#include "foundation/ui_command_buffer.h" #include "garbage_collected.h" #include "kraken_foundation.h" #include "qjs_patch.h" #include "wrapper_type_info.h" -#include "bindings/qjs/bom/dom_timer_coordinator.h" using JSExceptionHandler = std::function; @@ -154,7 +154,8 @@ static JSValue handleCallThisOnProxy(JSContext* ctx, JSValueConst this_val, int class ObjectProperty { KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectProperty); -public: + + public: ObjectProperty() = delete; // Define an property on object with a JSValue. @@ -164,7 +165,7 @@ class ObjectProperty { JSValue value() const { return m_value; } -private: + private: JSValue m_value{JS_NULL}; }; diff --git a/bridge/bindings/qjs/executing_context_data.cc b/bridge/bindings/qjs/executing_context_data.cc index 0474a379dc..d8e0eda910 100644 --- a/bridge/bindings/qjs/executing_context_data.cc +++ b/bridge/bindings/qjs/executing_context_data.cc @@ -3,7 +3,6 @@ * Author: Kraken Team. */ - #include "executing_context_data.h" #include "executing_context.h" @@ -72,4 +71,4 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty return classObject; } -} +} // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/executing_context_data.h b/bridge/bindings/qjs/executing_context_data.h index d379d0f9a2..abdc8d54a7 100644 --- a/bridge/bindings/qjs/executing_context_data.h +++ b/bridge/bindings/qjs/executing_context_data.h @@ -17,7 +17,7 @@ class ExecutionContext; // has a 1:1 relationship with ExecutionContext. class ExecutionContextData final { public: - explicit ExecutionContextData(ExecutionContext* context): m_context(context) {}; + explicit ExecutionContextData(ExecutionContext* context) : m_context(context){}; ExecutionContextData(const ExecutionContextData&) = delete; ExecutionContextData& operator=(const ExecutionContextData&) = delete; @@ -34,7 +34,6 @@ class ExecutionContextData final { ExecutionContext* m_context; }; - -} +} // namespace kraken::binding::qjs #endif // KRAKENBRIDGE_CONTEXT_DATA_H diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index b79b8eb0d9..1da6426a93 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -36,8 +36,10 @@ class GarbageCollected { public: using ParentMostGarbageCollectedType = T; - template P* initialize(JSContext* ctx, JSClassID* classId); - template P* initialize(JSContext* ctx, JSClassID* classId, JSClassExoticMethods* exoticMethods); + template + P* initialize(JSContext* ctx, JSClassID* classId); + template + P* initialize(JSContext* ctx, JSClassID* classId, JSClassExoticMethods* exoticMethods); // Must use MakeGarbageCollected. void* operator new(size_t) = delete; @@ -93,7 +95,8 @@ class MakeGarbageCollectedTrait { }; template -template P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId, JSClassExoticMethods* exoticMethods) { +template +P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId, JSClassExoticMethods* exoticMethods) { 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. @@ -143,7 +146,8 @@ template P* GarbageCollected::initialize(JSContext* ctx, JSClass } template -template P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId) { +template +P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId) { return initialize

(ctx, classId, nullptr); } diff --git a/bridge/bindings/qjs/garbage_collected_test.cc b/bridge/bindings/qjs/garbage_collected_test.cc index 4e43f1d7d1..c1e740884b 100644 --- a/bridge/bindings/qjs/garbage_collected_test.cc +++ b/bridge/bindings/qjs/garbage_collected_test.cc @@ -9,12 +9,13 @@ //#include "kraken_test_env.h" //#include "page.h" // -//namespace kraken::binding::qjs { +// namespace kraken::binding::qjs { // -//class ParentClass : public HostClass { +// 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); } +// 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); // @@ -24,10 +25,10 @@ // ObjectFunction m_foo{m_context, m_prototypeObject, "foo", foo, 0}; //}; // -//class SampleClass; -//static JSClassID kSampleClassId{0}; +// class SampleClass; +// static JSClassID kSampleClassId{0}; // -//class SampleClassInstance : public Instance { +// class SampleClassInstance : public Instance { // public: // explicit SampleClassInstance(HostClass* sampleClass) : Instance(sampleClass, "SampleClass", nullptr, kSampleClassId, finalizer){}; // @@ -41,8 +42,8 @@ // } //}; // -//std::once_flag kSampleClassOnceFlag; -//class SampleClass : public ParentClass { +// std::once_flag kSampleClassOnceFlag; +// class SampleClass : public ParentClass { // public: // explicit SampleClass(ExecutionContext* context) : ParentClass(context) { // std::call_once(kSampleClassOnceFlag, []() { JS_NewClassID(&kSampleClassId); }); @@ -61,7 +62,7 @@ // ObjectFunction m_f{m_context, m_prototypeObject, "f", f, 0}; //}; // -//TEST(HostClass, newInstance) { +// TEST(HostClass, newInstance) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -84,7 +85,7 @@ // EXPECT_EQ(logCalled, true); //} // -//TEST(HostClass, instanceOf) { +// TEST(HostClass, instanceOf) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -115,7 +116,7 @@ // EXPECT_EQ(logCalled, true); //} // -//TEST(HostClass, inheritance) { +// TEST(HostClass, inheritance) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -143,7 +144,7 @@ // EXPECT_EQ(logCalled, true); //} // -//TEST(HostClass, inherintanceInJavaScript) { +// TEST(HostClass, inherintanceInJavaScript) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -163,7 +164,7 @@ // context->defineGlobalProperty("SampleClass", sampleObject->jsObject); // // const char* code = R"( -//class Demo extends SampleClass { +// class Demo extends SampleClass { // constructor(name) { // super(); // this.name = name; @@ -173,8 +174,8 @@ // return this.name.toUpperCase(); // } //} -//let demo = new Demo('test'); -//console.log(demo.getName(), demo.f(), demo.foo()); +// let demo = new Demo('test'); +// console.log(demo.getName(), demo.f(), demo.foo()); //)"; // context->evaluateJavaScript(code, strlen(code), "vm://", 0); // @@ -182,7 +183,7 @@ // EXPECT_EQ(logCalled, true); //} // -//TEST(HostClass, haveFunctionProtoMethods) { +// TEST(HostClass, haveFunctionProtoMethods) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -198,7 +199,7 @@ // context->defineGlobalProperty("ParentClass", parentObject->jsObject); // // const char* code = R"( -//class Demo extends ParentClass { +// class Demo extends ParentClass { // constructor(name) { // super(); // this.name = name; @@ -208,7 +209,7 @@ // return this.name.toUpperCase(); // } //} -//console.log(Demo.call); +// console.log(Demo.call); //)"; // context->evaluateJavaScript(code, strlen(code), "vm://", 0); // @@ -216,7 +217,7 @@ // EXPECT_EQ(logCalled, true); //} // -//TEST(HostClass, multipleInstance) { +// TEST(HostClass, multipleInstance) { // bool static errorCalled = false; // auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { // errorCalled = true; @@ -272,10 +273,10 @@ // EXPECT_EQ(errorCalled, false); //} // -//std::once_flag kExoticClassOnceFlag; +// std::once_flag kExoticClassOnceFlag; // -//class ExoticClassInstance; -//class ExoticClass : public HostClass { +// class ExoticClassInstance; +// class ExoticClass : public HostClass { // public: // static JSClassID exoticClassID; // ExoticClass() = delete; @@ -288,10 +289,10 @@ // friend ExoticClassInstance; //}; // -//JSClassID ExoticClass::exoticClassID{0}; -//static bool exoticClassFreed = false; +// JSClassID ExoticClass::exoticClassID{0}; +// static bool exoticClassFreed = false; // -//class ExoticClassInstance : public Instance { +// class ExoticClassInstance : public Instance { // public: // ExoticClassInstance() = delete; // static JSClassExoticMethods methods; @@ -349,13 +350,13 @@ // double classValue{100.0}; //}; // -//JSClassExoticMethods ExoticClassInstance::methods{nullptr, nullptr, nullptr, nullptr, nullptr, getProperty, setProperty}; +// 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) { +// JSValue ExoticClass::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { // return (new ExoticClassInstance(this))->jsObject; //} // -//TEST(HostClass, exoticClass) { +// TEST(HostClass, exoticClass) { // bool static errorCalled = false; // bool static logCalled = false; // auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { @@ -383,7 +384,7 @@ // EXPECT_EQ(logCalled, true); //} // -//TEST(HostClass, setExoticClassProperty) { +// TEST(HostClass, setExoticClassProperty) { // bool static errorCalled = false; // bool static logCalled = false; // auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { @@ -423,11 +424,11 @@ //#include "kraken_test_env.h" //#include "page.h" // -//namespace kraken::binding::qjs { +// namespace kraken::binding::qjs { // -//static bool isSampleFree = false; +// static bool isSampleFree = false; // -//class SampleObject : public HostObject { +// class SampleObject : public HostObject { // public: // explicit SampleObject(ExecutionContext* context) : HostObject(context, "SampleObject"){}; // ~SampleObject() { isSampleFree = true; } @@ -459,7 +460,7 @@ // ObjectFunction m_f{m_context, jsObject, "f", f, 1}; //}; // -//TEST(HostObject, defineProperty) { +// TEST(HostObject, defineProperty) { // bool static logCalled = false; // bool static errorCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -479,7 +480,7 @@ // EXPECT_EQ(errorCalled, false); //} // -//TEST(ObjectProperty, worksWithProxy) { +// TEST(ObjectProperty, worksWithProxy) { // bool static logCalled = false; // bool static errorCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -495,12 +496,12 @@ // JSValue object = sampleObject->jsObject; // context->defineGlobalProperty("o", object); // std::string code = std::string(R"( -//let p = new Proxy(o, { +// let p = new Proxy(o, { // get(target, key, receiver) { // return Reflect.get(target, key, receiver); // } //}); -//console.log(p.foo); +// console.log(p.foo); //)"); // bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); // @@ -508,7 +509,7 @@ // EXPECT_EQ(errorCalled, false); //} // -//TEST(HostObject, defineFunction) { +// TEST(HostObject, defineFunction) { // bool static logCalled = false; // bool static errorCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -531,7 +532,7 @@ // EXPECT_EQ(isSampleFree, true); //} // -//class SampleExoticHostObject : public ExoticHostObject { +// class SampleExoticHostObject : public ExoticHostObject { // public: // explicit SampleExoticHostObject(ExecutionContext* context) : ExoticHostObject(context, "SampleObject"){}; // ~SampleExoticHostObject() { isSampleFree = true; } @@ -542,14 +543,14 @@ // private: //}; // -//JSValue SampleExoticHostObject::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { +// 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) { +// int SampleExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { // return 0; //} // -//TEST(ExoticHostObject, overriteGetterSetter) { +// TEST(ExoticHostObject, overriteGetterSetter) { // bool static logCalled = false; // bool static errorCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { diff --git a/bridge/bindings/qjs/native_value.cc b/bridge/bindings/qjs/native_value.cc index 85954aef13..73d3c37736 100644 --- a/bridge/bindings/qjs/native_value.cc +++ b/bridge/bindings/qjs/native_value.cc @@ -29,7 +29,6 @@ void NativeString::free() { delete[] string; } - NativeValue Native_NewNull() { return (NativeValue){0, .u = {.int64 = 0}, NativeTag::TAG_NULL}; } diff --git a/bridge/bindings/qjs/native_value.h b/bridge/bindings/qjs/native_value.h index 73549dec8b..8edaaf0d46 100644 --- a/bridge/bindings/qjs/native_value.h +++ b/bridge/bindings/qjs/native_value.h @@ -6,9 +6,9 @@ #ifndef KRAKENBRIDGE_NATIVE_VALUE_H #define KRAKENBRIDGE_NATIVE_VALUE_H -#include -#include #include +#include +#include #include enum NativeTag { diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index a7bc005cd0..02496a855c 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_WRAPPER_TYPE_INFO_H #define KRAKENBRIDGE_WRAPPER_TYPE_INFO_H -#include #include +#include #include "bindings/qjs/qjs_patch.h" #include "include/kraken_foundation.h" @@ -22,8 +22,7 @@ class WrapperTypeInfo final { 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) { + for (const WrapperTypeInfo* current = this; current; current = current->parent_class) { if (current == that) return true; } @@ -37,6 +36,6 @@ class WrapperTypeInfo final { JSClassID classId{0}; }; -} +} // namespace kraken::binding::qjs #endif // KRAKENBRIDGE_WRAPPER_TYPE_INFO_H diff --git a/bridge/include/kraken_foundation.h b/bridge/include/kraken_foundation.h index 2f8d4b1405..eb85806234 100644 --- a/bridge/include/kraken_foundation.h +++ b/bridge/include/kraken_foundation.h @@ -6,7 +6,6 @@ #ifndef KRAKENBRIDGE_FOUNDATION_H #define KRAKENBRIDGE_FOUNDATION_H -#include #include #include #include diff --git a/bridge/page.cc b/bridge/page.cc index f85d6a05c8..cc1a4df622 100644 --- a/bridge/page.cc +++ b/bridge/page.cc @@ -12,10 +12,10 @@ #include "bindings/qjs/bom/blob.h" #include "bindings/qjs/bom/console.h" +#include "bindings/qjs/bom/location.h" #include "bindings/qjs/bom/performance.h" #include "bindings/qjs/bom/screen.h" #include "bindings/qjs/bom/timer.h" -#include "bindings/qjs/bom/location.h" #include "bindings/qjs/bom/window.h" #include "bindings/qjs/dom/comment_node.h" #include "bindings/qjs/dom/custom_event.h" @@ -79,25 +79,25 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c bindTextNode(m_context); bindCommentNode(m_context); bindElement(m_context); -// bindAnchorElement(m_context); -// bindCanvasElement(m_context); -// bindImageElement(m_context); -// bindInputElement(m_context); -// bindObjectElement(m_context); -// bindScriptElement(m_context); -// bindTemplateElement(m_context); + // bindAnchorElement(m_context); + // bindCanvasElement(m_context); + // bindImageElement(m_context); + // bindInputElement(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); + // 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); + // bindPerformance(m_context); #if ENABLE_PROFILE nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); From 69964252ac633cff3b5eb2f15bf3419d3dff7b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=83=E5=BD=A6?= Date: Thu, 27 Jan 2022 16:25:56 +0800 Subject: [PATCH 006/498] refactor: css inline style --- bridge/bindings/qjs/dom/element.cc | 145 +++++++++++-------- bridge/bindings/qjs/dom/element.h | 26 ++-- bridge/bindings/qjs/dom/style_declaration.cc | 129 ++++++++++++----- bridge/bindings/qjs/dom/style_declaration.h | 18 +-- bridge/bindings/qjs/html_parser.cc | 56 ++----- 5 files changed, 213 insertions(+), 161 deletions(-) diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc index e8864a4e95..d10626f52e 100644 --- a/bridge/bindings/qjs/dom/element.cc +++ b/bridge/bindings/qjs/dom/element.cc @@ -17,6 +17,10 @@ namespace kraken::binding::qjs { +const std::string ATTR_ID = "id"; +const std::string ATTR_CLASS = "class"; +const std::string ATTR_STYLE = "style"; + void bindElement(std::unique_ptr& context) { JSValue classObject = Element::constructor(context.get()); JSValue prototype = Element::prototype(context.get()); @@ -54,7 +58,9 @@ void bindElement(std::unique_ptr& context) { INSTALL_READONLY_PROPERTY(Element, prototype, attributes); // Install properties. + INSTALL_PROPERTY(Element, prototype, id); INSTALL_PROPERTY(Element, prototype, className); + INSTALL_PROPERTY(Element, prototype, style); INSTALL_PROPERTY(Element, prototype, innerHTML); INSTALL_PROPERTY(Element, prototype, outerHTML); INSTALL_PROPERTY(Element, prototype, scrollTop); @@ -83,22 +89,23 @@ bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue ins return false; } -JSClassID ElementAttributes::classId{0}; -JSValue ElementAttributes::getAttribute(const std::string& name) { +JSClassID NamedNodeMap::classId{0}; + +JSValue NamedNodeMap::getNamedItem(const std::string& name) { bool numberIndex = isNumberIndex(name); if (numberIndex) { return JS_NULL; } - return JS_DupValue(m_ctx, m_attributes[name]); + return JS_DupValue(m_ctx, m_map[name]); } -JSValue ElementAttributes::setAttribute(const std::string& name, JSValue value) { +JSValue NamedNodeMap::setNamedItem(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()); + return JS_ThrowTypeError(m_ctx, "Failed to execute 'setNamedItem' on 'NamedNodeMap': '%s' is not a valid item name.", name.c_str()); } if (name == "class") { @@ -106,48 +113,44 @@ JSValue ElementAttributes::setAttribute(const std::string& name, JSValue 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]); + // If item exists, should free the previous value. + if (m_map.count(name) > 0) { + JS_FreeValue(m_ctx, m_map[name]); } - m_attributes[name] = JS_DupValue(m_ctx, value); + m_map[name] = JS_DupValue(m_ctx, value); return JS_NULL; } -bool ElementAttributes::hasAttribute(std::string& name) { +bool NamedNodeMap::hasNamedItem(std::string& name) { bool numberIndex = isNumberIndex(name); if (numberIndex) { return false; } - return m_attributes.count(name) > 0; + return m_map.count(name) > 0; } -void ElementAttributes::removeAttribute(std::string& name) { - JSValue value = m_attributes[name]; +void NamedNodeMap::removeNamedItem(std::string& name) { + JSValue value = m_map[name]; JS_FreeValue(m_ctx, value); - m_attributes.erase(name); + m_map.erase(name); } -void ElementAttributes::copyWith(ElementAttributes* attributes) { - for (auto& attr : attributes->m_attributes) { - m_attributes[attr.first] = JS_DupValue(m_ctx, attr.second); +void NamedNodeMap::copyWith(NamedNodeMap* map) { + for (auto& item : map->m_map) { + m_map[item.first] = JS_DupValue(m_ctx, item.second); } } -std::shared_ptr ElementAttributes::className() { - return m_className; -} - -std::string ElementAttributes::toString() { +std::string NamedNodeMap::toString() { std::string s; - for (auto& attr : m_attributes) { - s += attr.first + "="; - const char* pstr = JS_ToCString(m_ctx, attr.second); + for (auto& item : m_map) { + s += item.first + "="; + const char* pstr = JS_ToCString(m_ctx, item.second); s += "\"" + std::string(pstr) + "\""; JS_FreeCString(m_ctx, pstr); } @@ -155,14 +158,15 @@ std::string ElementAttributes::toString() { return s; } -void ElementAttributes::dispose() const { - for (auto& attr : m_attributes) { - JS_FreeValueRT(m_runtime, attr.second); +void NamedNodeMap::dispose() const { + for (auto& item : m_map) { + JS_FreeValueRT(m_runtime, item.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); + +void NamedNodeMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + for (auto& item : m_map) { + JS_MarkValue(rt, item.second, mark_func); } } @@ -220,7 +224,7 @@ IMPL_FUNCTION(Element, hasAttribute)(JSContext* ctx, JSValue this_val, int argc, JSValue nameValue = argv[0]; if (!JS_IsString(nameValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setAttribute' on 'Element': name attribute is not valid."); + return JS_ThrowTypeError(ctx, "Failed to execute 'hasAttribute' on 'Element': name attribute is not valid."); } auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); @@ -229,7 +233,7 @@ IMPL_FUNCTION(Element, hasAttribute)(JSContext* ctx, JSValue this_val, int argc, const char* cname = JS_ToCString(ctx, nameValue); std::string name = std::string(cname); - JSValue result = JS_NewBool(ctx, attributes->hasAttribute(name)); + JSValue result = JS_NewBool(ctx, attributes->hasNamedItem(name)); JS_FreeCString(ctx, cname); return result; @@ -253,15 +257,20 @@ IMPL_FUNCTION(Element, setAttribute)(JSContext* ctx, JSValue this_val, int argc, auto* attributes = element->m_attributes; - if (attributes->hasAttribute(name)) { - JSValue oldAttribute = attributes->getAttribute(name); - JSValue exception = attributes->setAttribute(name, attributeValue); + if (name == ATTR_STYLE) { + auto* style = element->m_style; + style->setCssText(jsValueToStdString(ctx, attributeValue)); + } + + if (attributes->hasNamedItem(name)) { + JSValue oldAttribute = attributes->getNamedItem(name); + JSValue exception = attributes->setNamedItem(name, attributeValue); if (JS_IsException(exception)) return exception; element->_didModifyAttribute(name, oldAttribute, attributeValue); JS_FreeValue(ctx, oldAttribute); } else { - JSValue exception = attributes->setAttribute(name, attributeValue); + JSValue exception = attributes->setNamedItem(name, attributeValue); if (JS_IsException(exception)) return exception; element->_didModifyAttribute(name, JS_NULL, attributeValue); @@ -293,8 +302,8 @@ IMPL_FUNCTION(Element, getAttribute)(JSContext* ctx, JSValue this_val, int argc, auto* attributes = element->m_attributes; - if (attributes->hasAttribute(name)) { - return attributes->getAttribute(name); + if (attributes->hasNamedItem(name)) { + return attributes->getNamedItem(name); } return JS_NULL; @@ -315,9 +324,9 @@ IMPL_FUNCTION(Element, removeAttribute)(JSContext* ctx, JSValue this_val, int ar 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); + if (attributes->hasNamedItem(name)) { + JSValue targetValue = attributes->getNamedItem(name); + attributes->removeNamedItem(name); element->_didModifyAttribute(name, targetValue, JS_NULL); JS_FreeValue(ctx, targetValue); @@ -450,14 +459,35 @@ IMPL_PROPERTY_GETTER(Element, tagName)(JSContext* ctx, JSValue this_val, int arg IMPL_PROPERTY_GETTER(Element, className)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); - return element->m_attributes->getAttribute("class"); + return element->getAttribute(ATTR_CLASS); } + IMPL_PROPERTY_SETTER(Element, className)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); - element->m_attributes->setAttribute("class", argv[0]); - std::unique_ptr args_01 = stringToNativeString("class"); - std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); + element->setAttribute(ATTR_CLASS, argv[0]); + return JS_NULL; +} + +IMPL_PROPERTY_GETTER(Element, id)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); + return element->getAttribute(ATTR_ID; +} + +IMPL_PROPERTY_SETTER(Element, id)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); + element->setAttribute(ATTR_ID, argv[0]); + return JS_NULL; +} + +IMPL_PROPERTY_GETTER(Element, style)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); + // The style property must return a CSS declaration block object. + return element->m_style; +} + +IMPL_PROPERTY_SETTER(Element, style)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); + element->setAttribute(ATTR_STYLE, argv[0]); return JS_NULL; } @@ -525,6 +555,7 @@ IMPL_PROPERTY_GETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int a NativeValue args[] = {Native_NewInt32(static_cast(ViewModuleProperty::scrollTop))}; return element->callNativeMethods("getViewModuleProperty", 1, args); } + IMPL_PROPERTY_SETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { getDartMethod()->flushUICommand(); auto* element = static_cast(JS_GetOpaque(this_val, Element::classId)); @@ -624,6 +655,7 @@ IMPL_PROPERTY_GETTER(Element, innerHTML)(JSContext* ctx, JSValue this_val, int a 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]); @@ -643,6 +675,7 @@ IMPL_PROPERTY_GETTER(Element, outerHTML)(JSContext* ctx, JSValue this_val, int a 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; } @@ -821,9 +854,8 @@ void Element::_notifyNodeRemoved(NodeInstance* insertionNode) { } void Element::_notifyChildRemoved() { - std::string prop = "id"; - if (m_attributes->hasAttribute(prop)) { - JSValue idValue = m_attributes->getAttribute(prop); + if (m_attributes->hasNamedItem(ATTR_ID)) { + JSValue idValue = m_attributes->getNamedItem(ATTR_ID); JSAtom id = JS_ValueToAtom(m_ctx, idValue); document()->removeElementById(id, this); JS_FreeValue(m_ctx, idValue); @@ -846,9 +878,8 @@ void Element::_notifyNodeInsert(NodeInstance* insertNode) { } void Element::_notifyChildInsert() { - std::string prop = "id"; - if (m_attributes->hasAttribute(prop)) { - JSValue idValue = m_attributes->getAttribute(prop); + if (m_attributes->hasNamedItem(ATTR_ID)) { + JSValue idValue = m_attributes->getNamedItem(ATTR_ID); JSAtom id = JS_ValueToAtom(m_ctx, idValue); document()->addElementById(id, this); JS_FreeValue(m_ctx, idValue); @@ -857,7 +888,7 @@ void Element::_notifyChildInsert() { } void Element::_didModifyAttribute(std::string& name, JSValue oldId, JSValue newId) { - if (name == "id") { + if (name == ATTR_ID) { _beforeUpdateId(oldId, newId); } } @@ -892,7 +923,7 @@ void Element::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { } // Element::Element(Element* element, std::string tagName, bool shouldAddUICommand): Node() { -// m_attributes = makeGarbageCollected()->initialize(m_ctx, &ElementAttributes::classId); +// m_attributes = makeGarbageCollected()->initialize(m_ctx, &NamedNodeMap::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)); @@ -907,10 +938,6 @@ void Element::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { JSClassExoticMethods Element::exoticMethods{nullptr, nullptr, nullptr, nullptr, hasProperty, getProperty, setProperty}; -StyleDeclarationInstance* Element::style() { - return m_style; -} - void Element::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const {} void Element::dispose() const {} diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h index d8ce16e0e2..3b2335d7ea 100644 --- a/bridge/bindings/qjs/dom/element.h +++ b/bridge/bindings/qjs/dom/element.h @@ -45,27 +45,27 @@ class SpaceSplitString { std::vector m_szData; }; + // TODO: refactor for better W3C standard support and higher performance. -class ElementAttributes : public GarbageCollected { +// https://dom.spec.whatwg.org/#interface-namednodemap +class NamedNodeMap : public GarbageCollected { public: static JSClassID classId; - FORCE_INLINE const char* getHumanReadableName() const override { return "ElementAttributes"; } + FORCE_INLINE const char* getHumanReadableName() const override { return "NamedNodeMap"; } 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(); + JSValue getNamedItem(const std::string& name); + JSValue setNamedItem(const std::string& name, JSValue value); + bool hasNamedItem(std::string& name); + void removeNamedItem(std::string& name); + void copyWith(NamedNodeMap* attributes); std::string toString(); private: - std::unordered_map m_attributes; - std::shared_ptr m_className{std::make_shared("")}; + std::unordered_map m_map; }; bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance); @@ -104,7 +104,9 @@ class Element : public Node { DEFINE_PROTOTYPE_READONLY_PROPERTY(children); DEFINE_PROTOTYPE_READONLY_PROPERTY(attributes); + DEFINE_PROTOTYPE_PROPERTY(id); DEFINE_PROTOTYPE_PROPERTY(className); + DEFINE_PROTOTYPE_PROPERTY(style); DEFINE_PROTOTYPE_PROPERTY(innerHTML); DEFINE_PROTOTYPE_PROPERTY(outerHTML); DEFINE_PROTOTYPE_PROPERTY(scrollTop); @@ -113,6 +115,7 @@ class Element : public Node { JSValue internalGetTextContent() override; void internalSetTextContent(JSValue content) override; + std::string className(); std::shared_ptr classNames(); std::string tagName(); std::string getRegisteredTagName(); @@ -126,9 +129,10 @@ class Element : public Node { protected: StyleDeclarationInstance* m_style{nullptr}; ElementAttributes* m_attributes{nullptr}; + std::string m_tagName; + std::string m_className; private: - std::string m_tagName; void _notifyNodeRemoved(Node* node) override; void _notifyChildRemoved(); void _notifyNodeInsert(Node* insertNode) override; diff --git a/bridge/bindings/qjs/dom/style_declaration.cc b/bridge/bindings/qjs/dom/style_declaration.cc index b47fa6f76b..a0bf573274 100644 --- a/bridge/bindings/qjs/dom/style_declaration.cc +++ b/bridge/bindings/qjs/dom/style_declaration.cc @@ -54,7 +54,7 @@ JSValue CSSStyleDeclaration::instanceConstructor(JSContext* ctx, JSValue func_ob JSValue eventTargetValue = argv[0]; auto eventTargetInstance = static_cast(JS_GetOpaque(eventTargetValue, EventTarget::classId(eventTargetValue))); - auto style = new StyleDeclarationInstance(this, eventTargetInstance); + auto style = new StyleDeclaration(this, eventTargetInstance); return style->jsObject; } @@ -64,63 +64,65 @@ CSSStyleDeclaration::CSSStyleDeclaration(ExecutionContext* context) : HostClass( std::call_once(kinitCSSStyleDeclarationFlag, []() { JS_NewClassID(&kCSSStyleDeclarationClassId); }); } -JSValue CSSStyleDeclaration::setProperty(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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)); + 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); + instance->setProperty(propertyName, propertyValue); JS_FreeCString(ctx, cPropertyName); return JS_UNDEFINED; } -JSValue CSSStyleDeclaration::removeProperty(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +IMPL_FUNCTION(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)); + 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); + instance->removeProperty(propertyName); JS_FreeCString(ctx, cPropertyName); return JS_UNDEFINED; } -JSValue CSSStyleDeclaration::getPropertyValue(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + +IMPL_FUNCTION(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)); + 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); + JSValue returnValue = instance->getPropertyValue(propertyName); JS_FreeCString(ctx, cPropertyName); return returnValue; } -StyleDeclarationInstance::StyleDeclarationInstance(CSSStyleDeclaration* cssStyleDeclaration, EventTargetInstance* ownerEventTarget) +StyleDeclaration::StyleDeclaration(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) { +StyleDeclaration::~StyleDeclaration() {} + +bool StyleDeclaration::setProperty(std::string& name, JSValue value) { name = parseJavaScriptCSSPropertyName(name); - properties[name] = jsValueToStdString(m_ctx, value); + m_properties[name] = jsValueToStdString(m_ctx, value); if (ownerEventTarget != nullptr) { std::unique_ptr args_01 = stringToNativeString(name); @@ -131,14 +133,14 @@ bool StyleDeclarationInstance::internalSetProperty(std::string& name, JSValue va return true; } -void StyleDeclarationInstance::internalRemoveProperty(std::string& name) { +void StyleDeclaration::removeProperty(std::string& name) { name = parseJavaScriptCSSPropertyName(name); - if (properties.count(name) == 0) { + if (m_properties.count(name) == 0) { return; } - properties.erase(name); + m_properties.erase(name); if (ownerEventTarget != nullptr) { std::unique_ptr args_01 = stringToNativeString(name); @@ -147,56 +149,104 @@ void StyleDeclarationInstance::internalRemoveProperty(std::string& name) { } } -JSValue StyleDeclarationInstance::internalGetPropertyValue(std::string& name) { +JSValue StyleDeclaration::getPropertyValue(std::string& name) { name = parseJavaScriptCSSPropertyName(name); - if (properties.count(name) > 0) { - return JS_NewString(m_ctx, properties[name].c_str()); + if (m_properties.count(name) > 0) { + return JS_NewString(m_ctx, m_properties[name].c_str()); } return JS_NewString(m_ctx, ""); } + +void parseRules(std::string& source, ParseRuleCallback callback, void* context) { + uint32_t idx = 0; + uint32_t start = idx; + uint32_t end = source.length(); + bool is_in_annotation = false; + bool is_in_search_key = false; + + std::string key; + std::string value; + + while (idx <= end) { + char c = source[idx]; + + if (c == ' ') { + start++; + } else if (c == '/' && source[idx + 1] == '*') { + is_in_annotation = true; + } else if (c == '*' && source[idx + 1] == '/') { + is_in_annotation = false; + } else if (c == ':' && !is_in_annotation && !is_in_search_key) { + key = source.substr(start, idx - start); + start = idx + 1; + is_in_search_key = true; + } else if ((c == ';' || idx == end) && !is_in_annotation) { + value = source.substr(start, idx - start); + start = idx + 1; + callback(context, key, value); + key = ""; + is_in_search_key = false; + } + + idx++; + } +} + +void StyleDeclaration::setCssText(std::string& cssText) { + + parseRules( + cssText, + [](void* p, std::string& key, std::string& value) { + auto* style = static_cast(p); + style->setProperty(key, value); + }, + this); +} + // TODO: add support for annotation CSS styleSheets. -std::string StyleDeclarationInstance::toString() { - if (properties.empty()) +std::string StyleDeclaration::toString() { + if (m_properties.empty()) return ""; std::string s; - for (auto& attr : properties) { - s += attr.first + ": " + attr.second + ";"; + for (auto& item : m_properties) { + s += item.first + ": " + item.second + ";"; } s += "\""; return s; } -void StyleDeclarationInstance::copyWith(StyleDeclarationInstance* instance) { - for (auto& attr : instance->properties) { - properties[attr.first] = attr.second; +void StyleDeclaration::copyWith(StyleDeclaration* style) { + for (auto& item : style->m_properties) { + m_properties[item.first] = item.second; } } -int StyleDeclarationInstance::hasProperty(JSContext* ctx, JSValue obj, JSAtom atom) { - auto* style = static_cast(JS_GetOpaque(obj, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); +bool StyleDeclaration::hasObjectProperty(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); - bool match = style->properties.count(name) >= 0; + bool match = style->m_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)); +// Property Accessors +int StyleDeclaration::setObjectProperty(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); + bool success = style->setProperty(name, value); JS_FreeCString(ctx, cname); return success; } -JSValue StyleDeclarationInstance::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { +JSValue StyleDeclaration::getObjectProperty(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)) { @@ -206,19 +256,20 @@ JSValue StyleDeclarationInstance::getProperty(JSContext* ctx, JSValue obj, JSAto } JS_FreeValue(ctx, prototype); - auto* style = static_cast(JS_GetOpaque(receiver, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); + 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); + JSValue result = style->getPropertyValue(name); JS_FreeCString(ctx, cname); return result; } -JSClassExoticMethods StyleDeclarationInstance::m_exoticMethods{ - nullptr, nullptr, nullptr, nullptr, nullptr, getProperty, setProperty, + +JSClassExoticMethods StyleDeclaration::m_exoticMethods{ + nullptr, nullptr, nullptr, nullptr, nullptr, getObjectProperty, setObjectProperty, }; -void StyleDeclarationInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { +void StyleDeclaration::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); diff --git a/bridge/bindings/qjs/dom/style_declaration.h b/bridge/bindings/qjs/dom/style_declaration.h index 66e71c947d..099aceb32d 100644 --- a/bridge/bindings/qjs/dom/style_declaration.h +++ b/bridge/bindings/qjs/dom/style_declaration.h @@ -33,22 +33,22 @@ class CSSStyleDeclaration : public GarbageCollected { CSSStyleDeclaration(); - bool internalSetProperty(std::string& name, JSValue value); - void internalRemoveProperty(std::string& name); - JSValue internalGetPropertyValue(std::string& name); + bool setProperty(std::string& name, JSValue value); + void removeProperty(std::string& name); + JSValue getPropertyValue(std::string& name); + void setCssText(std::string& cssText); std::string toString(); - void copyWith(CSSStyleDeclaration* instance); + void copyWith(CSSStyleDeclaration* style); DEFINE_FUNCTION(setProperty); DEFINE_FUNCTION(removeProperty); DEFINE_FUNCTION(getPropertyValue); 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); - - std::unordered_map properties; + static int hasObjectProperty(JSContext* ctx, JSValueConst obj, JSAtom atom); + static int setObjectProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); + static JSValue getObjectProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); + std::unordered_map m_properties; }; } // namespace kraken::binding::qjs diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/bindings/qjs/html_parser.cc index a7c2fc3ecb..5221b3b2b8 100644 --- a/bridge/bindings/qjs/html_parser.cc +++ b/bridge/bindings/qjs/html_parser.cc @@ -101,54 +101,24 @@ void HTMLParser::parseProperty(ElementInstance* element, GumboElement* gumboElem GumboVector* attributes = &gumboElement->attributes; for (int j = 0; j < attributes->length; ++j) { - auto* attribute = (GumboAttribute*)attributes->data[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; + std::string strName = attribute->name; + std::string strValue = 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 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 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); - 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); - } + JS_FreeValue(ctx, setAttributeFunc); + JS_FreeValue(ctx, key); + JS_FreeValue(ctx, value); } } From d8ea68256c70fb1dfd28885da672f964b7e568cd Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Thu, 27 Jan 2022 08:26:46 +0000 Subject: [PATCH 007/498] Committing clang-format changes --- bridge/bindings/qjs/dom/element.h | 1 - bridge/bindings/qjs/dom/style_declaration.cc | 4 ---- bridge/bindings/qjs/html_parser.cc | 2 +- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h index 3b2335d7ea..b4b8aefbf0 100644 --- a/bridge/bindings/qjs/dom/element.h +++ b/bridge/bindings/qjs/dom/element.h @@ -45,7 +45,6 @@ class SpaceSplitString { std::vector m_szData; }; - // TODO: refactor for better W3C standard support and higher performance. // https://dom.spec.whatwg.org/#interface-namednodemap class NamedNodeMap : public GarbageCollected { diff --git a/bridge/bindings/qjs/dom/style_declaration.cc b/bridge/bindings/qjs/dom/style_declaration.cc index a0bf573274..64bab674a3 100644 --- a/bridge/bindings/qjs/dom/style_declaration.cc +++ b/bridge/bindings/qjs/dom/style_declaration.cc @@ -98,7 +98,6 @@ IMPL_FUNCTION(CSSStyleDeclaration, removeProperty)(JSContext* ctx, JSValue this_ return JS_UNDEFINED; } - IMPL_FUNCTION(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."); @@ -159,7 +158,6 @@ JSValue StyleDeclaration::getPropertyValue(std::string& name) { return JS_NewString(m_ctx, ""); } - void parseRules(std::string& source, ParseRuleCallback callback, void* context) { uint32_t idx = 0; uint32_t start = idx; @@ -196,7 +194,6 @@ void parseRules(std::string& source, ParseRuleCallback callback, void* context) } void StyleDeclaration::setCssText(std::string& cssText) { - parseRules( cssText, [](void* p, std::string& key, std::string& value) { @@ -264,7 +261,6 @@ JSValue StyleDeclaration::getObjectProperty(JSContext* ctx, JSValue obj, JSAtom return result; } - JSClassExoticMethods StyleDeclaration::m_exoticMethods{ nullptr, nullptr, nullptr, nullptr, nullptr, getObjectProperty, setObjectProperty, }; diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/bindings/qjs/html_parser.cc index 5221b3b2b8..2a9e7fae89 100644 --- a/bridge/bindings/qjs/html_parser.cc +++ b/bridge/bindings/qjs/html_parser.cc @@ -103,7 +103,7 @@ void HTMLParser::parseProperty(ElementInstance* element, GumboElement* gumboElem for (int j = 0; j < attributes->length; ++j) { auto* attribute = (GumboAttribute*)attributes->data[j] - std::string strName = attribute->name; + std::string strName = attribute->name; std::string strValue = attribute->value; JSValue key = JS_NewString(ctx, strName.c_str()); From c14831b9747e9c20cfdc110a4d86b2c972943375 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 27 Jan 2022 21:10:13 +0800 Subject: [PATCH 008/498] refactor: rename namespace and remove useless code. --- bridge/CMakeLists.txt | 11 +- bridge/bindings/qjs/bom/blob.cc | 4 +- bridge/bindings/qjs/bom/blob.h | 4 +- bridge/bindings/qjs/bom/console.cc | 4 +- bridge/bindings/qjs/bom/console.h | 2 +- .../bindings/qjs/bom/dom_timer_coordinator.cc | 4 +- .../bindings/qjs/bom/dom_timer_coordinator.h | 4 +- bridge/bindings/qjs/bom/location.cc | 4 +- bridge/bindings/qjs/bom/location.h | 4 +- bridge/bindings/qjs/bom/performance.cc | 4 +- bridge/bindings/qjs/bom/performance.h | 4 +- bridge/bindings/qjs/bom/screen.cc | 4 +- bridge/bindings/qjs/bom/screen.h | 4 +- bridge/bindings/qjs/bom/timer.cc | 4 +- bridge/bindings/qjs/bom/timer.h | 4 +- bridge/bindings/qjs/bom/timer_test.cc | 1 - bridge/bindings/qjs/bom/window.cc | 4 +- bridge/bindings/qjs/bom/window.h | 4 +- bridge/bindings/qjs/dart_methods.cc | 44 ++++++ .../{include => bindings/qjs}/dart_methods.h | 11 +- bridge/bindings/qjs/dom/all_collection.cc | 4 +- bridge/bindings/qjs/dom/all_collection.h | 4 +- bridge/bindings/qjs/dom/comment_node.cc | 5 +- bridge/bindings/qjs/dom/comment_node.h | 4 +- bridge/bindings/qjs/dom/custom_event.cc | 5 +- bridge/bindings/qjs/dom/custom_event.h | 4 +- bridge/bindings/qjs/dom/document.cc | 6 +- bridge/bindings/qjs/dom/document.h | 4 +- bridge/bindings/qjs/dom/document_fragment.cc | 5 +- bridge/bindings/qjs/dom/document_fragment.h | 4 +- bridge/bindings/qjs/dom/element.cc | 4 +- bridge/bindings/qjs/dom/element.h | 4 +- bridge/bindings/qjs/dom/element_test.cc | 2 +- .../qjs/dom/elements/image_element.cc | 4 +- .../bindings/qjs/dom/elements/image_element.h | 4 +- .../qjs/dom/elements/template_element.cc | 4 +- .../qjs/dom/elements/template_element.h | 4 +- bridge/bindings/qjs/dom/event.cc | 5 +- bridge/bindings/qjs/dom/event.h | 4 +- bridge/bindings/qjs/dom/event_listener_map.cc | 4 +- bridge/bindings/qjs/dom/event_listener_map.h | 4 +- bridge/bindings/qjs/dom/event_target.cc | 5 +- bridge/bindings/qjs/dom/event_target.h | 6 +- bridge/bindings/qjs/dom/event_target_test.cc | 2 +- bridge/bindings/qjs/dom/events/touch_event.cc | 4 +- bridge/bindings/qjs/dom/events/touch_event.h | 4 +- .../dom/frame_request_callback_collection.cc | 4 +- .../dom/frame_request_callback_collection.h | 4 +- bridge/bindings/qjs/dom/node.cc | 5 +- bridge/bindings/qjs/dom/node.h | 4 +- .../qjs/dom/script_animation_controller.cc | 4 +- .../qjs/dom/script_animation_controller.h | 4 +- bridge/bindings/qjs/dom/style_declaration.cc | 5 +- bridge/bindings/qjs/dom/style_declaration.h | 4 +- bridge/bindings/qjs/dom/text_node.cc | 5 +- bridge/bindings/qjs/dom/text_node.h | 4 +- bridge/bindings/qjs/executing_context.cc | 66 +------- bridge/bindings/qjs/executing_context.h | 31 +--- bridge/bindings/qjs/executing_context_data.cc | 4 +- bridge/bindings/qjs/executing_context_data.h | 4 +- bridge/bindings/qjs/garbage_collected.h | 4 +- bridge/bindings/qjs/garbage_collected_test.cc | 8 +- bridge/bindings/qjs/heap_hashmap.h | 4 +- bridge/bindings/qjs/html_parser.cc | 8 +- bridge/bindings/qjs/html_parser.h | 6 +- bridge/bindings/qjs/module_manager.cc | 4 +- bridge/bindings/qjs/module_manager.h | 4 +- bridge/bindings/qjs/module_manager_test.cc | 4 +- bridge/bindings/qjs/native_string.cc | 81 ++++++++++ bridge/bindings/qjs/native_string.h | 61 ++++++++ bridge/bindings/qjs/wrapper_type_info.h | 4 +- bridge/dart_methods.cc | 78 ---------- bridge/foundation/closure.h | 15 -- bridge/foundation/inspector_task_queue.cc | 4 +- bridge/foundation/inspector_task_queue.h | 5 +- bridge/foundation/logging.cc | 4 +- bridge/foundation/logging.h | 48 +++++- bridge/foundation/macros.h | 44 ++++++ .../qjs => foundation}/native_value.cc | 26 +--- .../qjs => foundation}/native_value.h | 12 +- bridge/foundation/ref_counted_internal.h | 3 - bridge/foundation/ref_ptr.h | 1 + bridge/foundation/ref_ptr_internal.h | 1 - bridge/foundation/task_queue.cc | 4 +- bridge/foundation/task_queue.h | 7 +- bridge/foundation/ui_command_buffer.cc | 5 +- bridge/foundation/ui_command_buffer.h | 46 +++++- .../foundation/ui_command_callback_queue.cc | 6 +- bridge/foundation/ui_task_queue.cc | 4 +- bridge/foundation/ui_task_queue.h | 6 +- bridge/include/kraken_bridge.h | 60 +------- bridge/include/kraken_foundation.h | 144 ------------------ bridge/kraken_bridge.cc | 68 +++------ bridge/page.cc | 49 +++++- bridge/page.h | 24 ++- bridge/page_test.cc | 14 ++ bridge/page_test.h | 5 +- bridge/test/kraken_test_env.cc | 1 - 98 files changed, 601 insertions(+), 657 deletions(-) create mode 100644 bridge/bindings/qjs/dart_methods.cc rename bridge/{include => bindings/qjs}/dart_methods.h (92%) create mode 100644 bridge/bindings/qjs/native_string.cc create mode 100644 bridge/bindings/qjs/native_string.h delete mode 100644 bridge/dart_methods.cc delete mode 100644 bridge/foundation/closure.h create mode 100644 bridge/foundation/macros.h rename bridge/{bindings/qjs => foundation}/native_value.cc (94%) rename bridge/{bindings/qjs => foundation}/native_value.h (93%) delete mode 100644 bridge/include/kraken_foundation.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index fc075dd225..a25a55c2fe 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -83,8 +83,8 @@ endif() list(APPEND BRIDGE_SOURCE kraken_bridge.cc ${CMAKE_CURRENT_SOURCE_DIR}/include/kraken_bridge.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/kraken_foundation.h ${CMAKE_CURRENT_SOURCE_DIR}/include/dart_methods.h + foundation/macros.h foundation/logging.cc foundation/logging.h foundation/colors.h @@ -98,10 +98,11 @@ list(APPEND BRIDGE_SOURCE foundation/inspector_task_queue.cc foundation/task_queue.cc foundation/task_queue.h + foundation/native_value.cc + foundation/native_value.h foundation/ui_command_buffer.cc foundation/ui_command_buffer.h foundation/ui_command_callback_queue.cc - foundation/closure.h dart_methods.cc polyfill/dist/polyfill.cc ) @@ -193,8 +194,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/executing_context_data.h bindings/qjs/wrapper_type_info.h bindings/qjs/heap_hashmap.h - bindings/qjs/native_value.cc - bindings/qjs/native_value.h + bindings/qjs/native_string.cc + bindings/qjs/native_string.h bindings/qjs/qjs_patch.cc bindings/qjs/qjs_patch.h bindings/qjs/module_manager.cc @@ -288,7 +289,7 @@ endif () list(APPEND PUBLIC_HEADER include/kraken_bridge.h - ) +) add_library(kraken SHARED ${BRIDGE_SOURCE}) add_library(kraken_static STATIC ${BRIDGE_SOURCE}) diff --git a/bridge/bindings/qjs/bom/blob.cc b/bridge/bindings/qjs/bom/blob.cc index 3fe0fb6e28..dbe8bee144 100644 --- a/bridge/bindings/qjs/bom/blob.cc +++ b/bridge/bindings/qjs/bom/blob.cc @@ -6,7 +6,7 @@ #include "blob.h" #include "dart_methods.h" -namespace kraken::binding::qjs { +namespace kraken { void bindBlob(std::unique_ptr& context) { JSValue constructor = context->contextData()->constructorForType(&blobTypeInfo); @@ -260,4 +260,4 @@ uint8_t* Blob::bytes() { void Blob::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const {} void Blob::dispose() const {} -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/bom/blob.h b/bridge/bindings/qjs/bom/blob.h index db3312c25c..13d14e1308 100644 --- a/bridge/bindings/qjs/bom/blob.h +++ b/bridge/bindings/qjs/bom/blob.h @@ -9,7 +9,7 @@ #include "bindings/qjs/context_macros.h" #include "bindings/qjs/garbage_collected.h" -namespace kraken::binding::qjs { +namespace kraken { class BlobBuilder; class BlobInstance; @@ -113,6 +113,6 @@ auto blobCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_v const WrapperTypeInfo blobTypeInfo = {"Blob", nullptr, blobCreator}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_BLOB_H diff --git a/bridge/bindings/qjs/bom/console.cc b/bridge/bindings/qjs/bom/console.cc index 2fefe57b99..17243e85e1 100644 --- a/bridge/bindings/qjs/bom/console.cc +++ b/bridge/bindings/qjs/bom/console.cc @@ -5,7 +5,7 @@ #include "console.h" -namespace kraken::binding::qjs { +namespace kraken { JSValue print(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { std::stringstream stream; @@ -34,4 +34,4 @@ void bindConsole(ExecutionContext* context) { QJS_GLOBAL_BINDING_FUNCTION(context, print, "__kraken_print__", 2); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/bom/console.h b/bridge/bindings/qjs/bom/console.h index b8db2594b2..e239662f09 100644 --- a/bridge/bindings/qjs/bom/console.h +++ b/bridge/bindings/qjs/bom/console.h @@ -8,7 +8,7 @@ #include "bindings/qjs/executing_context.h" -namespace kraken::binding::qjs { +namespace kraken { void bindConsole(ExecutionContext* context); diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.cc b/bridge/bindings/qjs/bom/dom_timer_coordinator.cc index d95e0338c7..7ff63c46fe 100644 --- a/bridge/bindings/qjs/bom/dom_timer_coordinator.cc +++ b/bridge/bindings/qjs/bom/dom_timer_coordinator.cc @@ -11,7 +11,7 @@ #include "kraken_test_env.h" #endif -namespace kraken::binding::qjs { +namespace kraken { static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); @@ -80,4 +80,4 @@ void DOMTimerCoordinator::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_fu } } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.h b/bridge/bindings/qjs/bom/dom_timer_coordinator.h index cd3fcbb54b..f165562bfd 100644 --- a/bridge/bindings/qjs/bom/dom_timer_coordinator.h +++ b/bridge/bindings/qjs/bom/dom_timer_coordinator.h @@ -10,7 +10,7 @@ #include #include -namespace kraken::binding::qjs { +namespace kraken { class DOMTimer; class ExecutionContext; @@ -37,6 +37,6 @@ class DOMTimerCoordinator { std::vector m_abandonedTimers; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_BOM_DOM_TIMER_COORDINATOR_H_ diff --git a/bridge/bindings/qjs/bom/location.cc b/bridge/bindings/qjs/bom/location.cc index 85f8b77aa0..b43cee23a8 100644 --- a/bridge/bindings/qjs/bom/location.cc +++ b/bridge/bindings/qjs/bom/location.cc @@ -7,7 +7,7 @@ #include #include "dart_methods.h" -namespace kraken::binding::qjs { +namespace kraken { void bindLocation(std::unique_ptr& context) { auto* contextData = context->contextData(); @@ -42,4 +42,4 @@ void Location::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { void Location::dispose() const {} -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/bom/location.h b/bridge/bindings/qjs/bom/location.h index 8813793201..c198f525c7 100644 --- a/bridge/bindings/qjs/bom/location.h +++ b/bridge/bindings/qjs/bom/location.h @@ -10,7 +10,7 @@ #include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/wrapper_type_info.h" -namespace kraken::binding::qjs { +namespace kraken { void bindLocation(std::unique_ptr& context); @@ -38,6 +38,6 @@ auto locationCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst th const WrapperTypeInfo locationTypeInfo = {"Location", nullptr, locationCreator}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_LOCATION_H diff --git a/bridge/bindings/qjs/bom/performance.cc b/bridge/bindings/qjs/bom/performance.cc index 5c8d678395..7a8cfabc9b 100644 --- a/bridge/bindings/qjs/bom/performance.cc +++ b/bridge/bindings/qjs/bom/performance.cc @@ -9,7 +9,7 @@ #define PERFORMANCE_ENTRY_NONE_UNIQUE_ID -1024 -namespace kraken::binding::qjs { +namespace kraken { void bindPerformance(ExecutionContext* context) { auto* performance = Performance::instance(context); @@ -580,4 +580,4 @@ Rendering: %.*fms #endif -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/bom/performance.h b/bridge/bindings/qjs/bom/performance.h index 1348d82937..7a9d63479e 100644 --- a/bridge/bindings/qjs/bom/performance.h +++ b/bridge/bindings/qjs/bom/performance.h @@ -123,7 +123,7 @@ #include "bindings/qjs/host_object.h" -namespace kraken::binding::qjs { +namespace kraken { void bindPerformance(ExecutionContext* context); @@ -222,6 +222,6 @@ class Performance : public HostObject { #endif }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_PERFORMANCE_H diff --git a/bridge/bindings/qjs/bom/screen.cc b/bridge/bindings/qjs/bom/screen.cc index 56f3f3cc04..bd31b5b6ca 100644 --- a/bridge/bindings/qjs/bom/screen.cc +++ b/bridge/bindings/qjs/bom/screen.cc @@ -5,7 +5,7 @@ #include "screen.h" -namespace kraken::binding::qjs { +namespace kraken { void bindScreen(ExecutionContext* context) { auto* screen = new Screen(context); @@ -32,4 +32,4 @@ IMPL_PROPERTY_GETTER(Screen, height)(JSContext* ctx, JSValue this_val, int argc, return JS_NewFloat64(ctx, screen->height); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/bom/screen.h b/bridge/bindings/qjs/bom/screen.h index bc4fa774cb..2ac23ec116 100644 --- a/bridge/bindings/qjs/bom/screen.h +++ b/bridge/bindings/qjs/bom/screen.h @@ -10,7 +10,7 @@ #include "bindings/qjs/host_object.h" #include "dart_methods.h" -namespace kraken::binding::qjs { +namespace kraken { struct NativeScreen { double width; @@ -28,7 +28,7 @@ class Screen : public HostObject { void bindScreen(ExecutionContext* context); -} // namespace kraken::binding::qjs +} // namespace kraken class screen {}; diff --git a/bridge/bindings/qjs/bom/timer.cc b/bridge/bindings/qjs/bom/timer.cc index 4f4af60816..4692f694de 100644 --- a/bridge/bindings/qjs/bom/timer.cc +++ b/bridge/bindings/qjs/bom/timer.cc @@ -12,7 +12,7 @@ #include "kraken_test_env.h" #endif -namespace kraken::binding::qjs { +namespace kraken { DOMTimer::DOMTimer(JSValue callback) : m_callback(callback) {} @@ -225,4 +225,4 @@ void bindTimer(ExecutionContext* context) { QJS_GLOBAL_BINDING_FUNCTION(context, clearTimeout, "clearTimeout", 1); QJS_GLOBAL_BINDING_FUNCTION(context, clearTimeout, "clearInterval", 1); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/bom/timer.h b/bridge/bindings/qjs/bom/timer.h index 58cc951cfa..42862bc36b 100644 --- a/bridge/bindings/qjs/bom/timer.h +++ b/bridge/bindings/qjs/bom/timer.h @@ -10,7 +10,7 @@ #include "bindings/qjs/garbage_collected.h" #include "dom_timer_coordinator.h" -namespace kraken::binding::qjs { +namespace kraken { class DOMTimer : public GarbageCollected { public: @@ -36,6 +36,6 @@ class DOMTimer : public GarbageCollected { void bindTimer(ExecutionContext* context); -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_TIMER_H diff --git a/bridge/bindings/qjs/bom/timer_test.cc b/bridge/bindings/qjs/bom/timer_test.cc index d98a59733c..4e7f8062ed 100644 --- a/bridge/bindings/qjs/bom/timer_test.cc +++ b/bridge/bindings/qjs/bom/timer_test.cc @@ -4,7 +4,6 @@ */ #include "gtest/gtest.h" -#include "kraken_bridge.h" #include "kraken_test_env.h" #include "page.h" diff --git a/bridge/bindings/qjs/bom/window.cc b/bridge/bindings/qjs/bom/window.cc index 40f0310c36..fd04391060 100644 --- a/bridge/bindings/qjs/bom/window.cc +++ b/bridge/bindings/qjs/bom/window.cc @@ -10,7 +10,7 @@ #include "bindings/qjs/qjs_patch.h" #include "dart_methods.h" -namespace kraken::binding::qjs { +namespace kraken { void bindWindow(std::unique_ptr& context) { auto* contextData = context->contextData(); @@ -271,4 +271,4 @@ IMPL_PROPERTY_GETTER(Window, self)(JSContext* ctx, JSValue this_val, int argc, J return JS_GetGlobalObject(ctx); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/bom/window.h b/bridge/bindings/qjs/bom/window.h index 4d78939e26..ca33d315e1 100644 --- a/bridge/bindings/qjs/bom/window.h +++ b/bridge/bindings/qjs/bom/window.h @@ -11,7 +11,7 @@ #include "bindings/qjs/executing_context.h" #include "bindings/qjs/wrapper_type_info.h" -namespace kraken::binding::qjs { +namespace kraken { void bindWindow(ExecutionContext* context); @@ -58,6 +58,6 @@ auto windowCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this const WrapperTypeInfo windowTypeInfo = {"Window", &eventTargetTypeInfo, windowCreator}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_WINDOW_H diff --git a/bridge/bindings/qjs/dart_methods.cc b/bridge/bindings/qjs/dart_methods.cc new file mode 100644 index 0000000000..2f955890ae --- /dev/null +++ b/bridge/bindings/qjs/dart_methods.cc @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "dart_methods.h" +#include "foundation/macros.h" +#include + +namespace kraken { + +//std::shared_ptr methodPointer = std::make_shared() + + +std::shared_ptr 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(); + } +#endif +// return methodPointer; +} + +void registerDartMethods(std::shared_ptr methodPointer, uint64_t* methodBytes, int32_t length) { + +} + +void registerTestEnvDartMethods(std::shared_ptr methodPointer, uint64_t* methodBytes, int32_t length) { + +} + +#if ENABLE_PROFILE +void registerGetPerformanceEntries(GetPerformanceEntries getPerformanceEntries) { + methodPointer->getPerformanceEntries = getPerformanceEntries; +} +#endif + +} // namespace kraken diff --git a/bridge/include/dart_methods.h b/bridge/bindings/qjs/dart_methods.h similarity index 92% rename from bridge/include/dart_methods.h rename to bridge/bindings/qjs/dart_methods.h index 47bd2f24df..4ad2a663ec 100644 --- a/bridge/include/dart_methods.h +++ b/bridge/bindings/qjs/dart_methods.h @@ -11,10 +11,6 @@ #include #include -#define KRAKEN_EXPORT __attribute__((__visibility__("default"))) - -struct NativeScreen; - 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, NativeString* errmsg, NativeString* json); @@ -85,16 +81,13 @@ struct DartMethodPointer { InitDocument initDocument{nullptr}; }; -void registerDartMethods(uint64_t* methodBytes, int32_t length); +void registerDartMethods(std::shared_ptr methodPointer, uint64_t* methodBytes, int32_t length); #ifdef IS_TEST KRAKEN_EXPORT -void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); +void registerTestEnvDartMethods(std::shared_ptr methodPointer, uint64_t* methodBytes, int32_t length); #endif -KRAKEN_EXPORT -std::shared_ptr getDartMethod(); - } // namespace kraken #endif diff --git a/bridge/bindings/qjs/dom/all_collection.cc b/bridge/bindings/qjs/dom/all_collection.cc index 9a5692b416..5717b4e759 100644 --- a/bridge/bindings/qjs/dom/all_collection.cc +++ b/bridge/bindings/qjs/dom/all_collection.cc @@ -5,7 +5,7 @@ #include "all_collection.h" -namespace kraken::binding::qjs { +namespace kraken { JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { @@ -76,4 +76,4 @@ IMPL_PROPERTY_GETTER(AllCollection, length)(JSContext* ctx, JSValue this_val, in return JS_NewUint32(ctx, collection->m_nodes.size()); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/all_collection.h b/bridge/bindings/qjs/dom/all_collection.h index 0c1a43213f..c2fef3acd1 100644 --- a/bridge/bindings/qjs/dom/all_collection.h +++ b/bridge/bindings/qjs/dom/all_collection.h @@ -9,7 +9,7 @@ #include "bindings/qjs/host_object.h" #include "node.h" -namespace kraken::binding::qjs { +namespace kraken { class AllCollection : public HostObject { public: @@ -27,6 +27,6 @@ class AllCollection : public HostObject { std::vector m_nodes; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_ALL_COLLECTION_H diff --git a/bridge/bindings/qjs/dom/comment_node.cc b/bridge/bindings/qjs/dom/comment_node.cc index 6af1bef852..d2b6a705f7 100644 --- a/bridge/bindings/qjs/dom/comment_node.cc +++ b/bridge/bindings/qjs/dom/comment_node.cc @@ -5,9 +5,8 @@ #include "comment_node.h" #include "document.h" -#include "kraken_bridge.h" -namespace kraken::binding::qjs { +namespace kraken { void bindCommentNode(std::unique_ptr& context) { // auto* constructor = Comment::instance(context.get()); @@ -53,4 +52,4 @@ CommentInstance::CommentInstance(Comment* comment) : NodeInstance(comment, NodeT m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createComment, nativeEventTarget); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/comment_node.h b/bridge/bindings/qjs/dom/comment_node.h index 3e776e9f98..cffd59accb 100644 --- a/bridge/bindings/qjs/dom/comment_node.h +++ b/bridge/bindings/qjs/dom/comment_node.h @@ -8,7 +8,7 @@ #include "node.h" -namespace kraken::binding::qjs { +namespace kraken { void bindCommentNode(ExecutionContext* context); @@ -50,6 +50,6 @@ const WrapperTypeInfo commentTypeInfo = {"Comment", &nodeTypeInfo, commentCreato // friend Comment; //}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_COMMENT_NODE_H diff --git a/bridge/bindings/qjs/dom/custom_event.cc b/bridge/bindings/qjs/dom/custom_event.cc index f92a6a5f18..e8fb237a79 100644 --- a/bridge/bindings/qjs/dom/custom_event.cc +++ b/bridge/bindings/qjs/dom/custom_event.cc @@ -6,11 +6,10 @@ #include "custom_event.h" #include "bindings/qjs/native_value.h" #include "bindings/qjs/qjs_patch.h" -#include "kraken_bridge.h" #include -namespace kraken::binding::qjs { +namespace kraken { void bindCustomEvent(std::unique_ptr& context) { JSValue constructor = context->contextData()->constructorForType(&customEventTypeInfo); @@ -130,4 +129,4 @@ IMPL_PROPERTY_GETTER(CustomEvent, detail)(JSContext* ctx, JSValue this_val, int return JS_DupValue(ctx, customEventInstance->m_detail); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/custom_event.h b/bridge/bindings/qjs/dom/custom_event.h index d4f286ab67..e8de13d6f7 100644 --- a/bridge/bindings/qjs/dom/custom_event.h +++ b/bridge/bindings/qjs/dom/custom_event.h @@ -8,7 +8,7 @@ #include "event.h" -namespace kraken::binding::qjs { +namespace kraken { void bindCustomEvent(ExecutionContext* context); @@ -71,6 +71,6 @@ auto customEventCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst const WrapperTypeInfo customEventTypeInfo = {"CustomEvent", &eventTypeInfo, customEventCreator}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_CUSTOM_EVENT_H diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc index 4c12abaac3..650d616047 100644 --- a/bridge/bindings/qjs/dom/document.cc +++ b/bridge/bindings/qjs/dom/document.cc @@ -32,7 +32,9 @@ #include "events/.gen/popstate_event.h" #include "events/touch_event.h" -namespace kraken::binding::qjs { +#define DOCUMENT_TARGET_ID -2 + +namespace kraken { void traverseNode(Node* node, TraverseHandler handler) { bool shouldExit = handler(node); @@ -632,4 +634,4 @@ void Document::dispose() const { } } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/bindings/qjs/dom/document.h index 346287ede2..bde60659c7 100644 --- a/bridge/bindings/qjs/dom/document.h +++ b/bridge/bindings/qjs/dom/document.h @@ -11,7 +11,7 @@ #include "node.h" #include "script_animation_controller.h" -namespace kraken::binding::qjs { +namespace kraken { void bindDocument(ExecutionContext* context); @@ -85,6 +85,6 @@ auto documentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst th const WrapperTypeInfo documentTypeInfo = {"Document", &nodeTypeInfo, documentCreator}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_DOCUMENT_H diff --git a/bridge/bindings/qjs/dom/document_fragment.cc b/bridge/bindings/qjs/dom/document_fragment.cc index 2b2c096ad1..bc90a2c44c 100644 --- a/bridge/bindings/qjs/dom/document_fragment.cc +++ b/bridge/bindings/qjs/dom/document_fragment.cc @@ -5,9 +5,8 @@ #include "document_fragment.h" #include "document.h" -#include "kraken_bridge.h" -namespace kraken::binding::qjs { +namespace kraken { void bindDocumentFragment(std::unique_ptr& context) { JSValue classObject = context->contextData()->constructorForType(&documentFragmentInfo); @@ -34,4 +33,4 @@ DocumentFragment::DocumentFragment() { context()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::createDocumentFragment, nativeEventTarget); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/document_fragment.h b/bridge/bindings/qjs/dom/document_fragment.h index ba6dd85efa..dadfb93624 100644 --- a/bridge/bindings/qjs/dom/document_fragment.h +++ b/bridge/bindings/qjs/dom/document_fragment.h @@ -8,7 +8,7 @@ #include "node.h" -namespace kraken::binding::qjs { +namespace kraken { void bindDocumentFragment(ExecutionContext* context); @@ -31,6 +31,6 @@ auto documentFragmentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValue const WrapperTypeInfo documentFragmentInfo = {"DocumentFragment", &nodeTypeInfo, documentFragmentCreator}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_DOCUMENT_FRAGMENT_H diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc index d10626f52e..23197896af 100644 --- a/bridge/bindings/qjs/dom/element.cc +++ b/bridge/bindings/qjs/dom/element.cc @@ -15,7 +15,7 @@ #include "kraken_test_env.h" #endif -namespace kraken::binding::qjs { +namespace kraken { const std::string ATTR_ID = "id"; const std::string ATTR_CLASS = "class"; @@ -981,4 +981,4 @@ IMPL_PROPERTY_GETTER(BoundingClientRect, left)(JSContext* ctx, JSValue this_val, return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->left); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h index b4b8aefbf0..58cc3d1d8d 100644 --- a/bridge/bindings/qjs/dom/element.h +++ b/bridge/bindings/qjs/dom/element.h @@ -12,7 +12,7 @@ #include "node.h" #include "style_declaration.h" -namespace kraken::binding::qjs { +namespace kraken { void bindElement(ExecutionContext* context); @@ -190,6 +190,6 @@ class BoundingClientRect : public GarbageCollected { NativeBoundingClientRect* m_nativeBoundingClientRect{nullptr}; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_ELEMENT_H diff --git a/bridge/bindings/qjs/dom/element_test.cc b/bridge/bindings/qjs/dom/element_test.cc index dc76a6b938..b92d2c77ad 100644 --- a/bridge/bindings/qjs/dom/element_test.cc +++ b/bridge/bindings/qjs/dom/element_test.cc @@ -117,7 +117,7 @@ TEST(Element, instanceofEventTarget) { } TEST(Element, stringifyBoundingClientRect) { - using namespace kraken::binding::qjs; + using namespace kraken; bool static errorCalled = false; bool static logCalled = false; diff --git a/bridge/bindings/qjs/dom/elements/image_element.cc b/bridge/bindings/qjs/dom/elements/image_element.cc index 258705e2fc..d1859431f9 100644 --- a/bridge/bindings/qjs/dom/elements/image_element.cc +++ b/bridge/bindings/qjs/dom/elements/image_element.cc @@ -7,7 +7,7 @@ #include "bindings/qjs/qjs_patch.h" #include "page.h" -namespace kraken::binding::qjs { +namespace kraken { ImageElement::ImageElement(ExecutionContext* context) : Element(context) { JS_SetPrototype(m_ctx, m_prototypeObject, Element::instance(m_context)->prototype()); @@ -105,4 +105,4 @@ bool ImageElementInstance::dispatchEvent(EventInstance* event) { return result; } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/elements/image_element.h b/bridge/bindings/qjs/dom/elements/image_element.h index 70b9fd4f48..2af8eed083 100644 --- a/bridge/bindings/qjs/dom/elements/image_element.h +++ b/bridge/bindings/qjs/dom/elements/image_element.h @@ -8,7 +8,7 @@ #include "bindings/qjs/dom/element.h" -namespace kraken::binding::qjs { +namespace kraken { void bindImageElement(ExecutionContext* context); @@ -43,6 +43,6 @@ class ImageElementInstance : public ElementInstance { friend ImageElement; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_IMAGE_ELEMENTT_H diff --git a/bridge/bindings/qjs/dom/elements/template_element.cc b/bridge/bindings/qjs/dom/elements/template_element.cc index ef87ddcfb1..00a9efdfc0 100644 --- a/bridge/bindings/qjs/dom/elements/template_element.cc +++ b/bridge/bindings/qjs/dom/elements/template_element.cc @@ -8,7 +8,7 @@ #include "bindings/qjs/qjs_patch.h" #include "page.h" -namespace kraken::binding::qjs { +namespace kraken { TemplateElement::TemplateElement(ExecutionContext* context) : Element(context) { JS_SetPrototype(m_ctx, m_prototypeObject, Element::instance(m_context)->prototype()); @@ -38,4 +38,4 @@ void TemplateElementInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mar ElementInstance::trace(rt, val, mark_func); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/elements/template_element.h b/bridge/bindings/qjs/dom/elements/template_element.h index 0e8b0ca6a2..5df8e80ae1 100644 --- a/bridge/bindings/qjs/dom/elements/template_element.h +++ b/bridge/bindings/qjs/dom/elements/template_element.h @@ -9,7 +9,7 @@ #include "bindings/qjs/dom/document_fragment.h" #include "bindings/qjs/dom/element.h" -namespace kraken::binding::qjs { +namespace kraken { void bindTemplateElement(ExecutionContext* context); class TemplateElementInstance; @@ -42,6 +42,6 @@ class TemplateElementInstance : public ElementInstance { friend TemplateElement; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_TEMPLATE_ELEMENTT_H diff --git a/bridge/bindings/qjs/dom/event.cc b/bridge/bindings/qjs/dom/event.cc index b783b3db5c..a8917d3669 100644 --- a/bridge/bindings/qjs/dom/event.cc +++ b/bridge/bindings/qjs/dom/event.cc @@ -7,9 +7,8 @@ #include "bindings/qjs/qjs_patch.h" #include "custom_event.h" #include "event_target.h" -#include "kraken_bridge.h" -namespace kraken::binding::qjs { +namespace kraken { void bindEvent(std::unique_ptr& context) { JSValue constructor = Event::constructor(context.get()); @@ -207,4 +206,4 @@ void Event::dispose() const { delete nativeEvent; } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/event.h b/bridge/bindings/qjs/dom/event.h index 366d13dbe9..2dbe1033c8 100644 --- a/bridge/bindings/qjs/dom/event.h +++ b/bridge/bindings/qjs/dom/event.h @@ -9,7 +9,7 @@ #include "bindings/qjs/context_macros.h" #include "bindings/qjs/executing_context.h" -namespace kraken::binding::qjs { +namespace kraken { #define EVENT_CLICK "click" #define EVENT_INPUT "input" @@ -135,6 +135,6 @@ auto eventCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_ const WrapperTypeInfo eventTypeInfo = {"Event", nullptr, eventCreator}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_EVENT_H diff --git a/bridge/bindings/qjs/dom/event_listener_map.cc b/bridge/bindings/qjs/dom/event_listener_map.cc index bfb1bc89d0..0264b257ee 100644 --- a/bridge/bindings/qjs/dom/event_listener_map.cc +++ b/bridge/bindings/qjs/dom/event_listener_map.cc @@ -5,7 +5,7 @@ #include "event_listener_map.h" -namespace kraken::binding::qjs { +namespace kraken { 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()) { @@ -95,4 +95,4 @@ EventListenerMap::~EventListenerMap() { } } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/event_listener_map.h b/bridge/bindings/qjs/dom/event_listener_map.h index e9eddfb5cb..659b398b41 100644 --- a/bridge/bindings/qjs/dom/event_listener_map.h +++ b/bridge/bindings/qjs/dom/event_listener_map.h @@ -10,7 +10,7 @@ #include #include "include/kraken_foundation.h" -namespace kraken::binding::qjs { +namespace kraken { using EventListenerVector = std::vector; @@ -39,6 +39,6 @@ class EventListenerMap final { JSRuntime* m_runtime; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/bindings/qjs/dom/event_target.cc index 4b7d934282..ff8d1d8af8 100644 --- a/bridge/bindings/qjs/dom/event_target.cc +++ b/bridge/bindings/qjs/dom/event_target.cc @@ -13,13 +13,12 @@ #include "document.h" #include "element.h" #include "event.h" -#include "kraken_bridge.h" #if UNIT_TEST #include "kraken_test_env.h" #endif -namespace kraken::binding::qjs { +namespace kraken { static std::atomic globalEventTargetId{0}; #define GetPropertyCallPreFix "_getProperty_" @@ -481,4 +480,4 @@ void NativeEventTarget::dispatchEventImpl(int32_t contextId, NativeEventTarget* JS_FreeValue(context->ctx(), event->toQuickJS()); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/bindings/qjs/dom/event_target.h index d54f603290..e127f0497d 100644 --- a/bridge/bindings/qjs/dom/event_target.h +++ b/bridge/bindings/qjs/dom/event_target.h @@ -16,7 +16,7 @@ void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); #endif -namespace kraken::binding::qjs { +namespace kraken { class EventTarget; class NativeEventTarget; @@ -25,7 +25,7 @@ class Event; void bindEventTarget(ExecutionContext* context); -using NativeDispatchEvent = void (*)(NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); +using NativeDispatchEvent = void (*)(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); using CallNativeMethods = void (*)(void* nativePtr, NativeValue* returnValue, NativeString* method, int32_t argc, NativeValue* argv); struct NativeEventTarget { @@ -112,6 +112,6 @@ auto eventTargetCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst const WrapperTypeInfo eventTargetTypeInfo = {"EventTarget", nullptr, eventTargetCreator}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_EVENT_TARGET_H diff --git a/bridge/bindings/qjs/dom/event_target_test.cc b/bridge/bindings/qjs/dom/event_target_test.cc index c5bc6c5b84..c3a3804998 100644 --- a/bridge/bindings/qjs/dom/event_target_test.cc +++ b/bridge/bindings/qjs/dom/event_target_test.cc @@ -202,7 +202,7 @@ TEST(EventTarget, wontLeakWithStringProperty) { } TEST(EventTarget, dispatchEventOnGC) { - using namespace kraken::binding::qjs; + using namespace kraken; bool static errorCalled = false; bool static logCalled = false; diff --git a/bridge/bindings/qjs/dom/events/touch_event.cc b/bridge/bindings/qjs/dom/events/touch_event.cc index a807514644..bd4c69fc07 100644 --- a/bridge/bindings/qjs/dom/events/touch_event.cc +++ b/bridge/bindings/qjs/dom/events/touch_event.cc @@ -7,7 +7,7 @@ #include "bindings/qjs/qjs_patch.h" #include "page.h" -namespace kraken::binding::qjs { +namespace kraken { void bindTouchEvent(ExecutionContext* context) { auto* constructor = TouchEvent::instance(context); @@ -252,4 +252,4 @@ IMPL_PROPERTY_GETTER(TouchEvent, shiftKey)(JSContext* ctx, JSValue this_val, int TouchEventInstance::TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent) : EventInstance(event, nativeEvent) {} -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/events/touch_event.h b/bridge/bindings/qjs/dom/events/touch_event.h index b1de883b71..019666ebfb 100644 --- a/bridge/bindings/qjs/dom/events/touch_event.h +++ b/bridge/bindings/qjs/dom/events/touch_event.h @@ -8,7 +8,7 @@ #include "bindings/qjs/dom/element.h" -namespace kraken::binding::qjs { +namespace kraken { void bindTouchEvent(ExecutionContext* context); @@ -113,6 +113,6 @@ class TouchEventInstance : public EventInstance { friend TouchEvent; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_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 index dc3907c7c6..378bc6923f 100644 --- a/bridge/bindings/qjs/dom/frame_request_callback_collection.cc +++ b/bridge/bindings/qjs/dom/frame_request_callback_collection.cc @@ -5,7 +5,7 @@ #include "frame_request_callback_collection.h" -namespace kraken::binding::qjs { +namespace kraken { JSClassID FrameCallback::classId{0}; FrameCallback::FrameCallback(JSValue callback) : m_callback(callback) {} @@ -71,4 +71,4 @@ void FrameRequestCallbackCollection::trace(JSRuntime* rt, JSValue val, JS_MarkFu } } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/frame_request_callback_collection.h b/bridge/bindings/qjs/dom/frame_request_callback_collection.h index 8327555b7a..a101d5b032 100644 --- a/bridge/bindings/qjs/dom/frame_request_callback_collection.h +++ b/bridge/bindings/qjs/dom/frame_request_callback_collection.h @@ -8,7 +8,7 @@ #include "bindings/qjs/executing_context.h" -namespace kraken::binding::qjs { +namespace kraken { // |FrameCallback| is an interface type which generalizes callbacks which are // invoked when a script-based animation needs to be resampled. @@ -41,7 +41,7 @@ class FrameRequestCallbackCollection final { std::vector m_abandonedCallbacks; }; -} // namespace kraken::binding::qjs +} // namespace kraken class frame_request_callback_collection {}; diff --git a/bridge/bindings/qjs/dom/node.cc b/bridge/bindings/qjs/dom/node.cc index 2f3c5fc468..8aa0ac6a6e 100644 --- a/bridge/bindings/qjs/dom/node.cc +++ b/bridge/bindings/qjs/dom/node.cc @@ -9,10 +9,9 @@ #include "document.h" #include "document_fragment.h" #include "element.h" -#include "kraken_bridge.h" #include "text_node.h" -namespace kraken::binding::qjs { +namespace kraken { void bindNode(std::unique_ptr& context) { auto* contextData = context->contextData(); @@ -580,4 +579,4 @@ void Node::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { void Node::dispose() const {} -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/node.h b/bridge/bindings/qjs/dom/node.h index b47fd31090..0e98ce0516 100644 --- a/bridge/bindings/qjs/dom/node.h +++ b/bridge/bindings/qjs/dom/node.h @@ -11,7 +11,7 @@ #include "event_target.h" -namespace kraken::binding::qjs { +namespace kraken { class Element; class Document; @@ -106,6 +106,6 @@ auto nodeCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_v const WrapperTypeInfo nodeTypeInfo = {"Node", &eventTargetTypeInfo, nodeCreator}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_NODE_H diff --git a/bridge/bindings/qjs/dom/script_animation_controller.cc b/bridge/bindings/qjs/dom/script_animation_controller.cc index f28b4f552b..fd21563e52 100644 --- a/bridge/bindings/qjs/dom/script_animation_controller.cc +++ b/bridge/bindings/qjs/dom/script_animation_controller.cc @@ -11,7 +11,7 @@ #include "kraken_test_env.h" #endif -namespace kraken::binding::qjs { +namespace kraken { JSClassID ScriptAnimationController::classId{0}; @@ -62,4 +62,4 @@ void ScriptAnimationController::cancelFrameCallback(uint32_t callbackId) { m_frameRequestCallbackCollection.cancelFrameCallback(callbackId); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/script_animation_controller.h b/bridge/bindings/qjs/dom/script_animation_controller.h index cd07d97c56..4e3d4d92f4 100644 --- a/bridge/bindings/qjs/dom/script_animation_controller.h +++ b/bridge/bindings/qjs/dom/script_animation_controller.h @@ -9,7 +9,7 @@ #include "bindings/qjs/garbage_collected.h" #include "frame_request_callback_collection.h" -namespace kraken::binding::qjs { +namespace kraken { class ScriptAnimationController : public GarbageCollected { public: @@ -30,6 +30,6 @@ class ScriptAnimationController : public GarbageCollectedjsObject, mark_func); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/style_declaration.h b/bridge/bindings/qjs/dom/style_declaration.h index 099aceb32d..318d301e69 100644 --- a/bridge/bindings/qjs/dom/style_declaration.h +++ b/bridge/bindings/qjs/dom/style_declaration.h @@ -10,7 +10,7 @@ #include "bindings/qjs/dom/event_target.h" #include "bindings/qjs/garbage_collected.h" -namespace kraken::binding::qjs { +namespace kraken { void bindCSSStyleDeclaration(std::unique_ptr& context); @@ -51,6 +51,6 @@ class CSSStyleDeclaration : public GarbageCollected { std::unordered_map m_properties; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_STYLE_DECLARATION_H diff --git a/bridge/bindings/qjs/dom/text_node.cc b/bridge/bindings/qjs/dom/text_node.cc index 0ee6065437..b05c90388f 100644 --- a/bridge/bindings/qjs/dom/text_node.cc +++ b/bridge/bindings/qjs/dom/text_node.cc @@ -5,9 +5,8 @@ #include "text_node.h" #include "document.h" -#include "kraken_bridge.h" -namespace kraken::binding::qjs { +namespace kraken { std::once_flag kTextNodeInitFlag; @@ -84,4 +83,4 @@ void TextNode::internalSetTextContent(JSValue content) { std::unique_ptr args_02 = jsValueToNativeString(m_ctx, content); context()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::setProperty, *args_01, *args_02, nullptr); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/dom/text_node.h b/bridge/bindings/qjs/dom/text_node.h index dfc78c197b..a3a65afdc9 100644 --- a/bridge/bindings/qjs/dom/text_node.h +++ b/bridge/bindings/qjs/dom/text_node.h @@ -8,7 +8,7 @@ #include "node.h" -namespace kraken::binding::qjs { +namespace kraken { class TextNodeInstance; @@ -49,6 +49,6 @@ auto textNodeCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst th const WrapperTypeInfo textNodeType = {"TextNode", &nodeTypeInfo, textNodeCreator}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_TEXT_NODE_H diff --git a/bridge/bindings/qjs/executing_context.cc b/bridge/bindings/qjs/executing_context.cc index 295e3ecbf6..8459229641 100644 --- a/bridge/bindings/qjs/executing_context.cc +++ b/bridge/bindings/qjs/executing_context.cc @@ -10,17 +10,12 @@ #include "bindings/qjs/module_manager.h" #include "bom/dom_timer_coordinator.h" #include "garbage_collected.h" -#include "kraken_bridge.h" #include "qjs_patch.h" -namespace kraken::binding::qjs { +namespace kraken { 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 @@ -48,12 +43,6 @@ ExecutionContext::ExecutionContext(int32_t contextId, const JSExceptionHandler& 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); @@ -382,28 +371,6 @@ 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; @@ -414,35 +381,6 @@ void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01) { 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) { @@ -522,4 +460,4 @@ void ExecutionContext::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) m_timers.trace(rt, JS_NULL, mark_func); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/executing_context.h b/bridge/bindings/qjs/executing_context.h index 78b4b41dbd..86bb222198 100644 --- a/bridge/bindings/qjs/executing_context.h +++ b/bridge/bindings/qjs/executing_context.h @@ -16,17 +16,19 @@ #include #include #include +#include "foundation/macros.h" #include "bindings/qjs/bom/dom_timer_coordinator.h" #include "executing_context_data.h" #include "foundation/ui_command_buffer.h" #include "garbage_collected.h" #include "kraken_foundation.h" #include "qjs_patch.h" +#include "dart_methods.h" #include "wrapper_type_info.h" using JSExceptionHandler = std::function; -namespace kraken::binding::qjs { +namespace kraken { static std::once_flag kinitJSClassIDFlag; @@ -95,7 +97,8 @@ class ExecutionContext { DOMTimerCoordinator* timers(); FORCE_INLINE Document* document() { return m_document; }; - FORCE_INLINE foundation::UICommandBuffer* uiCommandBuffer() { return &m_commandBuffer; }; + FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &m_commandBuffer; }; + FORCE_INLINE std::unique_ptr& dartMethodPtr() { return m_dartMethodPtr; } void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); @@ -109,10 +112,6 @@ class ExecutionContext { struct list_head promise_job_list; struct list_head native_function_job_list; - static JSClassID kHostClassClassId; - static JSClassID kHostObjectClassId; - static JSClassID kHostExoticObjectClassId; - private: static void promiseRejectTracker(JSContext* ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void* opaque); void dispatchGlobalErrorEvent(JSValueConst error); @@ -129,7 +128,8 @@ class ExecutionContext { DOMTimerCoordinator m_timers; ExecutionContextGCTracker* m_gcTracker{nullptr}; ExecutionContextData m_data{this}; - foundation::UICommandBuffer m_commandBuffer{contextId}; + UICommandBuffer m_commandBuffer{contextId}; + std::unique_ptr m_dartMethodPtr = std::make_unique(); }; // The read object's method or properties via Proxy, we should redirect this_val from Proxy into target property of @@ -194,23 +194,8 @@ class JSValueHolder { 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); @@ -222,6 +207,6 @@ void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t de // JS object operation utilities. JSValue objectGetKeys(JSContext* ctx, JSValue obj); -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_JS_CONTEXT_H diff --git a/bridge/bindings/qjs/executing_context_data.cc b/bridge/bindings/qjs/executing_context_data.cc index d8e0eda910..949e5c5e18 100644 --- a/bridge/bindings/qjs/executing_context_data.cc +++ b/bridge/bindings/qjs/executing_context_data.cc @@ -6,7 +6,7 @@ #include "executing_context_data.h" #include "executing_context.h" -namespace kraken::binding::qjs { +namespace kraken { JSValue ExecutionContextData::constructorForType(const WrapperTypeInfo* type) { auto it = m_constructorMap.find(type); @@ -71,4 +71,4 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty return classObject; } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/executing_context_data.h b/bridge/bindings/qjs/executing_context_data.h index abdc8d54a7..c18b7120c4 100644 --- a/bridge/bindings/qjs/executing_context_data.h +++ b/bridge/bindings/qjs/executing_context_data.h @@ -9,7 +9,7 @@ #include #include "wrapper_type_info.h" -namespace kraken::binding::qjs { +namespace kraken { class ExecutionContext; @@ -34,6 +34,6 @@ class ExecutionContextData final { ExecutionContext* m_context; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_CONTEXT_DATA_H diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index 1da6426a93..e6d1a0057a 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -10,7 +10,7 @@ #include "include/kraken_foundation.h" #include "qjs_patch.h" -namespace kraken::binding::qjs { +namespace kraken { template class MakeGarbageCollectedTrait; @@ -159,6 +159,6 @@ T* makeGarbageCollected(Args&&... args) { return MakeGarbageCollectedTrait::allocate(std::forward(args)...); } -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_GARBAGE_COLLECTED_H diff --git a/bridge/bindings/qjs/garbage_collected_test.cc b/bridge/bindings/qjs/garbage_collected_test.cc index c1e740884b..060bb701f2 100644 --- a/bridge/bindings/qjs/garbage_collected_test.cc +++ b/bridge/bindings/qjs/garbage_collected_test.cc @@ -9,7 +9,7 @@ //#include "kraken_test_env.h" //#include "page.h" // -// namespace kraken::binding::qjs { +// namespace kraken { // // class ParentClass : public HostClass { // public: @@ -411,7 +411,7 @@ // EXPECT_EQ(logCalled, true); //} // -//} // namespace kraken::binding::qjs +//} // namespace kraken ///* // * Copyright (C) 2021 Alibaba Inc. All rights reserved. @@ -424,7 +424,7 @@ //#include "kraken_test_env.h" //#include "page.h" // -// namespace kraken::binding::qjs { +// namespace kraken { // // static bool isSampleFree = false; // @@ -573,4 +573,4 @@ // EXPECT_EQ(isSampleFree, true); //} // -//} // namespace kraken::binding::qjs +//} // namespace kraken diff --git a/bridge/bindings/qjs/heap_hashmap.h b/bridge/bindings/qjs/heap_hashmap.h index 8a5e98d8ad..8449fbe152 100644 --- a/bridge/bindings/qjs/heap_hashmap.h +++ b/bridge/bindings/qjs/heap_hashmap.h @@ -9,7 +9,7 @@ #include #include -namespace kraken::binding::qjs { +namespace kraken { template class HeapHashMap { @@ -98,6 +98,6 @@ void HeapHashMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) c } } -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/bindings/qjs/html_parser.cc index 2a9e7fae89..0fcdaeecb8 100644 --- a/bridge/bindings/qjs/html_parser.cc +++ b/bridge/bindings/qjs/html_parser.cc @@ -10,7 +10,7 @@ #include -namespace kraken::binding::qjs { +namespace kraken { inline std::string trim(std::string& str) { str.erase(0, str.find_first_not_of(' ')); // prefixing spaces @@ -71,7 +71,7 @@ void HTMLParser::traverseHTML(Node* root, GumboNode* node) { } } -bool HTMLParser::parseHTML(std::string html, NodeInstance* rootNode) { +bool HTMLParser::parseHTML(std::string html, Node* rootNode) { if (rootNode != nullptr) { rootNode->internalClearChild(); @@ -90,7 +90,7 @@ bool HTMLParser::parseHTML(std::string html, NodeInstance* rootNode) { return true; } -bool HTMLParser::parseHTML(const char* code, size_t codeLength, NodeInstance* rootNode) { +bool HTMLParser::parseHTML(const char* code, size_t codeLength, Node* rootNode) { std::string html = std::string(code, codeLength); return parseHTML(html, rootNode); } @@ -122,4 +122,4 @@ void HTMLParser::parseProperty(ElementInstance* element, GumboElement* gumboElem } } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/html_parser.h b/bridge/bindings/qjs/html_parser.h index 7e01625e8f..9d4703d12e 100644 --- a/bridge/bindings/qjs/html_parser.h +++ b/bridge/bindings/qjs/html_parser.h @@ -8,10 +8,10 @@ #include "bindings/qjs/dom/element.h" #include "executing_context.h" -#include "include/kraken_bridge.h" +#include "foundation/native_string.h" #include "third_party/gumbo-parser/src/gumbo.h" -namespace kraken::binding::qjs { +namespace kraken { class HTMLParser { public: @@ -23,6 +23,6 @@ class HTMLParser { static void traverseHTML(Node* root, GumboNode* node); static void parseProperty(Element* element, GumboElement* gumboElement); }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_HTML_PARSER_H diff --git a/bridge/bindings/qjs/module_manager.cc b/bridge/bindings/qjs/module_manager.cc index b08252c293..a7914697af 100644 --- a/bridge/bindings/qjs/module_manager.cc +++ b/bridge/bindings/qjs/module_manager.cc @@ -7,7 +7,7 @@ #include "page.h" #include "qjs_patch.h" -namespace kraken::binding::qjs { +namespace kraken { JSValue krakenModuleListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { if (argc < 1) { @@ -168,4 +168,4 @@ void bindModuleManager(ExecutionContext* context) { QJS_GLOBAL_BINDING_FUNCTION(context, flushUICommand, "__kraken_flush_ui_command__", 0); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/module_manager.h b/bridge/bindings/qjs/module_manager.h index 0ee8edf589..2dfef7a6a5 100644 --- a/bridge/bindings/qjs/module_manager.h +++ b/bridge/bindings/qjs/module_manager.h @@ -8,7 +8,7 @@ #include "executing_context.h" -namespace kraken::binding::qjs { +namespace kraken { struct ModuleContext { JSValue callback; @@ -18,6 +18,6 @@ struct ModuleContext { void bindModuleManager(ExecutionContext* context); void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, NativeString* errmsg, NativeString* json); -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_MODULE_MANAGER_H diff --git a/bridge/bindings/qjs/module_manager_test.cc b/bridge/bindings/qjs/module_manager_test.cc index 31899da571..055acbbc12 100644 --- a/bridge/bindings/qjs/module_manager_test.cc +++ b/bridge/bindings/qjs/module_manager_test.cc @@ -9,7 +9,7 @@ #include "kraken_test_env.h" #include "page.h" -namespace kraken::binding::qjs { +namespace kraken { TEST(ModuleManager, shouldThrowErrorWhenBadJSON) { bool static errorCalled = false; @@ -40,4 +40,4 @@ kraken.methodChannel.invokeMethod('abc', 'fn', object); EXPECT_EQ(errorCalled, true); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/native_string.cc b/bridge/bindings/qjs/native_string.cc new file mode 100644 index 0000000000..278f97b805 --- /dev/null +++ b/bridge/bindings/qjs/native_string.cc @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "native_string.h" +#include "bindings/qjs/qjs_patch.h" + +namespace kraken { + + +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; +} + +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; +} + + +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; +} + + + +} diff --git a/bridge/bindings/qjs/native_string.h b/bridge/bindings/qjs/native_string.h new file mode 100644 index 0000000000..92783e2aae --- /dev/null +++ b/bridge/bindings/qjs/native_string.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_NATIVE_STRING_H +#define KRAKENBRIDGE_NATIVE_STRING_H + +#include +#include +#include +#include +#include +#include + +namespace kraken { + +struct NativeString { + const uint16_t* string; + uint32_t length; + + NativeString* clone(); + void free(); +}; + +// 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); + +// 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); + +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); +} + +} + +class native_string {}; + +#endif // KRAKENBRIDGE_NATIVE_STRING_H diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 02496a855c..d849b3af89 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -11,7 +11,7 @@ #include "bindings/qjs/qjs_patch.h" #include "include/kraken_foundation.h" -namespace kraken::binding::qjs { +namespace kraken { // 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 @@ -36,6 +36,6 @@ class WrapperTypeInfo final { JSClassID classId{0}; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_WRAPPER_TYPE_INFO_H diff --git a/bridge/dart_methods.cc b/bridge/dart_methods.cc deleted file mode 100644 index b815895f64..0000000000 --- a/bridge/dart_methods.cc +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "dart_methods.h" -#include -#include "kraken_bridge.h" - -namespace kraken { - -std::shared_ptr methodPointer = std::make_shared(); - -std::shared_ptr 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(); - } -#endif - - return methodPointer; -} - -void registerDartMethods(uint64_t* methodBytes, int32_t length) { - size_t i = 0; - - methodPointer->invokeModule = reinterpret_cast(methodBytes[i++]); - methodPointer->requestBatchUpdate = reinterpret_cast(methodBytes[i++]); - methodPointer->reloadApp = reinterpret_cast(methodBytes[i++]); - methodPointer->setTimeout = reinterpret_cast(methodBytes[i++]); - methodPointer->setInterval = reinterpret_cast(methodBytes[i++]); - methodPointer->clearTimeout = reinterpret_cast(methodBytes[i++]); - methodPointer->requestAnimationFrame = reinterpret_cast(methodBytes[i++]); - methodPointer->cancelAnimationFrame = reinterpret_cast(methodBytes[i++]); - methodPointer->getScreen = reinterpret_cast(methodBytes[i++]); - methodPointer->devicePixelRatio = reinterpret_cast(methodBytes[i++]); - methodPointer->platformBrightness = reinterpret_cast(methodBytes[i++]); - methodPointer->toBlob = reinterpret_cast(methodBytes[i++]); - methodPointer->flushUICommand = reinterpret_cast(methodBytes[i++]); - methodPointer->initWindow = reinterpret_cast(methodBytes[i++]); - methodPointer->initDocument = reinterpret_cast(methodBytes[i++]); - -#if ENABLE_PROFILE - methodPointer->getPerformanceEntries = reinterpret_cast(methodBytes[i++]); -#else - i++; -#endif - - methodPointer->onJsError = reinterpret_cast(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(methodBytes[i++]); - methodPointer->matchImageSnapshot = reinterpret_cast(methodBytes[i++]); - methodPointer->environment = reinterpret_cast(methodBytes[i++]); - methodPointer->simulatePointer = reinterpret_cast(methodBytes[i++]); - methodPointer->simulateInputText = reinterpret_cast(methodBytes[i++]); - - assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); -} - -#if ENABLE_PROFILE -void registerGetPerformanceEntries(GetPerformanceEntries getPerformanceEntries) { - methodPointer->getPerformanceEntries = getPerformanceEntries; -} -#endif - -} // namespace kraken diff --git a/bridge/foundation/closure.h b/bridge/foundation/closure.h deleted file mode 100644 index bcb042af34..0000000000 --- a/bridge/foundation/closure.h +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright (C) 2020-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_CLOSURE_H -#define KRAKENBRIDGE_CLOSURE_H - -#include - -namespace fml { -using closure = std::function; -} - -#endif // KRAKENBRIDGE_CLOSURE_H diff --git a/bridge/foundation/inspector_task_queue.cc b/bridge/foundation/inspector_task_queue.cc index 42c8852398..486a726826 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 kraken { std::mutex InspectorTaskQueue::inspector_task_creation_mutex_{}; fml::RefPtr InspectorTaskQueue::instance_{}; -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/inspector_task_queue.h b/bridge/foundation/inspector_task_queue.h index 12c0a02105..17625d0855 100644 --- a/bridge/foundation/inspector_task_queue.h +++ b/bridge/foundation/inspector_task_queue.h @@ -9,7 +9,7 @@ #include "kraken_foundation.h" #include "task_queue.h" -namespace foundation { +namespace kraken { class InspectorTaskQueue; using Task = void (*)(void*); @@ -26,7 +26,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 +35,6 @@ class InspectorTaskQueue : public TaskQueue { static fml::RefPtr instance_; }; -} // namespace foundation +} // namespace kraken #endif // KRAKENBRIDGE_INSPECTOR_TASK_QUEUE_H diff --git a/bridge/foundation/logging.cc b/bridge/foundation/logging.cc index e8e312ac78..40a6f13d88 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -24,7 +24,7 @@ #include "inspector/impl/jsc_console_client_impl.h" #endif -namespace foundation { +namespace kraken { namespace { const char* const kLogSeverityNames[LOG_NUM_SEVERITIES] = {"VERBOSE", BOLD("INFO"), FYEL("WARN"), BOLD("DEBUG"), FRED("ERROR")}; @@ -141,4 +141,4 @@ void printLog(int32_t contextId, std::stringstream& stream, std::string level, v } } -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/logging.h b/bridge/foundation/logging.h index 9ca8e6a49a..5e2f47805a 100644 --- a/bridge/foundation/logging.h +++ b/bridge/foundation/logging.h @@ -7,10 +7,52 @@ #define KRAKEN_FOUNDATION_LOGGING_H_ #include - #include -#include "include/kraken_bridge.h" -namespace foundation {} // namespace foundation +#define KRAKEN_LOG_STREAM(severity) ::kraken::LogMessage(::kraken::LOG_##severity, __FILE__, __LINE__, nullptr).stream() + +#define KRAKEN_LAZY_STREAM(stream, condition) !(condition) ? (void)0 : ::kraken::LogMessageVoidify() & (stream) + +#define KRAKEN_EAT_STREAM_PARAMETERS(ignored) true || (ignored) ? (void)0 : ::LogMessageVoidify() & ::LogMessage(::LOG_FATAL, 0, 0, nullptr).stream() + +#define KRAKEN_LOG(severity) KRAKEN_LAZY_STREAM(KRAKEN_LOG_STREAM(severity), true) + +#define KRAKEN_CHECK(condition) KRAKEN_LAZY_STREAM(::kraken::LogMessage(::kraken::LOG_FATAL, __FILE__, __LINE__, #condition).stream(), !(condition)) + +namespace kraken { + +typedef int LogSeverity; + +// Default log levels. Negative values can be used for verbose log levels. +constexpr LogSeverity LOG_VERBOSE = 0; +constexpr LogSeverity LOG_INFO = 1; +constexpr LogSeverity LOG_WARN = 2; +constexpr LogSeverity LOG_DEBUG_ = 3; +constexpr LogSeverity LOG_ERROR = 4; +constexpr LogSeverity LOG_NUM_SEVERITIES = 5; +constexpr LogSeverity LOG_FATAL = 6; + +class LogMessageVoidify { +public: + void operator&(std::ostream&) {} +}; + +class LogMessage { + public: + LogMessage(LogSeverity severity, const char* file, int line, const char* condition); + ~LogMessage(); + + std::ostream& stream() { return stream_; } + + private: + std::ostringstream stream_; + const LogSeverity severity_; + const char* file_; + const int line_; +}; + +void printLog(int32_t contextId, std::stringstream& stream, std::string level, void* ctx); + +} // namespace kraken #endif // KRAKEN_FOUNDATION_LOGGING_H_ diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h new file mode 100644 index 0000000000..a65cf1ec79 --- /dev/null +++ b/bridge/foundation/macros.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MACROS_H +#define KRAKENBRIDGE_MACROS_H + +#if defined(__GNUC__) || defined(__clang__) +#define LIKELY(x) __builtin_expect(!!(x), 1) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#define FORCE_INLINE inline __attribute__((always_inline)) +#else +#define LIKELY(x) (x) +#define UNLIKELY(x) (x) +#define FORCE_INLINE inline +#endif + +#define assert_m(exp, msg) assert(((void)msg, exp)) + +#define KRAKEN_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete + +#define KRAKEN_DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete + +#define KRAKEN_DISALLOW_MOVE(TypeName) \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(TypeName&&) = delete + +#define KRAKEN_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete + +#define KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(const TypeName&) = delete; \ + TypeName& operator=(TypeName&&) = delete + +#define KRAKEN_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) + + +#endif // KRAKENBRIDGE_MACROS_H diff --git a/bridge/bindings/qjs/native_value.cc b/bridge/foundation/native_value.cc similarity index 94% rename from bridge/bindings/qjs/native_value.cc rename to bridge/foundation/native_value.cc index 92f372da8d..8bc6bdaf3d 100644 --- a/bridge/bindings/qjs/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -4,31 +4,17 @@ */ #include "native_value.h" -#include "bindings/qjs/dom/elements/image_element.h" +#include "bindings/qjs/executing_context.h" #include "bindings/qjs/qjs_patch.h" -#include "dom/element.h" -#include "dom/elements/.gen/canvas_element.h" -#include "kraken_bridge.h" +#include "bindings/qjs/dom/event_target.h" -namespace kraken::binding::qjs { +namespace kraken { + +using namespace kraken::binding::qjs; #define AnonymousFunctionCallPreFix "_anonymous_fn_" #define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" -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; -} - NativeValue Native_NewNull() { return (NativeValue){0, .u = {.int64 = 0}, NativeTag::TAG_NULL}; } @@ -161,7 +147,7 @@ NativeFunctionContext::~NativeFunctionContext() { 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))); + auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); std::string call_params = AnonymousFunctionCallPreFix + std::to_string(id); diff --git a/bridge/bindings/qjs/native_value.h b/bridge/foundation/native_value.h similarity index 93% rename from bridge/bindings/qjs/native_value.h rename to bridge/foundation/native_value.h index 8edaaf0d46..d031856943 100644 --- a/bridge/bindings/qjs/native_value.h +++ b/bridge/foundation/native_value.h @@ -11,6 +11,8 @@ #include #include +namespace kraken { + enum NativeTag { TAG_STRING = 0, TAG_INT = 1, @@ -25,16 +27,6 @@ enum NativeTag { enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeEventTarget = 4 }; -namespace kraken::binding::qjs { - -struct NativeString { - const uint16_t* string; - uint32_t length; - - NativeString* clone(); - void free(); -}; - class ExecutionContext; // Exchange data struct between dart and C++ diff --git a/bridge/foundation/ref_counted_internal.h b/bridge/foundation/ref_counted_internal.h index a196cf9cdd..5c01b96b9d 100644 --- a/bridge/foundation/ref_counted_internal.h +++ b/bridge/foundation/ref_counted_internal.h @@ -8,7 +8,6 @@ #define FLUTTER_FML_MEMORY_REF_COUNTED_INTERNAL_H_ #include -#include "include/kraken_bridge.h" #include "logging.h" namespace fml { @@ -76,8 +75,6 @@ class RefCountedThreadSafeBase { mutable bool adoption_required_; mutable bool destruction_started_; #endif - - KRAKEN_DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase); }; inline RefCountedThreadSafeBase::RefCountedThreadSafeBase() diff --git a/bridge/foundation/ref_ptr.h b/bridge/foundation/ref_ptr.h index ccf61a5a5c..ddadd0e382 100644 --- a/bridge/foundation/ref_ptr.h +++ b/bridge/foundation/ref_ptr.h @@ -14,6 +14,7 @@ #include "logging.h" #include "ref_ptr_internal.h" +#include "macros.h" namespace fml { diff --git a/bridge/foundation/ref_ptr_internal.h b/bridge/foundation/ref_ptr_internal.h index b81ffb2468..a3579864fe 100644 --- a/bridge/foundation/ref_ptr_internal.h +++ b/bridge/foundation/ref_ptr_internal.h @@ -6,7 +6,6 @@ #define FLUTTER_FML_MEMORY_REF_PTR_INTERNAL_H_ #include -#include "include/kraken_bridge.h" namespace fml { diff --git a/bridge/foundation/task_queue.cc b/bridge/foundation/task_queue.cc index 5322b6aab6..dc98c34459 100644 --- a/bridge/foundation/task_queue.cc +++ b/bridge/foundation/task_queue.cc @@ -5,7 +5,7 @@ #include "task_queue.h" -namespace foundation { +namespace kraken { int32_t TaskQueue::registerTask(const Task& task, void* data) { std::lock_guard guard(queue_mutex_); @@ -32,4 +32,4 @@ void TaskQueue::flushTask() { m_map.clear(); } -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/task_queue.h b/bridge/foundation/task_queue.h index 30be05d854..2339f5474c 100644 --- a/bridge/foundation/task_queue.h +++ b/bridge/foundation/task_queue.h @@ -8,11 +8,12 @@ #include #include -#include "closure.h" #include "ref_counter.h" #include "ref_ptr.h" -namespace foundation { +namespace kraken { + +using Task = void (*)(void*); class TaskQueue : public fml::RefCountedThreadSafe { public: @@ -35,6 +36,6 @@ class TaskQueue : public fml::RefCountedThreadSafe { FML_FRIEND_REF_COUNTED_THREAD_SAFE(TaskQueue); }; -} // namespace foundation +} // namespace kraken #endif // KRAKENBRIDGE_TASK_QUEUE_H diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 0d14560f24..36bb49aa79 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -5,9 +5,8 @@ #include "ui_command_buffer.h" #include "dart_methods.h" -#include "include/kraken_bridge.h" -namespace foundation { +namespace kraken { UICommandBuffer::UICommandBuffer(int32_t contextId) : contextId(contextId) {} @@ -73,4 +72,4 @@ void UICommandBuffer::clear() { update_batched = false; } -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 15b10b5bea..0a249dde16 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -6,7 +6,49 @@ #ifndef KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ #define KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ -namespace foundation { +#include +#include +#include "native_value.h" +#include "bindings/qjs/native_string.h" + +namespace kraken { + +enum UICommand { + createElement, + createTextNode, + createComment, + disposeEventTarget, + addEvent, + removeNode, + insertAdjacentNode, + setStyle, + setProperty, + removeProperty, + cloneNode, + removeEvent, + createDocumentFragment, +}; + +struct UICommandItem { + UICommandItem(int32_t id, int32_t type, NativeString args_01, NativeString args_02, void* nativePtr) + : type(type), + string_01(reinterpret_cast(args_01.string)), + args_01_length(args_01.length), + string_02(reinterpret_cast(args_02.string)), + args_02_length(args_02.length), + id(id), + nativePtr(reinterpret_cast(nativePtr)){}; + UICommandItem(int32_t id, int32_t type, NativeString args_01, void* nativePtr) + : type(type), string_01(reinterpret_cast(args_01.string)), args_01_length(args_01.length), id(id), nativePtr(reinterpret_cast(nativePtr)){}; + UICommandItem(int32_t id, int32_t type, void* nativePtr) : type(type), id(id), nativePtr(reinterpret_cast(nativePtr)){}; + int32_t type; + int32_t id; + int32_t args_01_length{0}; + int32_t args_02_length{0}; + int64_t string_01{0}; + int64_t string_02{0}; + int64_t nativePtr{0}; +}; class UICommandBuffer { public: @@ -26,6 +68,6 @@ class UICommandBuffer { std::vector queue; }; -} // namespace foundation +} // namespace kraken #endif // KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ diff --git a/bridge/foundation/ui_command_callback_queue.cc b/bridge/foundation/ui_command_callback_queue.cc index 5d0dceebc3..1182984337 100644 --- a/bridge/foundation/ui_command_callback_queue.cc +++ b/bridge/foundation/ui_command_callback_queue.cc @@ -3,9 +3,7 @@ * Author: Kraken Team. */ -#include "kraken_bridge.h" - -namespace foundation { +namespace kraken { UICommandCallbackQueue* UICommandCallbackQueue::instance() { static UICommandCallbackQueue* queue = nullptr; @@ -29,4 +27,4 @@ void UICommandCallbackQueue::registerCallback(const Callback& callback, void* da queue.emplace_back(item); } -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/ui_task_queue.cc b/bridge/foundation/ui_task_queue.cc index aa55c1b051..64f9d0492d 100644 --- a/bridge/foundation/ui_task_queue.cc +++ b/bridge/foundation/ui_task_queue.cc @@ -5,7 +5,7 @@ #include "ui_task_queue.h" -namespace foundation { +namespace kraken { std::mutex UITaskQueue::ui_task_creation_mutex_{}; fml::RefPtr UITaskQueue::instance_{}; @@ -15,4 +15,4 @@ int32_t UITaskQueue::registerTask(const Task& task, void* data) { return taskId; } -} // namespace foundation +} // namespace kraken diff --git a/bridge/foundation/ui_task_queue.h b/bridge/foundation/ui_task_queue.h index 62b73ae8be..b87d452c23 100644 --- a/bridge/foundation/ui_task_queue.h +++ b/bridge/foundation/ui_task_queue.h @@ -8,9 +8,7 @@ #include "task_queue.h" -namespace foundation { - -using Task = void (*)(void*); +namespace kraken { class UITaskQueue : public TaskQueue { public: @@ -30,6 +28,6 @@ class UITaskQueue : public TaskQueue { int m_contextId; }; -} // namespace foundation +} // namespace kraken #endif // KRAKENBRIDGE_UI_TASK_QUEUE_H diff --git a/bridge/include/kraken_bridge.h b/bridge/include/kraken_bridge.h index 6f29301a64..0cce47d4f5 100644 --- a/bridge/include/kraken_bridge.h +++ b/bridge/include/kraken_bridge.h @@ -8,26 +8,15 @@ #include -#include "dart_methods.h" -#include "kraken_foundation.h" - -#if KRAKEN_JSC_ENGINE -#include "kraken_bridge_jsc.h" -#elif KRAKEN_QUICK_JS_ENGINE -#endif - #define KRAKEN_EXPORT_C extern "C" __attribute__((visibility("default"))) __attribute__((used)) #define KRAKEN_EXPORT __attribute__((__visibility__("default"))) KRAKEN_EXPORT std::thread::id getUIThreadId(); -typedef struct void* NativeString*; - -struct NativeByteCode { - uint8_t* bytes; - int32_t length; -}; +typedef struct NativeString NativeString; +typedef struct NativeScreen NativeScreen; +typedef struct NativeByteCode NativeByteCode; struct KrakenInfo; @@ -38,43 +27,6 @@ struct KrakenInfo { const char* system_name{nullptr}; }; -enum UICommand { - createElement, - createTextNode, - createComment, - disposeEventTarget, - addEvent, - removeNode, - insertAdjacentNode, - setStyle, - setProperty, - removeProperty, - cloneNode, - removeEvent, - createDocumentFragment, -}; - -struct KRAKEN_EXPORT UICommandItem { - UICommandItem(int32_t id, int32_t type, NativeString args_01, NativeString args_02, void* nativePtr) - : type(type), - string_01(reinterpret_cast(args_01.string)), - args_01_length(args_01.length), - string_02(reinterpret_cast(args_02.string)), - args_02_length(args_02.length), - id(id), - nativePtr(reinterpret_cast(nativePtr)){}; - UICommandItem(int32_t id, int32_t type, NativeString args_01, void* nativePtr) - : type(type), string_01(reinterpret_cast(args_01.string)), args_01_length(args_01.length), id(id), nativePtr(reinterpret_cast(nativePtr)){}; - UICommandItem(int32_t id, int32_t type, void* nativePtr) : type(type), id(id), nativePtr(reinterpret_cast(nativePtr)){}; - int32_t type; - int32_t id; - int32_t 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); @@ -99,7 +51,7 @@ void reloadJsContext(int32_t contextId); KRAKEN_EXPORT_C void invokeModuleEvent(int32_t contextId, NativeString* module, const char* eventType, void* event, NativeString* extra); KRAKEN_EXPORT_C -void registerDartMethods(uint64_t* methodBytes, int32_t length); +void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length); KRAKEN_EXPORT_C NativeScreen* createScreen(double width, double height); KRAKEN_EXPORT_C @@ -111,9 +63,7 @@ void flushUITask(int32_t contextId); KRAKEN_EXPORT_C void registerUITask(int32_t contextId, Task task, void* data); KRAKEN_EXPORT_C -void flushUICommandCallback(); -KRAKEN_EXPORT_C -UICommandItem* getUICommandItems(int32_t contextId); +void* getUICommandItems(int32_t contextId); KRAKEN_EXPORT_C int64_t getUICommandItemSize(int32_t contextId); KRAKEN_EXPORT_C diff --git a/bridge/include/kraken_foundation.h b/bridge/include/kraken_foundation.h deleted file mode 100644 index eb85806234..0000000000 --- a/bridge/include/kraken_foundation.h +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (C) 2020 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_FOUNDATION_H -#define KRAKENBRIDGE_FOUNDATION_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#define WINDOW_TARGET_ID -1 -#define DOCUMENT_TARGET_ID -2 - -#define assert_m(exp, msg) assert(((void)msg, exp)) - -#define KRAKEN_EXPORT __attribute__((__visibility__("default"))) - -#if defined(__GNUC__) || defined(__clang__) -#define LIKELY(x) __builtin_expect(!!(x), 1) -#define UNLIKELY(x) __builtin_expect(!!(x), 0) -#define FORCE_INLINE inline __attribute__((always_inline)) -#else -#define LIKELY(x) (x) -#define UNLIKELY(x) (x) -#define FORCE_INLINE inline -#endif - -#define KRAKEN_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete - -#define KRAKEN_DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete - -#define KRAKEN_DISALLOW_MOVE(TypeName) \ - TypeName(TypeName&&) = delete; \ - TypeName& operator=(TypeName&&) = delete - -#define KRAKEN_DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&) = delete; \ - TypeName& operator=(const TypeName&) = delete - -#define KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \ - TypeName(const TypeName&) = delete; \ - TypeName(TypeName&&) = delete; \ - TypeName& operator=(const TypeName&) = delete; \ - TypeName& operator=(TypeName&&) = delete - -#define KRAKEN_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ - TypeName() = delete; \ - KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) - -struct NativeString; -struct UICommandItem; - -namespace foundation { - -// An un thread safe queue used for dart side to read ui command items. -class UICommandCallbackQueue { - public: - using Callback = void (*)(void*); - UICommandCallbackQueue() = default; - static KRAKEN_EXPORT UICommandCallbackQueue* instance(); - KRAKEN_EXPORT void registerCallback(const Callback& callback, void* data); - KRAKEN_EXPORT void flushCallbacks(); - - private: - struct CallbackItem { - CallbackItem(const Callback& callback, void* data) : callback(callback), data(data){}; - Callback callback; - void* data; - }; - - std::vector queue; -}; - -typedef int LogSeverity; - -// Default log levels. Negative values can be used for verbose log levels. -constexpr LogSeverity LOG_VERBOSE = 0; -constexpr LogSeverity LOG_INFO = 1; -constexpr LogSeverity LOG_WARN = 2; -constexpr LogSeverity LOG_DEBUG_ = 3; -constexpr LogSeverity LOG_ERROR = 4; -constexpr LogSeverity LOG_NUM_SEVERITIES = 5; -constexpr LogSeverity LOG_FATAL = 6; - -class LogMessageVoidify { - public: - void operator&(std::ostream&) {} -}; - -class KRAKEN_EXPORT LogMessage { - public: - LogMessage(LogSeverity severity, const char* file, int line, const char* condition); - ~LogMessage(); - - std::ostream& stream() { return stream_; } - - private: - std::ostringstream stream_; - const LogSeverity severity_; - const char* file_; - const int line_; - - KRAKEN_DISALLOW_COPY_AND_ASSIGN(LogMessage); -}; - -void printLog(int32_t contextId, std::stringstream& stream, std::string level, void* ctx); - -} // namespace foundation - -#define KRAKEN_LOG_STREAM(severity) ::foundation::LogMessage(::foundation::LOG_##severity, __FILE__, __LINE__, nullptr).stream() - -#define KRAKEN_LAZY_STREAM(stream, condition) !(condition) ? (void)0 : ::foundation::LogMessageVoidify() & (stream) - -#define KRAKEN_EAT_STREAM_PARAMETERS(ignored) true || (ignored) ? (void)0 : ::foundation::LogMessageVoidify() & ::foundation::LogMessage(::foundation::LOG_FATAL, 0, 0, nullptr).stream() - -#define KRAKEN_LOG(severity) KRAKEN_LAZY_STREAM(KRAKEN_LOG_STREAM(severity), true) - -#define KRAKEN_CHECK(condition) KRAKEN_LAZY_STREAM(::foundation::LogMessage(::foundation::LOG_FATAL, __FILE__, __LINE__, #condition).stream(), !(condition)) - -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); -} - -#endif diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 9f44000469..26237b63b1 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -3,24 +3,17 @@ * Author: Kraken Team. */ -#include "kraken_bridge.h" +#include +#include #include -#include "dart_methods.h" + +#include "include/kraken_bridge.h" #include "foundation/inspector_task_queue.h" #include "foundation/logging.h" #include "foundation/ui_task_queue.h" -#if KRAKEN_JSC_ENGINE -#include "bindings/jsc/KOM/performance.h" -#elif KRAKEN_QUICK_JS_ENGINE +#include "foundation/ui_command_buffer.h" +#include "bindings/qjs/native_string.h" #include "page.h" -#endif - -#if KRAKEN_JSC_ENGINE -#include "bridge_jsc.h" -#endif - -#include -#include #if defined(_WIN32) #define SYSTEM_NAME "windows" // Windows @@ -49,20 +42,6 @@ std::atomic inited{false}; std::atomic poolIndex{0}; int maxPoolSize = 0; -NativeScreen screen; - -std::thread::id uiThreadId; - -std::thread::id getUIThreadId() { - return uiThreadId; -} - -void printError(int32_t contextId, const char* errmsg) { - if (kraken::getDartMethod()->onJsError != nullptr) { - kraken::getDartMethod()->onJsError(contextId, errmsg); - } - KRAKEN_LOG(ERROR) << errmsg << std::endl; -} namespace { @@ -86,7 +65,6 @@ int32_t searchForAvailableContextId() { } // namespace void initJSPagePool(int poolSize) { - uiThreadId = std::this_thread::get_id(); // When dart hot restarted, should dispose previous bridge and clear task message queue. if (inited) { disposeAllPages(); @@ -96,7 +74,7 @@ void initJSPagePool(int poolSize) { kraken::KrakenPage::pageContextPool[i] = nullptr; } - kraken::KrakenPage::pageContextPool[0] = new kraken::KrakenPage(0, printError); + kraken::KrakenPage::pageContextPool[0] = new kraken::KrakenPage(0, nullptr); inited = true; maxPoolSize = poolSize; } @@ -122,7 +100,7 @@ int32_t allocateNewPage(int32_t targetContextId) { assert(kraken::KrakenPage::pageContextPool[targetContextId] == nullptr && (std::string("can not allocate page at index") + std::to_string(targetContextId) + std::string(": page have already exist.")).c_str()); - auto* page = new kraken::KrakenPage(targetContextId, printError); + auto* page = new kraken::KrakenPage(targetContextId, nullptr); kraken::KrakenPage::pageContextPool[targetContextId] = page; return targetContextId; } @@ -144,7 +122,7 @@ bool checkPage(int32_t contextId, void* context) { return page->getContext() == context; } -void evaluateScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine) { +void evaluateScripts(int32_t contextId, kraken::NativeString* code, const char* bundleFilename, int startLine) { assert(checkPage(contextId) && "evaluateScripts: contextId is not valid"); auto context = static_cast(getPage(contextId)); context->evaluateScript(code, bundleFilename, startLine); @@ -166,25 +144,27 @@ void reloadJsContext(int32_t contextId) { assert(checkPage(contextId) && "reloadJSContext: contextId is not valid"); auto bridgePtr = getPage(contextId); auto context = static_cast(bridgePtr); - auto newContext = new kraken::KrakenPage(contextId, printError); + auto newContext = new kraken::KrakenPage(contextId, nullptr); delete context; kraken::KrakenPage::pageContextPool[contextId] = newContext; } -void invokeModuleEvent(int32_t contextId, NativeString* moduleName, const char* eventType, void* event, NativeString* extra) { +void invokeModuleEvent(int32_t contextId, kraken::NativeString* moduleName, const char* eventType, void* event, kraken::NativeString* extra) { assert(checkPage(contextId) && "invokeEventListener: contextId is not valid"); auto context = static_cast(getPage(contextId)); context->invokeModuleEvent(moduleName, eventType, event, extra); } -void registerDartMethods(uint64_t* methodBytes, int32_t length) { - kraken::registerDartMethods(methodBytes, length); +void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length) { + assert(checkPage(contextId) && "registerDartMethods: contextId is not valid"); + auto context = static_cast(getPage(contextId)); + context->registerDartMethods(methodBytes, length); } NativeScreen* createScreen(double width, double height) { - screen.width = width; - screen.height = height; - return &screen; +// screen.width = width; +// screen.height = height; +// return &screen; } static KrakenInfo* krakenInfo{nullptr}; @@ -211,18 +191,14 @@ void dispatchUITask(int32_t contextId, void* context, void* callback) { } void flushUITask(int32_t contextId) { - foundation::UITaskQueue::instance(contextId)->flushTask(); + kraken::UITaskQueue::instance(contextId)->flushTask(); } void registerUITask(int32_t contextId, Task task, void* data) { - foundation::UITaskQueue::instance(contextId)->registerTask(task, data); + kraken::UITaskQueue::instance(contextId)->registerTask(task, data); }; -void flushUICommandCallback() { - foundation::UICommandCallbackQueue::instance()->flushCallbacks(); -} - -UICommandItem* getUICommandItems(int32_t contextId) { +void* getUICommandItems(int32_t contextId) { auto* page = static_cast(getPage(contextId)); if (page == nullptr) return nullptr; @@ -249,7 +225,7 @@ void registerContextDisposedCallbacks(int32_t contextId, Task task, void* data) } void registerPluginByteCode(uint8_t* bytes, int32_t length, const char* pluginName) { - kraken::KrakenPage::pluginByteCode[pluginName] = NativeByteCode{bytes, length}; + kraken::KrakenPage::pluginByteCode[pluginName] = kraken::NativeByteCode{bytes, length}; } int32_t profileModeEnabled() { diff --git a/bridge/page.cc b/bridge/page.cc index 445e16b20d..0274e4bf37 100644 --- a/bridge/page.cc +++ b/bridge/page.cc @@ -3,11 +3,11 @@ * Author: Kraken Team. */ -#include "polyfill.h" - #include + +#include "foundation/logging.h" +#include "polyfill.h" #include "bindings/qjs/qjs_patch.h" -#include "dart_methods.h" #include "page.h" #include "bindings/qjs/bom/blob.h" @@ -53,11 +53,16 @@ ConsoleMessageHandler KrakenPage::consoleMessageHandler{nullptr}; kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; -KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId) { +KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { #if ENABLE_PROFILE auto jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); #endif - m_context = new ExecutionContext(contextId, handler, this); + m_context = new ExecutionContext(contextId, [this](int32_t contextId, const char* message) { + if (m_context->dartMethodPtr()->onJsError != nullptr) { + m_context->dartMethodPtr()->onJsError(contextId, message); + } + KRAKEN_LOG(ERROR) << message << std::endl; + }, this); #if ENABLE_PROFILE auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; @@ -127,7 +132,7 @@ bool KrakenPage::parseHTML(const char* code, size_t length) { return true; } -void KrakenPage::invokeModuleEvent(NativeString* moduleName, const char* eventType, void* ptr, NativeString* extra) { +void KrakenPage::invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* ptr, NativeString* extra) { if (!m_context->isValid()) return; @@ -208,6 +213,38 @@ void KrakenPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { m_context->evaluateByteCode(bytes, byteLength); } +void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { + size_t i = 0; + + auto& dartMethodPointer = m_context->dartMethodPtr(); + + dartMethodPointer->invokeModule = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->requestBatchUpdate = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->reloadApp = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->setTimeout = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->setInterval = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->clearTimeout = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->requestAnimationFrame = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->cancelAnimationFrame = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->getScreen = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->devicePixelRatio = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->platformBrightness = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->toBlob = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->flushUICommand = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->initWindow = reinterpret_cast(methodBytes[i++]); + dartMethodPointer->initDocument = reinterpret_cast(methodBytes[i++]); + +#if ENABLE_PROFILE + methodPointer->getPerformanceEntries = reinterpret_cast(methodBytes[i++]); +#else + i++; +#endif + + dartMethodPointer->onJsError = reinterpret_cast(methodBytes[i++]); + + assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); +} + KrakenPage::~KrakenPage() { #if IS_TEST if (disposeCallback != nullptr) { diff --git a/bridge/page.h b/bridge/page.h index f41a53e5f7..d979b5b322 100644 --- a/bridge/page.h +++ b/bridge/page.h @@ -7,18 +7,25 @@ #define KRAKEN_JS_QJS_BRIDGE_H_ #include -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/html_parser.h" -#include "include/kraken_bridge.h" - #include #include #include +#include + +#include "bindings/qjs/executing_context.h" +#include "bindings/qjs/html_parser.h" +#include "bindings/qjs/native_string.h" namespace kraken { +struct NativeByteCode { + uint8_t* bytes; + int32_t length; +}; + class KrakenPage; using JSBridgeDisposeCallback = void (*)(KrakenPage* bridge); +using ConsoleMessageHandler = std::function; /// KrakenPage is class which manage all js objects create by flutter widget. /// Every flutter widgets have a corresponding KrakenPage, and all objects created by JavaScript are stored here, @@ -43,9 +50,11 @@ class KrakenPage final { uint8_t* dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength); void evaluateByteCode(uint8_t* bytes, size_t byteLength); - [[nodiscard]] kraken::binding::qjs::ExecutionContext* getContext() const { return m_context; } + void registerDartMethods(uint64_t* methodBytes, int32_t length); + + [[nodiscard]] ExecutionContext* getContext() const { return m_context; } - void invokeModuleEvent(NativeString* moduleName, const char* eventType, void* event, NativeString* extra); + void invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* event, NativeString* extra); void reportError(const char* errmsg); int32_t contextId; @@ -55,9 +64,10 @@ class KrakenPage final { JSBridgeDisposeCallback disposeCallback{nullptr}; #endif private: + const std::thread::id ownerThreadId; // 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; + ExecutionContext* m_context; JSExceptionHandler m_handler; }; diff --git a/bridge/page_test.cc b/bridge/page_test.cc index 4ced535a7e..b5c1fb7164 100644 --- a/bridge/page_test.cc +++ b/bridge/page_test.cc @@ -281,4 +281,18 @@ void KrakenPageTest::invokeExecuteTest(ExecuteCallback executeCallback) { executeTestCallback = JS_NULL; } +void KrakenPageTest::registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { + size_t i = 0; + + auto& dartMethodPtr = m_page_context->dartMethodPtr(); + + dartMethodPtr->onJsError = reinterpret_cast(methodBytes[i++]); + dartMethodPtr->matchImageSnapshot = reinterpret_cast(methodBytes[i++]); + dartMethodPtr->environment = reinterpret_cast(methodBytes[i++]); + dartMethodPtr->simulatePointer = reinterpret_cast(methodBytes[i++]); + dartMethodPtr->simulateInputText = reinterpret_cast(methodBytes[i++]); + + assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); +} + } // namespace kraken diff --git a/bridge/page_test.h b/bridge/page_test.h index 6e359083bf..b19761bd4b 100644 --- a/bridge/page_test.h +++ b/bridge/page_test.h @@ -15,7 +15,7 @@ namespace kraken { struct ImageSnapShotContext { JSValue callback; - binding::qjs::ExecutionContext* context; + ExecutionContext* context; list_head link; }; @@ -45,6 +45,7 @@ class KrakenPageTest final { 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); JSValue executeTestCallback{JS_NULL}; JSValue executeTestProxyObject{JS_NULL}; @@ -54,7 +55,7 @@ class KrakenPageTest final { /// the pointer of bridge, ownership belongs to JSBridge KrakenPage* m_page; /// the pointer of JSContext, overship belongs to JSContext - binding::qjs::ExecutionContext* m_page_context; + ExecutionContext* m_page_context; }; } // namespace kraken diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index a20332eb50..49869d0768 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -8,7 +8,6 @@ #include #include "bindings/qjs/dom/event_target.h" #include "dart_methods.h" -#include "include/kraken_bridge.h" #include "kraken_bridge_test.h" #include "page.h" From 5c1dd1bc3befe885c1db91318662eb56c3a165f6 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Sat, 29 Jan 2022 17:14:04 +0800 Subject: [PATCH 009/498] chore: reorganize file structure. --- bridge/CMakeLists.txt | 20 +++- bridge/bindings/qjs/dom/all_collection.cc | 79 ------------- bridge/bindings/qjs/dom/all_collection.h | 32 ------ .../qjs/dom/elements/image_element.cc | 108 ------------------ .../bindings/qjs/dom/elements/image_element.h | 48 -------- .../css/css_style_declaration.cc} | 2 +- .../css/css_style_declaration.h} | 6 +- bridge/core/dom/character_data.cc | 6 + bridge/core/dom/character_data.h | 11 ++ .../comment_node.cc => core/dom/comment.cc} | 2 +- .../dom/comment_node.h => core/dom/comment.h} | 6 +- bridge/{bindings/qjs => core}/dom/document.cc | 0 bridge/{bindings/qjs => core}/dom/document.h | 2 +- .../qjs => core}/dom/document_fragment.cc | 0 .../qjs => core}/dom/document_fragment.h | 0 .../qjs => core}/dom/document_test.cc | 0 bridge/{bindings/qjs => core}/dom/element.cc | 0 bridge/{bindings/qjs => core}/dom/element.h | 0 .../qjs => core}/dom/element_test.cc | 0 bridge/core/dom/events/close_event.cc | 5 + .../qjs => core}/dom/events/close_event.d.ts | 0 bridge/core/dom/events/close_event.h | 10 ++ .../dom => core/dom/events}/custom_event.cc | 0 .../dom => core/dom/events}/custom_event.h | 0 .../dom/events}/custom_event_test.cc | 0 .../qjs/dom => core/dom/events}/event.cc | 0 .../qjs/dom => core/dom/events}/event.h | 0 .../dom/events}/event_listener_map.cc | 0 .../dom/events}/event_listener_map.h | 0 .../dom => core/dom/events}/event_target.cc | 0 .../dom => core/dom/events}/event_target.h | 4 +- .../dom/events}/event_target_test.cc | 0 .../qjs/dom => core/dom/events}/event_test.cc | 0 .../dom/events/gesture_event.d.ts | 0 bridge/core/dom/events/input_event.cc | 5 + .../qjs => core}/dom/events/input_event.d.ts | 0 bridge/core/dom/events/input_event.h | 10 ++ .../dom/events/intersection_change_event.cc | 5 + .../events/intersection_change_event.d.ts} | 0 .../dom/events/intersection_change_event.h | 10 ++ .../qjs => core}/dom/events/touch_event.cc | 0 .../qjs => core}/dom/events/touch_event.h | 0 .../dom/frame_request_callback_collection.cc | 0 .../dom/frame_request_callback_collection.h | 0 bridge/{bindings/qjs => core}/dom/node.cc | 2 +- bridge/{bindings/qjs => core}/dom/node.h | 0 .../{bindings/qjs => core}/dom/node_test.cc | 0 .../dom/scripted_animation_controller.cc} | 2 +- .../dom/scripted_animation_controller.h} | 0 .../{bindings/qjs => core}/dom/text_node.cc | 0 bridge/{bindings/qjs => core}/dom/text_node.h | 0 .../qjs => core}/dom/text_node_test.cc | 0 .../qjs/bom => core/fileapi}/blob.cc | 0 .../{bindings/qjs/bom => core/fileapi}/blob.h | 0 .../qjs/bom => core/frame}/console.cc | 0 .../qjs/bom => core/frame}/console.h | 0 .../qjs/bom => core/frame}/console_test.cc | 0 .../frame}/dom_timer_coordinator.cc | 0 .../frame}/dom_timer_coordinator.h | 0 .../qjs/bom => core/frame}/location.cc | 0 .../qjs/bom => core/frame}/location.h | 0 .../qjs => core/frame}/module_manager.cc | 0 .../qjs => core/frame}/module_manager.h | 0 .../qjs => core/frame}/module_manager_test.cc | 0 .../qjs/bom => core/frame}/screen.cc | 0 .../{bindings/qjs/bom => core/frame}/screen.h | 0 .../{bindings/qjs/bom => core/frame}/timer.cc | 0 .../{bindings/qjs/bom => core/frame}/timer.h | 0 .../qjs/bom => core/frame}/timer_test.cc | 0 .../qjs/bom => core/frame}/window.cc | 0 .../{bindings/qjs/bom => core/frame}/window.h | 0 .../qjs/bom => core/frame}/window_test.cc | 0 bridge/core/html/html_all_collection.cc | 79 +++++++++++++ bridge/core/html/html_all_collection.h | 31 +++++ bridge/core/html/html_anchor_element.cc | 5 + .../html/html_anchor_element.d.ts} | 0 bridge/core/html/html_anchor_element.h | 10 ++ bridge/core/html/html_canvas_element.cc | 5 + .../html/html_canvas_element.d.ts} | 0 bridge/core/html/html_canvas_element.h | 10 ++ bridge/core/html/html_image_element.cc | 108 ++++++++++++++++++ bridge/core/html/html_image_element.h | 48 ++++++++ bridge/core/html/html_input_element.cc | 5 + .../html/html_input_element.d.ts} | 0 bridge/core/html/html_input_element.h | 10 ++ bridge/core/html/html_object_element.cc | 5 + .../html/html_object_element.d.ts} | 0 bridge/core/html/html_object_element.h | 10 ++ .../qjs => core/html}/html_parser.cc | 0 .../{bindings/qjs => core/html}/html_parser.h | 0 bridge/core/html/html_script_element.cc | 5 + .../html/html_script_element.d.ts} | 0 bridge/core/html/html_script_element.h | 10 ++ .../html/html_template_element.cc} | 2 +- .../html/html_template_element.h} | 4 +- .../qjs/bom => core/timing}/performance.cc | 0 .../qjs/bom => core/timing}/performance.h | 0 97 files changed, 437 insertions(+), 285 deletions(-) delete mode 100644 bridge/bindings/qjs/dom/all_collection.cc delete mode 100644 bridge/bindings/qjs/dom/all_collection.h delete mode 100644 bridge/bindings/qjs/dom/elements/image_element.cc delete mode 100644 bridge/bindings/qjs/dom/elements/image_element.h rename bridge/{bindings/qjs/dom/style_declaration.cc => core/css/css_style_declaration.cc} (99%) rename bridge/{bindings/qjs/dom/style_declaration.h => core/css/css_style_declaration.h} (92%) create mode 100644 bridge/core/dom/character_data.cc create mode 100644 bridge/core/dom/character_data.h rename bridge/{bindings/qjs/dom/comment_node.cc => core/dom/comment.cc} (98%) rename bridge/{bindings/qjs/dom/comment_node.h => core/dom/comment.h} (92%) rename bridge/{bindings/qjs => core}/dom/document.cc (100%) rename bridge/{bindings/qjs => core}/dom/document.h (98%) rename bridge/{bindings/qjs => core}/dom/document_fragment.cc (100%) rename bridge/{bindings/qjs => core}/dom/document_fragment.h (100%) rename bridge/{bindings/qjs => core}/dom/document_test.cc (100%) rename bridge/{bindings/qjs => core}/dom/element.cc (100%) rename bridge/{bindings/qjs => core}/dom/element.h (100%) rename bridge/{bindings/qjs => core}/dom/element_test.cc (100%) create mode 100644 bridge/core/dom/events/close_event.cc rename bridge/{bindings/qjs => core}/dom/events/close_event.d.ts (100%) create mode 100644 bridge/core/dom/events/close_event.h rename bridge/{bindings/qjs/dom => core/dom/events}/custom_event.cc (100%) rename bridge/{bindings/qjs/dom => core/dom/events}/custom_event.h (100%) rename bridge/{bindings/qjs/dom => core/dom/events}/custom_event_test.cc (100%) rename bridge/{bindings/qjs/dom => core/dom/events}/event.cc (100%) rename bridge/{bindings/qjs/dom => core/dom/events}/event.h (100%) rename bridge/{bindings/qjs/dom => core/dom/events}/event_listener_map.cc (100%) rename bridge/{bindings/qjs/dom => core/dom/events}/event_listener_map.h (100%) rename bridge/{bindings/qjs/dom => core/dom/events}/event_target.cc (100%) rename bridge/{bindings/qjs/dom => core/dom/events}/event_target.h (98%) rename bridge/{bindings/qjs/dom => core/dom/events}/event_target_test.cc (100%) rename bridge/{bindings/qjs/dom => core/dom/events}/event_test.cc (100%) rename bridge/{bindings/qjs => core}/dom/events/gesture_event.d.ts (100%) create mode 100644 bridge/core/dom/events/input_event.cc rename bridge/{bindings/qjs => core}/dom/events/input_event.d.ts (100%) create mode 100644 bridge/core/dom/events/input_event.h create mode 100644 bridge/core/dom/events/intersection_change_event.cc rename bridge/{bindings/qjs/dom/events/intersection_change.d.ts => core/dom/events/intersection_change_event.d.ts} (100%) create mode 100644 bridge/core/dom/events/intersection_change_event.h rename bridge/{bindings/qjs => core}/dom/events/touch_event.cc (100%) rename bridge/{bindings/qjs => core}/dom/events/touch_event.h (100%) rename bridge/{bindings/qjs => core}/dom/frame_request_callback_collection.cc (100%) rename bridge/{bindings/qjs => core}/dom/frame_request_callback_collection.h (100%) rename bridge/{bindings/qjs => core}/dom/node.cc (99%) rename bridge/{bindings/qjs => core}/dom/node.h (100%) rename bridge/{bindings/qjs => core}/dom/node_test.cc (100%) rename bridge/{bindings/qjs/dom/script_animation_controller.cc => core/dom/scripted_animation_controller.cc} (98%) rename bridge/{bindings/qjs/dom/script_animation_controller.h => core/dom/scripted_animation_controller.h} (100%) rename bridge/{bindings/qjs => core}/dom/text_node.cc (100%) rename bridge/{bindings/qjs => core}/dom/text_node.h (100%) rename bridge/{bindings/qjs => core}/dom/text_node_test.cc (100%) rename bridge/{bindings/qjs/bom => core/fileapi}/blob.cc (100%) rename bridge/{bindings/qjs/bom => core/fileapi}/blob.h (100%) rename bridge/{bindings/qjs/bom => core/frame}/console.cc (100%) rename bridge/{bindings/qjs/bom => core/frame}/console.h (100%) rename bridge/{bindings/qjs/bom => core/frame}/console_test.cc (100%) rename bridge/{bindings/qjs/bom => core/frame}/dom_timer_coordinator.cc (100%) rename bridge/{bindings/qjs/bom => core/frame}/dom_timer_coordinator.h (100%) rename bridge/{bindings/qjs/bom => core/frame}/location.cc (100%) rename bridge/{bindings/qjs/bom => core/frame}/location.h (100%) rename bridge/{bindings/qjs => core/frame}/module_manager.cc (100%) rename bridge/{bindings/qjs => core/frame}/module_manager.h (100%) rename bridge/{bindings/qjs => core/frame}/module_manager_test.cc (100%) rename bridge/{bindings/qjs/bom => core/frame}/screen.cc (100%) rename bridge/{bindings/qjs/bom => core/frame}/screen.h (100%) rename bridge/{bindings/qjs/bom => core/frame}/timer.cc (100%) rename bridge/{bindings/qjs/bom => core/frame}/timer.h (100%) rename bridge/{bindings/qjs/bom => core/frame}/timer_test.cc (100%) rename bridge/{bindings/qjs/bom => core/frame}/window.cc (100%) rename bridge/{bindings/qjs/bom => core/frame}/window.h (100%) rename bridge/{bindings/qjs/bom => core/frame}/window_test.cc (100%) create mode 100644 bridge/core/html/html_all_collection.cc create mode 100644 bridge/core/html/html_all_collection.h create mode 100644 bridge/core/html/html_anchor_element.cc rename bridge/{bindings/qjs/dom/elements/anchor_element.d.ts => core/html/html_anchor_element.d.ts} (100%) create mode 100644 bridge/core/html/html_anchor_element.h create mode 100644 bridge/core/html/html_canvas_element.cc rename bridge/{bindings/qjs/dom/elements/canvas_element.d.ts => core/html/html_canvas_element.d.ts} (100%) create mode 100644 bridge/core/html/html_canvas_element.h create mode 100644 bridge/core/html/html_image_element.cc create mode 100644 bridge/core/html/html_image_element.h create mode 100644 bridge/core/html/html_input_element.cc rename bridge/{bindings/qjs/dom/elements/input_element.d.ts => core/html/html_input_element.d.ts} (100%) create mode 100644 bridge/core/html/html_input_element.h create mode 100644 bridge/core/html/html_object_element.cc rename bridge/{bindings/qjs/dom/elements/object_element.d.ts => core/html/html_object_element.d.ts} (100%) create mode 100644 bridge/core/html/html_object_element.h rename bridge/{bindings/qjs => core/html}/html_parser.cc (100%) rename bridge/{bindings/qjs => core/html}/html_parser.h (100%) create mode 100644 bridge/core/html/html_script_element.cc rename bridge/{bindings/qjs/dom/elements/script_element.d.ts => core/html/html_script_element.d.ts} (100%) create mode 100644 bridge/core/html/html_script_element.h rename bridge/{bindings/qjs/dom/elements/template_element.cc => core/html/html_template_element.cc} (97%) rename bridge/{bindings/qjs/dom/elements/template_element.h => core/html/html_template_element.h} (93%) rename bridge/{bindings/qjs/bom => core/timing}/performance.cc (100%) rename bridge/{bindings/qjs/bom => core/timing}/performance.h (100%) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index a25a55c2fe..d1b5ec296e 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -83,7 +83,6 @@ endif() list(APPEND BRIDGE_SOURCE kraken_bridge.cc ${CMAKE_CURRENT_SOURCE_DIR}/include/kraken_bridge.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/dart_methods.h foundation/macros.h foundation/logging.cc foundation/logging.h @@ -103,7 +102,6 @@ list(APPEND BRIDGE_SOURCE foundation/ui_command_buffer.cc foundation/ui_command_buffer.h foundation/ui_command_callback_queue.cc - dart_methods.cc polyfill/dist/polyfill.cc ) @@ -198,6 +196,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/native_string.h bindings/qjs/qjs_patch.cc bindings/qjs/qjs_patch.h + bindings/qjs/dart_methods.cc + bindings/qjs/dart_methods.h bindings/qjs/module_manager.cc bindings/qjs/module_manager.h bindings/qjs/html_parser.cc @@ -278,6 +278,22 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/dom/custom_event.h bindings/qjs/dom/all_collection.cc bindings/qjs/dom/all_collection.h + + # Core sources + core/dom/character_data.cc + core/dom/character_data.h + core/dom/comment.cc + core/dom/comment.h + core/dom/node.cc + core/dom/node.h + core/dom/events/custom_event.cc + core/dom/events/custom_event.h + core/dom/events/event.h + core/dom/events/event.cc + core/dom/events/event_listener_map.cc + core/dom/events/event_listener_map.h + core/dom/events/event_target.cc + core/dom/events/event_target.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/dom/all_collection.cc b/bridge/bindings/qjs/dom/all_collection.cc deleted file mode 100644 index 5717b4e759..0000000000 --- a/bridge/bindings/qjs/dom/all_collection.cc +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "all_collection.h" - -namespace kraken { - -JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_NULL; - } - - uint32_t index; - JS_ToUint32(ctx, &index, argv[0]); - auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - - if (index >= collection->m_nodes.size()) { - return JS_NULL; - } - - auto node = collection->m_nodes[index]; - return node->jsObject; -} -JSValue AllCollection::add(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: 1 arguments required."); - } - - if (!JS_IsObject(argv[0])) { - return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: first arguments should be a object."); - } - - JSValue before = JS_NULL; - - if (argc == 2 && JS_IsObject(argv[1])) { - before = argv[1]; - } - - auto* node = static_cast(JS_GetOpaque(argv[0], ExecutionContext::kHostObjectClassId)); - auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - NodeInstance* beforeNode = nullptr; - - if (!JS_IsNull(before)) { - beforeNode = static_cast(JS_GetOpaque(before, ExecutionContext::kHostObjectClassId)); - } - - collection->internalAdd(node, beforeNode); - - return JS_NULL; -} -JSValue AllCollection::remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute remove() on HTMLAllCollection: 1 arguments required."); - } - - uint32_t index; - JS_ToUint32(ctx, &index, argv[0]); - auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - collection->m_nodes.erase(collection->m_nodes.begin() + index); - return JS_NULL; -} -void AllCollection::internalAdd(NodeInstance* node, NodeInstance* before) { - if (before != nullptr) { - auto it = std::find(m_nodes.begin(), m_nodes.end(), before); - m_nodes.erase(it); - m_nodes.insert(it, node); - } else { - m_nodes.emplace_back(node); - } -} - -IMPL_PROPERTY_GETTER(AllCollection, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - return JS_NewUint32(ctx, collection->m_nodes.size()); -} - -} // namespace kraken diff --git a/bridge/bindings/qjs/dom/all_collection.h b/bridge/bindings/qjs/dom/all_collection.h deleted file mode 100644 index c2fef3acd1..0000000000 --- a/bridge/bindings/qjs/dom/all_collection.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_ALL_COLLECTION_H -#define KRAKENBRIDGE_ALL_COLLECTION_H - -#include "bindings/qjs/host_object.h" -#include "node.h" - -namespace kraken { - -class AllCollection : public HostObject { - public: - AllCollection(ExecutionContext* context) : HostObject(context, "AllCollection"){}; - - static JSValue item(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue add(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue remove(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - DEFINE_READONLY_PROPERTY(length); - - void internalAdd(NodeInstance* node, NodeInstance* before); - - private: - std::vector m_nodes; -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_ALL_COLLECTION_H 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 d1859431f9..0000000000 --- a/bridge/bindings/qjs/dom/elements/image_element.cc +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "image_element.h" -#include "bindings/qjs/qjs_patch.h" -#include "page.h" - -namespace kraken { - -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->getNativeProperty("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::setProperty, *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->getNativeProperty("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::setProperty, *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->getNativeProperty("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->getNativeProperty("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->getNativeProperty("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::setProperty, *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->getNativeProperty("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::setProperty, *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->nativeEvent->type->string), event->nativeEvent->type->length); - std::string eventType = toUTF8(u16EventType); - bool result = EventTargetInstance::dispatchEvent(event); - - // Free image instance after load or error event triggered. - if ((eventType == "load" || eventType == "error") && !freed) { - freed = true; - unrefer(); - } - - return result; -} - -} // namespace kraken 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 2af8eed083..0000000000 --- a/bridge/bindings/qjs/dom/elements/image_element.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_IMAGE_ELEMENT_H -#define KRAKENBRIDGE_IMAGE_ELEMENT_H - -#include "bindings/qjs/dom/element.h" - -namespace kraken { - -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); - friend ImageElementInstance; -}; - -class ImageElementInstance : public ElementInstance { - public: - ImageElementInstance() = delete; - explicit ImageElementInstance(ImageElement* element); - bool dispatchEvent(EventInstance* event); - - private: - bool freed{false}; - friend ImageElement; -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_IMAGE_ELEMENTT_H diff --git a/bridge/bindings/qjs/dom/style_declaration.cc b/bridge/core/css/css_style_declaration.cc similarity index 99% rename from bridge/bindings/qjs/dom/style_declaration.cc rename to bridge/core/css/css_style_declaration.cc index 0201bb1e38..1fa355becc 100644 --- a/bridge/bindings/qjs/dom/style_declaration.cc +++ b/bridge/core/css/css_style_declaration.cc @@ -3,7 +3,7 @@ * Author: Kraken Team. */ -#include "style_declaration.h" +#include "css_style_declaration.h" #include "event_target.h" namespace kraken { diff --git a/bridge/bindings/qjs/dom/style_declaration.h b/bridge/core/css/css_style_declaration.h similarity index 92% rename from bridge/bindings/qjs/dom/style_declaration.h rename to bridge/core/css/css_style_declaration.h index 318d301e69..df1634fb23 100644 --- a/bridge/bindings/qjs/dom/style_declaration.h +++ b/bridge/core/css/css_style_declaration.h @@ -3,8 +3,8 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_STYLE_DECLARATION_H -#define KRAKENBRIDGE_STYLE_DECLARATION_H +#ifndef KRAKENBRIDGE_CSS_STYLE_DECLARATION_H +#define KRAKENBRIDGE_CSS_STYLE_DECLARATION_H #include "bindings/qjs/context_macros.h" #include "bindings/qjs/dom/event_target.h" @@ -53,4 +53,4 @@ class CSSStyleDeclaration : public GarbageCollected { } // namespace kraken -#endif // KRAKENBRIDGE_STYLE_DECLARATION_H +#endif // KRAKENBRIDGE_CSS_STYLE_DECLARATION_H diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc new file mode 100644 index 0000000000..9786018ea0 --- /dev/null +++ b/bridge/core/dom/character_data.cc @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "character_data.h" diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h new file mode 100644 index 0000000000..f67ac8c03b --- /dev/null +++ b/bridge/core/dom/character_data.h @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CHARACTER_DATA_H +#define KRAKENBRIDGE_CHARACTER_DATA_H + +class character_data {}; + +#endif // KRAKENBRIDGE_CHARACTER_DATA_H diff --git a/bridge/bindings/qjs/dom/comment_node.cc b/bridge/core/dom/comment.cc similarity index 98% rename from bridge/bindings/qjs/dom/comment_node.cc rename to bridge/core/dom/comment.cc index d2b6a705f7..11a90c7a8d 100644 --- a/bridge/bindings/qjs/dom/comment_node.cc +++ b/bridge/core/dom/comment.cc @@ -3,7 +3,7 @@ * Author: Kraken Team. */ -#include "comment_node.h" +#include "comment.h" #include "document.h" namespace kraken { diff --git a/bridge/bindings/qjs/dom/comment_node.h b/bridge/core/dom/comment.h similarity index 92% rename from bridge/bindings/qjs/dom/comment_node.h rename to bridge/core/dom/comment.h index cffd59accb..571c4f119e 100644 --- a/bridge/bindings/qjs/dom/comment_node.h +++ b/bridge/core/dom/comment.h @@ -3,8 +3,8 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_COMMENT_NODE_H -#define KRAKENBRIDGE_COMMENT_NODE_H +#ifndef KRAKENBRIDGE_COMMENT_H +#define KRAKENBRIDGE_COMMENT_H #include "node.h" @@ -52,4 +52,4 @@ const WrapperTypeInfo commentTypeInfo = {"Comment", &nodeTypeInfo, commentCreato } // namespace kraken -#endif // KRAKENBRIDGE_COMMENT_NODE_H +#endif // KRAKENBRIDGE_COMMENT_H diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/core/dom/document.cc similarity index 100% rename from bridge/bindings/qjs/dom/document.cc rename to bridge/core/dom/document.cc diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/core/dom/document.h similarity index 98% rename from bridge/bindings/qjs/dom/document.h rename to bridge/core/dom/document.h index bde60659c7..2d2b8bf5a9 100644 --- a/bridge/bindings/qjs/dom/document.h +++ b/bridge/core/dom/document.h @@ -9,7 +9,7 @@ #include "element.h" #include "frame_request_callback_collection.h" #include "node.h" -#include "script_animation_controller.h" +#include "scripted_animation_controller.h" namespace kraken { diff --git a/bridge/bindings/qjs/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc similarity index 100% rename from bridge/bindings/qjs/dom/document_fragment.cc rename to bridge/core/dom/document_fragment.cc diff --git a/bridge/bindings/qjs/dom/document_fragment.h b/bridge/core/dom/document_fragment.h similarity index 100% rename from bridge/bindings/qjs/dom/document_fragment.h rename to bridge/core/dom/document_fragment.h diff --git a/bridge/bindings/qjs/dom/document_test.cc b/bridge/core/dom/document_test.cc similarity index 100% rename from bridge/bindings/qjs/dom/document_test.cc rename to bridge/core/dom/document_test.cc diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/core/dom/element.cc similarity index 100% rename from bridge/bindings/qjs/dom/element.cc rename to bridge/core/dom/element.cc diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/core/dom/element.h similarity index 100% rename from bridge/bindings/qjs/dom/element.h rename to bridge/core/dom/element.h diff --git a/bridge/bindings/qjs/dom/element_test.cc b/bridge/core/dom/element_test.cc similarity index 100% rename from bridge/bindings/qjs/dom/element_test.cc rename to bridge/core/dom/element_test.cc diff --git a/bridge/core/dom/events/close_event.cc b/bridge/core/dom/events/close_event.cc new file mode 100644 index 0000000000..ddf72d20d0 --- /dev/null +++ b/bridge/core/dom/events/close_event.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "close_event.h" diff --git a/bridge/bindings/qjs/dom/events/close_event.d.ts b/bridge/core/dom/events/close_event.d.ts similarity index 100% rename from bridge/bindings/qjs/dom/events/close_event.d.ts rename to bridge/core/dom/events/close_event.d.ts diff --git a/bridge/core/dom/events/close_event.h b/bridge/core/dom/events/close_event.h new file mode 100644 index 0000000000..d9cf927807 --- /dev/null +++ b/bridge/core/dom/events/close_event.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_CLOSE_EVENT_H +#define KRAKENBRIDGE_CLOSE_EVENT_H + +class close_event {}; + +#endif // KRAKENBRIDGE_CLOSE_EVENT_H diff --git a/bridge/bindings/qjs/dom/custom_event.cc b/bridge/core/dom/events/custom_event.cc similarity index 100% rename from bridge/bindings/qjs/dom/custom_event.cc rename to bridge/core/dom/events/custom_event.cc diff --git a/bridge/bindings/qjs/dom/custom_event.h b/bridge/core/dom/events/custom_event.h similarity index 100% rename from bridge/bindings/qjs/dom/custom_event.h rename to bridge/core/dom/events/custom_event.h diff --git a/bridge/bindings/qjs/dom/custom_event_test.cc b/bridge/core/dom/events/custom_event_test.cc similarity index 100% rename from bridge/bindings/qjs/dom/custom_event_test.cc rename to bridge/core/dom/events/custom_event_test.cc diff --git a/bridge/bindings/qjs/dom/event.cc b/bridge/core/dom/events/event.cc similarity index 100% rename from bridge/bindings/qjs/dom/event.cc rename to bridge/core/dom/events/event.cc diff --git a/bridge/bindings/qjs/dom/event.h b/bridge/core/dom/events/event.h similarity index 100% rename from bridge/bindings/qjs/dom/event.h rename to bridge/core/dom/events/event.h diff --git a/bridge/bindings/qjs/dom/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc similarity index 100% rename from bridge/bindings/qjs/dom/event_listener_map.cc rename to bridge/core/dom/events/event_listener_map.cc diff --git a/bridge/bindings/qjs/dom/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h similarity index 100% rename from bridge/bindings/qjs/dom/event_listener_map.h rename to bridge/core/dom/events/event_listener_map.h diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/core/dom/events/event_target.cc similarity index 100% rename from bridge/bindings/qjs/dom/event_target.cc rename to bridge/core/dom/events/event_target.cc diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/core/dom/events/event_target.h similarity index 98% rename from bridge/bindings/qjs/dom/event_target.h rename to bridge/core/dom/events/event_target.h index e127f0497d..c190bd47c8 100644 --- a/bridge/bindings/qjs/dom/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -9,8 +9,8 @@ #include "bindings/qjs/context_macros.h" #include "bindings/qjs/executing_context.h" #include "bindings/qjs/heap_hashmap.h" -#include "bindings/qjs/native_value.h" -#include "event_listener_map.h" +//#include "bindings/qjs/native_value.h" +//#include "event_listener_map.h" #if UNIT_TEST void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); diff --git a/bridge/bindings/qjs/dom/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc similarity index 100% rename from bridge/bindings/qjs/dom/event_target_test.cc rename to bridge/core/dom/events/event_target_test.cc diff --git a/bridge/bindings/qjs/dom/event_test.cc b/bridge/core/dom/events/event_test.cc similarity index 100% rename from bridge/bindings/qjs/dom/event_test.cc rename to bridge/core/dom/events/event_test.cc diff --git a/bridge/bindings/qjs/dom/events/gesture_event.d.ts b/bridge/core/dom/events/gesture_event.d.ts similarity index 100% rename from bridge/bindings/qjs/dom/events/gesture_event.d.ts rename to bridge/core/dom/events/gesture_event.d.ts diff --git a/bridge/core/dom/events/input_event.cc b/bridge/core/dom/events/input_event.cc new file mode 100644 index 0000000000..eb80d75711 --- /dev/null +++ b/bridge/core/dom/events/input_event.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "input_event.h" diff --git a/bridge/bindings/qjs/dom/events/input_event.d.ts b/bridge/core/dom/events/input_event.d.ts similarity index 100% rename from bridge/bindings/qjs/dom/events/input_event.d.ts rename to bridge/core/dom/events/input_event.d.ts diff --git a/bridge/core/dom/events/input_event.h b/bridge/core/dom/events/input_event.h new file mode 100644 index 0000000000..ccdeba49bc --- /dev/null +++ b/bridge/core/dom/events/input_event.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_INPUT_EVENT_H +#define KRAKENBRIDGE_INPUT_EVENT_H + +class input_event {}; + +#endif // KRAKENBRIDGE_INPUT_EVENT_H diff --git a/bridge/core/dom/events/intersection_change_event.cc b/bridge/core/dom/events/intersection_change_event.cc new file mode 100644 index 0000000000..b18d9998ac --- /dev/null +++ b/bridge/core/dom/events/intersection_change_event.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "intersection_change_event.h" diff --git a/bridge/bindings/qjs/dom/events/intersection_change.d.ts b/bridge/core/dom/events/intersection_change_event.d.ts similarity index 100% rename from bridge/bindings/qjs/dom/events/intersection_change.d.ts rename to bridge/core/dom/events/intersection_change_event.d.ts diff --git a/bridge/core/dom/events/intersection_change_event.h b/bridge/core/dom/events/intersection_change_event.h new file mode 100644 index 0000000000..40e732b1da --- /dev/null +++ b/bridge/core/dom/events/intersection_change_event.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_INTERSECTION_CHANGE_EVENT_H +#define KRAKENBRIDGE_INTERSECTION_CHANGE_EVENT_H + +class intersection_change_event {}; + +#endif // KRAKENBRIDGE_INTERSECTION_CHANGE_EVENT_H diff --git a/bridge/bindings/qjs/dom/events/touch_event.cc b/bridge/core/dom/events/touch_event.cc similarity index 100% rename from bridge/bindings/qjs/dom/events/touch_event.cc rename to bridge/core/dom/events/touch_event.cc diff --git a/bridge/bindings/qjs/dom/events/touch_event.h b/bridge/core/dom/events/touch_event.h similarity index 100% rename from bridge/bindings/qjs/dom/events/touch_event.h rename to bridge/core/dom/events/touch_event.h diff --git a/bridge/bindings/qjs/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc similarity index 100% rename from bridge/bindings/qjs/dom/frame_request_callback_collection.cc rename to bridge/core/dom/frame_request_callback_collection.cc diff --git a/bridge/bindings/qjs/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h similarity index 100% rename from bridge/bindings/qjs/dom/frame_request_callback_collection.h rename to bridge/core/dom/frame_request_callback_collection.h diff --git a/bridge/bindings/qjs/dom/node.cc b/bridge/core/dom/node.cc similarity index 99% rename from bridge/bindings/qjs/dom/node.cc rename to bridge/core/dom/node.cc index 8aa0ac6a6e..1e5fbfc485 100644 --- a/bridge/bindings/qjs/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -5,7 +5,7 @@ #include "node.h" #include "bindings/qjs/qjs_patch.h" -#include "comment_node.h" +#include "comment.h" #include "document.h" #include "document_fragment.h" #include "element.h" diff --git a/bridge/bindings/qjs/dom/node.h b/bridge/core/dom/node.h similarity index 100% rename from bridge/bindings/qjs/dom/node.h rename to bridge/core/dom/node.h diff --git a/bridge/bindings/qjs/dom/node_test.cc b/bridge/core/dom/node_test.cc similarity index 100% rename from bridge/bindings/qjs/dom/node_test.cc rename to bridge/core/dom/node_test.cc diff --git a/bridge/bindings/qjs/dom/script_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc similarity index 98% rename from bridge/bindings/qjs/dom/script_animation_controller.cc rename to bridge/core/dom/scripted_animation_controller.cc index fd21563e52..7aa2bfa310 100644 --- a/bridge/bindings/qjs/dom/script_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -3,7 +3,7 @@ * Author: Kraken Team. */ -#include "script_animation_controller.h" +#include "scripted_animation_controller.h" #include "dart_methods.h" #include "frame_request_callback_collection.h" diff --git a/bridge/bindings/qjs/dom/script_animation_controller.h b/bridge/core/dom/scripted_animation_controller.h similarity index 100% rename from bridge/bindings/qjs/dom/script_animation_controller.h rename to bridge/core/dom/scripted_animation_controller.h diff --git a/bridge/bindings/qjs/dom/text_node.cc b/bridge/core/dom/text_node.cc similarity index 100% rename from bridge/bindings/qjs/dom/text_node.cc rename to bridge/core/dom/text_node.cc diff --git a/bridge/bindings/qjs/dom/text_node.h b/bridge/core/dom/text_node.h similarity index 100% rename from bridge/bindings/qjs/dom/text_node.h rename to bridge/core/dom/text_node.h diff --git a/bridge/bindings/qjs/dom/text_node_test.cc b/bridge/core/dom/text_node_test.cc similarity index 100% rename from bridge/bindings/qjs/dom/text_node_test.cc rename to bridge/core/dom/text_node_test.cc diff --git a/bridge/bindings/qjs/bom/blob.cc b/bridge/core/fileapi/blob.cc similarity index 100% rename from bridge/bindings/qjs/bom/blob.cc rename to bridge/core/fileapi/blob.cc diff --git a/bridge/bindings/qjs/bom/blob.h b/bridge/core/fileapi/blob.h similarity index 100% rename from bridge/bindings/qjs/bom/blob.h rename to bridge/core/fileapi/blob.h diff --git a/bridge/bindings/qjs/bom/console.cc b/bridge/core/frame/console.cc similarity index 100% rename from bridge/bindings/qjs/bom/console.cc rename to bridge/core/frame/console.cc diff --git a/bridge/bindings/qjs/bom/console.h b/bridge/core/frame/console.h similarity index 100% rename from bridge/bindings/qjs/bom/console.h rename to bridge/core/frame/console.h diff --git a/bridge/bindings/qjs/bom/console_test.cc b/bridge/core/frame/console_test.cc similarity index 100% rename from bridge/bindings/qjs/bom/console_test.cc rename to bridge/core/frame/console_test.cc diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc similarity index 100% rename from bridge/bindings/qjs/bom/dom_timer_coordinator.cc rename to bridge/core/frame/dom_timer_coordinator.cc diff --git a/bridge/bindings/qjs/bom/dom_timer_coordinator.h b/bridge/core/frame/dom_timer_coordinator.h similarity index 100% rename from bridge/bindings/qjs/bom/dom_timer_coordinator.h rename to bridge/core/frame/dom_timer_coordinator.h diff --git a/bridge/bindings/qjs/bom/location.cc b/bridge/core/frame/location.cc similarity index 100% rename from bridge/bindings/qjs/bom/location.cc rename to bridge/core/frame/location.cc diff --git a/bridge/bindings/qjs/bom/location.h b/bridge/core/frame/location.h similarity index 100% rename from bridge/bindings/qjs/bom/location.h rename to bridge/core/frame/location.h diff --git a/bridge/bindings/qjs/module_manager.cc b/bridge/core/frame/module_manager.cc similarity index 100% rename from bridge/bindings/qjs/module_manager.cc rename to bridge/core/frame/module_manager.cc diff --git a/bridge/bindings/qjs/module_manager.h b/bridge/core/frame/module_manager.h similarity index 100% rename from bridge/bindings/qjs/module_manager.h rename to bridge/core/frame/module_manager.h diff --git a/bridge/bindings/qjs/module_manager_test.cc b/bridge/core/frame/module_manager_test.cc similarity index 100% rename from bridge/bindings/qjs/module_manager_test.cc rename to bridge/core/frame/module_manager_test.cc diff --git a/bridge/bindings/qjs/bom/screen.cc b/bridge/core/frame/screen.cc similarity index 100% rename from bridge/bindings/qjs/bom/screen.cc rename to bridge/core/frame/screen.cc diff --git a/bridge/bindings/qjs/bom/screen.h b/bridge/core/frame/screen.h similarity index 100% rename from bridge/bindings/qjs/bom/screen.h rename to bridge/core/frame/screen.h diff --git a/bridge/bindings/qjs/bom/timer.cc b/bridge/core/frame/timer.cc similarity index 100% rename from bridge/bindings/qjs/bom/timer.cc rename to bridge/core/frame/timer.cc diff --git a/bridge/bindings/qjs/bom/timer.h b/bridge/core/frame/timer.h similarity index 100% rename from bridge/bindings/qjs/bom/timer.h rename to bridge/core/frame/timer.h diff --git a/bridge/bindings/qjs/bom/timer_test.cc b/bridge/core/frame/timer_test.cc similarity index 100% rename from bridge/bindings/qjs/bom/timer_test.cc rename to bridge/core/frame/timer_test.cc diff --git a/bridge/bindings/qjs/bom/window.cc b/bridge/core/frame/window.cc similarity index 100% rename from bridge/bindings/qjs/bom/window.cc rename to bridge/core/frame/window.cc diff --git a/bridge/bindings/qjs/bom/window.h b/bridge/core/frame/window.h similarity index 100% rename from bridge/bindings/qjs/bom/window.h rename to bridge/core/frame/window.h diff --git a/bridge/bindings/qjs/bom/window_test.cc b/bridge/core/frame/window_test.cc similarity index 100% rename from bridge/bindings/qjs/bom/window_test.cc rename to bridge/core/frame/window_test.cc diff --git a/bridge/core/html/html_all_collection.cc b/bridge/core/html/html_all_collection.cc new file mode 100644 index 0000000000..98318d2d82 --- /dev/null +++ b/bridge/core/html/html_all_collection.cc @@ -0,0 +1,79 @@ +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#include "html_all_collection.h" +// +//namespace kraken { +// +//JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// if (argc < 1) { +// return JS_NULL; +// } +// +// uint32_t index; +// JS_ToUint32(ctx, &index, argv[0]); +// auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// +// if (index >= collection->m_nodes.size()) { +// return JS_NULL; +// } +// +// auto node = collection->m_nodes[index]; +// return node->jsObject; +//} +//JSValue AllCollection::add(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// if (argc < 1) { +// return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: 1 arguments required."); +// } +// +// if (!JS_IsObject(argv[0])) { +// return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: first arguments should be a object."); +// } +// +// JSValue before = JS_NULL; +// +// if (argc == 2 && JS_IsObject(argv[1])) { +// before = argv[1]; +// } +// +// auto* node = static_cast(JS_GetOpaque(argv[0], ExecutionContext::kHostObjectClassId)); +// auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// NodeInstance* beforeNode = nullptr; +// +// if (!JS_IsNull(before)) { +// beforeNode = static_cast(JS_GetOpaque(before, ExecutionContext::kHostObjectClassId)); +// } +// +// collection->internalAdd(node, beforeNode); +// +// return JS_NULL; +//} +//JSValue AllCollection::remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// if (argc < 1) { +// return JS_ThrowTypeError(ctx, "Failed to execute remove() on HTMLAllCollection: 1 arguments required."); +// } +// +// uint32_t index; +// JS_ToUint32(ctx, &index, argv[0]); +// auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// collection->m_nodes.erase(collection->m_nodes.begin() + index); +// return JS_NULL; +//} +//void AllCollection::internalAdd(NodeInstance* node, NodeInstance* before) { +// if (before != nullptr) { +// auto it = std::find(m_nodes.begin(), m_nodes.end(), before); +// m_nodes.erase(it); +// m_nodes.insert(it, node); +// } else { +// m_nodes.emplace_back(node); +// } +//} +// +//IMPL_PROPERTY_GETTER(AllCollection, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* collection = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// return JS_NewUint32(ctx, collection->m_nodes.size()); +//} +// +//} // namespace kraken diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h new file mode 100644 index 0000000000..9946cd154a --- /dev/null +++ b/bridge/core/html/html_all_collection.h @@ -0,0 +1,31 @@ +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#ifndef KRAKENBRIDGE_HTML_ALL_COLLECTION_H +//#define KRAKENBRIDGE_HTML_ALL_COLLECTION_H +// +//#include "bindings/qjs/garbage_collected.h" +// +//namespace kraken { +// +//class HTMLAllCollection : public HostObject { +// public: +// AllCollection(ExecutionContext* context) : HostObject(context, "AllCollection"){}; +// +// static JSValue item(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); +// static JSValue add(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); +// static JSValue remove(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); +// +// DEFINE_READONLY_PROPERTY(length); +// +// void internalAdd(NodeInstance* node, NodeInstance* before); +// +// private: +// std::vector m_nodes; +//}; +// +//} // namespace kraken +// +//#endif // KRAKENBRIDGE_HTML_ALL_COLLECTION_H diff --git a/bridge/core/html/html_anchor_element.cc b/bridge/core/html/html_anchor_element.cc new file mode 100644 index 0000000000..4cb0181231 --- /dev/null +++ b/bridge/core/html/html_anchor_element.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "html_anchor_element.h" diff --git a/bridge/bindings/qjs/dom/elements/anchor_element.d.ts b/bridge/core/html/html_anchor_element.d.ts similarity index 100% rename from bridge/bindings/qjs/dom/elements/anchor_element.d.ts rename to bridge/core/html/html_anchor_element.d.ts diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h new file mode 100644 index 0000000000..5060a7b75c --- /dev/null +++ b/bridge/core/html/html_anchor_element.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H +#define KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H + +class html_anchor_element {}; + +#endif // KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H diff --git a/bridge/core/html/html_canvas_element.cc b/bridge/core/html/html_canvas_element.cc new file mode 100644 index 0000000000..f67320f440 --- /dev/null +++ b/bridge/core/html/html_canvas_element.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "html_canvas_element.h" diff --git a/bridge/bindings/qjs/dom/elements/canvas_element.d.ts b/bridge/core/html/html_canvas_element.d.ts similarity index 100% rename from bridge/bindings/qjs/dom/elements/canvas_element.d.ts rename to bridge/core/html/html_canvas_element.d.ts diff --git a/bridge/core/html/html_canvas_element.h b/bridge/core/html/html_canvas_element.h new file mode 100644 index 0000000000..c27cb8fdb2 --- /dev/null +++ b/bridge/core/html/html_canvas_element.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_HTML_CANVAS_ELEMENT_H +#define KRAKENBRIDGE_HTML_CANVAS_ELEMENT_H + +class html_canvas_element {}; + +#endif // KRAKENBRIDGE_HTML_CANVAS_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..874d50634a --- /dev/null +++ b/bridge/core/html/html_image_element.cc @@ -0,0 +1,108 @@ +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#include "html_image_element.h" +//#include "bindings/qjs/qjs_patch.h" +//#include "page.h" +// +//namespace kraken { +// +//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->getNativeProperty("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::setProperty, *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->getNativeProperty("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::setProperty, *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->getNativeProperty("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->getNativeProperty("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->getNativeProperty("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::setProperty, *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->getNativeProperty("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::setProperty, *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->nativeEvent->type->string), event->nativeEvent->type->length); +// std::string eventType = toUTF8(u16EventType); +// bool result = EventTargetInstance::dispatchEvent(event); +// +// // Free image instance after load or error event triggered. +// if ((eventType == "load" || eventType == "error") && !freed) { +// freed = true; +// unrefer(); +// } +// +// return result; +//} +// +//} // namespace kraken diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h new file mode 100644 index 0000000000..e1e2d051d4 --- /dev/null +++ b/bridge/core/html/html_image_element.h @@ -0,0 +1,48 @@ +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#ifndef KRAKENBRIDGE_HTML_IMAGE_ELEMENT_H +//#define KRAKENBRIDGE_HTML_IMAGE_ELEMENT_H +// +//#include "bindings/qjs/dom/element.h" +// +//namespace kraken { +// +//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); +// friend ImageElementInstance; +//}; +// +//class ImageElementInstance : public ElementInstance { +// public: +// ImageElementInstance() = delete; +// explicit ImageElementInstance(ImageElement* element); +// bool dispatchEvent(EventInstance* event); +// +// private: +// bool freed{false}; +// friend ImageElement; +//}; +// +//} // namespace kraken +// +//#endif // KRAKENBRIDGE_IMAGE_ELEMENTT_H diff --git a/bridge/core/html/html_input_element.cc b/bridge/core/html/html_input_element.cc new file mode 100644 index 0000000000..a805cb1822 --- /dev/null +++ b/bridge/core/html/html_input_element.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "html_input_element.h" diff --git a/bridge/bindings/qjs/dom/elements/input_element.d.ts b/bridge/core/html/html_input_element.d.ts similarity index 100% rename from bridge/bindings/qjs/dom/elements/input_element.d.ts rename to bridge/core/html/html_input_element.d.ts diff --git a/bridge/core/html/html_input_element.h b/bridge/core/html/html_input_element.h new file mode 100644 index 0000000000..5de2b3fd30 --- /dev/null +++ b/bridge/core/html/html_input_element.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_HTML_INPUT_ELEMENT_H +#define KRAKENBRIDGE_HTML_INPUT_ELEMENT_H + +class html_input_element {}; + +#endif // KRAKENBRIDGE_HTML_INPUT_ELEMENT_H diff --git a/bridge/core/html/html_object_element.cc b/bridge/core/html/html_object_element.cc new file mode 100644 index 0000000000..85470bacba --- /dev/null +++ b/bridge/core/html/html_object_element.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "html_object_element.h" diff --git a/bridge/bindings/qjs/dom/elements/object_element.d.ts b/bridge/core/html/html_object_element.d.ts similarity index 100% rename from bridge/bindings/qjs/dom/elements/object_element.d.ts rename to bridge/core/html/html_object_element.d.ts diff --git a/bridge/core/html/html_object_element.h b/bridge/core/html/html_object_element.h new file mode 100644 index 0000000000..043a7c60ca --- /dev/null +++ b/bridge/core/html/html_object_element.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_HTML_OBJECT_ELEMENT_H +#define KRAKENBRIDGE_HTML_OBJECT_ELEMENT_H + +class html_object_element {}; + +#endif // KRAKENBRIDGE_HTML_OBJECT_ELEMENT_H diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/core/html/html_parser.cc similarity index 100% rename from bridge/bindings/qjs/html_parser.cc rename to bridge/core/html/html_parser.cc diff --git a/bridge/bindings/qjs/html_parser.h b/bridge/core/html/html_parser.h similarity index 100% rename from bridge/bindings/qjs/html_parser.h rename to bridge/core/html/html_parser.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..5fcdb836d0 --- /dev/null +++ b/bridge/core/html/html_script_element.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/1/29. +// + +#include "html_script_element.h" diff --git a/bridge/bindings/qjs/dom/elements/script_element.d.ts b/bridge/core/html/html_script_element.d.ts similarity index 100% rename from bridge/bindings/qjs/dom/elements/script_element.d.ts rename to bridge/core/html/html_script_element.d.ts diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h new file mode 100644 index 0000000000..168a58386c --- /dev/null +++ b/bridge/core/html/html_script_element.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/1/29. +// + +#ifndef KRAKENBRIDGE_HTML_SCRIPT_ELEMENT_H +#define KRAKENBRIDGE_HTML_SCRIPT_ELEMENT_H + +class html_script_element {}; + +#endif // KRAKENBRIDGE_HTML_SCRIPT_ELEMENT_H diff --git a/bridge/bindings/qjs/dom/elements/template_element.cc b/bridge/core/html/html_template_element.cc similarity index 97% rename from bridge/bindings/qjs/dom/elements/template_element.cc rename to bridge/core/html/html_template_element.cc index 00a9efdfc0..5beff41da8 100644 --- a/bridge/bindings/qjs/dom/elements/template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -3,7 +3,7 @@ * Author: Kraken Team. */ -#include "template_element.h" +#include "html_template_element.h" #include "bindings/qjs/dom/text_node.h" #include "bindings/qjs/qjs_patch.h" #include "page.h" diff --git a/bridge/bindings/qjs/dom/elements/template_element.h b/bridge/core/html/html_template_element.h similarity index 93% rename from bridge/bindings/qjs/dom/elements/template_element.h rename to bridge/core/html/html_template_element.h index 5df8e80ae1..ab25115fb2 100644 --- a/bridge/bindings/qjs/dom/elements/template_element.h +++ b/bridge/core/html/html_template_element.h @@ -3,8 +3,8 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_TEMPLATE_ELEMENT_H -#define KRAKENBRIDGE_TEMPLATE_ELEMENT_H +#ifndef KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H +#define KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H #include "bindings/qjs/dom/document_fragment.h" #include "bindings/qjs/dom/element.h" diff --git a/bridge/bindings/qjs/bom/performance.cc b/bridge/core/timing/performance.cc similarity index 100% rename from bridge/bindings/qjs/bom/performance.cc rename to bridge/core/timing/performance.cc diff --git a/bridge/bindings/qjs/bom/performance.h b/bridge/core/timing/performance.h similarity index 100% rename from bridge/bindings/qjs/bom/performance.h rename to bridge/core/timing/performance.h From ace400216474f19a6431b73a4a3b789bdaa2b0e8 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Sat, 29 Jan 2022 22:16:59 +0800 Subject: [PATCH 010/498] chore: fix core compile --- bridge/CMakeLists.txt | 81 -------- bridge/bindings/qjs/wrapper_type_info.h | 1 - .../qjs => core}/executing_context.cc | 5 - .../qjs => core}/executing_context.h | 13 +- .../qjs => core}/executing_context_data.cc | 0 .../qjs => core}/executing_context_data.h | 3 +- .../executing_context_test.cc} | 0 bridge/foundation/inspector_task_queue.h | 1 - bridge/foundation/native_value.h | 1 + bridge/foundation/ui_command_buffer.cc | 13 +- bridge/foundation/ui_command_buffer.h | 6 +- .../foundation/ui_command_callback_queue.cc | 30 --- bridge/page.cc | 194 +++++++++--------- bridge/test/test.cmake | 30 +-- 14 files changed, 132 insertions(+), 246 deletions(-) rename bridge/{bindings/qjs => core}/executing_context.cc (99%) rename bridge/{bindings/qjs => core}/executing_context.h (96%) rename bridge/{bindings/qjs => core}/executing_context_data.cc (100%) rename bridge/{bindings/qjs => core}/executing_context_data.h (94%) rename bridge/{bindings/qjs/js_context_test.cc => core/executing_context_test.cc} (100%) delete mode 100644 bridge/foundation/ui_command_callback_queue.cc diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index d1b5ec296e..ad4f4f4bb3 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -101,7 +101,6 @@ list(APPEND BRIDGE_SOURCE foundation/native_value.h foundation/ui_command_buffer.cc foundation/ui_command_buffer.h - foundation/ui_command_callback_queue.cc polyfill/dist/polyfill.cc ) @@ -198,86 +197,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/qjs_patch.h bindings/qjs/dart_methods.cc bindings/qjs/dart_methods.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/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/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/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/dom/character_data.cc diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index d849b3af89..897f3d13cf 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -9,7 +9,6 @@ #include #include #include "bindings/qjs/qjs_patch.h" -#include "include/kraken_foundation.h" namespace kraken { diff --git a/bridge/bindings/qjs/executing_context.cc b/bridge/core/executing_context.cc similarity index 99% rename from bridge/bindings/qjs/executing_context.cc rename to bridge/core/executing_context.cc index 8459229641..e47fb51dfd 100644 --- a/bridge/bindings/qjs/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -189,11 +189,6 @@ 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; diff --git a/bridge/bindings/qjs/executing_context.h b/bridge/core/executing_context.h similarity index 96% rename from bridge/bindings/qjs/executing_context.h rename to bridge/core/executing_context.h index 86bb222198..9976074d1f 100644 --- a/bridge/bindings/qjs/executing_context.h +++ b/bridge/core/executing_context.h @@ -17,14 +17,13 @@ #include #include #include "foundation/macros.h" -#include "bindings/qjs/bom/dom_timer_coordinator.h" #include "executing_context_data.h" #include "foundation/ui_command_buffer.h" -#include "garbage_collected.h" -#include "kraken_foundation.h" -#include "qjs_patch.h" -#include "dart_methods.h" -#include "wrapper_type_info.h" +#include "bindings/qjs/garbage_collected.h" +//#include "garbage_collected.h" +//#include "qjs_patch.h" +//#include "dart_methods.h" +//#include "wrapper_type_info.h" using JSExceptionHandler = std::function; @@ -82,7 +81,7 @@ class ExecutionContext { JSValue global(); JSContext* ctx(); static JSRuntime* runtime(); - int32_t getContextId() const; + FORCE_INLINE int32_t getContextId() const { return contextId; }; void* getOwner(); bool handleException(JSValue* exc); void drainPendingPromiseJobs(); diff --git a/bridge/bindings/qjs/executing_context_data.cc b/bridge/core/executing_context_data.cc similarity index 100% rename from bridge/bindings/qjs/executing_context_data.cc rename to bridge/core/executing_context_data.cc diff --git a/bridge/bindings/qjs/executing_context_data.h b/bridge/core/executing_context_data.h similarity index 94% rename from bridge/bindings/qjs/executing_context_data.h rename to bridge/core/executing_context_data.h index c18b7120c4..a24cd43e8f 100644 --- a/bridge/bindings/qjs/executing_context_data.h +++ b/bridge/core/executing_context_data.h @@ -7,7 +7,8 @@ #define KRAKENBRIDGE_CONTEXT_DATA_H #include -#include "wrapper_type_info.h" +#include +#include "bindings/qjs/wrapper_type_info.h" namespace kraken { diff --git a/bridge/bindings/qjs/js_context_test.cc b/bridge/core/executing_context_test.cc similarity index 100% rename from bridge/bindings/qjs/js_context_test.cc rename to bridge/core/executing_context_test.cc diff --git a/bridge/foundation/inspector_task_queue.h b/bridge/foundation/inspector_task_queue.h index 17625d0855..e24c5aa922 100644 --- a/bridge/foundation/inspector_task_queue.h +++ b/bridge/foundation/inspector_task_queue.h @@ -6,7 +6,6 @@ #ifndef KRAKENBRIDGE_INSPECTOR_TASK_QUEUE_H #define KRAKENBRIDGE_INSPECTOR_TASK_QUEUE_H -#include "kraken_foundation.h" #include "task_queue.h" namespace kraken { diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index d031856943..3453105b92 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -10,6 +10,7 @@ #include #include #include +#include "bindings/qjs/native_string.h" namespace kraken { diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 36bb49aa79..39ff4e7ffe 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -4,15 +4,16 @@ */ #include "ui_command_buffer.h" -#include "dart_methods.h" +#include "bindings/qjs/dart_methods.h" +#include "bindings/qjs/executing_context.h" namespace kraken { -UICommandBuffer::UICommandBuffer(int32_t contextId) : contextId(contextId) {} +UICommandBuffer::UICommandBuffer(ExecutionContext *context) : m_context(context) {} void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate) { if (batchedUpdate) { - kraken::getDartMethod()->requestBatchUpdate(contextId); + m_context->dartMethodPtr()->requestBatchUpdate(m_context->getContextId()); update_batched = true; } @@ -23,7 +24,7 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND - kraken::getDartMethod()->requestBatchUpdate(contextId); + m_context->dartMethodPtr()->requestBatchUpdate(m_context->getContextId()); #endif update_batched = true; } @@ -35,7 +36,7 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND - kraken::getDartMethod()->requestBatchUpdate(contextId); + m_context->dartMethodPtr()->requestBatchUpdate(m_context->getContextId()); update_batched = true; #endif } @@ -47,7 +48,7 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01 void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01, NativeString& args_02, void* nativePtr) { #if FLUTTER_BACKEND if (!update_batched) { - kraken::getDartMethod()->requestBatchUpdate(contextId); + m_context->dartMethodPtr()->requestBatchUpdate(m_context->getContextId()); update_batched = true; } #endif diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 0a249dde16..25d9eee78c 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -13,6 +13,8 @@ namespace kraken { +class ExecutionContext; + enum UICommand { createElement, createTextNode, @@ -53,7 +55,7 @@ struct UICommandItem { class UICommandBuffer { public: UICommandBuffer() = delete; - explicit UICommandBuffer(int32_t contextId); + explicit UICommandBuffer(ExecutionContext* context); 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); @@ -63,7 +65,7 @@ class UICommandBuffer { void clear(); private: - int32_t contextId; + ExecutionContext *m_context{nullptr}; std::atomic update_batched{false}; std::vector queue; }; diff --git a/bridge/foundation/ui_command_callback_queue.cc b/bridge/foundation/ui_command_callback_queue.cc deleted file mode 100644 index 1182984337..0000000000 --- a/bridge/foundation/ui_command_callback_queue.cc +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2020 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -namespace kraken { - -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 kraken diff --git a/bridge/page.cc b/bridge/page.cc index 0274e4bf37..6a26d3d412 100644 --- a/bridge/page.cc +++ b/bridge/page.cc @@ -10,39 +10,39 @@ #include "bindings/qjs/qjs_patch.h" #include "page.h" -#include "bindings/qjs/bom/blob.h" -#include "bindings/qjs/bom/console.h" -#include "bindings/qjs/bom/location.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/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" +//#include "bindings/qjs/bom/blob.h" +//#include "bindings/qjs/bom/console.h" +//#include "bindings/qjs/bom/location.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/image_element.h" +//#include "bindings/qjs/dom/elements/template_element.h" +//#include "bindings/qjs/dom/event.h" +//#include "bindings/qjs/dom/event_target.h" +//#include "bindings/qjs/dom/events/.gen/close_event.h" +//#include "bindings/qjs/dom/events/.gen/gesture_event.h" +//#include "bindings/qjs/dom/events/.gen/input_event.h" +//#include "bindings/qjs/dom/events/.gen/intersection_change.h" +//#include "bindings/qjs/dom/events/.gen/media_error_event.h" +//#include "bindings/qjs/dom/events/.gen/message_event.h" +//#include "bindings/qjs/dom/events/.gen/mouse_event.h" +//#include "bindings/qjs/dom/events/.gen/popstate_event.h" +//#include "bindings/qjs/dom/events/touch_event.h" +//#include "bindings/qjs/dom/style_declaration.h" +//#include "bindings/qjs/dom/text_node.h" +//#include "bindings/qjs/module_manager.h" namespace kraken { @@ -71,21 +71,21 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c 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); - bindLocation(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); +// bindConsole(m_context); +// bindTimer(m_context); +// bindScreen(m_context); +// bindModuleManager(m_context); +// bindEventTarget(m_context); +// bindBlob(m_context); +// bindLocation(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); @@ -93,7 +93,7 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c // bindObjectElement(m_context); // bindScriptElement(m_context); // bindTemplateElement(m_context); - bindCSSStyleDeclaration(m_context); +// bindCSSStyleDeclaration(m_context); // bindCloseEvent(m_context); // bindGestureEvent(m_context); // bindInputEvent(m_context); @@ -103,7 +103,7 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c // bindMessageEvent(m_context); // bindPopStateEvent(m_context); // bindTouchEvent(m_context); - bindDocument(m_context); +// bindDocument(m_context); // bindPerformance(m_context); #if ENABLE_PROFILE @@ -123,56 +123,56 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c } bool KrakenPage::parseHTML(const char* code, size_t length) { - if (!m_context->isValid()) - return false; - JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); - auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId)); - HTMLParser::parseHTML(code, length, body); - JS_FreeValue(m_context->ctx(), bodyValue); - return true; +// if (!m_context->isValid()) +// return false; +// JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); +// auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId)); +// HTMLParser::parseHTML(code, length, body); +// JS_FreeValue(m_context->ctx(), bodyValue); +// return true; } void KrakenPage::invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* ptr, NativeString* extra) { - if (!m_context->isValid()) - return; - - JSValue eventObject = JS_NULL; - if (ptr != nullptr) { - std::string type = std::string(eventType); - auto* rawEvent = static_cast(ptr)->bytes; - Event* event = Event::create(m_context->ctx(), reinterpret_cast(rawEvent)); - eventObject = event->toQuickJS(); - } - - 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(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); - } +// if (!m_context->isValid()) +// return; +// +// JSValue eventObject = JS_NULL; +// if (ptr != nullptr) { +// std::string type = std::string(eventType); +// auto* rawEvent = static_cast(ptr)->bytes; +// Event* event = Event::create(m_context->ctx(), reinterpret_cast(rawEvent)); +// eventObject = event->toQuickJS(); +// } +// +// 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(extra->string), extra->length); +// std::string extraString = toUTF8(u16Extra); +// extraObject = JS_ParseJSON(m_context->ctx(), extraString.c_str(), extraString.size(), ""); +// } +// +// { +// struct list_head *el, *el1; +// list_for_each_safe(el, el1, &m_context->module_job_list) { +// auto* module = list_entry(el, ModuleContext, link); +// JSValue callback = module->callback; +// +// JSValue arguments[] = {moduleNameValue, eventObject, extraObject}; +// JSValue returnValue = JS_Call(m_context->ctx(), callback, m_context->global(), 3, arguments); +// m_context->handleException(&returnValue); +// JS_FreeValue(m_context->ctx(), returnValue); +// } +// } +// +// JS_FreeValue(m_context->ctx(), moduleNameValue); +// +// if (rawEvent != nullptr) { +// JS_FreeValue(m_context->ctx(), eventObject); +// } +// if (extra != nullptr) { +// JS_FreeValue(m_context->ctx(), extraObject); +// } } void KrakenPage::evaluateScript(const NativeString* script, const char* url, int startLine) { diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index e77b1de3bb..a36c742df6 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -16,21 +16,21 @@ list(APPEND KRAKEN_TEST_SOURCE list(APPEND KRAKEN_UNIT_TEST_SOURCE ./test/kraken_test_env.cc ./test/kraken_test_env.h - ./bindings/qjs/js_context_test.cc - ./bindings/qjs/bom/timer_test.cc - ./bindings/qjs/bom/console_test.cc - ./bindings/qjs/qjs_patch_test.cc - ./bindings/qjs/garbage_collected_test.cc - ./bindings/qjs/dom/event_target_test.cc - ./bindings/qjs/module_manager_test.cc - ./bindings/qjs/dom/node_test.cc - ./bindings/qjs/dom/event_test.cc - ./bindings/qjs/dom/element_test.cc - ./bindings/qjs/dom/document_test.cc - ./bindings/qjs/dom/text_node_test.cc - ./bindings/qjs/bom/window_test.cc - ./bindings/qjs/dom/custom_event_test.cc - ./bindings/qjs/module_manager_test.cc + ./bindings/qjs/executing_context_test.cc +# ./bindings/qjs/bom/timer_test.cc +# ./bindings/qjs/bom/console_test.cc +# ./bindings/qjs/qjs_patch_test.cc +# ./bindings/qjs/garbage_collected_test.cc +# ./bindings/qjs/dom/event_target_test.cc +# ./bindings/qjs/module_manager_test.cc +# ./bindings/qjs/dom/node_test.cc +# ./bindings/qjs/dom/event_test.cc +# ./bindings/qjs/dom/element_test.cc +# ./bindings/qjs/dom/document_test.cc +# ./bindings/qjs/dom/text_node_test.cc +# ./bindings/qjs/bom/window_test.cc +# ./bindings/qjs/dom/custom_event_test.cc +# ./bindings/qjs/module_manager_test.cc ) ### kraken_unit_test executable From d55a1998dbbcc01e25eb23b55436cf71a9984792 Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 30 Jan 2022 13:27:54 +0800 Subject: [PATCH 011/498] refactor: core modules compile success. --- bridge/CMakeLists.txt | 51 +- bridge/bindings/qjs/binding_initializer.cc | 49 ++ bridge/bindings/qjs/binding_initializer.h | 51 ++ bridge/bindings/qjs/dart_methods.cc | 44 -- bridge/bindings/qjs/garbage_collected.h | 3 +- .../qjs/{context_macros.h => macros.h} | 6 +- ...ative_string.cc => native_string_utils.cc} | 17 +- ...{native_string.h => native_string_utils.h} | 19 +- bridge/core/css/css_style_declaration.h | 2 +- bridge/{bindings/qjs => core}/dart_methods.h | 7 - bridge/core/dom/element.h | 2 - bridge/core/dom/events/event.h | 2 +- bridge/core/dom/events/event_listener_map.h | 1 - bridge/core/dom/events/event_target.cc | 10 +- bridge/core/dom/events/event_target.h | 11 +- bridge/core/dom/node.h | 2 +- bridge/core/executing_context.cc | 59 +- bridge/core/executing_context.h | 13 +- bridge/core/fileapi/blob.h | 2 +- bridge/core/frame/dom_timer_coordinator.cc | 3 +- bridge/core/frame/timer.h | 1 - bridge/foundation/native_string.cc | 26 + bridge/foundation/native_string.h | 23 + bridge/foundation/native_value.cc | 110 ++-- bridge/foundation/native_value.h | 2 +- bridge/foundation/ui_command_buffer.cc | 4 +- bridge/foundation/ui_command_buffer.h | 2 +- bridge/foundation/ui_task_queue.cc | 1 - bridge/include/kraken_bridge_test.h | 3 +- bridge/kraken_bridge.cc | 5 +- bridge/kraken_bridge_test.cc | 14 +- bridge/page.cc | 78 +-- bridge/page.h | 6 +- bridge/page_test.cc | 387 ++++++----- bridge/page_test.h | 3 +- bridge/test/kraken_test_env.cc | 614 +++++++++--------- bridge/test/kraken_test_env.h | 45 +- bridge/test/test.cmake | 2 +- 38 files changed, 837 insertions(+), 843 deletions(-) create mode 100644 bridge/bindings/qjs/binding_initializer.cc create mode 100644 bridge/bindings/qjs/binding_initializer.h delete mode 100644 bridge/bindings/qjs/dart_methods.cc rename bridge/bindings/qjs/{context_macros.h => macros.h} (96%) rename bridge/bindings/qjs/{native_string.cc => native_string_utils.cc} (82%) rename bridge/bindings/qjs/{native_string.h => native_string_utils.h} (83%) rename bridge/{bindings/qjs => core}/dart_methods.h (93%) create mode 100644 bridge/foundation/native_string.cc create mode 100644 bridge/foundation/native_string.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index ad4f4f4bb3..e0d361ab89 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -87,6 +87,8 @@ list(APPEND BRIDGE_SOURCE foundation/logging.cc foundation/logging.h foundation/colors.h + foundation/native_string.cc + foundation/native_string.h foundation/ref_counted_internal.h foundation/ref_counter.h foundation/ref_ptr.h @@ -184,35 +186,40 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") list(APPEND BRIDGE_SOURCE page.cc page.h + + # Binding files + bindings/qjs/binding_initializer.cc + bindings/qjs/binding_initializer.h bindings/qjs/garbage_collected.h - bindings/qjs/executing_context.cc - bindings/qjs/executing_context.h - bindings/qjs/executing_context_data.cc - bindings/qjs/executing_context_data.h bindings/qjs/wrapper_type_info.h bindings/qjs/heap_hashmap.h - bindings/qjs/native_string.cc - bindings/qjs/native_string.h + bindings/qjs/native_string_utils.cc + bindings/qjs/native_string_utils.h bindings/qjs/qjs_patch.cc bindings/qjs/qjs_patch.h - bindings/qjs/dart_methods.cc - bindings/qjs/dart_methods.h + core/dart_methods.h # Core sources - core/dom/character_data.cc - core/dom/character_data.h - core/dom/comment.cc - core/dom/comment.h - core/dom/node.cc - core/dom/node.h - core/dom/events/custom_event.cc - core/dom/events/custom_event.h - core/dom/events/event.h - core/dom/events/event.cc - core/dom/events/event_listener_map.cc - core/dom/events/event_listener_map.h - core/dom/events/event_target.cc - core/dom/events/event_target.h + core/executing_context.cc + core/executing_context.h + core/executing_context_data.cc + core/executing_context_data.h + core/frame/dom_timer_coordinator.cc + core/frame/dom_timer_coordinator.h +# core/dom/character_data.cc +# core/dom/character_data.h +# core/dom/comment.cc +# core/dom/comment.h +# core/dom/node.cc +# core/dom/node.h +# core/dom/events/custom_event.cc +# core/dom/events/custom_event.h +# core/dom/events/event.h +# core/dom/events/event.cc +# core/dom/events/event_listener_map.cc +# core/dom/events/event_listener_map.h +# core/dom/events/event_target.cc +# core/dom/events/event_target.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc new file mode 100644 index 0000000000..20fc0b15cb --- /dev/null +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -0,0 +1,49 @@ +/* +* Copyright (C) 2019 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#include "binding_initializer.h" + + +//#include "bindings/qjs/bom/blob.h" +//#include "bindings/qjs/bom/console.h" +//#include "bindings/qjs/bom/location.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/image_element.h" +//#include "bindings/qjs/dom/elements/template_element.h" +//#include "bindings/qjs/dom/event.h" +//#include "bindings/qjs/dom/event_target.h" +//#include "bindings/qjs/dom/events/.gen/close_event.h" +//#include "bindings/qjs/dom/events/.gen/gesture_event.h" +//#include "bindings/qjs/dom/events/.gen/input_event.h" +//#include "bindings/qjs/dom/events/.gen/intersection_change.h" +//#include "bindings/qjs/dom/events/.gen/media_error_event.h" +//#include "bindings/qjs/dom/events/.gen/message_event.h" +//#include "bindings/qjs/dom/events/.gen/mouse_event.h" +//#include "bindings/qjs/dom/events/.gen/popstate_event.h" +//#include "bindings/qjs/dom/events/touch_event.h" +//#include "bindings/qjs/dom/style_declaration.h" +//#include "bindings/qjs/dom/text_node.h" +//#include "bindings/qjs/module_manager.h" + +namespace kraken { + +void initBinding() { + +} + +} diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h new file mode 100644 index 0000000000..87e3d686c6 --- /dev/null +++ b/bridge/bindings/qjs/binding_initializer.h @@ -0,0 +1,51 @@ +/* +* Copyright (C) 2019 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_BINDING_INITIALIZER_H +#define KRAKENBRIDGE_BINDING_INITIALIZER_H + +namespace kraken { + +void initBinding(); + + +// bindConsole(m_context); +// bindTimer(m_context); +// bindScreen(m_context); +// bindModuleManager(m_context); +// bindEventTarget(m_context); +// bindBlob(m_context); +// bindLocation(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); +// 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); + +} + +#endif // KRAKENBRIDGE_BINDING_INITIALIZER_H diff --git a/bridge/bindings/qjs/dart_methods.cc b/bridge/bindings/qjs/dart_methods.cc deleted file mode 100644 index 2f955890ae..0000000000 --- a/bridge/bindings/qjs/dart_methods.cc +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "dart_methods.h" -#include "foundation/macros.h" -#include - -namespace kraken { - -//std::shared_ptr methodPointer = std::make_shared() - - -std::shared_ptr 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(); - } -#endif -// return methodPointer; -} - -void registerDartMethods(std::shared_ptr methodPointer, uint64_t* methodBytes, int32_t length) { - -} - -void registerTestEnvDartMethods(std::shared_ptr methodPointer, uint64_t* methodBytes, int32_t length) { - -} - -#if ENABLE_PROFILE -void registerGetPerformanceEntries(GetPerformanceEntries getPerformanceEntries) { - methodPointer->getPerformanceEntries = getPerformanceEntries; -} -#endif - -} // namespace kraken diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index e6d1a0057a..dec717603b 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -7,7 +7,8 @@ #define KRAKENBRIDGE_GARBAGE_COLLECTED_H #include -#include "include/kraken_foundation.h" + +#include "foundation/macros.h" #include "qjs_patch.h" namespace kraken { diff --git a/bridge/bindings/qjs/context_macros.h b/bridge/bindings/qjs/macros.h similarity index 96% rename from bridge/bindings/qjs/context_macros.h rename to bridge/bindings/qjs/macros.h index e50a85484c..2f44bb15c8 100644 --- a/bridge/bindings/qjs/context_macros.h +++ b/bridge/bindings/qjs/macros.h @@ -3,8 +3,8 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_CONTEXT_MACROS_H -#define KRAKENBRIDGE_CONTEXT_MACROS_H +#ifndef KRAKENBRIDGE_BINDING_MACROS_H +#define KRAKENBRIDGE_BINDING_MACROS_H #define QJS_GLOBAL_BINDING_FUNCTION(context, function, name, argc) \ { \ @@ -52,4 +52,4 @@ static JSValue setter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); \ }; -#endif // KRAKENBRIDGE_CONTEXT_MACROS_H +#endif // KRAKENBRIDGE_BINDING_MACROS_H diff --git a/bridge/bindings/qjs/native_string.cc b/bridge/bindings/qjs/native_string_utils.cc similarity index 82% rename from bridge/bindings/qjs/native_string.cc rename to bridge/bindings/qjs/native_string_utils.cc index 278f97b805..3f726022f4 100644 --- a/bridge/bindings/qjs/native_string.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -3,26 +3,11 @@ * Author: Kraken Team. */ -#include "native_string.h" +#include "native_string_utils.h" #include "bindings/qjs/qjs_patch.h" namespace kraken { - -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; -} - std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value) { bool isValueString = true; if (JS_IsNull(value)) { diff --git a/bridge/bindings/qjs/native_string.h b/bridge/bindings/qjs/native_string_utils.h similarity index 83% rename from bridge/bindings/qjs/native_string.h rename to bridge/bindings/qjs/native_string_utils.h index 92783e2aae..5677807fe3 100644 --- a/bridge/bindings/qjs/native_string.h +++ b/bridge/bindings/qjs/native_string_utils.h @@ -3,25 +3,18 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_NATIVE_STRING_H -#define KRAKENBRIDGE_NATIVE_STRING_H +#ifndef KRAKENBRIDGE_NATIVE_STRING_UTILS_H +#define KRAKENBRIDGE_NATIVE_STRING_UTILS_H #include -#include #include #include #include #include -namespace kraken { - -struct NativeString { - const uint16_t* string; - uint32_t length; +#include "foundation/native_string.h" - NativeString* clone(); - void free(); -}; +namespace kraken { // Convert to string and return a full copy of NativeString from JSValue. std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue value); @@ -56,6 +49,4 @@ void fromUTF8(const std::string& source, std::basic_string methodPointer, uint64_t* methodBytes, int32_t length); - -#ifdef IS_TEST -KRAKEN_EXPORT -void registerTestEnvDartMethods(std::shared_ptr methodPointer, uint64_t* methodBytes, int32_t length); -#endif - } // namespace kraken #endif diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 58cc3d1d8d..6a81ed3cf8 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -7,10 +7,8 @@ #define KRAKENBRIDGE_ELEMENT_H #include -#include "bindings/qjs/executing_context.h" #include "bindings/qjs/garbage_collected.h" #include "node.h" -#include "style_declaration.h" namespace kraken { diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 2dbe1033c8..1305fdf253 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -6,7 +6,7 @@ #ifndef KRAKENBRIDGE_EVENT_H #define KRAKENBRIDGE_EVENT_H -#include "bindings/qjs/context_macros.h" +#include "bindings/qjs/macros.h" #include "bindings/qjs/executing_context.h" namespace kraken { diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index 659b398b41..a33ac79102 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -8,7 +8,6 @@ #include #include -#include "include/kraken_foundation.h" namespace kraken { diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index ff8d1d8af8..2e532dd9c3 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -3,16 +3,14 @@ * Author: Kraken Team. */ -#include "event_target.h" - #include -#include "bindings/qjs/bom/window.h" -#include "bindings/qjs/dom/text_node.h" + +#include "event_target.h" #include "bindings/qjs/qjs_patch.h" #include "custom_event.h" -#include "document.h" -#include "element.h" #include "event.h" +#include "core/dom/node.h" +#include "core/frame/window.h" #if UNIT_TEST #include "kraken_test_env.h" diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index c190bd47c8..cdcd0f76e9 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -6,11 +6,11 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H -#include "bindings/qjs/context_macros.h" -#include "bindings/qjs/executing_context.h" +#include "foundation/macros.h" +#include "bindings/qjs/macros.h" #include "bindings/qjs/heap_hashmap.h" -//#include "bindings/qjs/native_value.h" -//#include "event_listener_map.h" +#include "core/executing_context.h" +#include "event_listener_map.h" #if UNIT_TEST void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); @@ -71,8 +71,9 @@ class EventTarget : public GarbageCollected { virtual bool dispatchEvent(Event* event); FORCE_INLINE int32_t eventTargetId() const { return m_eventTargetId; } - protected: JSValue callNativeMethods(const char* method, int32_t argc, NativeValue* argv); + + protected: JSValue getNativeProperty(const char* prop); // Used for legacy "onEvent" attribute APIs. diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 0e98ce0516..98f0af692e 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -9,7 +9,7 @@ #include #include -#include "event_target.h" +#include "events/event_target.h" namespace kraken { diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index e47fb51dfd..dbf820edba 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -4,13 +4,6 @@ */ #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" namespace kraken { @@ -77,32 +70,32 @@ ExecutionContext::~ExecutionContext() { 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; - } - } +// { +// 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. { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 9976074d1f..fff00c4152 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -17,20 +17,17 @@ #include #include #include "foundation/macros.h" -#include "executing_context_data.h" #include "foundation/ui_command_buffer.h" #include "bindings/qjs/garbage_collected.h" -//#include "garbage_collected.h" -//#include "qjs_patch.h" -//#include "dart_methods.h" -//#include "wrapper_type_info.h" + +#include "executing_context_data.h" +#include "frame/dom_timer_coordinator.h" +#include "dart_methods.h" using JSExceptionHandler = std::function; namespace kraken { -static std::once_flag kinitJSClassIDFlag; - class ExecutionContext; class Document; @@ -127,7 +124,7 @@ class ExecutionContext { DOMTimerCoordinator m_timers; ExecutionContextGCTracker* m_gcTracker{nullptr}; ExecutionContextData m_data{this}; - UICommandBuffer m_commandBuffer{contextId}; + UICommandBuffer m_commandBuffer{this}; std::unique_ptr m_dartMethodPtr = std::make_unique(); }; diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 13d14e1308..dd4dfd98f0 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -6,7 +6,7 @@ #ifndef KRAKENBRIDGE_BLOB_H #define KRAKENBRIDGE_BLOB_H -#include "bindings/qjs/context_macros.h" +#include "bindings/qjs/macros.h" #include "bindings/qjs/garbage_collected.h" namespace kraken { diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 7ff63c46fe..9a38c60645 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -4,7 +4,8 @@ */ #include "dom_timer_coordinator.h" -#include "dart_methods.h" +#include "core/dart_methods.h" +#include "core/executing_context.h" #include "timer.h" #if UNIT_TEST diff --git a/bridge/core/frame/timer.h b/bridge/core/frame/timer.h index 42862bc36b..bc8fc1c0bc 100644 --- a/bridge/core/frame/timer.h +++ b/bridge/core/frame/timer.h @@ -6,7 +6,6 @@ #ifndef KRAKENBRIDGE_TIMER_H #define KRAKENBRIDGE_TIMER_H -#include "bindings/qjs/executing_context.h" #include "bindings/qjs/garbage_collected.h" #include "dom_timer_coordinator.h" diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc new file mode 100644 index 0000000000..305d9da950 --- /dev/null +++ b/bridge/foundation/native_string.cc @@ -0,0 +1,26 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#include "native_string.h" +#include + +namespace kraken { + +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/foundation/native_string.h b/bridge/foundation/native_string.h new file mode 100644 index 0000000000..bb86cc586d --- /dev/null +++ b/bridge/foundation/native_string.h @@ -0,0 +1,23 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_NATIVE_STRING_H +#define KRAKENBRIDGE_NATIVE_STRING_H + +#include + +namespace kraken { + +struct NativeString { + const uint16_t* string; + uint32_t length; + + NativeString* clone(); + void free(); +}; + +} + +#endif // KRAKENBRIDGE_NATIVE_STRING_H diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 8bc6bdaf3d..29787e524e 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -4,14 +4,12 @@ */ #include "native_value.h" -#include "bindings/qjs/executing_context.h" #include "bindings/qjs/qjs_patch.h" -#include "bindings/qjs/dom/event_target.h" +#include "core/executing_context.h" +#include "core/dom/events/event_target.h" namespace kraken { -using namespace kraken::binding::qjs; - #define AnonymousFunctionCallPreFix "_anonymous_fn_" #define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" @@ -123,13 +121,13 @@ NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { auto* functionContext = new NativeFunctionContext{context, value}; return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); } else if (JS_IsObject(value)) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { - auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); - return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); - } +// auto* context = static_cast(JS_GetContextOpaque(ctx)); +// if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { +// auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); +// return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); +// } - return Native_NewJSON(context, value); +// return Native_NewJSON(context, value); } return Native_NewNull(); @@ -146,19 +144,19 @@ NativeFunctionContext::~NativeFunctionContext() { } 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->callNativeMethods(call_params.c_str(), argc, arguments); - delete[] arguments; - return returnValue; +// 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->callNativeMethods(call_params.c_str(), argc, arguments); +// delete[] arguments; +// return returnValue; } void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int32_t contextId, const char* errmsg) { @@ -193,31 +191,31 @@ void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int } 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->callNativeMethods(call_params.c_str(), argc + 3, arguments); - delete[] arguments; - - return promise; +// 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->callNativeMethods(call_params.c_str(), argc + 3, arguments); +// delete[] arguments; +// +// return promise; } JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { @@ -251,14 +249,14 @@ JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { case NativeTag::TAG_POINTER: { auto* ptr = value.u.ptr; int ptrType = (int)value.float64; - if (ptrType == static_cast(JSPointerType::NativeBoundingClientRect)) { - return (new BoundingClientRect(context, static_cast(ptr)))->jsObject; - } else if (ptrType == static_cast(JSPointerType::NativeCanvasRenderingContext2D)) { - return (new CanvasRenderingContext2D(context, static_cast(ptr)))->jsObject; - } else if (ptrType == static_cast(JSPointerType::NativeEventTarget)) { - auto* nativeEventTarget = static_cast(ptr); - return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); - } +// if (ptrType == static_cast(JSPointerType::NativeBoundingClientRect)) { +// return (new BoundingClientRect(context, static_cast(ptr)))->jsObject; +// } else if (ptrType == static_cast(JSPointerType::NativeCanvasRenderingContext2D)) { +// return (new CanvasRenderingContext2D(context, static_cast(ptr)))->jsObject; +// } else if (ptrType == static_cast(JSPointerType::NativeEventTarget)) { +// auto* nativeEventTarget = static_cast(ptr); +// return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); +// } } case NativeTag::TAG_FUNCTION: { int64_t functionId = value.u.int64; diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 3453105b92..705aa3c335 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -10,7 +10,7 @@ #include #include #include -#include "bindings/qjs/native_string.h" +#include "bindings/qjs/native_string_utils.h" namespace kraken { diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 39ff4e7ffe..25563796d5 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -4,8 +4,8 @@ */ #include "ui_command_buffer.h" -#include "bindings/qjs/dart_methods.h" -#include "bindings/qjs/executing_context.h" +#include "core/dart_methods.h" +#include "core/executing_context.h" namespace kraken { diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 25d9eee78c..f59fd3e94f 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -9,7 +9,7 @@ #include #include #include "native_value.h" -#include "bindings/qjs/native_string.h" +#include "bindings/qjs/native_string_utils.h" namespace kraken { diff --git a/bridge/foundation/ui_task_queue.cc b/bridge/foundation/ui_task_queue.cc index 64f9d0492d..85c1937ec8 100644 --- a/bridge/foundation/ui_task_queue.cc +++ b/bridge/foundation/ui_task_queue.cc @@ -11,7 +11,6 @@ fml::RefPtr 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; } diff --git a/bridge/include/kraken_bridge_test.h b/bridge/include/kraken_bridge_test.h index 7aa60103c5..c524ec92ac 100644 --- a/bridge/include/kraken_bridge_test.h +++ b/bridge/include/kraken_bridge_test.h @@ -6,7 +6,6 @@ #ifndef KRAKEN_BRIDGE_TEST_EXPORT_H #define KRAKEN_BRIDGE_TEST_EXPORT_H -#include #include "kraken_bridge.h" KRAKEN_EXPORT_C @@ -20,6 +19,6 @@ KRAKEN_EXPORT_C void executeTest(int32_t contextId, ExecuteCallback executeCallback); KRAKEN_EXPORT_C -void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); +void registerTestEnvDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length); #endif diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 26237b63b1..3a3328c8d3 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -12,7 +12,7 @@ #include "foundation/logging.h" #include "foundation/ui_task_queue.h" #include "foundation/ui_command_buffer.h" -#include "bindings/qjs/native_string.h" +#include "bindings/qjs/native_string_utils.h" #include "page.h" #if defined(_WIN32) @@ -186,7 +186,8 @@ void setConsoleMessageHandler(ConsoleMessageHandler handler) { } void dispatchUITask(int32_t contextId, void* context, void* callback) { - assert(std::this_thread::get_id() == getUIThreadId()); + auto* page = static_cast(getPage(contextId)); + assert(std::this_thread::get_id() == page->currentThread()); reinterpret_cast(callback)(context); } diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index efca8deb6e..46f5dcdfe6 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -4,13 +4,8 @@ */ #include "kraken_bridge_test.h" -#include "dart_methods.h" - -#if KRAKEN_JSC_ENGINE -#include "bridge_test_jsc.h" -#elif KRAKEN_QUICK_JS_ENGINE #include "page_test.h" -#endif +#include "bindings/qjs/native_string_utils.h" #include std::unordered_map bridgeTestPool = std::unordered_map(); @@ -21,7 +16,7 @@ void initTestFramework(int32_t contextId) { bridgeTestPool[contextId] = bridgeTest; } -int8_t evaluateTestScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine) { +int8_t evaluateTestScripts(int32_t contextId, kraken::NativeString* code, const char* bundleFilename, int startLine) { auto bridgeTest = bridgeTestPool[contextId]; return bridgeTest->evaluateTestScripts(code->string, code->length, bundleFilename, startLine); } @@ -31,6 +26,7 @@ void executeTest(int32_t contextId, ExecuteCallback executeCallback) { bridgeTest->invokeExecuteTest(executeCallback); } -void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { - kraken::registerTestEnvDartMethods(methodBytes, length); +void registerTestEnvDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length) { + auto bridgeTest = bridgeTestPool[contextId]; + bridgeTest->registerTestEnvDartMethods(methodBytes, length); } diff --git a/bridge/page.cc b/bridge/page.cc index 6a26d3d412..39525901e6 100644 --- a/bridge/page.cc +++ b/bridge/page.cc @@ -4,50 +4,17 @@ */ #include +#include +#include #include "foundation/logging.h" #include "polyfill.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/binding_initializer.h" #include "page.h" -//#include "bindings/qjs/bom/blob.h" -//#include "bindings/qjs/bom/console.h" -//#include "bindings/qjs/bom/location.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/image_element.h" -//#include "bindings/qjs/dom/elements/template_element.h" -//#include "bindings/qjs/dom/event.h" -//#include "bindings/qjs/dom/event_target.h" -//#include "bindings/qjs/dom/events/.gen/close_event.h" -//#include "bindings/qjs/dom/events/.gen/gesture_event.h" -//#include "bindings/qjs/dom/events/.gen/input_event.h" -//#include "bindings/qjs/dom/events/.gen/intersection_change.h" -//#include "bindings/qjs/dom/events/.gen/media_error_event.h" -//#include "bindings/qjs/dom/events/.gen/message_event.h" -//#include "bindings/qjs/dom/events/.gen/mouse_event.h" -//#include "bindings/qjs/dom/events/.gen/popstate_event.h" -//#include "bindings/qjs/dom/events/touch_event.h" -//#include "bindings/qjs/dom/style_declaration.h" -//#include "bindings/qjs/dom/text_node.h" -//#include "bindings/qjs/module_manager.h" namespace kraken { -using namespace binding::qjs; - std::unordered_map KrakenPage::pluginByteCode{}; ConsoleMessageHandler KrakenPage::consoleMessageHandler{nullptr}; @@ -71,40 +38,7 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c 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); -// bindLocation(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); - // 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); + initBinding(); #if ENABLE_PROFILE nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); @@ -245,6 +179,10 @@ void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); } +std::thread::id KrakenPage::currentThread() const { + return ownerThreadId; +} + KrakenPage::~KrakenPage() { #if IS_TEST if (disposeCallback != nullptr) { diff --git a/bridge/page.h b/bridge/page.h index d979b5b322..9ced0129d1 100644 --- a/bridge/page.h +++ b/bridge/page.h @@ -12,9 +12,8 @@ #include #include -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/html_parser.h" -#include "bindings/qjs/native_string.h" +#include "foundation/native_string.h" +#include "core/executing_context.h" namespace kraken { @@ -51,6 +50,7 @@ class KrakenPage final { void evaluateByteCode(uint8_t* bytes, size_t byteLength); void registerDartMethods(uint64_t* methodBytes, int32_t length); + std::thread::id currentThread() const; [[nodiscard]] ExecutionContext* getContext() const { return m_context; } diff --git a/bridge/page_test.cc b/bridge/page_test.cc index b5c1fb7164..61c6ed425a 100644 --- a/bridge/page_test.cc +++ b/bridge/page_test.cc @@ -4,7 +4,6 @@ */ #include "page_test.h" -#include "bindings/qjs/bom/blob.h" #include "testframework.h" namespace kraken { @@ -24,7 +23,7 @@ bool KrakenPageTest::parseTestHTML(const uint16_t* code, size_t codeLength) { static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue& callback = argv[0]; - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); if (!JS_IsObject(callback)) { return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); } @@ -40,63 +39,63 @@ static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSVa } 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(JS_GetContextOpaque(ctx)); - - if (!JS_IsObject(blobValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); - } - auto blob = static_cast(JS_GetOpaque(blobValue, kraken::binding::qjs::Blob::kBlobClassID)); - - if (blob == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); - } - - if (!JS_IsString(screenShotValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be an string."); - } - - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); - } - - if (getDartMethod()->matchImageSnapshot == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); - } - - std::unique_ptr screenShotNativeString = kraken::binding::qjs::jsValueToNativeString(ctx, screenShotValue); - auto bridge = static_cast(static_cast(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(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); +// JSValue& blobValue = argv[0]; +// JSValue& screenShotValue = argv[1]; +// JSValue& callbackValue = argv[2]; +// auto* context = static_cast(JS_GetContextOpaque(ctx)); +// +// if (!JS_IsObject(blobValue)) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); +// } +// auto blob = static_cast(JS_GetOpaque(blobValue, kraken::Blob::kBlobClassID)); +// +// if (blob == nullptr) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); +// } +// +// if (!JS_IsString(screenShotValue)) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be an string."); +// } +// +// if (!JS_IsObject(callbackValue)) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); +// } +// +// if (!JS_IsFunction(ctx, callbackValue)) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); +// } +// +// if (getDartMethod()->matchImageSnapshot == nullptr) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); +// } +// +// std::unique_ptr screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); +// auto bridge = static_cast(static_cast(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(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; } @@ -113,103 +112,103 @@ static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSVa } static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (getDartMethod()->simulatePointer == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); - } - - auto* context = static_cast(JS_GetContextOpaque(ctx)); - - JSValue inputArrayValue = argv[0]; - if (!JS_IsObject(inputArrayValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); - } - - JSValue pointerValue = argv[1]; - if (!JS_IsNumber(pointerValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); - } - - uint32_t length; - JSValue lengthValue = JS_GetPropertyStr(ctx, inputArrayValue, "length"); - JS_ToUint32(ctx, &length, lengthValue); - JS_FreeValue(ctx, lengthValue); - - auto** mousePointerList = new MousePointer*[length]; - - for (int i = 0; i < length; i++) { - auto mouse = new MousePointer(); - JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); - mouse->contextId = context->getContextId(); - JSValue xValue = JS_GetPropertyUint32(ctx, params, 0); - JSValue yValue = JS_GetPropertyUint32(ctx, params, 1); - JSValue changeValue = JS_GetPropertyUint32(ctx, params, 2); - - double x; - double y; - double change; - - JS_ToFloat64(ctx, &x, xValue); - JS_ToFloat64(ctx, &y, yValue); - JS_ToFloat64(ctx, &change, changeValue); - - mouse->x = x; - mouse->y = y; - mouse->change = change; - mousePointerList[i] = mouse; - - JS_FreeValue(ctx, params); - JS_FreeValue(ctx, xValue); - JS_FreeValue(ctx, yValue); - JS_FreeValue(ctx, changeValue); - } - - uint32_t pointer; - JS_ToUint32(ctx, &pointer, pointerValue); - - getDartMethod()->simulatePointer(mousePointerList, length, pointer); - - delete[] mousePointerList; - - return JS_NULL; +// if (getDartMethod()->simulatePointer == nullptr) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); +// } +// +// auto* context = static_cast(JS_GetContextOpaque(ctx)); +// +// JSValue inputArrayValue = argv[0]; +// if (!JS_IsObject(inputArrayValue)) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); +// } +// +// JSValue pointerValue = argv[1]; +// if (!JS_IsNumber(pointerValue)) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); +// } +// +// uint32_t length; +// JSValue lengthValue = JS_GetPropertyStr(ctx, inputArrayValue, "length"); +// JS_ToUint32(ctx, &length, lengthValue); +// JS_FreeValue(ctx, lengthValue); +// +// auto** mousePointerList = new MousePointer*[length]; +// +// for (int i = 0; i < length; i++) { +// auto mouse = new MousePointer(); +// JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); +// mouse->contextId = context->getContextId(); +// JSValue xValue = JS_GetPropertyUint32(ctx, params, 0); +// JSValue yValue = JS_GetPropertyUint32(ctx, params, 1); +// JSValue changeValue = JS_GetPropertyUint32(ctx, params, 2); +// +// double x; +// double y; +// double change; +// +// JS_ToFloat64(ctx, &x, xValue); +// JS_ToFloat64(ctx, &y, yValue); +// JS_ToFloat64(ctx, &change, changeValue); +// +// mouse->x = x; +// mouse->y = y; +// mouse->change = change; +// mousePointerList[i] = mouse; +// +// JS_FreeValue(ctx, params); +// JS_FreeValue(ctx, xValue); +// JS_FreeValue(ctx, yValue); +// JS_FreeValue(ctx, changeValue); +// } +// +// uint32_t pointer; +// JS_ToUint32(ctx, &pointer, pointerValue); +// +// getDartMethod()->simulatePointer(mousePointerList, length, pointer); +// +// delete[] mousePointerList; +// +// return JS_NULL; } static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (getDartMethod()->simulateInputText == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); - } - - JSValue& charStringValue = argv[0]; - - if (!JS_IsString(charStringValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); - } - - std::unique_ptr nativeString = kraken::binding::qjs::jsValueToNativeString(ctx, charStringValue); - getDartMethod()->simulateInputText(nativeString.get()); - nativeString->free(); - return JS_NULL; +// if (getDartMethod()->simulateInputText == nullptr) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); +// } +// +// JSValue& charStringValue = argv[0]; +// +// if (!JS_IsString(charStringValue)) { +// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); +// } +// +// std::unique_ptr nativeString = kraken::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(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(JS_GetOpaque(bodyValue, binding::qjs::Element::classId())); - binding::qjs::HTMLParser::parseHTML(strHTML, body); - - JS_FreeValue(ctx, bodyValue); - } - - return JS_NULL; +// auto* context = static_cast(JS_GetContextOpaque(ctx)); +// +// if (argc == 1) { +// JSValue& html = argv[0]; +// +// std::string strHTML = jsValueToStdString(ctx, html); +// +// JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); +// auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); +// 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(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); JSValue globalErrorFunc = JS_GetPropertyStr(ctx, context->global(), "triggerGlobalError"); @@ -223,62 +222,62 @@ static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int arg } KrakenPageTest::KrakenPageTest(KrakenPage* bridge) : m_page(bridge), m_page_context(bridge->getContext()) { - bridge->owner = this; - bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast(bridge->owner); }; - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); - QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); - - initKrakenTestFramework(bridge); - init_list_head(&image_link); +// bridge->owner = this; +// bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast(bridge->owner); }; +// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); +// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); +// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); +// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); +// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); +// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); +// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); + +// initKrakenTestFramework(bridge); +// init_list_head(&image_link); } struct ExecuteCallbackContext { ExecuteCallbackContext() = delete; - explicit ExecuteCallbackContext(binding::qjs::ExecutionContext* context, ExecuteCallback executeCallback) : executeCallback(executeCallback), context(context){}; + explicit ExecuteCallbackContext(ExecutionContext* context, ExecuteCallback executeCallback) : executeCallback(executeCallback), context(context){}; ExecuteCallback executeCallback; - binding::qjs::ExecutionContext* context; + ExecutionContext* context; }; void KrakenPageTest::invokeExecuteTest(ExecuteCallback executeCallback) { - if (JS_IsNull(executeTestCallback)) { - return; - } - if (!JS_IsFunction(m_page_context->ctx(), executeTestCallback)) { - return; - } - - auto done = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) -> JSValue { - JSValue& statusValue = argv[0]; - JSValue proxyObject = func_data[0]; - auto* callbackContext = static_cast(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 status = kraken::binding::qjs::jsValueToNativeString(ctx, statusValue); - callbackContext->executeCallback(callbackContext->context->getContextId(), status.get()); - return JS_NULL; - }; - auto* callbackContext = new ExecuteCallbackContext(m_page_context, executeCallback); - executeTestProxyObject = JS_NewObject(m_page_context->ctx()); - JS_SetOpaque(executeTestProxyObject, callbackContext); - JSValue callbackData[]{executeTestProxyObject}; - JSValue callback = JS_NewCFunctionData(m_page_context->ctx(), done, 0, 0, 1, callbackData); - - JSValue arguments[] = {callback}; - JSValue result = JS_Call(m_page_context->ctx(), executeTestCallback, executeTestCallback, 1, arguments); - m_page_context->handleException(&result); - m_page_context->drainPendingPromiseJobs(); - JS_FreeValue(m_page_context->ctx(), executeTestCallback); - JS_FreeValue(m_page_context->ctx(), callback); - executeTestCallback = JS_NULL; +// 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(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 status = kraken::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; } void KrakenPageTest::registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { diff --git a/bridge/page_test.h b/bridge/page_test.h index b19761bd4b..59a6f4aa06 100644 --- a/bridge/page_test.h +++ b/bridge/page_test.h @@ -6,9 +6,8 @@ #ifndef KRAKENBRIDGE_PAGE_TEST_H #define KRAKENBRIDGE_PAGE_TEST_H -#include "bindings/qjs/dom/document.h" -#include "bindings/qjs/html_parser.h" #include "kraken_bridge_test.h" +#include "core/executing_context.h" #include "page.h" namespace kraken { diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 49869d0768..3584813b1e 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -1,307 +1,307 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "kraken_test_env.h" -#include -#include -#include "bindings/qjs/dom/event_target.h" -#include "dart_methods.h" -#include "kraken_bridge_test.h" -#include "page.h" - -#if defined(__linux__) || defined(__APPLE__) -static int64_t get_time_ms(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); -} -#else -/* more portable, but does not work if the date is updated */ -static int64_t get_time_ms(void) { - struct timeval tv; - gettimeofday(&tv, NULL); - return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); -} -#endif - -typedef struct { - struct list_head link; - int64_t timeout; - DOMTimer* timer; - int32_t contextId; - bool isInterval; - AsyncCallback func; -} JSOSTimer; - -typedef struct { - struct list_head link; - FrameCallback* callback; - int32_t contextId; - AsyncRAFCallback handler; - int32_t callbackId; -} JSFrameCallback; - -typedef struct JSThreadState { - std::unordered_map os_timers; /* list of timer.link */ - std::unordered_map os_frameCallbacks; -} JSThreadState; - -static void unlink_timer(JSThreadState* ts, JSOSTimer* th) { - ts->os_timers.erase(th->timer->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) { - return nullptr; -}; - -void TEST_requestBatchUpdate(int32_t contextId){}; - -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(JS_GetContextOpaque(timer->ctx())); - JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); - JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); - th->timeout = get_time_ms() + timeout; - th->func = callback; - th->timer = timer; - th->contextId = contextId; - th->isInterval = false; - int32_t id = timerId++; - - ts->os_timers[id] = th; - - 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(JS_GetContextOpaque(timer->ctx())); - JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); - JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); - th->timeout = get_time_ms() + timeout; - th->func = callback; - th->timer = timer; - th->contextId = contextId; - th->isInterval = true; - int32_t id = timerId++; - - ts->os_timers[id] = th; - - return id; -} - -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(JS_GetContextOpaque(frameCallback->ctx())); - JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); - JSFrameCallback* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); - th->handler = handler; - th->callback = frameCallback; - th->contextId = context->getContextId(); - int32_t id = callbackId++; - - th->callbackId = id; - - ts->os_frameCallbacks[id] = th; - - return id; -} - -void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { - auto* page = static_cast(getPage(contextId)); - auto* context = page->getContext(); - JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); - ts->os_frameCallbacks.erase(id); -} - -void TEST_clearTimeout(int32_t contextId, int32_t timerId) { - auto* page = static_cast(getPage(contextId)); - auto* context = page->getContext(); - JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); - ts->os_timers.erase(timerId); -} - -NativeScreen* TEST_getScreen(int32_t contextId) { - return nullptr; -}; - -double TEST_devicePixelRatio(int32_t contextId) { - return 1.0; -} - -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_initDocument(int32_t contextId, void* nativePtr) {} - -#if ENABLE_PROFILE -NativePerformanceEntryList* TEST_getPerformanceEntries(int32_t) {} -#endif - -std::once_flag testInitOnceFlag; -static int32_t inited{false}; - -std::unique_ptr TEST_init(OnJSError onJsError) { - uint32_t contextId; - if (inited) { - contextId = allocateNewPage(-1); - } else { - contextId = 0; - } - std::call_once(testInitOnceFlag, []() { - initJSPagePool(1024 * 1024); - inited = true; - }); - initTestFramework(contextId); - auto* page = static_cast(getPage(contextId)); - auto* context = page->getContext(); - JSThreadState* th = new JSThreadState(); - JS_SetRuntimeOpaque(context->runtime(), th); - - TEST_mockDartMethods(onJsError); - - return std::unique_ptr(page); -} - -std::unique_ptr TEST_init() { - return TEST_init(nullptr); -} - -std::unique_ptr TEST_allocateNewPage() { - uint32_t newContextId = allocateNewPage(-1); - initTestFramework(newContextId); - return std::unique_ptr(static_cast(getPage(newContextId))); -} - -static bool jsPool(ExecutionContext* context) { - JSRuntime* rt = context->runtime(); - JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); - int64_t cur_time, delay; - struct list_head* el; - - if (ts->os_timers.empty() && ts->os_frameCallbacks.empty()) - return true; /* no more events */ - - if (!ts->os_timers.empty()) { - cur_time = get_time_ms(); - for (auto& entry : ts->os_timers) { - JSOSTimer* th = entry.second; - delay = th->timeout - cur_time; - if (delay <= 0) { - AsyncCallback func; - /* the timer expired */ - func = th->func; - - if (th->isInterval) { - func(th->timer, th->contextId, nullptr); - } else { - th->func = nullptr; - func(th->timer, th->contextId, nullptr); - unlink_timer(ts, th); - } - - return false; - } - } - } - - if (!ts->os_frameCallbacks.empty()) { - for (auto& entry : ts->os_frameCallbacks) { - JSFrameCallback* th = entry.second; - AsyncRAFCallback handler = th->handler; - th->handler = nullptr; - handler(th->callback, th->contextId, 0, nullptr); - unlink_callback(ts, th); - return false; - } - } - - return false; -} - -void TEST_runLoop(ExecutionContext* context) { - for (;;) { - context->drainPendingPromiseJobs(); - if (jsPool(context)) - break; - } -} - -void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { - NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); - auto nativeEventType = stringToNativeString(type); - NativeString* rawEventType = nativeEventType.release(); - - NativeEvent* nativeEvent = new NativeEvent{rawEventType}; - - RawEvent* rawEvent = new RawEvent{reinterpret_cast(nativeEvent)}; - - NativeEventTarget::dispatchEventImpl(contextId, nativeEventTarget, rawEventType, rawEvent, false); -} - -void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv) {} - -std::unordered_map> unitTestEnvMap; -std::shared_ptr TEST_getEnv(int32_t contextUniqueId) { - if (unitTestEnvMap.count(contextUniqueId) == 0) { - unitTestEnvMap[contextUniqueId] = std::make_shared(); - } - - return unitTestEnvMap[contextUniqueId]; -} - -void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { - if (unitTestEnvMap.count(contextUniqueId) == 0) { - unitTestEnvMap[contextUniqueId] = std::make_shared(); - } - - unitTestEnvMap[contextUniqueId]->onEventTargetDisposed = callback; -} - -void TEST_mockDartMethods(OnJSError onJSError) { - std::vector mockMethods{ - reinterpret_cast(TEST_invokeModule), - reinterpret_cast(TEST_requestBatchUpdate), - reinterpret_cast(TEST_reloadApp), - reinterpret_cast(TEST_setTimeout), - reinterpret_cast(TEST_setInterval), - reinterpret_cast(TEST_clearTimeout), - reinterpret_cast(TEST_requestAnimationFrame), - reinterpret_cast(TEST_cancelAnimationFrame), - reinterpret_cast(TEST_getScreen), - reinterpret_cast(TEST_devicePixelRatio), - reinterpret_cast(TEST_platformBrightness), - reinterpret_cast(TEST_toBlob), - reinterpret_cast(TEST_flushUICommand), - reinterpret_cast(TEST_initWindow), - reinterpret_cast(TEST_initDocument), - }; - -#if ENABLE_PROFILE - mockMethods.emplace_back(reinterpret_cast(TEST_getPerformanceEntries)); -#else - mockMethods.emplace_back(0); -#endif - - mockMethods.emplace_back(reinterpret_cast(onJSError)); - registerDartMethods(mockMethods.data(), mockMethods.size()); -} +///* +// * Copyright (C) 2021 Alibaba Inc. All rights reserved. +// * Author: Kraken Team. +// */ +// +//#include "kraken_test_env.h" +//#include +//#include +//#include "bindings/qjs/dom/event_target.h" +//#include "dart_methods.h" +//#include "kraken_bridge_test.h" +//#include "page.h" +// +//#if defined(__linux__) || defined(__APPLE__) +//static int64_t get_time_ms(void) { +// struct timespec ts; +// clock_gettime(CLOCK_MONOTONIC, &ts); +// return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); +//} +//#else +///* more portable, but does not work if the date is updated */ +//static int64_t get_time_ms(void) { +// struct timeval tv; +// gettimeofday(&tv, NULL); +// return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); +//} +//#endif +// +//typedef struct { +// struct list_head link; +// int64_t timeout; +// DOMTimer* timer; +// int32_t contextId; +// bool isInterval; +// AsyncCallback func; +//} JSOSTimer; +// +//typedef struct { +// struct list_head link; +// FrameCallback* callback; +// int32_t contextId; +// AsyncRAFCallback handler; +// int32_t callbackId; +//} JSFrameCallback; +// +//typedef struct JSThreadState { +// std::unordered_map os_timers; /* list of timer.link */ +// std::unordered_map os_frameCallbacks; +//} JSThreadState; +// +//static void unlink_timer(JSThreadState* ts, JSOSTimer* th) { +// ts->os_timers.erase(th->timer->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) { +// return nullptr; +//}; +// +//void TEST_requestBatchUpdate(int32_t contextId){}; +// +//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(JS_GetContextOpaque(timer->ctx())); +// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); +// JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); +// th->timeout = get_time_ms() + timeout; +// th->func = callback; +// th->timer = timer; +// th->contextId = contextId; +// th->isInterval = false; +// int32_t id = timerId++; +// +// ts->os_timers[id] = th; +// +// 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(JS_GetContextOpaque(timer->ctx())); +// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); +// JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); +// th->timeout = get_time_ms() + timeout; +// th->func = callback; +// th->timer = timer; +// th->contextId = contextId; +// th->isInterval = true; +// int32_t id = timerId++; +// +// ts->os_timers[id] = th; +// +// return id; +//} +// +//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(JS_GetContextOpaque(frameCallback->ctx())); +// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); +// JSFrameCallback* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); +// th->handler = handler; +// th->callback = frameCallback; +// th->contextId = context->getContextId(); +// int32_t id = callbackId++; +// +// th->callbackId = id; +// +// ts->os_frameCallbacks[id] = th; +// +// return id; +//} +// +//void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { +// auto* page = static_cast(getPage(contextId)); +// auto* context = page->getContext(); +// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); +// ts->os_frameCallbacks.erase(id); +//} +// +//void TEST_clearTimeout(int32_t contextId, int32_t timerId) { +// auto* page = static_cast(getPage(contextId)); +// auto* context = page->getContext(); +// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); +// ts->os_timers.erase(timerId); +//} +// +//NativeScreen* TEST_getScreen(int32_t contextId) { +// return nullptr; +//}; +// +//double TEST_devicePixelRatio(int32_t contextId) { +// return 1.0; +//} +// +//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_initDocument(int32_t contextId, void* nativePtr) {} +// +//#if ENABLE_PROFILE +//NativePerformanceEntryList* TEST_getPerformanceEntries(int32_t) {} +//#endif +// +//std::once_flag testInitOnceFlag; +//static int32_t inited{false}; +// +//std::unique_ptr TEST_init(OnJSError onJsError) { +// uint32_t contextId; +// if (inited) { +// contextId = allocateNewPage(-1); +// } else { +// contextId = 0; +// } +// std::call_once(testInitOnceFlag, []() { +// initJSPagePool(1024 * 1024); +// inited = true; +// }); +// initTestFramework(contextId); +// auto* page = static_cast(getPage(contextId)); +// auto* context = page->getContext(); +// JSThreadState* th = new JSThreadState(); +// JS_SetRuntimeOpaque(context->runtime(), th); +// +// TEST_mockDartMethods(onJsError); +// +// return std::unique_ptr(page); +//} +// +//std::unique_ptr TEST_init() { +// return TEST_init(nullptr); +//} +// +//std::unique_ptr TEST_allocateNewPage() { +// uint32_t newContextId = allocateNewPage(-1); +// initTestFramework(newContextId); +// return std::unique_ptr(static_cast(getPage(newContextId))); +//} +// +//static bool jsPool(ExecutionContext* context) { +// JSRuntime* rt = context->runtime(); +// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); +// int64_t cur_time, delay; +// struct list_head* el; +// +// if (ts->os_timers.empty() && ts->os_frameCallbacks.empty()) +// return true; /* no more events */ +// +// if (!ts->os_timers.empty()) { +// cur_time = get_time_ms(); +// for (auto& entry : ts->os_timers) { +// JSOSTimer* th = entry.second; +// delay = th->timeout - cur_time; +// if (delay <= 0) { +// AsyncCallback func; +// /* the timer expired */ +// func = th->func; +// +// if (th->isInterval) { +// func(th->timer, th->contextId, nullptr); +// } else { +// th->func = nullptr; +// func(th->timer, th->contextId, nullptr); +// unlink_timer(ts, th); +// } +// +// return false; +// } +// } +// } +// +// if (!ts->os_frameCallbacks.empty()) { +// for (auto& entry : ts->os_frameCallbacks) { +// JSFrameCallback* th = entry.second; +// AsyncRAFCallback handler = th->handler; +// th->handler = nullptr; +// handler(th->callback, th->contextId, 0, nullptr); +// unlink_callback(ts, th); +// return false; +// } +// } +// +// return false; +//} +// +//void TEST_runLoop(ExecutionContext* context) { +// for (;;) { +// context->drainPendingPromiseJobs(); +// if (jsPool(context)) +// break; +// } +//} +// +//void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { +// NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); +// auto nativeEventType = stringToNativeString(type); +// NativeString* rawEventType = nativeEventType.release(); +// +// NativeEvent* nativeEvent = new NativeEvent{rawEventType}; +// +// RawEvent* rawEvent = new RawEvent{reinterpret_cast(nativeEvent)}; +// +// NativeEventTarget::dispatchEventImpl(contextId, nativeEventTarget, rawEventType, rawEvent, false); +//} +// +//void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv) {} +// +//std::unordered_map> unitTestEnvMap; +//std::shared_ptr TEST_getEnv(int32_t contextUniqueId) { +// if (unitTestEnvMap.count(contextUniqueId) == 0) { +// unitTestEnvMap[contextUniqueId] = std::make_shared(); +// } +// +// return unitTestEnvMap[contextUniqueId]; +//} +// +//void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { +// if (unitTestEnvMap.count(contextUniqueId) == 0) { +// unitTestEnvMap[contextUniqueId] = std::make_shared(); +// } +// +// unitTestEnvMap[contextUniqueId]->onEventTargetDisposed = callback; +//} +// +//void TEST_mockDartMethods(OnJSError onJSError) { +// std::vector mockMethods{ +// reinterpret_cast(TEST_invokeModule), +// reinterpret_cast(TEST_requestBatchUpdate), +// reinterpret_cast(TEST_reloadApp), +// reinterpret_cast(TEST_setTimeout), +// reinterpret_cast(TEST_setInterval), +// reinterpret_cast(TEST_clearTimeout), +// reinterpret_cast(TEST_requestAnimationFrame), +// reinterpret_cast(TEST_cancelAnimationFrame), +// reinterpret_cast(TEST_getScreen), +// reinterpret_cast(TEST_devicePixelRatio), +// reinterpret_cast(TEST_platformBrightness), +// reinterpret_cast(TEST_toBlob), +// reinterpret_cast(TEST_flushUICommand), +// reinterpret_cast(TEST_initWindow), +// reinterpret_cast(TEST_initDocument), +// }; +// +//#if ENABLE_PROFILE +// mockMethods.emplace_back(reinterpret_cast(TEST_getPerformanceEntries)); +//#else +// mockMethods.emplace_back(0); +//#endif +// +// mockMethods.emplace_back(reinterpret_cast(onJSError)); +// registerDartMethods(mockMethods.data(), mockMethods.size()); +//} diff --git a/bridge/test/kraken_test_env.h b/bridge/test/kraken_test_env.h index 34e527034f..8078fd3a2b 100644 --- a/bridge/test/kraken_test_env.h +++ b/bridge/test/kraken_test_env.h @@ -7,30 +7,27 @@ #define KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ #include -#include "bindings/qjs/bom/timer.h" -#include "bindings/qjs/dom/event_target.h" -#include "bindings/qjs/dom/frame_request_callback_collection.h" -#include "include/dart_methods.h" +//#include "bindings/qjs/bom/timer.h" +//#include "bindings/qjs/dom/event_target.h" +//#include "bindings/qjs/dom/frame_request_callback_collection.h" #include "page.h" - -using namespace kraken::binding::qjs; - -// Trigger a callbacks before GC free the eventTargets. -using TEST_OnEventTargetDisposed = void (*)(kraken::binding::qjs::EventTargetInstance* eventTargetInstance); -struct UnitTestEnv { - TEST_OnEventTargetDisposed onEventTargetDisposed{nullptr}; -}; - -// Mock dart methods and add async timer to emulate kraken environment in C++ unit test. - -std::unique_ptr TEST_init(OnJSError onJsError); -std::unique_ptr TEST_init(); -std::unique_ptr TEST_allocateNewPage(); -void TEST_runLoop(ExecutionContext* context); -void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); -void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); -void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); -void TEST_mockDartMethods(OnJSError onJSError); -std::shared_ptr TEST_getEnv(int32_t contextUniqueId); +// +//// Trigger a callbacks before GC free the eventTargets. +//using TEST_OnEventTargetDisposed = void (*)(kraken::binding::qjs::EventTargetInstance* eventTargetInstance); +//struct UnitTestEnv { +// TEST_OnEventTargetDisposed onEventTargetDisposed{nullptr}; +//}; +// +//// Mock dart methods and add async timer to emulate kraken environment in C++ unit test. +// +//std::unique_ptr TEST_init(OnJSError onJsError); +//std::unique_ptr TEST_init(); +//std::unique_ptr TEST_allocateNewPage(); +//void TEST_runLoop(ExecutionContext* context); +//void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); +//void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); +//void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); +//void TEST_mockDartMethods(OnJSError onJSError); +//std::shared_ptr TEST_getEnv(int32_t contextUniqueId); #endif // KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index a36c742df6..b2d54f7cda 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -16,7 +16,7 @@ list(APPEND KRAKEN_TEST_SOURCE list(APPEND KRAKEN_UNIT_TEST_SOURCE ./test/kraken_test_env.cc ./test/kraken_test_env.h - ./bindings/qjs/executing_context_test.cc + ./core/executing_context_test.cc # ./bindings/qjs/bom/timer_test.cc # ./bindings/qjs/bom/console_test.cc # ./bindings/qjs/qjs_patch_test.cc From 065ee274f0d3899a9f29e131452b8ebd7baab934 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Sun, 30 Jan 2022 05:29:47 +0000 Subject: [PATCH 012/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 13 +- bridge/bindings/qjs/binding_initializer.h | 9 +- bridge/bindings/qjs/native_string_utils.cc | 5 +- bridge/bindings/qjs/native_string_utils.h | 8 +- bridge/core/css/css_style_declaration.h | 2 +- bridge/core/dom/events/event.h | 2 +- bridge/core/dom/events/event_target.cc | 6 +- bridge/core/dom/events/event_target.h | 4 +- bridge/core/executing_context.cc | 53 ++- bridge/core/executing_context.h | 4 +- bridge/core/executing_context_data.h | 2 +- bridge/core/fileapi/blob.h | 2 +- bridge/core/html/html_all_collection.cc | 12 +- bridge/core/html/html_all_collection.h | 4 +- bridge/core/html/html_image_element.cc | 32 +- bridge/core/html/html_image_element.h | 10 +- bridge/foundation/logging.h | 6 +- bridge/foundation/macros.h | 1 - bridge/foundation/native_string.cc | 9 +- bridge/foundation/native_string.h | 8 +- bridge/foundation/native_value.cc | 108 +++--- bridge/foundation/native_value.h | 2 +- bridge/foundation/ref_ptr.h | 2 +- bridge/foundation/ui_command_buffer.cc | 2 +- bridge/foundation/ui_command_buffer.h | 22 +- bridge/kraken_bridge.cc | 14 +- bridge/kraken_bridge_test.cc | 4 +- bridge/page.cc | 114 ++++--- bridge/page.h | 4 +- bridge/page_test.cc | 378 ++++++++++----------- bridge/page_test.h | 2 +- bridge/test/kraken_test_env.cc | 76 ++--- bridge/test/kraken_test_env.h | 22 +- 33 files changed, 467 insertions(+), 475 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 20fc0b15cb..af56625f52 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -1,11 +1,10 @@ /* -* Copyright (C) 2019 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "binding_initializer.h" - //#include "bindings/qjs/bom/blob.h" //#include "bindings/qjs/bom/console.h" //#include "bindings/qjs/bom/location.h" @@ -42,8 +41,6 @@ namespace kraken { -void initBinding() { - -} +void initBinding() {} -} +} // namespace kraken diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h index 87e3d686c6..919eab4013 100644 --- a/bridge/bindings/qjs/binding_initializer.h +++ b/bridge/bindings/qjs/binding_initializer.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_BINDING_INITIALIZER_H #define KRAKENBRIDGE_BINDING_INITIALIZER_H @@ -10,7 +10,6 @@ namespace kraken { void initBinding(); - // bindConsole(m_context); // bindTimer(m_context); // bindScreen(m_context); @@ -46,6 +45,6 @@ void initBinding(); // bindDocument(m_context); // bindPerformance(m_context); -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDING_INITIALIZER_H diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index 3f726022f4..aaec1d3287 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -30,7 +30,6 @@ std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue valu return ptr; } - std::unique_ptr stringToNativeString(const std::string& string) { std::u16string utf16; fromUTF8(string, utf16); @@ -61,6 +60,4 @@ std::string jsAtomToStdString(JSContext* ctx, JSAtom atom) { return str; } - - -} +} // namespace kraken diff --git a/bridge/bindings/qjs/native_string_utils.h b/bridge/bindings/qjs/native_string_utils.h index 5677807fe3..309c734e8e 100644 --- a/bridge/bindings/qjs/native_string_utils.h +++ b/bridge/bindings/qjs/native_string_utils.h @@ -7,10 +7,10 @@ #define KRAKENBRIDGE_NATIVE_STRING_UTILS_H #include -#include -#include -#include #include +#include +#include +#include #include "foundation/native_string.h" @@ -47,6 +47,6 @@ void fromUTF8(const std::string& source, std::basic_string -#include "event_target.h" #include "bindings/qjs/qjs_patch.h" -#include "custom_event.h" -#include "event.h" #include "core/dom/node.h" #include "core/frame/window.h" +#include "custom_event.h" +#include "event.h" +#include "event_target.h" #if UNIT_TEST #include "kraken_test_env.h" diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index cdcd0f76e9..2246c8afb2 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -6,11 +6,11 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H -#include "foundation/macros.h" -#include "bindings/qjs/macros.h" #include "bindings/qjs/heap_hashmap.h" +#include "bindings/qjs/macros.h" #include "core/executing_context.h" #include "event_listener_map.h" +#include "foundation/macros.h" #if UNIT_TEST void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index dbf820edba..a856d97cb0 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -70,32 +70,32 @@ ExecutionContext::~ExecutionContext() { 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; -// } -// } + // { + // 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. { @@ -369,7 +369,6 @@ void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01) { args_01.length = length; } - // An lock free context validator. bool isContextValid(int32_t contextId) { if (contextId > running_context_list) diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index fff00c4152..e06cee6694 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -16,13 +16,13 @@ #include #include #include +#include "bindings/qjs/garbage_collected.h" #include "foundation/macros.h" #include "foundation/ui_command_buffer.h" -#include "bindings/qjs/garbage_collected.h" +#include "dart_methods.h" #include "executing_context_data.h" #include "frame/dom_timer_coordinator.h" -#include "dart_methods.h" using JSExceptionHandler = std::function; diff --git a/bridge/core/executing_context_data.h b/bridge/core/executing_context_data.h index a24cd43e8f..90e612df4f 100644 --- a/bridge/core/executing_context_data.h +++ b/bridge/core/executing_context_data.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_CONTEXT_DATA_H #define KRAKENBRIDGE_CONTEXT_DATA_H -#include #include +#include #include "bindings/qjs/wrapper_type_info.h" namespace kraken { diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index dd4dfd98f0..cd63e0a5db 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_BLOB_H #define KRAKENBRIDGE_BLOB_H -#include "bindings/qjs/macros.h" #include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/macros.h" namespace kraken { diff --git a/bridge/core/html/html_all_collection.cc b/bridge/core/html/html_all_collection.cc index 98318d2d82..0f6894bc77 100644 --- a/bridge/core/html/html_all_collection.cc +++ b/bridge/core/html/html_all_collection.cc @@ -5,9 +5,9 @@ // //#include "html_all_collection.h" // -//namespace kraken { +// namespace kraken { // -//JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // if (argc < 1) { // return JS_NULL; // } @@ -23,7 +23,7 @@ // auto node = collection->m_nodes[index]; // return node->jsObject; //} -//JSValue AllCollection::add(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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."); // } @@ -50,7 +50,7 @@ // // return JS_NULL; //} -//JSValue AllCollection::remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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."); // } @@ -61,7 +61,7 @@ // collection->m_nodes.erase(collection->m_nodes.begin() + index); // return JS_NULL; //} -//void AllCollection::internalAdd(NodeInstance* node, NodeInstance* before) { +// 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); @@ -71,7 +71,7 @@ // } //} // -//IMPL_PROPERTY_GETTER(AllCollection, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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()); //} diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h index 9946cd154a..9483d30fd0 100644 --- a/bridge/core/html/html_all_collection.h +++ b/bridge/core/html/html_all_collection.h @@ -8,9 +8,9 @@ // //#include "bindings/qjs/garbage_collected.h" // -//namespace kraken { +// namespace kraken { // -//class HTMLAllCollection : public HostObject { +// class HTMLAllCollection : public HostObject { // public: // AllCollection(ExecutionContext* context) : HostObject(context, "AllCollection"){}; // diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index 874d50634a..a7017caf2b 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -7,28 +7,28 @@ //#include "bindings/qjs/qjs_patch.h" //#include "page.h" // -//namespace kraken { +// namespace kraken { // -//ImageElement::ImageElement(ExecutionContext* context) : Element(context) { +// ImageElement::ImageElement(ExecutionContext* context) : Element(context) { // JS_SetPrototype(m_ctx, m_prototypeObject, Element::instance(m_context)->prototype()); //} // -//void bindImageElement(ExecutionContext* context) { +// 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) { +// 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) { +// 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->getNativeProperty("width"); //} -//IMPL_PROPERTY_SETTER(ImageElement, width)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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); @@ -36,12 +36,12 @@ // element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); // return JS_NULL; //} -//IMPL_PROPERTY_GETTER(ImageElement, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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->getNativeProperty("height"); //} -//IMPL_PROPERTY_SETTER(ImageElement, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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); @@ -49,22 +49,22 @@ // element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); // return JS_NULL; //} -//IMPL_PROPERTY_GETTER(ImageElement, naturalWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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->getNativeProperty("naturalWidth"); //} -//IMPL_PROPERTY_GETTER(ImageElement, naturalHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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->getNativeProperty("naturalHeight"); //} -//IMPL_PROPERTY_GETTER(ImageElement, src)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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->getNativeProperty("src"); //} -//IMPL_PROPERTY_SETTER(ImageElement, src)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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); @@ -72,12 +72,12 @@ // element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); // return JS_NULL; //} -//IMPL_PROPERTY_GETTER(ImageElement, loading)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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->getNativeProperty("loading"); //} -//IMPL_PROPERTY_SETTER(ImageElement, loading)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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); @@ -86,12 +86,12 @@ // return JS_NULL; //} // -//ImageElementInstance::ImageElementInstance(ImageElement* element) : ElementInstance(element, "img", true) { +// ImageElementInstance::ImageElementInstance(ImageElement* element) : ElementInstance(element, "img", true) { // // Protect image instance util load or error event triggered. // refer(); //} // -//bool ImageElementInstance::dispatchEvent(EventInstance* event) { +// bool ImageElementInstance::dispatchEvent(EventInstance* event) { // std::u16string u16EventType = std::u16string(reinterpret_cast(event->nativeEvent->type->string), event->nativeEvent->type->length); // std::string eventType = toUTF8(u16EventType); // bool result = EventTargetInstance::dispatchEvent(event); diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index e1e2d051d4..4df787ea41 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -8,12 +8,12 @@ // //#include "bindings/qjs/dom/element.h" // -//namespace kraken { +// namespace kraken { // -//void bindImageElement(ExecutionContext* context); +// void bindImageElement(ExecutionContext* context); // -//class ImageElementInstance; -//class ImageElement : public Element { +// class ImageElementInstance; +// class ImageElement : public Element { // public: // ImageElement() = delete; // explicit ImageElement(ExecutionContext* context); @@ -32,7 +32,7 @@ // friend ImageElementInstance; //}; // -//class ImageElementInstance : public ElementInstance { +// class ImageElementInstance : public ElementInstance { // public: // ImageElementInstance() = delete; // explicit ImageElementInstance(ImageElement* element); diff --git a/bridge/foundation/logging.h b/bridge/foundation/logging.h index 5e2f47805a..2efe33d7af 100644 --- a/bridge/foundation/logging.h +++ b/bridge/foundation/logging.h @@ -33,18 +33,18 @@ constexpr LogSeverity LOG_NUM_SEVERITIES = 5; constexpr LogSeverity LOG_FATAL = 6; class LogMessageVoidify { -public: + public: void operator&(std::ostream&) {} }; class LogMessage { - public: + public: LogMessage(LogSeverity severity, const char* file, int line, const char* condition); ~LogMessage(); std::ostream& stream() { return stream_; } - private: + private: std::ostringstream stream_; const LogSeverity severity_; const char* file_; diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h index a65cf1ec79..85ae551827 100644 --- a/bridge/foundation/macros.h +++ b/bridge/foundation/macros.h @@ -40,5 +40,4 @@ TypeName() = delete; \ KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) - #endif // KRAKENBRIDGE_MACROS_H diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc index 305d9da950..1df95b89c3 100644 --- a/bridge/foundation/native_string.cc +++ b/bridge/foundation/native_string.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "native_string.h" #include @@ -22,5 +22,4 @@ void NativeString::free() { delete[] string; } - -} +} // namespace kraken diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h index bb86cc586d..5fd8ee84b1 100644 --- a/bridge/foundation/native_string.h +++ b/bridge/foundation/native_string.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_NATIVE_STRING_H #define KRAKENBRIDGE_NATIVE_STRING_H @@ -18,6 +18,6 @@ struct NativeString { void free(); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_NATIVE_STRING_H diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 29787e524e..47cd2ad8a5 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -5,8 +5,8 @@ #include "native_value.h" #include "bindings/qjs/qjs_patch.h" -#include "core/executing_context.h" #include "core/dom/events/event_target.h" +#include "core/executing_context.h" namespace kraken { @@ -121,13 +121,13 @@ NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { auto* functionContext = new NativeFunctionContext{context, value}; return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); } else if (JS_IsObject(value)) { -// auto* context = static_cast(JS_GetContextOpaque(ctx)); -// if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { -// auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); -// return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); -// } + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { + // auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); + // return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); + // } -// return Native_NewJSON(context, value); + // return Native_NewJSON(context, value); } return Native_NewNull(); @@ -144,19 +144,19 @@ NativeFunctionContext::~NativeFunctionContext() { } 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->callNativeMethods(call_params.c_str(), argc, arguments); -// delete[] arguments; -// return returnValue; + // 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->callNativeMethods(call_params.c_str(), argc, arguments); + // delete[] arguments; + // return returnValue; } void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int32_t contextId, const char* errmsg) { @@ -191,31 +191,31 @@ void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int } 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->callNativeMethods(call_params.c_str(), argc + 3, arguments); -// delete[] arguments; -// -// return promise; + // 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->callNativeMethods(call_params.c_str(), argc + 3, arguments); + // delete[] arguments; + // + // return promise; } JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { @@ -249,14 +249,14 @@ JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { case NativeTag::TAG_POINTER: { auto* ptr = value.u.ptr; int ptrType = (int)value.float64; -// if (ptrType == static_cast(JSPointerType::NativeBoundingClientRect)) { -// return (new BoundingClientRect(context, static_cast(ptr)))->jsObject; -// } else if (ptrType == static_cast(JSPointerType::NativeCanvasRenderingContext2D)) { -// return (new CanvasRenderingContext2D(context, static_cast(ptr)))->jsObject; -// } else if (ptrType == static_cast(JSPointerType::NativeEventTarget)) { -// auto* nativeEventTarget = static_cast(ptr); -// return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); -// } + // if (ptrType == static_cast(JSPointerType::NativeBoundingClientRect)) { + // return (new BoundingClientRect(context, static_cast(ptr)))->jsObject; + // } else if (ptrType == static_cast(JSPointerType::NativeCanvasRenderingContext2D)) { + // return (new CanvasRenderingContext2D(context, static_cast(ptr)))->jsObject; + // } else if (ptrType == static_cast(JSPointerType::NativeEventTarget)) { + // auto* nativeEventTarget = static_cast(ptr); + // return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); + // } } case NativeTag::TAG_FUNCTION: { int64_t functionId = value.u.int64; @@ -270,4 +270,4 @@ JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { return JS_NULL; } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 705aa3c335..60f9ac51ae 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -67,6 +67,6 @@ NativeValue Native_NewJSON(ExecutionContext* context, JSValue& value); NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value); JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value); -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_NATIVE_VALUE_H diff --git a/bridge/foundation/ref_ptr.h b/bridge/foundation/ref_ptr.h index ddadd0e382..3b47ebd881 100644 --- a/bridge/foundation/ref_ptr.h +++ b/bridge/foundation/ref_ptr.h @@ -13,8 +13,8 @@ #include #include "logging.h" -#include "ref_ptr_internal.h" #include "macros.h" +#include "ref_ptr_internal.h" namespace fml { diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 25563796d5..b0046fa21b 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -9,7 +9,7 @@ namespace kraken { -UICommandBuffer::UICommandBuffer(ExecutionContext *context) : m_context(context) {} +UICommandBuffer::UICommandBuffer(ExecutionContext* context) : m_context(context) {} void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate) { if (batchedUpdate) { diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index f59fd3e94f..033b7991ab 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -6,10 +6,10 @@ #ifndef KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ #define KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ -#include #include -#include "native_value.h" +#include #include "bindings/qjs/native_string_utils.h" +#include "native_value.h" namespace kraken { @@ -33,15 +33,15 @@ enum UICommand { struct UICommandItem { UICommandItem(int32_t id, int32_t type, NativeString args_01, NativeString args_02, void* nativePtr) - : type(type), - string_01(reinterpret_cast(args_01.string)), - args_01_length(args_01.length), - string_02(reinterpret_cast(args_02.string)), - args_02_length(args_02.length), - id(id), - nativePtr(reinterpret_cast(nativePtr)){}; + : type(type), + string_01(reinterpret_cast(args_01.string)), + args_01_length(args_01.length), + string_02(reinterpret_cast(args_02.string)), + args_02_length(args_02.length), + id(id), + nativePtr(reinterpret_cast(nativePtr)){}; UICommandItem(int32_t id, int32_t type, NativeString args_01, void* nativePtr) - : type(type), string_01(reinterpret_cast(args_01.string)), args_01_length(args_01.length), id(id), nativePtr(reinterpret_cast(nativePtr)){}; + : type(type), string_01(reinterpret_cast(args_01.string)), args_01_length(args_01.length), id(id), nativePtr(reinterpret_cast(nativePtr)){}; UICommandItem(int32_t id, int32_t type, void* nativePtr) : type(type), id(id), nativePtr(reinterpret_cast(nativePtr)){}; int32_t type; int32_t id; @@ -65,7 +65,7 @@ class UICommandBuffer { void clear(); private: - ExecutionContext *m_context{nullptr}; + ExecutionContext* m_context{nullptr}; std::atomic update_batched{false}; std::vector queue; }; diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 3a3328c8d3..b6877af116 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -4,15 +4,15 @@ */ #include -#include #include +#include -#include "include/kraken_bridge.h" +#include "bindings/qjs/native_string_utils.h" #include "foundation/inspector_task_queue.h" #include "foundation/logging.h" -#include "foundation/ui_task_queue.h" #include "foundation/ui_command_buffer.h" -#include "bindings/qjs/native_string_utils.h" +#include "foundation/ui_task_queue.h" +#include "include/kraken_bridge.h" #include "page.h" #if defined(_WIN32) @@ -162,9 +162,9 @@ void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t lengt } NativeScreen* createScreen(double width, double height) { -// screen.width = width; -// screen.height = height; -// return &screen; + // screen.width = width; + // screen.height = height; + // return &screen; } static KrakenInfo* krakenInfo{nullptr}; diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index 46f5dcdfe6..7ff425d4fa 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -4,9 +4,9 @@ */ #include "kraken_bridge_test.h" -#include "page_test.h" -#include "bindings/qjs/native_string_utils.h" #include +#include "bindings/qjs/native_string_utils.h" +#include "page_test.h" std::unordered_map bridgeTestPool = std::unordered_map(); diff --git a/bridge/page.cc b/bridge/page.cc index 39525901e6..3ea6052bd5 100644 --- a/bridge/page.cc +++ b/bridge/page.cc @@ -7,11 +7,10 @@ #include #include -#include "foundation/logging.h" -#include "polyfill.h" #include "bindings/qjs/binding_initializer.h" +#include "foundation/logging.h" #include "page.h" - +#include "polyfill.h" namespace kraken { @@ -24,12 +23,15 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c #if ENABLE_PROFILE auto jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); #endif - m_context = new ExecutionContext(contextId, [this](int32_t contextId, const char* message) { - if (m_context->dartMethodPtr()->onJsError != nullptr) { - m_context->dartMethodPtr()->onJsError(contextId, message); - } - KRAKEN_LOG(ERROR) << message << std::endl; - }, this); + m_context = new ExecutionContext( + contextId, + [this](int32_t contextId, const char* message) { + if (m_context->dartMethodPtr()->onJsError != nullptr) { + m_context->dartMethodPtr()->onJsError(contextId, message); + } + KRAKEN_LOG(ERROR) << message << std::endl; + }, + this); #if ENABLE_PROFILE auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; @@ -57,56 +59,56 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c } bool KrakenPage::parseHTML(const char* code, size_t length) { -// if (!m_context->isValid()) -// return false; -// JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); -// auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId)); -// HTMLParser::parseHTML(code, length, body); -// JS_FreeValue(m_context->ctx(), bodyValue); -// return true; + // if (!m_context->isValid()) + // return false; + // JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); + // auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId)); + // HTMLParser::parseHTML(code, length, body); + // JS_FreeValue(m_context->ctx(), bodyValue); + // return true; } void KrakenPage::invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* ptr, NativeString* extra) { -// if (!m_context->isValid()) -// return; -// -// JSValue eventObject = JS_NULL; -// if (ptr != nullptr) { -// std::string type = std::string(eventType); -// auto* rawEvent = static_cast(ptr)->bytes; -// Event* event = Event::create(m_context->ctx(), reinterpret_cast(rawEvent)); -// eventObject = event->toQuickJS(); -// } -// -// 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(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); -// } + // if (!m_context->isValid()) + // return; + // + // JSValue eventObject = JS_NULL; + // if (ptr != nullptr) { + // std::string type = std::string(eventType); + // auto* rawEvent = static_cast(ptr)->bytes; + // Event* event = Event::create(m_context->ctx(), reinterpret_cast(rawEvent)); + // eventObject = event->toQuickJS(); + // } + // + // 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(extra->string), extra->length); + // std::string extraString = toUTF8(u16Extra); + // extraObject = JS_ParseJSON(m_context->ctx(), extraString.c_str(), extraString.size(), ""); + // } + // + // { + // struct list_head *el, *el1; + // list_for_each_safe(el, el1, &m_context->module_job_list) { + // auto* module = list_entry(el, ModuleContext, link); + // JSValue callback = module->callback; + // + // JSValue arguments[] = {moduleNameValue, eventObject, extraObject}; + // JSValue returnValue = JS_Call(m_context->ctx(), callback, m_context->global(), 3, arguments); + // m_context->handleException(&returnValue); + // JS_FreeValue(m_context->ctx(), returnValue); + // } + // } + // + // JS_FreeValue(m_context->ctx(), moduleNameValue); + // + // if (rawEvent != nullptr) { + // JS_FreeValue(m_context->ctx(), eventObject); + // } + // if (extra != nullptr) { + // JS_FreeValue(m_context->ctx(), extraObject); + // } } void KrakenPage::evaluateScript(const NativeString* script, const char* url, int startLine) { diff --git a/bridge/page.h b/bridge/page.h index 9ced0129d1..de15d7856c 100644 --- a/bridge/page.h +++ b/bridge/page.h @@ -9,11 +9,11 @@ #include #include #include -#include #include +#include -#include "foundation/native_string.h" #include "core/executing_context.h" +#include "foundation/native_string.h" namespace kraken { diff --git a/bridge/page_test.cc b/bridge/page_test.cc index 61c6ed425a..b93e033937 100644 --- a/bridge/page_test.cc +++ b/bridge/page_test.cc @@ -39,63 +39,63 @@ static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSVa } 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(JS_GetContextOpaque(ctx)); -// -// if (!JS_IsObject(blobValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); -// } -// auto blob = static_cast(JS_GetOpaque(blobValue, kraken::Blob::kBlobClassID)); -// -// if (blob == nullptr) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); -// } -// -// if (!JS_IsString(screenShotValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be an string."); -// } -// -// if (!JS_IsObject(callbackValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); -// } -// -// if (!JS_IsFunction(ctx, callbackValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); -// } -// -// if (getDartMethod()->matchImageSnapshot == nullptr) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); -// } -// -// std::unique_ptr screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); -// auto bridge = static_cast(static_cast(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(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); + // JSValue& blobValue = argv[0]; + // JSValue& screenShotValue = argv[1]; + // JSValue& callbackValue = argv[2]; + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // + // if (!JS_IsObject(blobValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + // } + // auto blob = static_cast(JS_GetOpaque(blobValue, kraken::Blob::kBlobClassID)); + // + // if (blob == nullptr) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + // } + // + // if (!JS_IsString(screenShotValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be an string."); + // } + // + // if (!JS_IsObject(callbackValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); + // } + // + // if (!JS_IsFunction(ctx, callbackValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); + // } + // + // if (getDartMethod()->matchImageSnapshot == nullptr) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); + // } + // + // std::unique_ptr screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); + // auto bridge = static_cast(static_cast(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(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; } @@ -112,99 +112,99 @@ static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSVa } static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { -// if (getDartMethod()->simulatePointer == nullptr) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); -// } -// -// auto* context = static_cast(JS_GetContextOpaque(ctx)); -// -// JSValue inputArrayValue = argv[0]; -// if (!JS_IsObject(inputArrayValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); -// } -// -// JSValue pointerValue = argv[1]; -// if (!JS_IsNumber(pointerValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); -// } -// -// uint32_t length; -// JSValue lengthValue = JS_GetPropertyStr(ctx, inputArrayValue, "length"); -// JS_ToUint32(ctx, &length, lengthValue); -// JS_FreeValue(ctx, lengthValue); -// -// auto** mousePointerList = new MousePointer*[length]; -// -// for (int i = 0; i < length; i++) { -// auto mouse = new MousePointer(); -// JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); -// mouse->contextId = context->getContextId(); -// JSValue xValue = JS_GetPropertyUint32(ctx, params, 0); -// JSValue yValue = JS_GetPropertyUint32(ctx, params, 1); -// JSValue changeValue = JS_GetPropertyUint32(ctx, params, 2); -// -// double x; -// double y; -// double change; -// -// JS_ToFloat64(ctx, &x, xValue); -// JS_ToFloat64(ctx, &y, yValue); -// JS_ToFloat64(ctx, &change, changeValue); -// -// mouse->x = x; -// mouse->y = y; -// mouse->change = change; -// mousePointerList[i] = mouse; -// -// JS_FreeValue(ctx, params); -// JS_FreeValue(ctx, xValue); -// JS_FreeValue(ctx, yValue); -// JS_FreeValue(ctx, changeValue); -// } -// -// uint32_t pointer; -// JS_ToUint32(ctx, &pointer, pointerValue); -// -// getDartMethod()->simulatePointer(mousePointerList, length, pointer); -// -// delete[] mousePointerList; -// -// return JS_NULL; + // if (getDartMethod()->simulatePointer == nullptr) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); + // } + // + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // + // JSValue inputArrayValue = argv[0]; + // if (!JS_IsObject(inputArrayValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); + // } + // + // JSValue pointerValue = argv[1]; + // if (!JS_IsNumber(pointerValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); + // } + // + // uint32_t length; + // JSValue lengthValue = JS_GetPropertyStr(ctx, inputArrayValue, "length"); + // JS_ToUint32(ctx, &length, lengthValue); + // JS_FreeValue(ctx, lengthValue); + // + // auto** mousePointerList = new MousePointer*[length]; + // + // for (int i = 0; i < length; i++) { + // auto mouse = new MousePointer(); + // JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); + // mouse->contextId = context->getContextId(); + // JSValue xValue = JS_GetPropertyUint32(ctx, params, 0); + // JSValue yValue = JS_GetPropertyUint32(ctx, params, 1); + // JSValue changeValue = JS_GetPropertyUint32(ctx, params, 2); + // + // double x; + // double y; + // double change; + // + // JS_ToFloat64(ctx, &x, xValue); + // JS_ToFloat64(ctx, &y, yValue); + // JS_ToFloat64(ctx, &change, changeValue); + // + // mouse->x = x; + // mouse->y = y; + // mouse->change = change; + // mousePointerList[i] = mouse; + // + // JS_FreeValue(ctx, params); + // JS_FreeValue(ctx, xValue); + // JS_FreeValue(ctx, yValue); + // JS_FreeValue(ctx, changeValue); + // } + // + // uint32_t pointer; + // JS_ToUint32(ctx, &pointer, pointerValue); + // + // getDartMethod()->simulatePointer(mousePointerList, length, pointer); + // + // delete[] mousePointerList; + // + // return JS_NULL; } -static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { -// if (getDartMethod()->simulateInputText == nullptr) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); -// } -// -// JSValue& charStringValue = argv[0]; -// -// if (!JS_IsString(charStringValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); -// } -// -// std::unique_ptr nativeString = kraken::jsValueToNativeString(ctx, charStringValue); -// getDartMethod()->simulateInputText(nativeString.get()); -// nativeString->free(); -// return JS_NULL; +static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv){ + // if (getDartMethod()->simulateInputText == nullptr) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); + // } + // + // JSValue& charStringValue = argv[0]; + // + // if (!JS_IsString(charStringValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); + // } + // + // std::unique_ptr nativeString = kraken::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(JS_GetContextOpaque(ctx)); -// -// if (argc == 1) { -// JSValue& html = argv[0]; -// -// std::string strHTML = jsValueToStdString(ctx, html); -// -// JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); -// auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); -// HTMLParser::parseHTML(strHTML, body); -// -// JS_FreeValue(ctx, bodyValue); -// } -// -// return JS_NULL; + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // + // if (argc == 1) { + // JSValue& html = argv[0]; + // + // std::string strHTML = jsValueToStdString(ctx, html); + // + // JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); + // auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); + // HTMLParser::parseHTML(strHTML, body); + // + // JS_FreeValue(ctx, bodyValue); + // } + // + // return JS_NULL; } static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { @@ -222,18 +222,18 @@ static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int arg } KrakenPageTest::KrakenPageTest(KrakenPage* bridge) : m_page(bridge), m_page_context(bridge->getContext()) { -// bridge->owner = this; -// bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast(bridge->owner); }; -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); + // bridge->owner = this; + // bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast(bridge->owner); }; + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); -// initKrakenTestFramework(bridge); -// init_list_head(&image_link); + // initKrakenTestFramework(bridge); + // init_list_head(&image_link); } struct ExecuteCallbackContext { @@ -245,39 +245,39 @@ struct ExecuteCallbackContext { }; void KrakenPageTest::invokeExecuteTest(ExecuteCallback executeCallback) { -// if (JS_IsNull(executeTestCallback)) { -// return; -// } -// if (!JS_IsFunction(m_page_context->ctx(), executeTestCallback)) { -// return; -// } -// -// auto done = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) -> JSValue { -// JSValue& statusValue = argv[0]; -// JSValue proxyObject = func_data[0]; -// auto* callbackContext = static_cast(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 status = kraken::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; + // 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(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 status = kraken::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; } void KrakenPageTest::registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { diff --git a/bridge/page_test.h b/bridge/page_test.h index 59a6f4aa06..219e4d060d 100644 --- a/bridge/page_test.h +++ b/bridge/page_test.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_PAGE_TEST_H #define KRAKENBRIDGE_PAGE_TEST_H -#include "kraken_bridge_test.h" #include "core/executing_context.h" +#include "kraken_bridge_test.h" #include "page.h" namespace kraken { diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 3584813b1e..84df6a5435 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -12,21 +12,21 @@ //#include "page.h" // //#if defined(__linux__) || defined(__APPLE__) -//static int64_t get_time_ms(void) { +// static int64_t get_time_ms(void) { // struct timespec ts; // clock_gettime(CLOCK_MONOTONIC, &ts); // return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); //} //#else ///* more portable, but does not work if the date is updated */ -//static int64_t get_time_ms(void) { +// static int64_t get_time_ms(void) { // struct timeval tv; // gettimeofday(&tv, NULL); // return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); //} //#endif // -//typedef struct { +// typedef struct { // struct list_head link; // int64_t timeout; // DOMTimer* timer; @@ -35,7 +35,7 @@ // AsyncCallback func; //} JSOSTimer; // -//typedef struct { +// typedef struct { // struct list_head link; // FrameCallback* callback; // int32_t contextId; @@ -43,30 +43,30 @@ // int32_t callbackId; //} JSFrameCallback; // -//typedef struct JSThreadState { +// typedef struct JSThreadState { // std::unordered_map os_timers; /* list of timer.link */ // std::unordered_map os_frameCallbacks; //} JSThreadState; // -//static void unlink_timer(JSThreadState* ts, JSOSTimer* th) { +// static void unlink_timer(JSThreadState* ts, JSOSTimer* th) { // ts->os_timers.erase(th->timer->timerId()); //} // -//static void unlink_callback(JSThreadState* ts, JSFrameCallback* th) { +// static void unlink_callback(JSThreadState* ts, JSFrameCallback* th) { // ts->os_frameCallbacks.erase(th->callbackId); //} // -//NativeString* TEST_invokeModule(void* callbackContext, int32_t contextId, NativeString* moduleName, NativeString* method, NativeString* params, AsyncModuleCallback callback) { +// NativeString* TEST_invokeModule(void* callbackContext, int32_t contextId, NativeString* moduleName, NativeString* method, NativeString* params, AsyncModuleCallback callback) { // return nullptr; //}; // -//void TEST_requestBatchUpdate(int32_t contextId){}; +// void TEST_requestBatchUpdate(int32_t contextId){}; // -//void TEST_reloadApp(int32_t contextId) {} +// void TEST_reloadApp(int32_t contextId) {} // -//int32_t timerId = 0; +// int32_t timerId = 0; // -//int32_t TEST_setTimeout(DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { +// int32_t TEST_setTimeout(DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { // JSRuntime* rt = JS_GetRuntime(timer->ctx()); // auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); // JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); @@ -83,7 +83,7 @@ // return id; //} // -//int32_t TEST_setInterval(DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { +// int32_t TEST_setInterval(DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { // JSRuntime* rt = JS_GetRuntime(timer->ctx()); // auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); // JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); @@ -100,9 +100,9 @@ // return id; //} // -//int32_t callbackId = 0; +// int32_t callbackId = 0; // -//uint32_t TEST_requestAnimationFrame(FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { +// uint32_t TEST_requestAnimationFrame(FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { // JSRuntime* rt = JS_GetRuntime(frameCallback->ctx()); // auto* context = static_cast(JS_GetContextOpaque(frameCallback->ctx())); // JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); @@ -119,48 +119,48 @@ // return id; //} // -//void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { +// void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { // auto* page = static_cast(getPage(contextId)); // auto* context = page->getContext(); // JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); // ts->os_frameCallbacks.erase(id); //} // -//void TEST_clearTimeout(int32_t contextId, int32_t timerId) { +// void TEST_clearTimeout(int32_t contextId, int32_t timerId) { // auto* page = static_cast(getPage(contextId)); // auto* context = page->getContext(); // JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); // ts->os_timers.erase(timerId); //} // -//NativeScreen* TEST_getScreen(int32_t contextId) { +// NativeScreen* TEST_getScreen(int32_t contextId) { // return nullptr; //}; // -//double TEST_devicePixelRatio(int32_t contextId) { +// double TEST_devicePixelRatio(int32_t contextId) { // return 1.0; //} // -//NativeString* TEST_platformBrightness(int32_t contextId) { +// NativeString* TEST_platformBrightness(int32_t contextId) { // return nullptr; //} // -//void TEST_toBlob(void* callbackContext, int32_t contextId, AsyncBlobCallback blobCallback, int32_t elementId, double devicePixelRatio) {} +// void TEST_toBlob(void* callbackContext, int32_t contextId, AsyncBlobCallback blobCallback, int32_t elementId, double devicePixelRatio) {} // -//void TEST_flushUICommand() {} +// void TEST_flushUICommand() {} // -//void TEST_initWindow(int32_t contextId, void* nativePtr) {} +// void TEST_initWindow(int32_t contextId, void* nativePtr) {} // -//void TEST_initDocument(int32_t contextId, void* nativePtr) {} +// void TEST_initDocument(int32_t contextId, void* nativePtr) {} // //#if ENABLE_PROFILE -//NativePerformanceEntryList* TEST_getPerformanceEntries(int32_t) {} +// NativePerformanceEntryList* TEST_getPerformanceEntries(int32_t) {} //#endif // -//std::once_flag testInitOnceFlag; -//static int32_t inited{false}; +// std::once_flag testInitOnceFlag; +// static int32_t inited{false}; // -//std::unique_ptr TEST_init(OnJSError onJsError) { +// std::unique_ptr TEST_init(OnJSError onJsError) { // uint32_t contextId; // if (inited) { // contextId = allocateNewPage(-1); @@ -182,17 +182,17 @@ // return std::unique_ptr(page); //} // -//std::unique_ptr TEST_init() { +// std::unique_ptr TEST_init() { // return TEST_init(nullptr); //} // -//std::unique_ptr TEST_allocateNewPage() { +// std::unique_ptr TEST_allocateNewPage() { // uint32_t newContextId = allocateNewPage(-1); // initTestFramework(newContextId); // return std::unique_ptr(static_cast(getPage(newContextId))); //} // -//static bool jsPool(ExecutionContext* context) { +// static bool jsPool(ExecutionContext* context) { // JSRuntime* rt = context->runtime(); // JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); // int64_t cur_time, delay; @@ -238,7 +238,7 @@ // return false; //} // -//void TEST_runLoop(ExecutionContext* context) { +// void TEST_runLoop(ExecutionContext* context) { // for (;;) { // context->drainPendingPromiseJobs(); // if (jsPool(context)) @@ -246,7 +246,7 @@ // } //} // -//void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { +// void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { // NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); // auto nativeEventType = stringToNativeString(type); // NativeString* rawEventType = nativeEventType.release(); @@ -258,10 +258,10 @@ // NativeEventTarget::dispatchEventImpl(contextId, nativeEventTarget, rawEventType, rawEvent, false); //} // -//void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv) {} +// void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv) {} // -//std::unordered_map> unitTestEnvMap; -//std::shared_ptr TEST_getEnv(int32_t contextUniqueId) { +// std::unordered_map> unitTestEnvMap; +// std::shared_ptr TEST_getEnv(int32_t contextUniqueId) { // if (unitTestEnvMap.count(contextUniqueId) == 0) { // unitTestEnvMap[contextUniqueId] = std::make_shared(); // } @@ -269,7 +269,7 @@ // return unitTestEnvMap[contextUniqueId]; //} // -//void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { +// void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { // if (unitTestEnvMap.count(contextUniqueId) == 0) { // unitTestEnvMap[contextUniqueId] = std::make_shared(); // } @@ -277,7 +277,7 @@ // unitTestEnvMap[contextUniqueId]->onEventTargetDisposed = callback; //} // -//void TEST_mockDartMethods(OnJSError onJSError) { +// void TEST_mockDartMethods(OnJSError onJSError) { // std::vector mockMethods{ // reinterpret_cast(TEST_invokeModule), // reinterpret_cast(TEST_requestBatchUpdate), diff --git a/bridge/test/kraken_test_env.h b/bridge/test/kraken_test_env.h index 8078fd3a2b..bb3138e662 100644 --- a/bridge/test/kraken_test_env.h +++ b/bridge/test/kraken_test_env.h @@ -13,21 +13,21 @@ #include "page.h" // //// Trigger a callbacks before GC free the eventTargets. -//using TEST_OnEventTargetDisposed = void (*)(kraken::binding::qjs::EventTargetInstance* eventTargetInstance); -//struct UnitTestEnv { +// using TEST_OnEventTargetDisposed = void (*)(kraken::binding::qjs::EventTargetInstance* eventTargetInstance); +// struct UnitTestEnv { // TEST_OnEventTargetDisposed onEventTargetDisposed{nullptr}; //}; // //// Mock dart methods and add async timer to emulate kraken environment in C++ unit test. // -//std::unique_ptr TEST_init(OnJSError onJsError); -//std::unique_ptr TEST_init(); -//std::unique_ptr TEST_allocateNewPage(); -//void TEST_runLoop(ExecutionContext* context); -//void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); -//void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); -//void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); -//void TEST_mockDartMethods(OnJSError onJSError); -//std::shared_ptr TEST_getEnv(int32_t contextUniqueId); +// std::unique_ptr TEST_init(OnJSError onJsError); +// std::unique_ptr TEST_init(); +// std::unique_ptr TEST_allocateNewPage(); +// void TEST_runLoop(ExecutionContext* context); +// void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); +// void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); +// void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); +// void TEST_mockDartMethods(OnJSError onJSError); +// std::shared_ptr TEST_getEnv(int32_t contextUniqueId); #endif // KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ From 25702408954289d772914156085fa9e13be48737 Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 6 Feb 2022 22:57:43 +0800 Subject: [PATCH 013/498] chore: tmp --- bridge/CMakeLists.txt | 15 +- bridge/bindings/qjs/binding_initializer.h | 1 - bridge/bindings/qjs/dom/elements/.gitignore | 1 - bridge/bindings/qjs/exception_state.cc | 6 + bridge/bindings/qjs/exception_state.h | 21 + bridge/bindings/qjs/garbage_collected.h | 1 + bridge/bindings/qjs/qjs_function.cc | 6 + bridge/bindings/qjs/qjs_function.h | 33 ++ bridge/bindings/qjs/script_value.cc | 11 + bridge/bindings/qjs/script_value.h | 28 + bridge/{core/frame => bindings/qjs}/timer.cc | 120 +--- bridge/bindings/qjs/timer.h | 17 + .../dom/frame_request_callback_collection.h | 2 +- bridge/core/executing_context_test.cc | 8 +- bridge/core/frame/dom_timer.cc | 98 +++ bridge/core/frame/{timer.h => dom_timer.h} | 8 +- bridge/core/frame/dom_timer_coordinator.cc | 2 +- .../frame/window_or_worker_global_scope.cc | 6 + .../frame/window_or_worker_global_scope.h | 21 + bridge/foundation/macros.h | 14 + bridge/polyfill/src/index.ts | 58 +- bridge/test/kraken_test_env.cc | 557 +++++++++--------- bridge/test/kraken_test_env.h | 15 +- bridge/test/run_integration_test.cc | 6 +- 24 files changed, 622 insertions(+), 433 deletions(-) delete mode 100644 bridge/bindings/qjs/dom/elements/.gitignore create mode 100644 bridge/bindings/qjs/exception_state.cc create mode 100644 bridge/bindings/qjs/exception_state.h create mode 100644 bridge/bindings/qjs/qjs_function.cc create mode 100644 bridge/bindings/qjs/qjs_function.h create mode 100644 bridge/bindings/qjs/script_value.cc create mode 100644 bridge/bindings/qjs/script_value.h rename bridge/{core/frame => bindings/qjs}/timer.cc (54%) create mode 100644 bridge/bindings/qjs/timer.h create mode 100644 bridge/core/frame/dom_timer.cc rename bridge/core/frame/{timer.h => dom_timer.h} (84%) create mode 100644 bridge/core/frame/window_or_worker_global_scope.cc create mode 100644 bridge/core/frame/window_or_worker_global_scope.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index e0d361ab89..73fc461bba 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -197,15 +197,28 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/native_string_utils.h bindings/qjs/qjs_patch.cc bindings/qjs/qjs_patch.h - core/dart_methods.h + bindings/qjs/qjs_function.cc + bindings/qjs/qjs_function.h + bindings/qjs/script_value.cc + bindings/qjs/script_value.h + bindings/qjs/exception_state.cc + bindings/qjs/exception_state.h + + bindings/qjs/timer.cc + bindings/qjs/timer.h # Core sources core/executing_context.cc core/executing_context.h core/executing_context_data.cc core/executing_context_data.h + core/dart_methods.h + core/frame/dom_timer.cc + core/frame/dom_timer.h core/frame/dom_timer_coordinator.cc core/frame/dom_timer_coordinator.h + core/frame/window_or_worker_global_scope.cc + core/frame/window_or_worker_global_scope.h # core/dom/character_data.cc # core/dom/character_data.h # core/dom/comment.cc diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h index 87e3d686c6..a643e3dec3 100644 --- a/bridge/bindings/qjs/binding_initializer.h +++ b/bridge/bindings/qjs/binding_initializer.h @@ -10,7 +10,6 @@ namespace kraken { void initBinding(); - // bindConsole(m_context); // bindTimer(m_context); // bindScreen(m_context); 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/exception_state.cc b/bridge/bindings/qjs/exception_state.cc new file mode 100644 index 0000000000..bd0bf26877 --- /dev/null +++ b/bridge/bindings/qjs/exception_state.cc @@ -0,0 +1,6 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#include "exception_state.h" diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h new file mode 100644 index 0000000000..7bdcca9cae --- /dev/null +++ b/bridge/bindings/qjs/exception_state.h @@ -0,0 +1,21 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_EXCEPTION_STATE_H +#define KRAKENBRIDGE_EXCEPTION_STATE_H + +namespace kraken { + +// ExceptionState is a scope-like class and provides a way to throw an exception. +class ExceptionState { + public: + + private: + +}; + +} + +#endif // KRAKENBRIDGE_EXCEPTION_STATE_H diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index dec717603b..c47a027514 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -7,6 +7,7 @@ #define KRAKENBRIDGE_GARBAGE_COLLECTED_H #include +#include #include "foundation/macros.h" #include "qjs_patch.h" diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc new file mode 100644 index 0000000000..0157a1b98d --- /dev/null +++ b/bridge/bindings/qjs/qjs_function.cc @@ -0,0 +1,6 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#include "qjs_function.h" diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h new file mode 100644 index 0000000000..07a48f21c7 --- /dev/null +++ b/bridge/bindings/qjs/qjs_function.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_QJS_FUNCTION_H +#define KRAKENBRIDGE_QJS_FUNCTION_H + +#include "garbage_collected.h" + +namespace kraken { + +// https://webidl.spec.whatwg.org/#dfn-callback-interface +class QJSFunction : public GarbageCollected { + public: + static QJSFunction* create(JSContext* ctx, JSValue function) { return makeGarbageCollected(ctx, function); } + + explicit QJSFunction(JSContext* ctx, JSValue function) : m_function(JS_DupValue(ctx, function)){}; + + const char* getHumanReadableName() const override; + + [[nodiscard]] + + void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void dispose() const override; + + private: + JSValue m_function{JS_NULL}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_QJS_FUNCTION_H diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc new file mode 100644 index 0000000000..18c73ca3bb --- /dev/null +++ b/bridge/bindings/qjs/script_value.cc @@ -0,0 +1,11 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + + +#include "script_value.h" + +namespace kraken { + +} diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h new file mode 100644 index 0000000000..648a2b236e --- /dev/null +++ b/bridge/bindings/qjs/script_value.h @@ -0,0 +1,28 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_SCRIPT_VALUE_H +#define KRAKENBRIDGE_SCRIPT_VALUE_H + +#include +#include "foundation/macros.h" + +namespace kraken { + +// ScriptValue is a QuickJS JSValue wrapper which hold all information to hide out QuickJS running details. +class ScriptValue final { + KRAKEN_DISALLOW_NEW(); + + public: + explicit ScriptValue(JSContext* ctx, JSValue value): m_ctx(ctx), m_value(value) {}; + + private: + JSContext* m_ctx{nullptr}; + JSValue m_value{JS_NULL}; +}; + +} + +#endif // KRAKENBRIDGE_SCRIPT_VALUE_H diff --git a/bridge/core/frame/timer.cc b/bridge/bindings/qjs/timer.cc similarity index 54% rename from bridge/core/frame/timer.cc rename to bridge/bindings/qjs/timer.cc index 4692f694de..1035a10380 100644 --- a/bridge/core/frame/timer.cc +++ b/bridge/bindings/qjs/timer.cc @@ -1,101 +1,12 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ #include "timer.h" -#include "bindings/qjs/garbage_collected.h" -#include "bindings/qjs/qjs_patch.h" -#include "dart_methods.h" - -#if UNIT_TEST -#include "kraken_test_env.h" -#endif namespace kraken { -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."); @@ -130,9 +41,9 @@ static JSValue setTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSVal #endif // Create a timer object to keep track timer callback. - auto* timer = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(context->ctx(), &DOMTimer::classId); + auto* timer = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(context->ctx(), &DOMTimer::classId); - auto timerId = getDartMethod()->setTimeout(timer, context->getContextId(), handleTransientCallback, timeout); + auto timerId = context->dartMethodPtr()->setTimeout(timer, context->getContextId(), handleTransientCallback, timeout); // Register timerId. timer->setTimerId(timerId); @@ -174,14 +85,14 @@ static JSValue setInterval(JSContext* ctx, JSValueConst this_val, int argc, JSVa return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); } - if (getDartMethod()->setInterval == nullptr) { + if (context->dartMethodPtr()->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); + auto* timer = makeGarbageCollected(JS_DupValue(ctx, callbackValue))->initialize(context->ctx(), &DOMTimer::classId); - uint32_t timerId = getDartMethod()->setInterval(timer, context->getContextId(), handlePersistentCallback, timeout); + uint32_t timerId = context->dartMethodPtr()->setInterval(timer, context->getContextId(), handlePersistentCallback, timeout); // Register timerId. timer->setTimerId(timerId); @@ -209,20 +120,21 @@ static JSValue clearTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSV int32_t id; JS_ToInt32(ctx, &id, timeIdValue); - if (getDartMethod()->clearTimeout == nullptr) { + if (context->dartMethodPtr()->clearTimeout == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); } - getDartMethod()->clearTimeout(context->getContextId(), id); + context->dartMethodPtr()->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); + // QJS_GLOBAL_BINDING_FUNCTION(context, setTimeout, "setTimeout", 2); + // QJS_GLOBAL_BINDING_FUNCTION(context, setInterval, "setInterval", 2); + // QJS_GLOBAL_BINDING_FUNCTION(context, clearTimeout, "clearTimeout", 1); + // QJS_GLOBAL_BINDING_FUNCTION(context, clearTimeout, "clearInterval", 1); +} + } -} // namespace kraken diff --git a/bridge/bindings/qjs/timer.h b/bridge/bindings/qjs/timer.h new file mode 100644 index 0000000000..ffda495c6c --- /dev/null +++ b/bridge/bindings/qjs/timer.h @@ -0,0 +1,17 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_DOM_TIMER_H +#define KRAKENBRIDGE_TIMER_H + +#include "core/executing_context.h" + +namespace kraken { + +void bindTimer(ExecutionContext* context); + +} + +#endif // KRAKENBRIDGE_DOM_TIMER_H diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index a101d5b032..843ac237f4 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -6,7 +6,7 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ #define KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ -#include "bindings/qjs/executing_context.h" +#include "core/executing_context.h" namespace kraken { diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 367ff6d55f..f0d1d17c8d 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -90,7 +90,7 @@ TEST(Context, accessGetUICommandItemsAfterDisposed) { TEST(Context, disposeContext) { initJSPagePool(1024 * 1024); - TEST_mockDartMethods(nullptr); + TEST_mockDartMethods(0, nullptr); uint32_t contextId = 0; auto bridge = static_cast(getPage(contextId)); static bool disposed = false; @@ -159,7 +159,7 @@ 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 = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); + std::unique_ptr nativeString = kraken::jsValueToNativeString(bridge->getContext()->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++) { @@ -171,7 +171,7 @@ TEST(jsValueToNativeString, utf8String) { TEST(jsValueToNativeString, unicodeChinese) { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); JSValue str = JS_NewString(bridge->getContext()->ctx(), "这是你的优乐美"); - std::unique_ptr nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); + std::unique_ptr nativeString = kraken::jsValueToNativeString(bridge->getContext()->ctx(), str); std::u16string expectedString = u"这是你的优乐美"; EXPECT_EQ(nativeString->length, expectedString.size()); for (int i = 0; i < nativeString->length; i++) { @@ -183,7 +183,7 @@ TEST(jsValueToNativeString, unicodeChinese) { TEST(jsValueToNativeString, emoji) { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); JSValue str = JS_NewString(bridge->getContext()->ctx(), "……🤪"); - std::unique_ptr nativeString = kraken::binding::qjs::jsValueToNativeString(bridge->getContext()->ctx(), str); + std::unique_ptr nativeString = kraken::jsValueToNativeString(bridge->getContext()->ctx(), str); std::u16string expectedString = u"……🤪"; EXPECT_EQ(nativeString->length, expectedString.length()); for (int i = 0; i < nativeString->length; i++) { diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc new file mode 100644 index 0000000000..dddf15f2d1 --- /dev/null +++ b/bridge/core/frame/dom_timer.cc @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "dom_timer.h" +#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/qjs_patch.h" +#include "core/dart_methods.h" + +#if UNIT_TEST +#include "kraken_test_env.h" +#endif + +namespace kraken { + +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); +} +} // namespace kraken diff --git a/bridge/core/frame/timer.h b/bridge/core/frame/dom_timer.h similarity index 84% rename from bridge/core/frame/timer.h rename to bridge/core/frame/dom_timer.h index bc8fc1c0bc..75d503e828 100644 --- a/bridge/core/frame/timer.h +++ b/bridge/core/frame/dom_timer.h @@ -3,8 +3,8 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_TIMER_H -#define KRAKENBRIDGE_TIMER_H +#ifndef KRAKENBRIDGE_DOM_TIMER_H +#define KRAKENBRIDGE_DOM_TIMER_H #include "bindings/qjs/garbage_collected.h" #include "dom_timer_coordinator.h" @@ -33,8 +33,6 @@ class DOMTimer : public GarbageCollected { JSValue m_callback; }; -void bindTimer(ExecutionContext* context); - } // namespace kraken -#endif // KRAKENBRIDGE_TIMER_H +#endif // KRAKENBRIDGE_DOM_TIMER_H diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 9a38c60645..5d95896b04 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -6,7 +6,7 @@ #include "dom_timer_coordinator.h" #include "core/dart_methods.h" #include "core/executing_context.h" -#include "timer.h" +#include "dom_timer.h" #if UNIT_TEST #include "kraken_test_env.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..d5fc9b5410 --- /dev/null +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -0,0 +1,6 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#include "window_or_worker_global_scope.h" 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..3006afc733 --- /dev/null +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -0,0 +1,21 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H +#define KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H + +#include "core/executing_context.h" + +namespace kraken { + +class WindowOrWorkerGlobalScope { + public: + static int setTimeout(ExecutionContext* context, ); + +}; + +} + +#endif // KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h index a65cf1ec79..946c852c7d 100644 --- a/bridge/foundation/macros.h +++ b/bridge/foundation/macros.h @@ -26,6 +26,20 @@ TypeName(TypeName&&) = delete; \ TypeName& operator=(TypeName&&) = delete +// KRAKEN_DISALLOW_NEW(): Cannot be allocated with new operators but can be a +// part of object, a value object in collections or stack allocated. If it has +// Members you need a trace method and the containing object needs to call that +// trace method. +// +#define KRAKEN_DISALLOW_NEW() \ + public: \ + using IsDisallowNewMarker = int; \ + void* operator new(size_t, void* location) { return location; } \ + \ + private: \ + void* operator new(size_t) = delete; \ + + #define KRAKEN_DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&) = delete; \ TypeName& operator=(const TypeName&) = delete diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index df4ee65e9f..d73334f6fb 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -1,36 +1,36 @@ import 'es6-promise/dist/es6-promise.auto'; -import './dom'; -import './query-selector'; +// import './dom'; +// import './query-selector'; import { console } from './console'; -import { fetch, Request, Response, Headers } from './fetch'; -import { matchMedia } from './match-media'; -import { location } from './location'; -import { history } from './history'; -import { navigator } from './navigator'; -import { XMLHttpRequest } from './xhr'; -import { asyncStorage } from './async-storage'; -import { URLSearchParams } from './url-search-params'; -import { URL } from './url'; -import { kraken } from './kraken'; -import { ErrorEvent, PromiseRejectionEvent } from './events'; +// import { fetch, Request, Response, Headers } from './fetch'; +// import { matchMedia } from './match-media'; +// import { location } from './location'; +// import { history } from './history'; +// import { navigator } from './navigator'; +// import { XMLHttpRequest } from './xhr'; +// import { asyncStorage } from './async-storage'; +// import { URLSearchParams } from './url-search-params'; +// import { URL } from './url'; +// import { kraken } from './kraken'; +// import { ErrorEvent, PromiseRejectionEvent } from './events'; -defineGlobalProperty('ErrorEvent', ErrorEvent); -defineGlobalProperty('PromiseRejectionEvent', PromiseRejectionEvent); +// defineGlobalProperty('ErrorEvent', ErrorEvent); +// defineGlobalProperty('PromiseRejectionEvent', PromiseRejectionEvent); defineGlobalProperty('console', console); -defineGlobalProperty('Request', Request); -defineGlobalProperty('Response', Response); -defineGlobalProperty('Headers', Headers); -defineGlobalProperty('fetch', fetch); -defineGlobalProperty('matchMedia', matchMedia); -defineGlobalProperty('location', location); -defineGlobalProperty('history', history); -defineGlobalProperty('navigator', navigator); -defineGlobalProperty('XMLHttpRequest', XMLHttpRequest); -defineGlobalProperty('asyncStorage', asyncStorage); -defineGlobalProperty('URLSearchParams', URLSearchParams); -defineGlobalProperty('URL', URL); -defineGlobalProperty('kraken', kraken); -defineGlobalProperty('ErrorEvent', ErrorEvent); +// defineGlobalProperty('Request', Request); +// defineGlobalProperty('Response', Response); +// defineGlobalProperty('Headers', Headers); +// defineGlobalProperty('fetch', fetch); +// defineGlobalProperty('matchMedia', matchMedia); +// defineGlobalProperty('location', location); +// defineGlobalProperty('history', history); +// defineGlobalProperty('navigator', navigator); +// defineGlobalProperty('XMLHttpRequest', XMLHttpRequest); +// defineGlobalProperty('asyncStorage', asyncStorage); +// defineGlobalProperty('URLSearchParams', URLSearchParams); +// defineGlobalProperty('URL', URL); +// defineGlobalProperty('kraken', kraken); +// defineGlobalProperty('ErrorEvent', ErrorEvent); function defineGlobalProperty(key: string, value: any, isEnumerable: boolean = true) { Object.defineProperty(globalThis, key, { diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 3584813b1e..2791eccad1 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -1,251 +1,283 @@ -///* -// * Copyright (C) 2021 Alibaba Inc. All rights reserved. -// * Author: Kraken Team. -// */ -// -//#include "kraken_test_env.h" -//#include -//#include -//#include "bindings/qjs/dom/event_target.h" -//#include "dart_methods.h" -//#include "kraken_bridge_test.h" -//#include "page.h" -// -//#if defined(__linux__) || defined(__APPLE__) -//static int64_t get_time_ms(void) { -// struct timespec ts; -// clock_gettime(CLOCK_MONOTONIC, &ts); -// return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); -//} -//#else -///* more portable, but does not work if the date is updated */ -//static int64_t get_time_ms(void) { -// struct timeval tv; -// gettimeofday(&tv, NULL); -// return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); -//} -//#endif -// -//typedef struct { -// struct list_head link; -// int64_t timeout; -// DOMTimer* timer; -// int32_t contextId; -// bool isInterval; -// AsyncCallback func; -//} JSOSTimer; -// -//typedef struct { -// struct list_head link; -// FrameCallback* callback; -// int32_t contextId; -// AsyncRAFCallback handler; -// int32_t callbackId; -//} JSFrameCallback; -// -//typedef struct JSThreadState { -// std::unordered_map os_timers; /* list of timer.link */ -// std::unordered_map os_frameCallbacks; -//} JSThreadState; -// -//static void unlink_timer(JSThreadState* ts, JSOSTimer* th) { -// ts->os_timers.erase(th->timer->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) { -// return nullptr; -//}; -// -//void TEST_requestBatchUpdate(int32_t contextId){}; -// -//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(JS_GetContextOpaque(timer->ctx())); -// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); -// JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); -// th->timeout = get_time_ms() + timeout; -// th->func = callback; -// th->timer = timer; -// th->contextId = contextId; -// th->isInterval = false; -// int32_t id = timerId++; -// -// ts->os_timers[id] = th; -// -// 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(JS_GetContextOpaque(timer->ctx())); -// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); -// JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); -// th->timeout = get_time_ms() + timeout; -// th->func = callback; -// th->timer = timer; -// th->contextId = contextId; -// th->isInterval = true; -// int32_t id = timerId++; -// -// ts->os_timers[id] = th; -// -// return id; -//} -// -//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(JS_GetContextOpaque(frameCallback->ctx())); -// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); -// JSFrameCallback* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); -// th->handler = handler; -// th->callback = frameCallback; -// th->contextId = context->getContextId(); -// int32_t id = callbackId++; -// -// th->callbackId = id; -// -// ts->os_frameCallbacks[id] = th; -// -// return id; -//} -// -//void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { -// auto* page = static_cast(getPage(contextId)); -// auto* context = page->getContext(); -// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); -// ts->os_frameCallbacks.erase(id); -//} -// -//void TEST_clearTimeout(int32_t contextId, int32_t timerId) { -// auto* page = static_cast(getPage(contextId)); -// auto* context = page->getContext(); -// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); -// ts->os_timers.erase(timerId); -//} -// -//NativeScreen* TEST_getScreen(int32_t contextId) { -// return nullptr; -//}; -// -//double TEST_devicePixelRatio(int32_t contextId) { -// return 1.0; -//} -// -//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_initDocument(int32_t contextId, void* nativePtr) {} -// -//#if ENABLE_PROFILE -//NativePerformanceEntryList* TEST_getPerformanceEntries(int32_t) {} -//#endif -// -//std::once_flag testInitOnceFlag; -//static int32_t inited{false}; -// -//std::unique_ptr TEST_init(OnJSError onJsError) { -// uint32_t contextId; -// if (inited) { -// contextId = allocateNewPage(-1); -// } else { -// contextId = 0; -// } -// std::call_once(testInitOnceFlag, []() { -// initJSPagePool(1024 * 1024); -// inited = true; -// }); -// initTestFramework(contextId); -// auto* page = static_cast(getPage(contextId)); -// auto* context = page->getContext(); -// JSThreadState* th = new JSThreadState(); -// JS_SetRuntimeOpaque(context->runtime(), th); -// -// TEST_mockDartMethods(onJsError); -// -// return std::unique_ptr(page); -//} -// -//std::unique_ptr TEST_init() { -// return TEST_init(nullptr); -//} -// -//std::unique_ptr TEST_allocateNewPage() { -// uint32_t newContextId = allocateNewPage(-1); -// initTestFramework(newContextId); -// return std::unique_ptr(static_cast(getPage(newContextId))); -//} -// -//static bool jsPool(ExecutionContext* context) { -// JSRuntime* rt = context->runtime(); -// JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); -// int64_t cur_time, delay; -// struct list_head* el; -// -// if (ts->os_timers.empty() && ts->os_frameCallbacks.empty()) -// return true; /* no more events */ -// -// if (!ts->os_timers.empty()) { -// cur_time = get_time_ms(); -// for (auto& entry : ts->os_timers) { -// JSOSTimer* th = entry.second; -// delay = th->timeout - cur_time; -// if (delay <= 0) { -// AsyncCallback func; -// /* the timer expired */ -// func = th->func; -// -// if (th->isInterval) { -// func(th->timer, th->contextId, nullptr); -// } else { -// th->func = nullptr; -// func(th->timer, th->contextId, nullptr); -// unlink_timer(ts, th); -// } -// -// return false; -// } -// } -// } -// -// if (!ts->os_frameCallbacks.empty()) { -// for (auto& entry : ts->os_frameCallbacks) { -// JSFrameCallback* th = entry.second; -// AsyncRAFCallback handler = th->handler; -// th->handler = nullptr; -// handler(th->callback, th->contextId, 0, nullptr); -// unlink_callback(ts, th); -// return false; -// } -// } -// -// return false; -//} -// -//void TEST_runLoop(ExecutionContext* context) { -// for (;;) { -// context->drainPendingPromiseJobs(); -// if (jsPool(context)) -// break; -// } -//} -// +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include +#include + +#include "kraken_test_env.h" +#include "kraken_bridge_test.h" +#include "foundation/native_string.h" +#include "core/frame/dom_timer.h" +#include "core/dom/frame_request_callback_collection.h" +#include "page.h" + +#if defined(__linux__) || defined(__APPLE__) +static int64_t get_time_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); +} +#else +/* more portable, but does not work if the date is updated */ +static int64_t get_time_ms(void) { + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); +} +#endif + +typedef struct { + struct list_head link; + int64_t timeout; + kraken::DOMTimer* timer; + int32_t contextId; + bool isInterval; + AsyncCallback func; +} JSOSTimer; + +typedef struct { + struct list_head link; + kraken::FrameCallback* callback; + int32_t contextId; + AsyncRAFCallback handler; + int32_t callbackId; +} JSFrameCallback; + +typedef struct JSThreadState { + std::unordered_map os_timers; /* list of timer.link */ + std::unordered_map os_frameCallbacks; +} JSThreadState; + +static void unlink_timer(JSThreadState* ts, JSOSTimer* th) { + ts->os_timers.erase(th->timer->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) { + return nullptr; +}; + +void TEST_requestBatchUpdate(int32_t contextId){}; + +void TEST_reloadApp(int32_t contextId) {} + +int32_t timerId = 0; + +int32_t TEST_setTimeout(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { + JSRuntime* rt = JS_GetRuntime(timer->ctx()); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); + JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); + th->timeout = get_time_ms() + timeout; + th->func = callback; + th->timer = timer; + th->contextId = contextId; + th->isInterval = false; + int32_t id = timerId++; + + ts->os_timers[id] = th; + + return id; +} + +int32_t TEST_setInterval(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { + JSRuntime* rt = JS_GetRuntime(timer->ctx()); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); + JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); + th->timeout = get_time_ms() + timeout; + th->func = callback; + th->timer = timer; + th->contextId = contextId; + th->isInterval = true; + int32_t id = timerId++; + + ts->os_timers[id] = th; + + return id; +} + +int32_t callbackId = 0; + +uint32_t TEST_requestAnimationFrame(kraken::FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { + JSRuntime* rt = JS_GetRuntime(frameCallback->ctx()); + auto* context = static_cast(JS_GetContextOpaque(frameCallback->ctx())); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); + JSFrameCallback* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); + th->handler = handler; + th->callback = frameCallback; + th->contextId = context->getContextId(); + int32_t id = callbackId++; + + th->callbackId = id; + + ts->os_frameCallbacks[id] = th; + + return id; +} + +void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { + auto* page = static_cast(getPage(contextId)); + auto* context = page->getContext(); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); + ts->os_frameCallbacks.erase(id); +} + +void TEST_clearTimeout(int32_t contextId, int32_t timerId) { + auto* page = static_cast(getPage(contextId)); + auto* context = page->getContext(); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); + ts->os_timers.erase(timerId); +} + +NativeScreen* TEST_getScreen(int32_t contextId) { + return nullptr; +}; + +double TEST_devicePixelRatio(int32_t contextId) { + return 1.0; +} + +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_initDocument(int32_t contextId, void* nativePtr) {} + +#if ENABLE_PROFILE +NativePerformanceEntryList* TEST_getPerformanceEntries(int32_t) {} +#endif + +std::once_flag testInitOnceFlag; +static int32_t inited{false}; + +std::unique_ptr TEST_init(OnJSError onJsError) { + uint32_t contextId; + if (inited) { + contextId = allocateNewPage(-1); + } else { + contextId = 0; + } + std::call_once(testInitOnceFlag, []() { + initJSPagePool(1024 * 1024); + inited = true; + }); + initTestFramework(contextId); + auto* page = static_cast(getPage(contextId)); + auto* context = page->getContext(); + JSThreadState* th = new JSThreadState(); + JS_SetRuntimeOpaque(context->runtime(), th); + + TEST_mockDartMethods(contextId, onJsError); + + return std::unique_ptr(page); +} + +std::unique_ptr TEST_init() { + return TEST_init(nullptr); +} + +std::unique_ptr TEST_allocateNewPage() { + uint32_t newContextId = allocateNewPage(-1); + initTestFramework(newContextId); + return std::unique_ptr(static_cast(getPage(newContextId))); +} + +static bool jsPool(kraken::ExecutionContext* context) { + JSRuntime* rt = context->runtime(); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); + int64_t cur_time, delay; + struct list_head* el; + + if (ts->os_timers.empty() && ts->os_frameCallbacks.empty()) + return true; /* no more events */ + + if (!ts->os_timers.empty()) { + cur_time = get_time_ms(); + for (auto& entry : ts->os_timers) { + JSOSTimer* th = entry.second; + delay = th->timeout - cur_time; + if (delay <= 0) { + AsyncCallback func; + /* the timer expired */ + func = th->func; + + if (th->isInterval) { + func(th->timer, th->contextId, nullptr); + } else { + th->func = nullptr; + func(th->timer, th->contextId, nullptr); + unlink_timer(ts, th); + } + + return false; + } + } + } + + if (!ts->os_frameCallbacks.empty()) { + for (auto& entry : ts->os_frameCallbacks) { + JSFrameCallback* th = entry.second; + AsyncRAFCallback handler = th->handler; + th->handler = nullptr; + handler(th->callback, th->contextId, 0, nullptr); + unlink_callback(ts, th); + return false; + } + } + + return false; +} + +void TEST_runLoop(kraken::ExecutionContext* context) { + for (;;) { + context->drainPendingPromiseJobs(); + if (jsPool(context)) + break; + } +} + + +void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { + std::vector mockMethods{ + reinterpret_cast(TEST_invokeModule), + reinterpret_cast(TEST_requestBatchUpdate), + reinterpret_cast(TEST_reloadApp), + reinterpret_cast(TEST_setTimeout), + reinterpret_cast(TEST_setInterval), + reinterpret_cast(TEST_clearTimeout), + reinterpret_cast(TEST_requestAnimationFrame), + reinterpret_cast(TEST_cancelAnimationFrame), + reinterpret_cast(TEST_getScreen), + reinterpret_cast(TEST_devicePixelRatio), + reinterpret_cast(TEST_platformBrightness), + reinterpret_cast(TEST_toBlob), + reinterpret_cast(TEST_flushUICommand), + reinterpret_cast(TEST_initWindow), + reinterpret_cast(TEST_initDocument), + }; + +#if ENABLE_PROFILE + mockMethods.emplace_back(reinterpret_cast(TEST_getPerformanceEntries)); +#else + mockMethods.emplace_back(0); +#endif + + mockMethods.emplace_back(reinterpret_cast(onJSError)); + registerDartMethods(contextId, mockMethods.data(), mockMethods.size()); +} + //void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { // NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); // auto nativeEventType = stringToNativeString(type); @@ -276,32 +308,3 @@ // // unitTestEnvMap[contextUniqueId]->onEventTargetDisposed = callback; //} -// -//void TEST_mockDartMethods(OnJSError onJSError) { -// std::vector mockMethods{ -// reinterpret_cast(TEST_invokeModule), -// reinterpret_cast(TEST_requestBatchUpdate), -// reinterpret_cast(TEST_reloadApp), -// reinterpret_cast(TEST_setTimeout), -// reinterpret_cast(TEST_setInterval), -// reinterpret_cast(TEST_clearTimeout), -// reinterpret_cast(TEST_requestAnimationFrame), -// reinterpret_cast(TEST_cancelAnimationFrame), -// reinterpret_cast(TEST_getScreen), -// reinterpret_cast(TEST_devicePixelRatio), -// reinterpret_cast(TEST_platformBrightness), -// reinterpret_cast(TEST_toBlob), -// reinterpret_cast(TEST_flushUICommand), -// reinterpret_cast(TEST_initWindow), -// reinterpret_cast(TEST_initDocument), -// }; -// -//#if ENABLE_PROFILE -// mockMethods.emplace_back(reinterpret_cast(TEST_getPerformanceEntries)); -//#else -// mockMethods.emplace_back(0); -//#endif -// -// mockMethods.emplace_back(reinterpret_cast(onJSError)); -// registerDartMethods(mockMethods.data(), mockMethods.size()); -//} diff --git a/bridge/test/kraken_test_env.h b/bridge/test/kraken_test_env.h index 8078fd3a2b..ce3b673ad1 100644 --- a/bridge/test/kraken_test_env.h +++ b/bridge/test/kraken_test_env.h @@ -7,9 +7,8 @@ #define KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ #include -//#include "bindings/qjs/bom/timer.h" -//#include "bindings/qjs/dom/event_target.h" -//#include "bindings/qjs/dom/frame_request_callback_collection.h" +#include "core/executing_context.h" +#include "foundation/logging.h" #include "page.h" // //// Trigger a callbacks before GC free the eventTargets. @@ -20,14 +19,14 @@ // //// Mock dart methods and add async timer to emulate kraken environment in C++ unit test. // -//std::unique_ptr TEST_init(OnJSError onJsError); -//std::unique_ptr TEST_init(); -//std::unique_ptr TEST_allocateNewPage(); -//void TEST_runLoop(ExecutionContext* context); +std::unique_ptr TEST_init(OnJSError onJsError); +std::unique_ptr TEST_init(); +std::unique_ptr TEST_allocateNewPage(); +void TEST_runLoop(kraken::ExecutionContext* context); +void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); //void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); //void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); //void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); -//void TEST_mockDartMethods(OnJSError onJSError); //std::shared_ptr TEST_getEnv(int32_t contextUniqueId); #endif // KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index 96ee78bd28..732d73d78f 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -5,6 +5,7 @@ #include #include "gtest/gtest.h" +#include "foundation/logging.h" #include "kraken_bridge_test.h" #include "kraken_test_env.h" #include "page.h" @@ -36,7 +37,10 @@ TEST(IntegrationTest, runSpecs) { std::string code = readTestSpec(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - executeTest(context->getContextId(), [](int32_t contextId, NativeString* status) -> void* { KRAKEN_LOG(VERBOSE) << "done"; }); + executeTest(context->getContextId(), [](int32_t contextId, NativeString* status) -> void* { + KRAKEN_LOG(VERBOSE) << "done"; + return nullptr; + }); TEST_runLoop(context); } From e9fea9b59fc736f90c9e6237dd7b27db89ca73e2 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Mon, 7 Feb 2022 03:24:33 +0000 Subject: [PATCH 014/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 13 +- bridge/bindings/qjs/binding_initializer.h | 8 +- bridge/bindings/qjs/exception_state.cc | 6 +- bridge/bindings/qjs/exception_state.h | 10 +- bridge/bindings/qjs/native_string_utils.cc | 5 +- bridge/bindings/qjs/native_string_utils.h | 8 +- bridge/bindings/qjs/qjs_function.cc | 6 +- bridge/bindings/qjs/qjs_function.h | 3 +- bridge/bindings/qjs/script_value.cc | 11 +- bridge/bindings/qjs/script_value.h | 10 +- bridge/bindings/qjs/timer.cc | 8 +- bridge/bindings/qjs/timer.h | 6 +- bridge/core/css/css_style_declaration.h | 2 +- bridge/core/dom/events/event.h | 2 +- bridge/core/dom/events/event_target.cc | 6 +- bridge/core/dom/events/event_target.h | 4 +- bridge/core/executing_context.cc | 53 ++- bridge/core/executing_context.h | 4 +- bridge/core/executing_context_data.h | 2 +- bridge/core/fileapi/blob.h | 2 +- .../frame/window_or_worker_global_scope.cc | 6 +- .../frame/window_or_worker_global_scope.h | 9 +- bridge/core/html/html_all_collection.cc | 12 +- bridge/core/html/html_all_collection.h | 4 +- bridge/core/html/html_image_element.cc | 32 +- bridge/core/html/html_image_element.h | 10 +- bridge/foundation/logging.h | 6 +- bridge/foundation/macros.h | 16 +- bridge/foundation/native_string.cc | 9 +- bridge/foundation/native_string.h | 8 +- bridge/foundation/native_value.cc | 108 ++--- bridge/foundation/native_value.h | 2 +- bridge/foundation/ref_ptr.h | 2 +- bridge/foundation/ui_command_buffer.cc | 2 +- bridge/foundation/ui_command_buffer.h | 22 +- bridge/kraken_bridge.cc | 14 +- bridge/kraken_bridge_test.cc | 4 +- bridge/page.cc | 114 +++--- bridge/page.h | 4 +- bridge/page_test.cc | 378 +++++++++--------- bridge/page_test.h | 2 +- bridge/test/kraken_test_env.cc | 19 +- bridge/test/kraken_test_env.h | 12 +- bridge/test/run_integration_test.cc | 2 +- 44 files changed, 476 insertions(+), 490 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 20fc0b15cb..af56625f52 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -1,11 +1,10 @@ /* -* Copyright (C) 2019 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "binding_initializer.h" - //#include "bindings/qjs/bom/blob.h" //#include "bindings/qjs/bom/console.h" //#include "bindings/qjs/bom/location.h" @@ -42,8 +41,6 @@ namespace kraken { -void initBinding() { - -} +void initBinding() {} -} +} // namespace kraken diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h index a643e3dec3..919eab4013 100644 --- a/bridge/bindings/qjs/binding_initializer.h +++ b/bridge/bindings/qjs/binding_initializer.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_BINDING_INITIALIZER_H #define KRAKENBRIDGE_BINDING_INITIALIZER_H @@ -45,6 +45,6 @@ void initBinding(); // bindDocument(m_context); // bindPerformance(m_context); -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDING_INITIALIZER_H diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index bd0bf26877..ed8a8881bb 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "exception_state.h" diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index 7bdcca9cae..491e4026a3 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_EXCEPTION_STATE_H #define KRAKENBRIDGE_EXCEPTION_STATE_H @@ -11,11 +11,9 @@ namespace kraken { // ExceptionState is a scope-like class and provides a way to throw an exception. class ExceptionState { public: - private: - }; -} +} // namespace kraken #endif // KRAKENBRIDGE_EXCEPTION_STATE_H diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index 3f726022f4..aaec1d3287 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -30,7 +30,6 @@ std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue valu return ptr; } - std::unique_ptr stringToNativeString(const std::string& string) { std::u16string utf16; fromUTF8(string, utf16); @@ -61,6 +60,4 @@ std::string jsAtomToStdString(JSContext* ctx, JSAtom atom) { return str; } - - -} +} // namespace kraken diff --git a/bridge/bindings/qjs/native_string_utils.h b/bridge/bindings/qjs/native_string_utils.h index 5677807fe3..309c734e8e 100644 --- a/bridge/bindings/qjs/native_string_utils.h +++ b/bridge/bindings/qjs/native_string_utils.h @@ -7,10 +7,10 @@ #define KRAKENBRIDGE_NATIVE_STRING_UTILS_H #include -#include -#include -#include #include +#include +#include +#include #include "foundation/native_string.h" @@ -47,6 +47,6 @@ void fromUTF8(const std::string& source, std::basic_string { [[nodiscard]] - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void + trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; void dispose() const override; private: diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 18c73ca3bb..8273717306 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -1,11 +1,8 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ - + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "script_value.h" -namespace kraken { - -} +namespace kraken {} diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 648a2b236e..67a3de5a25 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_SCRIPT_VALUE_H #define KRAKENBRIDGE_SCRIPT_VALUE_H @@ -16,13 +16,13 @@ class ScriptValue final { KRAKEN_DISALLOW_NEW(); public: - explicit ScriptValue(JSContext* ctx, JSValue value): m_ctx(ctx), m_value(value) {}; + explicit ScriptValue(JSContext* ctx, JSValue value) : m_ctx(ctx), m_value(value){}; private: JSContext* m_ctx{nullptr}; JSValue m_value{JS_NULL}; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_SCRIPT_VALUE_H diff --git a/bridge/bindings/qjs/timer.cc b/bridge/bindings/qjs/timer.cc index 1035a10380..30ab10f129 100644 --- a/bridge/bindings/qjs/timer.cc +++ b/bridge/bindings/qjs/timer.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "timer.h" @@ -137,4 +137,4 @@ void bindTimer(ExecutionContext* context) { // QJS_GLOBAL_BINDING_FUNCTION(context, clearTimeout, "clearInterval", 1); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/timer.h b/bridge/bindings/qjs/timer.h index ffda495c6c..059a089019 100644 --- a/bridge/bindings/qjs/timer.h +++ b/bridge/bindings/qjs/timer.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_DOM_TIMER_H #define KRAKENBRIDGE_TIMER_H diff --git a/bridge/core/css/css_style_declaration.h b/bridge/core/css/css_style_declaration.h index 244b84d88d..40e18149a5 100644 --- a/bridge/core/css/css_style_declaration.h +++ b/bridge/core/css/css_style_declaration.h @@ -6,9 +6,9 @@ #ifndef KRAKENBRIDGE_CSS_STYLE_DECLARATION_H #define KRAKENBRIDGE_CSS_STYLE_DECLARATION_H -#include "bindings/qjs/macros.h" #include "bindings/qjs/dom/event_target.h" #include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/macros.h" namespace kraken { diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 1305fdf253..10b439188c 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_EVENT_H #define KRAKENBRIDGE_EVENT_H -#include "bindings/qjs/macros.h" #include "bindings/qjs/executing_context.h" +#include "bindings/qjs/macros.h" namespace kraken { diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 2e532dd9c3..8fb3c6c864 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -5,12 +5,12 @@ #include -#include "event_target.h" #include "bindings/qjs/qjs_patch.h" -#include "custom_event.h" -#include "event.h" #include "core/dom/node.h" #include "core/frame/window.h" +#include "custom_event.h" +#include "event.h" +#include "event_target.h" #if UNIT_TEST #include "kraken_test_env.h" diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index cdcd0f76e9..2246c8afb2 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -6,11 +6,11 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H -#include "foundation/macros.h" -#include "bindings/qjs/macros.h" #include "bindings/qjs/heap_hashmap.h" +#include "bindings/qjs/macros.h" #include "core/executing_context.h" #include "event_listener_map.h" +#include "foundation/macros.h" #if UNIT_TEST void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index dbf820edba..a856d97cb0 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -70,32 +70,32 @@ ExecutionContext::~ExecutionContext() { 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; -// } -// } + // { + // 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. { @@ -369,7 +369,6 @@ void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01) { args_01.length = length; } - // An lock free context validator. bool isContextValid(int32_t contextId) { if (contextId > running_context_list) diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index fff00c4152..e06cee6694 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -16,13 +16,13 @@ #include #include #include +#include "bindings/qjs/garbage_collected.h" #include "foundation/macros.h" #include "foundation/ui_command_buffer.h" -#include "bindings/qjs/garbage_collected.h" +#include "dart_methods.h" #include "executing_context_data.h" #include "frame/dom_timer_coordinator.h" -#include "dart_methods.h" using JSExceptionHandler = std::function; diff --git a/bridge/core/executing_context_data.h b/bridge/core/executing_context_data.h index a24cd43e8f..90e612df4f 100644 --- a/bridge/core/executing_context_data.h +++ b/bridge/core/executing_context_data.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_CONTEXT_DATA_H #define KRAKENBRIDGE_CONTEXT_DATA_H -#include #include +#include #include "bindings/qjs/wrapper_type_info.h" namespace kraken { diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index dd4dfd98f0..cd63e0a5db 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_BLOB_H #define KRAKENBRIDGE_BLOB_H -#include "bindings/qjs/macros.h" #include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/macros.h" namespace kraken { diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index d5fc9b5410..25d946dd19 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "window_or_worker_global_scope.h" diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h index 3006afc733..5a0a7942f3 100644 --- a/bridge/core/frame/window_or_worker_global_scope.h +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H #define KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H @@ -13,9 +13,8 @@ namespace kraken { class WindowOrWorkerGlobalScope { public: static int setTimeout(ExecutionContext* context, ); - }; -} +} // namespace kraken #endif // KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H diff --git a/bridge/core/html/html_all_collection.cc b/bridge/core/html/html_all_collection.cc index 98318d2d82..0f6894bc77 100644 --- a/bridge/core/html/html_all_collection.cc +++ b/bridge/core/html/html_all_collection.cc @@ -5,9 +5,9 @@ // //#include "html_all_collection.h" // -//namespace kraken { +// namespace kraken { // -//JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // if (argc < 1) { // return JS_NULL; // } @@ -23,7 +23,7 @@ // auto node = collection->m_nodes[index]; // return node->jsObject; //} -//JSValue AllCollection::add(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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."); // } @@ -50,7 +50,7 @@ // // return JS_NULL; //} -//JSValue AllCollection::remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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."); // } @@ -61,7 +61,7 @@ // collection->m_nodes.erase(collection->m_nodes.begin() + index); // return JS_NULL; //} -//void AllCollection::internalAdd(NodeInstance* node, NodeInstance* before) { +// 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); @@ -71,7 +71,7 @@ // } //} // -//IMPL_PROPERTY_GETTER(AllCollection, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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()); //} diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h index 9946cd154a..9483d30fd0 100644 --- a/bridge/core/html/html_all_collection.h +++ b/bridge/core/html/html_all_collection.h @@ -8,9 +8,9 @@ // //#include "bindings/qjs/garbage_collected.h" // -//namespace kraken { +// namespace kraken { // -//class HTMLAllCollection : public HostObject { +// class HTMLAllCollection : public HostObject { // public: // AllCollection(ExecutionContext* context) : HostObject(context, "AllCollection"){}; // diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index 874d50634a..a7017caf2b 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -7,28 +7,28 @@ //#include "bindings/qjs/qjs_patch.h" //#include "page.h" // -//namespace kraken { +// namespace kraken { // -//ImageElement::ImageElement(ExecutionContext* context) : Element(context) { +// ImageElement::ImageElement(ExecutionContext* context) : Element(context) { // JS_SetPrototype(m_ctx, m_prototypeObject, Element::instance(m_context)->prototype()); //} // -//void bindImageElement(ExecutionContext* context) { +// 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) { +// 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) { +// 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->getNativeProperty("width"); //} -//IMPL_PROPERTY_SETTER(ImageElement, width)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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); @@ -36,12 +36,12 @@ // element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); // return JS_NULL; //} -//IMPL_PROPERTY_GETTER(ImageElement, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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->getNativeProperty("height"); //} -//IMPL_PROPERTY_SETTER(ImageElement, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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); @@ -49,22 +49,22 @@ // element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); // return JS_NULL; //} -//IMPL_PROPERTY_GETTER(ImageElement, naturalWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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->getNativeProperty("naturalWidth"); //} -//IMPL_PROPERTY_GETTER(ImageElement, naturalHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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->getNativeProperty("naturalHeight"); //} -//IMPL_PROPERTY_GETTER(ImageElement, src)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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->getNativeProperty("src"); //} -//IMPL_PROPERTY_SETTER(ImageElement, src)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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); @@ -72,12 +72,12 @@ // element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); // return JS_NULL; //} -//IMPL_PROPERTY_GETTER(ImageElement, loading)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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->getNativeProperty("loading"); //} -//IMPL_PROPERTY_SETTER(ImageElement, loading)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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); @@ -86,12 +86,12 @@ // return JS_NULL; //} // -//ImageElementInstance::ImageElementInstance(ImageElement* element) : ElementInstance(element, "img", true) { +// ImageElementInstance::ImageElementInstance(ImageElement* element) : ElementInstance(element, "img", true) { // // Protect image instance util load or error event triggered. // refer(); //} // -//bool ImageElementInstance::dispatchEvent(EventInstance* event) { +// bool ImageElementInstance::dispatchEvent(EventInstance* event) { // std::u16string u16EventType = std::u16string(reinterpret_cast(event->nativeEvent->type->string), event->nativeEvent->type->length); // std::string eventType = toUTF8(u16EventType); // bool result = EventTargetInstance::dispatchEvent(event); diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index e1e2d051d4..4df787ea41 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -8,12 +8,12 @@ // //#include "bindings/qjs/dom/element.h" // -//namespace kraken { +// namespace kraken { // -//void bindImageElement(ExecutionContext* context); +// void bindImageElement(ExecutionContext* context); // -//class ImageElementInstance; -//class ImageElement : public Element { +// class ImageElementInstance; +// class ImageElement : public Element { // public: // ImageElement() = delete; // explicit ImageElement(ExecutionContext* context); @@ -32,7 +32,7 @@ // friend ImageElementInstance; //}; // -//class ImageElementInstance : public ElementInstance { +// class ImageElementInstance : public ElementInstance { // public: // ImageElementInstance() = delete; // explicit ImageElementInstance(ImageElement* element); diff --git a/bridge/foundation/logging.h b/bridge/foundation/logging.h index 5e2f47805a..2efe33d7af 100644 --- a/bridge/foundation/logging.h +++ b/bridge/foundation/logging.h @@ -33,18 +33,18 @@ constexpr LogSeverity LOG_NUM_SEVERITIES = 5; constexpr LogSeverity LOG_FATAL = 6; class LogMessageVoidify { -public: + public: void operator&(std::ostream&) {} }; class LogMessage { - public: + public: LogMessage(LogSeverity severity, const char* file, int line, const char* condition); ~LogMessage(); std::ostream& stream() { return stream_; } - private: + private: std::ostringstream stream_; const LogSeverity severity_; const char* file_; diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h index 946c852c7d..47ad3e7c15 100644 --- a/bridge/foundation/macros.h +++ b/bridge/foundation/macros.h @@ -31,14 +31,13 @@ // Members you need a trace method and the containing object needs to call that // trace method. // -#define KRAKEN_DISALLOW_NEW() \ - public: \ - using IsDisallowNewMarker = int; \ - void* operator new(size_t, void* location) { return location; } \ - \ - private: \ - void* operator new(size_t) = delete; \ - +#define KRAKEN_DISALLOW_NEW() \ + public: \ + using IsDisallowNewMarker = int; \ + void* operator new(size_t, void* location) { return location; } \ + \ + private: \ + void* operator new(size_t) = delete; #define KRAKEN_DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&) = delete; \ @@ -54,5 +53,4 @@ TypeName() = delete; \ KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) - #endif // KRAKENBRIDGE_MACROS_H diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc index 305d9da950..1df95b89c3 100644 --- a/bridge/foundation/native_string.cc +++ b/bridge/foundation/native_string.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "native_string.h" #include @@ -22,5 +22,4 @@ void NativeString::free() { delete[] string; } - -} +} // namespace kraken diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h index bb86cc586d..5fd8ee84b1 100644 --- a/bridge/foundation/native_string.h +++ b/bridge/foundation/native_string.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_NATIVE_STRING_H #define KRAKENBRIDGE_NATIVE_STRING_H @@ -18,6 +18,6 @@ struct NativeString { void free(); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_NATIVE_STRING_H diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 29787e524e..47cd2ad8a5 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -5,8 +5,8 @@ #include "native_value.h" #include "bindings/qjs/qjs_patch.h" -#include "core/executing_context.h" #include "core/dom/events/event_target.h" +#include "core/executing_context.h" namespace kraken { @@ -121,13 +121,13 @@ NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { auto* functionContext = new NativeFunctionContext{context, value}; return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); } else if (JS_IsObject(value)) { -// auto* context = static_cast(JS_GetContextOpaque(ctx)); -// if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { -// auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); -// return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); -// } + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { + // auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); + // return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); + // } -// return Native_NewJSON(context, value); + // return Native_NewJSON(context, value); } return Native_NewNull(); @@ -144,19 +144,19 @@ NativeFunctionContext::~NativeFunctionContext() { } 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->callNativeMethods(call_params.c_str(), argc, arguments); -// delete[] arguments; -// return returnValue; + // 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->callNativeMethods(call_params.c_str(), argc, arguments); + // delete[] arguments; + // return returnValue; } void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int32_t contextId, const char* errmsg) { @@ -191,31 +191,31 @@ void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int } 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->callNativeMethods(call_params.c_str(), argc + 3, arguments); -// delete[] arguments; -// -// return promise; + // 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->callNativeMethods(call_params.c_str(), argc + 3, arguments); + // delete[] arguments; + // + // return promise; } JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { @@ -249,14 +249,14 @@ JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { case NativeTag::TAG_POINTER: { auto* ptr = value.u.ptr; int ptrType = (int)value.float64; -// if (ptrType == static_cast(JSPointerType::NativeBoundingClientRect)) { -// return (new BoundingClientRect(context, static_cast(ptr)))->jsObject; -// } else if (ptrType == static_cast(JSPointerType::NativeCanvasRenderingContext2D)) { -// return (new CanvasRenderingContext2D(context, static_cast(ptr)))->jsObject; -// } else if (ptrType == static_cast(JSPointerType::NativeEventTarget)) { -// auto* nativeEventTarget = static_cast(ptr); -// return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); -// } + // if (ptrType == static_cast(JSPointerType::NativeBoundingClientRect)) { + // return (new BoundingClientRect(context, static_cast(ptr)))->jsObject; + // } else if (ptrType == static_cast(JSPointerType::NativeCanvasRenderingContext2D)) { + // return (new CanvasRenderingContext2D(context, static_cast(ptr)))->jsObject; + // } else if (ptrType == static_cast(JSPointerType::NativeEventTarget)) { + // auto* nativeEventTarget = static_cast(ptr); + // return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); + // } } case NativeTag::TAG_FUNCTION: { int64_t functionId = value.u.int64; @@ -270,4 +270,4 @@ JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { return JS_NULL; } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 705aa3c335..60f9ac51ae 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -67,6 +67,6 @@ NativeValue Native_NewJSON(ExecutionContext* context, JSValue& value); NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value); JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value); -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_NATIVE_VALUE_H diff --git a/bridge/foundation/ref_ptr.h b/bridge/foundation/ref_ptr.h index ddadd0e382..3b47ebd881 100644 --- a/bridge/foundation/ref_ptr.h +++ b/bridge/foundation/ref_ptr.h @@ -13,8 +13,8 @@ #include #include "logging.h" -#include "ref_ptr_internal.h" #include "macros.h" +#include "ref_ptr_internal.h" namespace fml { diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 25563796d5..b0046fa21b 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -9,7 +9,7 @@ namespace kraken { -UICommandBuffer::UICommandBuffer(ExecutionContext *context) : m_context(context) {} +UICommandBuffer::UICommandBuffer(ExecutionContext* context) : m_context(context) {} void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate) { if (batchedUpdate) { diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index f59fd3e94f..033b7991ab 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -6,10 +6,10 @@ #ifndef KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ #define KRAKENBRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ -#include #include -#include "native_value.h" +#include #include "bindings/qjs/native_string_utils.h" +#include "native_value.h" namespace kraken { @@ -33,15 +33,15 @@ enum UICommand { struct UICommandItem { UICommandItem(int32_t id, int32_t type, NativeString args_01, NativeString args_02, void* nativePtr) - : type(type), - string_01(reinterpret_cast(args_01.string)), - args_01_length(args_01.length), - string_02(reinterpret_cast(args_02.string)), - args_02_length(args_02.length), - id(id), - nativePtr(reinterpret_cast(nativePtr)){}; + : type(type), + string_01(reinterpret_cast(args_01.string)), + args_01_length(args_01.length), + string_02(reinterpret_cast(args_02.string)), + args_02_length(args_02.length), + id(id), + nativePtr(reinterpret_cast(nativePtr)){}; UICommandItem(int32_t id, int32_t type, NativeString args_01, void* nativePtr) - : type(type), string_01(reinterpret_cast(args_01.string)), args_01_length(args_01.length), id(id), nativePtr(reinterpret_cast(nativePtr)){}; + : type(type), string_01(reinterpret_cast(args_01.string)), args_01_length(args_01.length), id(id), nativePtr(reinterpret_cast(nativePtr)){}; UICommandItem(int32_t id, int32_t type, void* nativePtr) : type(type), id(id), nativePtr(reinterpret_cast(nativePtr)){}; int32_t type; int32_t id; @@ -65,7 +65,7 @@ class UICommandBuffer { void clear(); private: - ExecutionContext *m_context{nullptr}; + ExecutionContext* m_context{nullptr}; std::atomic update_batched{false}; std::vector queue; }; diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 3a3328c8d3..b6877af116 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -4,15 +4,15 @@ */ #include -#include #include +#include -#include "include/kraken_bridge.h" +#include "bindings/qjs/native_string_utils.h" #include "foundation/inspector_task_queue.h" #include "foundation/logging.h" -#include "foundation/ui_task_queue.h" #include "foundation/ui_command_buffer.h" -#include "bindings/qjs/native_string_utils.h" +#include "foundation/ui_task_queue.h" +#include "include/kraken_bridge.h" #include "page.h" #if defined(_WIN32) @@ -162,9 +162,9 @@ void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t lengt } NativeScreen* createScreen(double width, double height) { -// screen.width = width; -// screen.height = height; -// return &screen; + // screen.width = width; + // screen.height = height; + // return &screen; } static KrakenInfo* krakenInfo{nullptr}; diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index 46f5dcdfe6..7ff425d4fa 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -4,9 +4,9 @@ */ #include "kraken_bridge_test.h" -#include "page_test.h" -#include "bindings/qjs/native_string_utils.h" #include +#include "bindings/qjs/native_string_utils.h" +#include "page_test.h" std::unordered_map bridgeTestPool = std::unordered_map(); diff --git a/bridge/page.cc b/bridge/page.cc index 39525901e6..3ea6052bd5 100644 --- a/bridge/page.cc +++ b/bridge/page.cc @@ -7,11 +7,10 @@ #include #include -#include "foundation/logging.h" -#include "polyfill.h" #include "bindings/qjs/binding_initializer.h" +#include "foundation/logging.h" #include "page.h" - +#include "polyfill.h" namespace kraken { @@ -24,12 +23,15 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c #if ENABLE_PROFILE auto jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); #endif - m_context = new ExecutionContext(contextId, [this](int32_t contextId, const char* message) { - if (m_context->dartMethodPtr()->onJsError != nullptr) { - m_context->dartMethodPtr()->onJsError(contextId, message); - } - KRAKEN_LOG(ERROR) << message << std::endl; - }, this); + m_context = new ExecutionContext( + contextId, + [this](int32_t contextId, const char* message) { + if (m_context->dartMethodPtr()->onJsError != nullptr) { + m_context->dartMethodPtr()->onJsError(contextId, message); + } + KRAKEN_LOG(ERROR) << message << std::endl; + }, + this); #if ENABLE_PROFILE auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; @@ -57,56 +59,56 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c } bool KrakenPage::parseHTML(const char* code, size_t length) { -// if (!m_context->isValid()) -// return false; -// JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); -// auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId)); -// HTMLParser::parseHTML(code, length, body); -// JS_FreeValue(m_context->ctx(), bodyValue); -// return true; + // if (!m_context->isValid()) + // return false; + // JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); + // auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId)); + // HTMLParser::parseHTML(code, length, body); + // JS_FreeValue(m_context->ctx(), bodyValue); + // return true; } void KrakenPage::invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* ptr, NativeString* extra) { -// if (!m_context->isValid()) -// return; -// -// JSValue eventObject = JS_NULL; -// if (ptr != nullptr) { -// std::string type = std::string(eventType); -// auto* rawEvent = static_cast(ptr)->bytes; -// Event* event = Event::create(m_context->ctx(), reinterpret_cast(rawEvent)); -// eventObject = event->toQuickJS(); -// } -// -// 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(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); -// } + // if (!m_context->isValid()) + // return; + // + // JSValue eventObject = JS_NULL; + // if (ptr != nullptr) { + // std::string type = std::string(eventType); + // auto* rawEvent = static_cast(ptr)->bytes; + // Event* event = Event::create(m_context->ctx(), reinterpret_cast(rawEvent)); + // eventObject = event->toQuickJS(); + // } + // + // 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(extra->string), extra->length); + // std::string extraString = toUTF8(u16Extra); + // extraObject = JS_ParseJSON(m_context->ctx(), extraString.c_str(), extraString.size(), ""); + // } + // + // { + // struct list_head *el, *el1; + // list_for_each_safe(el, el1, &m_context->module_job_list) { + // auto* module = list_entry(el, ModuleContext, link); + // JSValue callback = module->callback; + // + // JSValue arguments[] = {moduleNameValue, eventObject, extraObject}; + // JSValue returnValue = JS_Call(m_context->ctx(), callback, m_context->global(), 3, arguments); + // m_context->handleException(&returnValue); + // JS_FreeValue(m_context->ctx(), returnValue); + // } + // } + // + // JS_FreeValue(m_context->ctx(), moduleNameValue); + // + // if (rawEvent != nullptr) { + // JS_FreeValue(m_context->ctx(), eventObject); + // } + // if (extra != nullptr) { + // JS_FreeValue(m_context->ctx(), extraObject); + // } } void KrakenPage::evaluateScript(const NativeString* script, const char* url, int startLine) { diff --git a/bridge/page.h b/bridge/page.h index 9ced0129d1..de15d7856c 100644 --- a/bridge/page.h +++ b/bridge/page.h @@ -9,11 +9,11 @@ #include #include #include -#include #include +#include -#include "foundation/native_string.h" #include "core/executing_context.h" +#include "foundation/native_string.h" namespace kraken { diff --git a/bridge/page_test.cc b/bridge/page_test.cc index 61c6ed425a..b93e033937 100644 --- a/bridge/page_test.cc +++ b/bridge/page_test.cc @@ -39,63 +39,63 @@ static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSVa } 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(JS_GetContextOpaque(ctx)); -// -// if (!JS_IsObject(blobValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); -// } -// auto blob = static_cast(JS_GetOpaque(blobValue, kraken::Blob::kBlobClassID)); -// -// if (blob == nullptr) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); -// } -// -// if (!JS_IsString(screenShotValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be an string."); -// } -// -// if (!JS_IsObject(callbackValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); -// } -// -// if (!JS_IsFunction(ctx, callbackValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); -// } -// -// if (getDartMethod()->matchImageSnapshot == nullptr) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); -// } -// -// std::unique_ptr screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); -// auto bridge = static_cast(static_cast(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(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); + // JSValue& blobValue = argv[0]; + // JSValue& screenShotValue = argv[1]; + // JSValue& callbackValue = argv[2]; + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // + // if (!JS_IsObject(blobValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + // } + // auto blob = static_cast(JS_GetOpaque(blobValue, kraken::Blob::kBlobClassID)); + // + // if (blob == nullptr) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + // } + // + // if (!JS_IsString(screenShotValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be an string."); + // } + // + // if (!JS_IsObject(callbackValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); + // } + // + // if (!JS_IsFunction(ctx, callbackValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); + // } + // + // if (getDartMethod()->matchImageSnapshot == nullptr) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); + // } + // + // std::unique_ptr screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); + // auto bridge = static_cast(static_cast(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(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; } @@ -112,99 +112,99 @@ static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSVa } static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { -// if (getDartMethod()->simulatePointer == nullptr) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); -// } -// -// auto* context = static_cast(JS_GetContextOpaque(ctx)); -// -// JSValue inputArrayValue = argv[0]; -// if (!JS_IsObject(inputArrayValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); -// } -// -// JSValue pointerValue = argv[1]; -// if (!JS_IsNumber(pointerValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); -// } -// -// uint32_t length; -// JSValue lengthValue = JS_GetPropertyStr(ctx, inputArrayValue, "length"); -// JS_ToUint32(ctx, &length, lengthValue); -// JS_FreeValue(ctx, lengthValue); -// -// auto** mousePointerList = new MousePointer*[length]; -// -// for (int i = 0; i < length; i++) { -// auto mouse = new MousePointer(); -// JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); -// mouse->contextId = context->getContextId(); -// JSValue xValue = JS_GetPropertyUint32(ctx, params, 0); -// JSValue yValue = JS_GetPropertyUint32(ctx, params, 1); -// JSValue changeValue = JS_GetPropertyUint32(ctx, params, 2); -// -// double x; -// double y; -// double change; -// -// JS_ToFloat64(ctx, &x, xValue); -// JS_ToFloat64(ctx, &y, yValue); -// JS_ToFloat64(ctx, &change, changeValue); -// -// mouse->x = x; -// mouse->y = y; -// mouse->change = change; -// mousePointerList[i] = mouse; -// -// JS_FreeValue(ctx, params); -// JS_FreeValue(ctx, xValue); -// JS_FreeValue(ctx, yValue); -// JS_FreeValue(ctx, changeValue); -// } -// -// uint32_t pointer; -// JS_ToUint32(ctx, &pointer, pointerValue); -// -// getDartMethod()->simulatePointer(mousePointerList, length, pointer); -// -// delete[] mousePointerList; -// -// return JS_NULL; + // if (getDartMethod()->simulatePointer == nullptr) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); + // } + // + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // + // JSValue inputArrayValue = argv[0]; + // if (!JS_IsObject(inputArrayValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); + // } + // + // JSValue pointerValue = argv[1]; + // if (!JS_IsNumber(pointerValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); + // } + // + // uint32_t length; + // JSValue lengthValue = JS_GetPropertyStr(ctx, inputArrayValue, "length"); + // JS_ToUint32(ctx, &length, lengthValue); + // JS_FreeValue(ctx, lengthValue); + // + // auto** mousePointerList = new MousePointer*[length]; + // + // for (int i = 0; i < length; i++) { + // auto mouse = new MousePointer(); + // JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); + // mouse->contextId = context->getContextId(); + // JSValue xValue = JS_GetPropertyUint32(ctx, params, 0); + // JSValue yValue = JS_GetPropertyUint32(ctx, params, 1); + // JSValue changeValue = JS_GetPropertyUint32(ctx, params, 2); + // + // double x; + // double y; + // double change; + // + // JS_ToFloat64(ctx, &x, xValue); + // JS_ToFloat64(ctx, &y, yValue); + // JS_ToFloat64(ctx, &change, changeValue); + // + // mouse->x = x; + // mouse->y = y; + // mouse->change = change; + // mousePointerList[i] = mouse; + // + // JS_FreeValue(ctx, params); + // JS_FreeValue(ctx, xValue); + // JS_FreeValue(ctx, yValue); + // JS_FreeValue(ctx, changeValue); + // } + // + // uint32_t pointer; + // JS_ToUint32(ctx, &pointer, pointerValue); + // + // getDartMethod()->simulatePointer(mousePointerList, length, pointer); + // + // delete[] mousePointerList; + // + // return JS_NULL; } -static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { -// if (getDartMethod()->simulateInputText == nullptr) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); -// } -// -// JSValue& charStringValue = argv[0]; -// -// if (!JS_IsString(charStringValue)) { -// return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); -// } -// -// std::unique_ptr nativeString = kraken::jsValueToNativeString(ctx, charStringValue); -// getDartMethod()->simulateInputText(nativeString.get()); -// nativeString->free(); -// return JS_NULL; +static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv){ + // if (getDartMethod()->simulateInputText == nullptr) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); + // } + // + // JSValue& charStringValue = argv[0]; + // + // if (!JS_IsString(charStringValue)) { + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); + // } + // + // std::unique_ptr nativeString = kraken::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(JS_GetContextOpaque(ctx)); -// -// if (argc == 1) { -// JSValue& html = argv[0]; -// -// std::string strHTML = jsValueToStdString(ctx, html); -// -// JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); -// auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); -// HTMLParser::parseHTML(strHTML, body); -// -// JS_FreeValue(ctx, bodyValue); -// } -// -// return JS_NULL; + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // + // if (argc == 1) { + // JSValue& html = argv[0]; + // + // std::string strHTML = jsValueToStdString(ctx, html); + // + // JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); + // auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); + // HTMLParser::parseHTML(strHTML, body); + // + // JS_FreeValue(ctx, bodyValue); + // } + // + // return JS_NULL; } static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { @@ -222,18 +222,18 @@ static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int arg } KrakenPageTest::KrakenPageTest(KrakenPage* bridge) : m_page(bridge), m_page_context(bridge->getContext()) { -// bridge->owner = this; -// bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast(bridge->owner); }; -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); -// QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); + // bridge->owner = this; + // bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast(bridge->owner); }; + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); -// initKrakenTestFramework(bridge); -// init_list_head(&image_link); + // initKrakenTestFramework(bridge); + // init_list_head(&image_link); } struct ExecuteCallbackContext { @@ -245,39 +245,39 @@ struct ExecuteCallbackContext { }; void KrakenPageTest::invokeExecuteTest(ExecuteCallback executeCallback) { -// if (JS_IsNull(executeTestCallback)) { -// return; -// } -// if (!JS_IsFunction(m_page_context->ctx(), executeTestCallback)) { -// return; -// } -// -// auto done = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) -> JSValue { -// JSValue& statusValue = argv[0]; -// JSValue proxyObject = func_data[0]; -// auto* callbackContext = static_cast(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 status = kraken::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; + // 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(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 status = kraken::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; } void KrakenPageTest::registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { diff --git a/bridge/page_test.h b/bridge/page_test.h index 59a6f4aa06..219e4d060d 100644 --- a/bridge/page_test.h +++ b/bridge/page_test.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_PAGE_TEST_H #define KRAKENBRIDGE_PAGE_TEST_H -#include "kraken_bridge_test.h" #include "core/executing_context.h" +#include "kraken_bridge_test.h" #include "page.h" namespace kraken { diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 2791eccad1..d9d397a36b 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -6,11 +6,11 @@ #include #include -#include "kraken_test_env.h" -#include "kraken_bridge_test.h" -#include "foundation/native_string.h" -#include "core/frame/dom_timer.h" #include "core/dom/frame_request_callback_collection.h" +#include "core/frame/dom_timer.h" +#include "foundation/native_string.h" +#include "kraken_bridge_test.h" +#include "kraken_test_env.h" #include "page.h" #if defined(__linux__) || defined(__APPLE__) @@ -248,7 +248,6 @@ void TEST_runLoop(kraken::ExecutionContext* context) { } } - void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { std::vector mockMethods{ reinterpret_cast(TEST_invokeModule), @@ -278,7 +277,7 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { registerDartMethods(contextId, mockMethods.data(), mockMethods.size()); } -//void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { +// void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { // NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); // auto nativeEventType = stringToNativeString(type); // NativeString* rawEventType = nativeEventType.release(); @@ -290,10 +289,10 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { // NativeEventTarget::dispatchEventImpl(contextId, nativeEventTarget, rawEventType, rawEvent, false); //} // -//void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv) {} +// void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv) {} // -//std::unordered_map> unitTestEnvMap; -//std::shared_ptr TEST_getEnv(int32_t contextUniqueId) { +// std::unordered_map> unitTestEnvMap; +// std::shared_ptr TEST_getEnv(int32_t contextUniqueId) { // if (unitTestEnvMap.count(contextUniqueId) == 0) { // unitTestEnvMap[contextUniqueId] = std::make_shared(); // } @@ -301,7 +300,7 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { // return unitTestEnvMap[contextUniqueId]; //} // -//void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { +// void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { // if (unitTestEnvMap.count(contextUniqueId) == 0) { // unitTestEnvMap[contextUniqueId] = std::make_shared(); // } diff --git a/bridge/test/kraken_test_env.h b/bridge/test/kraken_test_env.h index ce3b673ad1..d30fcb74bc 100644 --- a/bridge/test/kraken_test_env.h +++ b/bridge/test/kraken_test_env.h @@ -12,8 +12,8 @@ #include "page.h" // //// Trigger a callbacks before GC free the eventTargets. -//using TEST_OnEventTargetDisposed = void (*)(kraken::binding::qjs::EventTargetInstance* eventTargetInstance); -//struct UnitTestEnv { +// using TEST_OnEventTargetDisposed = void (*)(kraken::binding::qjs::EventTargetInstance* eventTargetInstance); +// struct UnitTestEnv { // TEST_OnEventTargetDisposed onEventTargetDisposed{nullptr}; //}; // @@ -24,9 +24,9 @@ std::unique_ptr TEST_init(); std::unique_ptr TEST_allocateNewPage(); void TEST_runLoop(kraken::ExecutionContext* context); void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); -//void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); -//void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); -//void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); -//std::shared_ptr TEST_getEnv(int32_t contextUniqueId); +// void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); +// void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); +// void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); +// std::shared_ptr TEST_getEnv(int32_t contextUniqueId); #endif // KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index 732d73d78f..5ae3929e14 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -4,8 +4,8 @@ */ #include -#include "gtest/gtest.h" #include "foundation/logging.h" +#include "gtest/gtest.h" #include "kraken_bridge_test.h" #include "kraken_test_env.h" #include "page.h" From 63f27a70e2089dd7d9e1aa9dea6c156779cd0d7a Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Mon, 7 Feb 2022 20:13:34 +0800 Subject: [PATCH 015/498] refactor: refactor initialize and refactor timer. --- bridge/CMakeLists.txt | 11 +- bridge/bindings/qjs/binding_initializer.cc | 6 +- bridge/bindings/qjs/binding_initializer.h | 39 +---- bridge/bindings/qjs/exception_state.cc | 32 +++++ bridge/bindings/qjs/exception_state.h | 16 ++- bridge/bindings/qjs/garbage_collected.h | 9 +- bridge/bindings/qjs/member_installer.cc | 32 +++++ bridge/bindings/qjs/member_installer.h | 50 +++++++ bridge/bindings/qjs/qjs_function.cc | 38 +++++ bridge/bindings/qjs/qjs_function.h | 14 +- .../bindings/qjs/{timer.cc => qjs_window.cc} | 71 +++++----- bridge/bindings/qjs/qjs_window.h | 20 +++ bridge/bindings/qjs/rejected_promises.cc | 4 +- bridge/bindings/qjs/rejected_promises.h | 2 +- bridge/bindings/qjs/script_value.cc | 16 ++- bridge/bindings/qjs/script_value.h | 9 +- bridge/bindings/qjs/timer.h | 17 --- bridge/bindings/qjs/visitor.cc | 14 ++ bridge/bindings/qjs/visitor.h | 26 ++++ bridge/core/dart_methods.h | 3 + bridge/core/dom/events/event_target.h | 2 +- bridge/core/executing_context.cc | 133 +++++++++--------- bridge/core/executing_context.h | 9 +- bridge/core/frame/dom_timer.cc | 65 ++------- bridge/core/frame/dom_timer.h | 7 +- bridge/core/frame/dom_timer_coordinator.cc | 6 +- bridge/core/frame/dom_timer_coordinator.h | 3 +- .../frame/window_or_worker_global_scope.cc | 101 +++++++++++++ .../frame/window_or_worker_global_scope.h | 6 +- bridge/foundation/native_value.cc | 1 - bridge/page.cc | 2 +- 31 files changed, 521 insertions(+), 243 deletions(-) create mode 100644 bridge/bindings/qjs/member_installer.cc create mode 100644 bridge/bindings/qjs/member_installer.h rename bridge/bindings/qjs/{timer.cc => qjs_window.cc} (61%) create mode 100644 bridge/bindings/qjs/qjs_window.h delete mode 100644 bridge/bindings/qjs/timer.h create mode 100644 bridge/bindings/qjs/visitor.cc create mode 100644 bridge/bindings/qjs/visitor.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 73fc461bba..1f56bf3668 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -190,6 +190,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") # Binding files bindings/qjs/binding_initializer.cc bindings/qjs/binding_initializer.h + bindings/qjs/member_installer.cc + bindings/qjs/member_installer.h bindings/qjs/garbage_collected.h bindings/qjs/wrapper_type_info.h bindings/qjs/heap_hashmap.h @@ -203,9 +205,12 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/script_value.h bindings/qjs/exception_state.cc bindings/qjs/exception_state.h - - bindings/qjs/timer.cc - bindings/qjs/timer.h + bindings/qjs/visitor.cc + bindings/qjs/visitor.h + bindings/qjs/rejected_promises.h + bindings/qjs/rejected_promises.cc + bindings/qjs/qjs_window.cc + bindings/qjs/qjs_window.h # Core sources core/executing_context.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index af56625f52..dde7f1260f 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -5,6 +5,8 @@ #include "binding_initializer.h" +#include "qjs_window.h" + //#include "bindings/qjs/bom/blob.h" //#include "bindings/qjs/bom/console.h" //#include "bindings/qjs/bom/location.h" @@ -41,6 +43,8 @@ namespace kraken { -void initBinding() {} +void installBindings(JSContext* ctx) { + QJSWindow::installGlobalFunctions(ctx); +} } // namespace kraken diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h index 919eab4013..8af0324b22 100644 --- a/bridge/bindings/qjs/binding_initializer.h +++ b/bridge/bindings/qjs/binding_initializer.h @@ -6,44 +6,11 @@ #ifndef KRAKENBRIDGE_BINDING_INITIALIZER_H #define KRAKENBRIDGE_BINDING_INITIALIZER_H -namespace kraken { +#include -void initBinding(); +namespace kraken { -// bindConsole(m_context); -// bindTimer(m_context); -// bindScreen(m_context); -// bindModuleManager(m_context); -// bindEventTarget(m_context); -// bindBlob(m_context); -// bindLocation(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); -// 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); +void installBindings(JSContext* ctx); } // namespace kraken diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index ed8a8881bb..e6d2a95d56 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -4,3 +4,35 @@ */ #include "exception_state.h" + +namespace kraken { + +void ExceptionState::throwException(JSContext* ctx, ErrorType type, const char* message) { + switch(type) { + case ErrorType::TypeError: + m_exception = JS_ThrowTypeError(ctx, "%s", message); + break; + case InternalError : + m_exception = JS_ThrowInternalError(ctx, "%s", message); + break; + case RangeError: + m_exception = JS_ThrowRangeError(ctx, "%s", message); + break; + case ReferenceError: + m_exception = JS_ThrowReferenceError(ctx, "%s", message); + break; + case SyntaxError: + m_exception = JS_ThrowSyntaxError(ctx, "%s", message); + break; + } +} + +bool ExceptionState::hasException() { + return !JS_IsNull(m_exception); +} + +JSValue ExceptionState::toQuickJS() { + return m_exception; +} + +} diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index 491e4026a3..0c03a27658 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -6,12 +6,26 @@ #ifndef KRAKENBRIDGE_EXCEPTION_STATE_H #define KRAKENBRIDGE_EXCEPTION_STATE_H +#include + namespace kraken { -// ExceptionState is a scope-like class and provides a way to throw an exception. +enum ErrorType { + TypeError, + InternalError, + RangeError, + ReferenceError, + SyntaxError +}; + +// ExceptionState is a scope-like class and provides a way to store an exception. class ExceptionState { public: + void throwException(JSContext* ctx, ErrorType type, const char* message); + bool hasException(); + JSValue toQuickJS(); private: + JSValue m_exception{JS_NULL}; }; } // namespace kraken diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index c47a027514..a3de7d8d8a 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -11,6 +11,7 @@ #include "foundation/macros.h" #include "qjs_patch.h" +#include "visitor.h" namespace kraken { @@ -55,7 +56,7 @@ class GarbageCollected { * This Trace method must be override by objects inheriting from * GarbageCollected. */ - virtual void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func) const = 0; + virtual void trace(Visitor* visitor) const = 0; /** * Called before underline JavaScript object been collected by GC. @@ -81,6 +82,7 @@ class GarbageCollected { JSContext* m_ctx{nullptr}; JSRuntime* m_runtime{nullptr}; GarbageCollected(){}; + GarbageCollected(JSContext* ctx): m_runtime(JS_GetRuntime(ctx)), m_ctx(ctx) {}; friend class MakeGarbageCollectedTrait; }; @@ -102,7 +104,7 @@ P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId, JSClassEx 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. + /// ClassId should be a static toQuickJS 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); @@ -116,7 +118,8 @@ P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId, JSClassEx /// 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); + Visitor visitor{rt, mark_func}; + object->trace(&visitor); }; /// Define custom behavior when call getProperty, setProperty on object. diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc new file mode 100644 index 0000000000..bc05ea884d --- /dev/null +++ b/bridge/bindings/qjs/member_installer.cc @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "member_installer.h" + +namespace kraken { + +int combinePropFlags(JSPropFlag a, JSPropFlag b) { + return a | b; +} +int combinePropFlags(JSPropFlag a, JSPropFlag b, JSPropFlag c) { + return a | b | c; +} + +void MemberInstaller::installAttributes(JSContext* ctx, JSValue root, std::initializer_list config) { + for (auto& c : config) { + JS_DefinePropertyValueStr(ctx, root, c.name, JS_DupValue(ctx, c.value), c.flag); + } +} + +int compilePropFlags(); + +void MemberInstaller::installFunctions(JSContext* ctx, JSValue root, std::initializer_list config) { + for (auto& c : config) { + JSValue function = JS_NewCFunction(ctx, c.function, c.name, c.length); + JS_DefinePropertyValueStr(ctx, root, c.name, function, c.flag); + } +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h new file mode 100644 index 0000000000..a00ad53484 --- /dev/null +++ b/bridge/bindings/qjs/member_installer.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MEMBER_INSTALLER_H +#define KRAKENBRIDGE_MEMBER_INSTALLER_H + +#include +#include + +namespace kraken { + +// 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; + JSValue value; + int flag; // Flags for object properties. + }; + + struct FunctionConfig { + FunctionConfig& operator=(const FunctionConfig&) = delete; + const char* name; + JSCFunction* function; + size_t length; + int flag; // Flags for object properties. + }; + + static void installAttributes(JSContext* ctx, JSValue root, std::initializer_list); + static void installFunctions(JSContext* ctx, JSValue root, std::initializer_list); +}; + +} + +#endif // KRAKENBRIDGE_MEMBER_INSTALLER_H diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 29bd5b626d..ef80477813 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -4,3 +4,41 @@ */ #include "qjs_function.h" +#include + +namespace kraken { + +bool QJSFunction::isFunction(JSContext* ctx) { + return JS_IsFunction(ctx, m_function); +} + +ScriptValue QJSFunction::invoke(JSContext* ctx, 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, m_function); + + JSValue argv[std::max(1, argc)]; + + for(int i = 0; i < argc; i ++) { + argv[0 + i] = arguments[i].toQuickJS(); + } + + JSValue returnValue = JS_Call(ctx, m_function, JS_UNDEFINED, argc, argv); + + // Free the previous duplicated function. + JS_FreeValue(m_ctx, m_function); + + return ScriptValue(ctx, returnValue); +} + +const char* QJSFunction::getHumanReadableName() const { + return "QJSFunction"; +} + +void QJSFunction::trace(Visitor* visitor) const { + visitor->trace(m_function); +} + +void QJSFunction::dispose() const { + JS_FreeValueRT(m_runtime, m_function); +} +} diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index c6613c6924..d4f61fabce 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -7,6 +7,7 @@ #define KRAKENBRIDGE_QJS_FUNCTION_H #include "garbage_collected.h" +#include "script_value.h" namespace kraken { @@ -14,15 +15,16 @@ namespace kraken { class QJSFunction : public GarbageCollected { public: static QJSFunction* create(JSContext* ctx, JSValue function) { return makeGarbageCollected(ctx, function); } + explicit QJSFunction(JSContext* ctx, JSValue function) : m_function(JS_DupValue(ctx, function)), GarbageCollected(ctx) {}; - explicit QJSFunction(JSContext* ctx, JSValue function) : m_function(JS_DupValue(ctx, function)){}; + bool isFunction(JSContext* ctx); - const char* getHumanReadableName() const override; - - [[nodiscard]] + // Performs "invoke". + // https://webidl.spec.whatwg.org/#invoke-a-callback-function + ScriptValue invoke(JSContext* ctx, int32_t argc, ScriptValue* arguments); - void - trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + const char* getHumanReadableName() const override; + void trace(Visitor* visitor) const override; void dispose() const override; private: diff --git a/bridge/bindings/qjs/timer.cc b/bridge/bindings/qjs/qjs_window.cc similarity index 61% rename from bridge/bindings/qjs/timer.cc rename to bridge/bindings/qjs/qjs_window.cc index 30ab10f129..207a4c978b 100644 --- a/bridge/bindings/qjs/timer.cc +++ b/bridge/bindings/qjs/qjs_window.cc @@ -3,7 +3,13 @@ * Author: Kraken Team. */ -#include "timer.h" +#include "qjs_window.h" +#include +#include "qjs_function.h" +#include "exception_state.h" +#include "member_installer.h" +#include "core/executing_context.h" +#include "core/frame/window_or_worker_global_scope.h" namespace kraken { @@ -34,21 +40,14 @@ static JSValue setTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSVal 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 = context->dartMethodPtr()->setTimeout(timer, context->getContextId(), handleTransientCallback, timeout); + QJSFunction* handler = QJSFunction::create(ctx, callbackValue); + ExceptionState exceptionState; - // Register timerId. - timer->setTimerId(timerId); + int32_t timerId = WindowOrWorkerGlobalScope::setTimeout(context, handler, timeout, &exceptionState); - context->timers()->installNewTimer(context, timerId, timer); + if (exceptionState.hasException()) { + return exceptionState.toQuickJS(); + } // `-1` represents ffi error occurred. if (timerId == -1) { @@ -85,18 +84,13 @@ static JSValue setInterval(JSContext* ctx, JSValueConst this_val, int argc, JSVa return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); } - if (context->dartMethodPtr()->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); + QJSFunction* handler = QJSFunction::create(ctx, callbackValue); + ExceptionState exception; + int32_t timerId = WindowOrWorkerGlobalScope::setInterval(context, handler, timeout, &exception); - uint32_t timerId = context->dartMethodPtr()->setInterval(timer, context->getContextId(), handlePersistentCallback, timeout); - - // Register timerId. - timer->setTimerId(timerId); - context->timers()->installNewTimer(context, timerId, timer); + if (exception.hasException()) { + return exception.toQuickJS(); + } if (timerId == -1) { return JS_ThrowTypeError(ctx, "Failed to execute 'setInterval': dart method (setInterval) got unexpected error."); @@ -120,21 +114,26 @@ static JSValue clearTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSV int32_t id; JS_ToInt32(ctx, &id, timeIdValue); - if (context->dartMethodPtr()->clearTimeout == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); - } + ExceptionState exception; + WindowOrWorkerGlobalScope::clearTimeout(context, id, &exception); - context->dartMethodPtr()->clearTimeout(context->getContextId(), id); + if (exception.hasException()) { + return exception.toQuickJS(); + } - 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); +void QJSWindow::installGlobalFunctions(JSContext* ctx) { + std::initializer_list functionConfig { + {"setTimeout", setTimeout, 2, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + {"setInterval", setInterval, 2, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + {"clearTimeout", clearTimeout, 0, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + }; + + JSValue globalObject = JS_GetGlobalObject(ctx); + MemberInstaller::installFunctions(ctx, globalObject, functionConfig); + JS_FreeValue(ctx, globalObject); } -} // namespace kraken +} diff --git a/bridge/bindings/qjs/qjs_window.h b/bridge/bindings/qjs/qjs_window.h new file mode 100644 index 0000000000..8ca006e17a --- /dev/null +++ b/bridge/bindings/qjs/qjs_window.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_QJS_WINDOW_H +#define KRAKENBRIDGE_QJS_WINDOW_H + +#include + +namespace kraken { + +class QJSWindow final { + public: + static void installGlobalFunctions(JSContext* ctx); +}; + +} + +#endif // KRAKENBRIDGE_QJS_WINDOW_H diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 3af332f73f..3db40c5679 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -4,9 +4,9 @@ */ #include "rejected_promises.h" -#include "executing_context.h" +#include "core/executing_context.h" -namespace kraken::binding::qjs { +namespace kraken { RejectedPromises::Message::Message(ExecutionContext* context, JSValue promise, JSValue reason) : m_runtime(context->runtime()), m_promise(JS_DupValue(context->ctx(), promise)), m_reason(JS_DupValue(context->ctx(), reason)) {} diff --git a/bridge/bindings/qjs/rejected_promises.h b/bridge/bindings/qjs/rejected_promises.h index 4c7ceaf973..bbbc87ee31 100644 --- a/bridge/bindings/qjs/rejected_promises.h +++ b/bridge/bindings/qjs/rejected_promises.h @@ -11,7 +11,7 @@ #include #include -namespace kraken::binding::qjs { +namespace kraken { class ExecutionContext; diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 8273717306..7a6a2b51dc 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -5,4 +5,18 @@ #include "script_value.h" -namespace kraken {} +namespace kraken { + +bool ScriptValue::isEmpty() { + return JS_IsNull(m_value); +} + +JSValue ScriptValue::toQuickJS() { + return m_value; +} + +bool ScriptValue::isException() { + return JS_IsException(m_value); +} + +} diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 67a3de5a25..ff6d87edbb 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -16,7 +16,14 @@ class ScriptValue final { KRAKEN_DISALLOW_NEW(); public: - explicit ScriptValue(JSContext* ctx, JSValue value) : m_ctx(ctx), m_value(value){}; + explicit ScriptValue(JSContext* ctx, JSValue value) : m_ctx(ctx), m_value(JS_DupValue(ctx, value)){}; + ~ScriptValue() { + JS_FreeValue(m_ctx, m_value); + } + bool isEmpty(); + JSValue toQuickJS(); + + bool isException(); private: JSContext* m_ctx{nullptr}; diff --git a/bridge/bindings/qjs/timer.h b/bridge/bindings/qjs/timer.h deleted file mode 100644 index 059a089019..0000000000 --- a/bridge/bindings/qjs/timer.h +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_DOM_TIMER_H -#define KRAKENBRIDGE_TIMER_H - -#include "core/executing_context.h" - -namespace kraken { - -void bindTimer(ExecutionContext* context); - -} - -#endif // KRAKENBRIDGE_DOM_TIMER_H diff --git a/bridge/bindings/qjs/visitor.cc b/bridge/bindings/qjs/visitor.cc new file mode 100644 index 0000000000..407f7aacbf --- /dev/null +++ b/bridge/bindings/qjs/visitor.cc @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "visitor.h" + +namespace kraken { + +void Visitor::trace(JSValue value) { + JS_MarkValue(m_runtime, value, m_markFunc); +} + +} diff --git a/bridge/bindings/qjs/visitor.h b/bridge/bindings/qjs/visitor.h new file mode 100644 index 0000000000..73920a4ad7 --- /dev/null +++ b/bridge/bindings/qjs/visitor.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_VISITOR_H +#define KRAKENBRIDGE_VISITOR_H + +#include + +namespace kraken { + +class Visitor final { + public: + explicit Visitor(JSRuntime* rt, JS_MarkFunc* markFunc): m_runtime(rt), m_markFunc(markFunc) {}; + + void trace(JSValue value); + + private: + JSRuntime* m_runtime{nullptr}; + JS_MarkFunc* m_markFunc{nullptr}; +}; + +} + +#endif // KRAKENBRIDGE_VISITOR_H diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index 9e61daeb24..ef1de832a9 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -6,6 +6,9 @@ #ifndef KRAKEN_DART_METHODS_H_ #define KRAKEN_DART_METHODS_H_ +/// Functions implements at dart side, including timer, Rendering and module API. +/// Communicate via Dart FFI. + #include "kraken_bridge.h" #include diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 2246c8afb2..6392a40f8e 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -65,7 +65,7 @@ class EventTarget : public GarbageCollected { DEFINE_FUNCTION(removeEventListener); DEFINE_FUNCTION(dispatchEvent); - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void trace(Visitor* visitor) const override; void dispose() const override; virtual bool dispatchEvent(Event* event); diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 5da08b1c7c..62a1d2559c 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -21,9 +21,9 @@ std::unique_ptr createJSContext(int32_t contextId, const JSExc static JSRuntime* m_runtime{nullptr}; -void ExecutionContextGCTracker::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { +void ExecutionContextGCTracker::trace(Visitor* visitor) const { auto* context = static_cast(JS_GetContextOpaque(m_ctx)); - context->trace(rt, context->global(), mark_func); + context->trace(visitor); } void ExecutionContextGCTracker::dispose() const {} @@ -187,8 +187,8 @@ void* ExecutionContext::getOwner() { return owner; } -bool ExecutionContext::handleException(JSValue* exception) { - if (JS_IsException(*exception)) { +bool ExecutionContext::handleException(JSValue* exc) { + if (JS_IsException(*exc)) { JSValue error = JS_GetException(m_ctx); reportError(error); dispatchGlobalErrorEvent(this, error); @@ -199,6 +199,11 @@ bool ExecutionContext::handleException(JSValue* exception) { return true; } +bool ExecutionContext::handleException(ScriptValue* exc) { + JSValue value = exc->toQuickJS(); + handleException(&value); +} + JSValue ExecutionContext::global() { return globalObject; } @@ -283,65 +288,65 @@ uint8_t* ExecutionContext::dumpByteCode(const char* code, uint32_t codeLength, c } void ExecutionContext::dispatchGlobalErrorEvent(ExecutionContext* context, JSValueConst error) { - JSContext* ctx = context->ctx(); - auto* window = static_cast(JS_GetOpaque(context->global(), Window::classId())); - - { - JSValue ErrorEventValue = JS_GetPropertyStr(ctx, context->global(), "ErrorEvent"); - JSValue errorType = JS_NewString(ctx, "error"); - JSValue errorInit = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, errorInit, "error", JS_DupValue(ctx, error)); - JS_SetPropertyStr(ctx, errorInit, "message", JS_GetPropertyStr(ctx, error, "message")); - JS_SetPropertyStr(ctx, errorInit, "lineno", JS_GetPropertyStr(ctx, error, "lineNumber")); - JS_SetPropertyStr(ctx, errorInit, "filename", JS_GetPropertyStr(ctx, error, "fileName")); - JS_SetPropertyStr(ctx, errorInit, "colno", JS_NewUint32(ctx, 0)); - JSValue arguments[] = {errorType, errorInit}; - JSValue errorEventValue = JS_CallConstructor(context->ctx(), ErrorEventValue, 2, arguments); - if (JS_IsException(errorEventValue)) { - context->handleException(&errorEventValue); - return; - } - - auto* errorEvent = static_cast(JS_GetOpaque(errorEventValue, Event::kEventClassID)); - window->dispatchEvent(errorEvent); - - JS_FreeValue(ctx, ErrorEventValue); - JS_FreeValue(ctx, errorEventValue); - JS_FreeValue(ctx, errorType); - JS_FreeValue(ctx, errorInit); - - context->drainPendingPromiseJobs(); - } +// JSContext* ctx = context->ctx(); +// auto* window = static_cast(JS_GetOpaque(context->global(), Window::classId())); +// +// { +// JSValue ErrorEventValue = JS_GetPropertyStr(ctx, context->global(), "ErrorEvent"); +// JSValue errorType = JS_NewString(ctx, "error"); +// JSValue errorInit = JS_NewObject(ctx); +// JS_SetPropertyStr(ctx, errorInit, "error", JS_DupValue(ctx, error)); +// JS_SetPropertyStr(ctx, errorInit, "message", JS_GetPropertyStr(ctx, error, "message")); +// JS_SetPropertyStr(ctx, errorInit, "lineno", JS_GetPropertyStr(ctx, error, "lineNumber")); +// JS_SetPropertyStr(ctx, errorInit, "filename", JS_GetPropertyStr(ctx, error, "fileName")); +// JS_SetPropertyStr(ctx, errorInit, "colno", JS_NewUint32(ctx, 0)); +// JSValue arguments[] = {errorType, errorInit}; +// JSValue errorEventValue = JS_CallConstructor(context->ctx(), ErrorEventValue, 2, arguments); +// if (JS_IsException(errorEventValue)) { +// context->handleException(&errorEventValue); +// return; +// } +// +// auto* errorEvent = static_cast(JS_GetOpaque(errorEventValue, Event::kEventClassID)); +// window->dispatchEvent(errorEvent); +// +// JS_FreeValue(ctx, ErrorEventValue); +// JS_FreeValue(ctx, errorEventValue); +// JS_FreeValue(ctx, errorType); +// JS_FreeValue(ctx, errorInit); +// +// context->drainPendingPromiseJobs(); +// } } static void dispatchPromiseRejectionEvent(const char* eventType, 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)); - window->dispatchEvent(rejectEvent); - - JS_FreeValue(ctx, errorType); - JS_FreeValue(ctx, errorInit); - JS_FreeValue(ctx, rejectEventValue); - JS_FreeValue(ctx, PromiseRejectionEventValue); - - context->drainPendingPromiseJobs(); - } +// 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)); +// 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) { @@ -420,6 +425,10 @@ DOMTimerCoordinator* ExecutionContext::timers() { return &m_timers; } +void ExecutionContext::trace(Visitor* visitor) { + m_timers.trace(visitor); +} + void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01) { if (!JS_IsString(key)) return; @@ -504,8 +513,4 @@ JSValue objectGetKeys(JSContext* ctx, JSValue obj) { return result; } -void ExecutionContext::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - m_timers.trace(rt, JS_NULL, mark_func); -} - } // namespace kraken diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 4285b913e7..664a5729a3 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -17,13 +17,14 @@ #include #include #include "bindings/qjs/garbage_collected.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 "rejected_promises.h" using JSExceptionHandler = std::function; @@ -56,7 +57,7 @@ class ExecutionContextGCTracker : public GarbageCollected& dartMethodPtr() { return m_dartMethodPtr; } - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); + void trace(Visitor* visitor); std::chrono::time_point timeOrigin; std::unordered_map constructorMap; diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index dddf15f2d1..6ed91a101c 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -6,7 +6,7 @@ #include "dom_timer.h" #include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/qjs_patch.h" -#include "core/dart_methods.h" +#include "core/executing_context.h" #if UNIT_TEST #include "kraken_test_env.h" @@ -14,33 +14,28 @@ namespace kraken { -DOMTimer::DOMTimer(JSValue callback) : m_callback(callback) {} +DOMTimer::DOMTimer(QJSFunction* 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)) + if (!m_callback->isFunction(m_ctx)) 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); + ScriptValue returnValue = m_callback->invoke(m_ctx, 0, nullptr); - if (JS_IsException(returnValue)) { + if (returnValue.isException()) { 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::trace(Visitor* visitor) const { + m_callback->trace(visitor); } void DOMTimer::dispose() const { - JS_FreeValueRT(m_runtime, m_callback); + m_callback->dispose(); } int32_t DOMTimer::timerId() { @@ -51,48 +46,4 @@ 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); -} } // namespace kraken diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index 75d503e828..336bab831f 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -7,6 +7,7 @@ #define KRAKENBRIDGE_DOM_TIMER_H #include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/qjs_function.h" #include "dom_timer_coordinator.h" namespace kraken { @@ -14,7 +15,7 @@ namespace kraken { class DOMTimer : public GarbageCollected { public: static JSClassID classId; - DOMTimer(JSValue callback); + DOMTimer(QJSFunction* callback); // Trigger timer callback. void fire(); @@ -24,13 +25,13 @@ class DOMTimer : public GarbageCollected { [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "DOMTimer"; } - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void trace(Visitor* visitor) const override; void dispose() const override; private: int32_t m_timerId{-1}; int32_t m_isInterval{false}; - JSValue m_callback; + QJSFunction* m_callback; }; } // namespace kraken diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 5d95896b04..44564109d3 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -66,15 +66,15 @@ DOMTimer* DOMTimerCoordinator::getTimerById(int32_t timerId) { return m_activeTimers[timerId]; } -void DOMTimerCoordinator::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { +void DOMTimerCoordinator::trace(Visitor* visitor) { for (auto& timer : m_activeTimers) { - JS_MarkValue(rt, timer.second->toQuickJS(), mark_func); + visitor->trace(timer.second->toQuickJS()); } // Recycle all abandoned timers. if (!m_abandonedTimers.empty()) { for (auto& timer : m_abandonedTimers) { - JS_MarkValue(rt, timer->toQuickJS(), mark_func); + visitor->trace(timer->toQuickJS()); } // All abandoned timers should be freed at the sweep stage. m_abandonedTimers.clear(); diff --git a/bridge/core/frame/dom_timer_coordinator.h b/bridge/core/frame/dom_timer_coordinator.h index f165562bfd..2ff4ec217c 100644 --- a/bridge/core/frame/dom_timer_coordinator.h +++ b/bridge/core/frame/dom_timer_coordinator.h @@ -9,6 +9,7 @@ #include #include #include +#include "bindings/qjs/visitor.h" namespace kraken { @@ -30,7 +31,7 @@ class DOMTimerCoordinator { void* removeTimeoutById(int32_t timerId); DOMTimer* getTimerById(int32_t timerId); - void trace(JSRuntime* rt, JSValueConst val, JS_MarkFunc* mark_func); + void trace(Visitor* visitor); private: std::unordered_map m_activeTimers; diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 25d946dd19..64235b926e 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -4,3 +4,104 @@ */ #include "window_or_worker_global_scope.h" +#include "core/frame/dom_timer.h" + +namespace kraken { + +static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { + auto* context = 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); +} + + +int WindowOrWorkerGlobalScope::setTimeout(ExecutionContext* context, 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 = makeGarbageCollected(handler)->initialize(context->ctx(), &DOMTimer::classId); + + auto timerId = context->dartMethodPtr()->setTimeout(timer, context->getContextId(), handleTransientCallback, timeout); + + // Register timerId. + timer->setTimerId(timerId); + + context->timers()->installNewTimer(context, timerId, timer); + + return timerId; +} + +int WindowOrWorkerGlobalScope::setInterval(ExecutionContext* context, 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 = makeGarbageCollected(handler)->initialize(context->ctx(), &DOMTimer::classId); + + uint32_t timerId = context->dartMethodPtr()->setInterval(timer, context->getContextId(), handlePersistentCallback, timeout); + + // Register timerId. + timer->setTimerId(timerId); + context->timers()->installNewTimer(context, timerId, timer); + + return timerId; +} + +void WindowOrWorkerGlobalScope::clearTimeout(ExecutionContext* 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->getContextId(), timerId); + + context->timers()->removeTimeoutById(timerId); +} + +} diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h index 5a0a7942f3..c134db2ed7 100644 --- a/bridge/core/frame/window_or_worker_global_scope.h +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -7,12 +7,16 @@ #define KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H #include "core/executing_context.h" +#include "bindings/qjs/qjs_function.h" +#include "bindings/qjs/exception_state.h" namespace kraken { class WindowOrWorkerGlobalScope { public: - static int setTimeout(ExecutionContext* context, ); + static int setTimeout(ExecutionContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception); + static int setInterval(ExecutionContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception); + static void clearTimeout(ExecutionContext* context, int32_t timerId, ExceptionState* exception); }; } // namespace kraken diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index af21e736ca..2e1c7d70d9 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -5,7 +5,6 @@ #include "native_value.h" #include "bindings/qjs/qjs_patch.h" -#include "core/dom/events/event_target.h" #include "core/executing_context.h" namespace kraken { diff --git a/bridge/page.cc b/bridge/page.cc index 3ea6052bd5..6b7c7e968b 100644 --- a/bridge/page.cc +++ b/bridge/page.cc @@ -40,7 +40,7 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_START); #endif - initBinding(); + installBindings(m_context->ctx()); #if ENABLE_PROFILE nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); From a5bf7b40d5fd849f83fd35dda091cb4c99821cc2 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Tue, 8 Feb 2022 21:45:18 +0800 Subject: [PATCH 016/498] refactor: refactor invokeModule and refactor test framework. --- bridge/CMakeLists.txt | 29 ++- bridge/bindings/qjs/exception_state.cc | 4 + bridge/bindings/qjs/exception_state.h | 5 + bridge/bindings/qjs/garbage_collected.h | 8 +- .../qjs/{visitor.cc => gc_visitor.cc} | 4 +- bridge/bindings/qjs/gc_visitor.h | 27 ++ bridge/bindings/qjs/native_string_utils.cc | 2 +- .../qjs/{qjs_patch.cc => qjs_engine_patch.cc} | 2 +- .../qjs/{qjs_patch.h => qjs_engine_patch.h} | 6 +- bridge/bindings/qjs/qjs_function.cc | 2 +- bridge/bindings/qjs/qjs_function.h | 2 +- bridge/bindings/qjs/qjs_module_manager.cc | 97 +++++++ bridge/bindings/qjs/qjs_module_manager.h | 24 ++ bridge/bindings/qjs/qjs_page.cc | 14 ++ bridge/bindings/qjs/qjs_page.h | 20 ++ bridge/bindings/qjs/qjs_patch_test.cc | 2 +- bridge/bindings/qjs/script_value.cc | 45 ++++ bridge/bindings/qjs/script_value.h | 29 ++- bridge/bindings/qjs/visitor.h | 26 -- bridge/bindings/qjs/wrapper_type_info.h | 2 +- bridge/core/dart_methods.h | 10 +- bridge/core/dom/events/custom_event.cc | 2 +- bridge/core/dom/events/event.cc | 2 +- bridge/core/dom/events/event_target.cc | 2 +- bridge/core/dom/events/touch_event.cc | 2 +- .../dom/frame_request_callback_collection.cc | 4 +- .../dom/frame_request_callback_collection.h | 2 +- bridge/core/dom/node.cc | 2 +- bridge/core/executing_context.cc | 46 +++- bridge/core/executing_context.h | 27 +- bridge/core/executing_context_test.cc | 3 + bridge/core/frame/dom_timer.cc | 4 +- bridge/core/frame/dom_timer.h | 2 +- bridge/core/frame/dom_timer_coordinator.cc | 4 +- bridge/core/frame/dom_timer_coordinator.h | 4 +- bridge/core/frame/module_callback.cc | 24 ++ bridge/core/frame/module_callback.h | 36 +++ .../core/frame/module_callback_coordinator.cc | 32 +++ .../core/frame/module_callback_coordinator.h | 33 +++ bridge/core/frame/module_listener.cc | 20 ++ bridge/core/frame/module_listener.h | 29 +++ .../core/frame/module_listener_container.cc | 20 ++ bridge/core/frame/module_listener_container.h | 26 ++ bridge/core/frame/module_manager.cc | 159 ++++-------- bridge/core/frame/module_manager.h | 14 +- bridge/core/frame/screen.h | 22 +- bridge/core/frame/window.cc | 2 +- .../frame/window_or_worker_global_scope.cc | 4 - bridge/core/html/html_template_element.cc | 2 +- bridge/{ => core}/page.cc | 50 +--- bridge/{ => core}/page.h | 8 - bridge/foundation/logging.cc | 2 +- bridge/foundation/native_value.cc | 2 +- bridge/kraken_bridge.cc | 4 +- bridge/kraken_bridge_test.cc | 20 +- bridge/page_test.h | 62 ----- bridge/polyfill/scripts/js_to_c.js | 12 +- bridge/polyfill/src/index.ts | 1 - .../kraken_test_context.cc} | 237 +++++++++--------- bridge/test/kraken_test_context.h | 43 ++++ bridge/test/kraken_test_env.cc | 36 +-- bridge/test/kraken_test_env.h | 18 +- bridge/test/test.cmake | 4 +- 63 files changed, 904 insertions(+), 484 deletions(-) rename bridge/bindings/qjs/{visitor.cc => gc_visitor.cc} (71%) create mode 100644 bridge/bindings/qjs/gc_visitor.h rename bridge/bindings/qjs/{qjs_patch.cc => qjs_engine_patch.cc} (99%) rename bridge/bindings/qjs/{qjs_patch.h => qjs_engine_patch.h} (97%) create mode 100644 bridge/bindings/qjs/qjs_module_manager.cc create mode 100644 bridge/bindings/qjs/qjs_module_manager.h create mode 100644 bridge/bindings/qjs/qjs_page.cc create mode 100644 bridge/bindings/qjs/qjs_page.h delete mode 100644 bridge/bindings/qjs/visitor.h create mode 100644 bridge/core/frame/module_callback.cc create mode 100644 bridge/core/frame/module_callback.h create mode 100644 bridge/core/frame/module_callback_coordinator.cc create mode 100644 bridge/core/frame/module_callback_coordinator.h create mode 100644 bridge/core/frame/module_listener.cc create mode 100644 bridge/core/frame/module_listener.h create mode 100644 bridge/core/frame/module_listener_container.cc create mode 100644 bridge/core/frame/module_listener_container.h rename bridge/{ => core}/page.cc (80%) rename bridge/{ => core}/page.h (93%) delete mode 100644 bridge/page_test.h rename bridge/{page_test.cc => test/kraken_test_context.cc} (66%) create mode 100644 bridge/test/kraken_test_context.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 1f56bf3668..eebd39d119 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -184,9 +184,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") list(APPEND BRIDGE_LINK_LIBS quickjs) list(APPEND BRIDGE_SOURCE - page.cc - page.h - # Binding files bindings/qjs/binding_initializer.cc bindings/qjs/binding_initializer.h @@ -197,24 +194,30 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/heap_hashmap.h bindings/qjs/native_string_utils.cc bindings/qjs/native_string_utils.h - bindings/qjs/qjs_patch.cc - bindings/qjs/qjs_patch.h + bindings/qjs/qjs_engine_patch.cc + bindings/qjs/qjs_engine_patch.h bindings/qjs/qjs_function.cc bindings/qjs/qjs_function.h bindings/qjs/script_value.cc bindings/qjs/script_value.h bindings/qjs/exception_state.cc bindings/qjs/exception_state.h - bindings/qjs/visitor.cc - bindings/qjs/visitor.h + bindings/qjs/gc_visitor.cc + bindings/qjs/gc_visitor.h bindings/qjs/rejected_promises.h bindings/qjs/rejected_promises.cc bindings/qjs/qjs_window.cc bindings/qjs/qjs_window.h + bindings/qjs/qjs_page.cc + bindings/qjs/qjs_page.h + bindings/qjs/qjs_module_manager.cc + bindings/qjs/qjs_module_manager.h # Core sources core/executing_context.cc core/executing_context.h + core/page.h + core/page.cc core/executing_context_data.cc core/executing_context_data.h core/dart_methods.h @@ -224,6 +227,18 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/frame/dom_timer_coordinator.h core/frame/window_or_worker_global_scope.cc core/frame/window_or_worker_global_scope.h + core/frame/module_listener.cc + core/frame/module_listener.h + core/frame/module_listener_container.cc + core/frame/module_listener_container.h + core/frame/module_manager.cc + core/frame/module_manager.h + core/frame/module_callback.cc + core/frame/module_callback.h + core/frame/module_callback_coordinator.cc + core/frame/module_callback_coordinator.h + core/dom/frame_request_callback_collection.cc + core/dom/frame_request_callback_collection.h # core/dom/character_data.cc # core/dom/character_data.h # core/dom/comment.cc diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index e6d2a95d56..6c3c1ab97d 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -27,6 +27,10 @@ void ExceptionState::throwException(JSContext* ctx, ErrorType type, const char* } } +void ExceptionState::throwException(JSContext* ctx, JSValue exception) { + m_exception = JS_DupValue(ctx, exception); +} + bool ExceptionState::hasException() { return !JS_IsNull(m_exception); } diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index 0c03a27658..235e98456d 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -7,6 +7,7 @@ #define KRAKENBRIDGE_EXCEPTION_STATE_H #include +#include "foundation/macros.h" namespace kraken { @@ -20,12 +21,16 @@ enum ErrorType { // ExceptionState is a scope-like class and provides a way to store an exception. class ExceptionState { + // ExceptionState should only allocate at stack. + KRAKEN_DISALLOW_NEW(); public: void throwException(JSContext* ctx, ErrorType type, const char* message); + void throwException(JSContext* ctx, JSValue exception); bool hasException(); JSValue toQuickJS(); private: JSValue m_exception{JS_NULL}; + JSContext* m_ctx; }; } // namespace kraken diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index a3de7d8d8a..99b11dde44 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -10,8 +10,8 @@ #include #include "foundation/macros.h" -#include "qjs_patch.h" -#include "visitor.h" +#include "gc_visitor.h" +#include "qjs_engine_patch.h" namespace kraken { @@ -56,7 +56,7 @@ class GarbageCollected { * This Trace method must be override by objects inheriting from * GarbageCollected. */ - virtual void trace(Visitor* visitor) const = 0; + virtual void trace(GCVisitor* visitor) const = 0; /** * Called before underline JavaScript object been collected by GC. @@ -118,7 +118,7 @@ P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId, JSClassEx /// 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))); - Visitor visitor{rt, mark_func}; + GCVisitor visitor{rt, mark_func}; object->trace(&visitor); }; diff --git a/bridge/bindings/qjs/visitor.cc b/bridge/bindings/qjs/gc_visitor.cc similarity index 71% rename from bridge/bindings/qjs/visitor.cc rename to bridge/bindings/qjs/gc_visitor.cc index 407f7aacbf..a8d8a65af2 100644 --- a/bridge/bindings/qjs/visitor.cc +++ b/bridge/bindings/qjs/gc_visitor.cc @@ -3,11 +3,11 @@ * Author: Kraken Team. */ -#include "visitor.h" +#include "gc_visitor.h" namespace kraken { -void Visitor::trace(JSValue value) { +void GCVisitor::trace(JSValue value) { JS_MarkValue(m_runtime, value, m_markFunc); } diff --git a/bridge/bindings/qjs/gc_visitor.h b/bridge/bindings/qjs/gc_visitor.h new file mode 100644 index 0000000000..2146d08238 --- /dev/null +++ b/bridge/bindings/qjs/gc_visitor.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_GC_VISITOR_H +#define KRAKENBRIDGE_GC_VISITOR_H + +#include + +namespace kraken { + +// Use GCVisitor to keep track gc managed members in C++ class. +class GCVisitor final { + public: + explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc): m_runtime(rt), m_markFunc(markFunc) {}; + + void trace(JSValue value); + + private: + JSRuntime* m_runtime{nullptr}; + JS_MarkFunc* m_markFunc{nullptr}; +}; + +} + +#endif // KRAKENBRIDGE_GC_VISITOR_H diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index aaec1d3287..bcfef06d40 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -4,7 +4,7 @@ */ #include "native_string_utils.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" namespace kraken { diff --git a/bridge/bindings/qjs/qjs_patch.cc b/bridge/bindings/qjs/qjs_engine_patch.cc similarity index 99% rename from bridge/bindings/qjs/qjs_patch.cc rename to bridge/bindings/qjs/qjs_engine_patch.cc index 264820fbb6..7975a189e6 100644 --- a/bridge/bindings/qjs/qjs_patch.cc +++ b/bridge/bindings/qjs/qjs_engine_patch.cc @@ -3,7 +3,7 @@ * Author: Kraken Team. */ -#include "qjs_patch.h" +#include "qjs_engine_patch.h" #include #include #include diff --git a/bridge/bindings/qjs/qjs_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h similarity index 97% rename from bridge/bindings/qjs/qjs_patch.h rename to bridge/bindings/qjs/qjs_engine_patch.h index 75b8b80e8c..de9597d7c3 100644 --- a/bridge/bindings/qjs/qjs_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -3,8 +3,8 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_QJS_PATCH_H -#define KRAKENBRIDGE_QJS_PATCH_H +#ifndef KRAKENBRIDGE_QJS_ENGINE_PATCH_H +#define KRAKENBRIDGE_QJS_ENGINE_PATCH_H #include #include @@ -110,4 +110,4 @@ JSValue JS_GetProxyTarget(JSValue value); } #endif -#endif // KRAKENBRIDGE_QJS_PATCH_H +#endif // KRAKENBRIDGE_QJS_ENGINE_PATCH_H diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index ef80477813..c15bdf1dfc 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -34,7 +34,7 @@ const char* QJSFunction::getHumanReadableName() const { return "QJSFunction"; } -void QJSFunction::trace(Visitor* visitor) const { +void QJSFunction::trace(GCVisitor* visitor) const { visitor->trace(m_function); } diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index d4f61fabce..96f160a8de 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -24,7 +24,7 @@ class QJSFunction : public GarbageCollected { ScriptValue invoke(JSContext* ctx, int32_t argc, ScriptValue* arguments); const char* getHumanReadableName() const override; - void trace(Visitor* visitor) const override; + void trace(GCVisitor* visitor) const override; void dispose() const override; private: diff --git a/bridge/bindings/qjs/qjs_module_manager.cc b/bridge/bindings/qjs/qjs_module_manager.cc new file mode 100644 index 0000000000..cfdabd77e8 --- /dev/null +++ b/bridge/bindings/qjs/qjs_module_manager.cc @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "qjs_module_manager.h" +#include "member_installer.h" +#include "qjs_function.h" +#include "core/frame/module_manager.h" + +namespace kraken { + + +JSValue krakenModuleListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + if (argc < 1) { + return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': 1 parameter required, but only 0 present."); + } + + JSValue callbackValue = argv[0]; + if (!JS_IsObject(callbackValue)) { + return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': parameter 1 (callback) must be a function."); + } + + if (!JS_IsFunction(ctx, callbackValue)) { + return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': parameter 1 (callback) must be a function."); + } + + auto context = static_cast(JS_GetContextOpaque(ctx)); + + QJSFunction* handler = QJSFunction::create(ctx, callbackValue); + ExceptionState exception; + + ModuleManager::addModuleListener(context, handler, &exception); + if (exception.hasException()) { + return exception.toQuickJS(); + } + +// auto* link = new ModuleContext{JS_DupValue(ctx, callbackValue), context}; +// list_add_tail(&link->link, &context->module_job_list); + + return JS_NULL; +} + +JSValue krakenInvokeModule(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + if (argc < 2) { + return JS_ThrowTypeError(ctx, "Failed to execute 'kraken.invokeModule()': 2 arguments required."); + } + + ScriptValue moduleName = ScriptValue(ctx, argv[0]); + ScriptValue methodValue = ScriptValue(ctx, argv[1]); + ScriptValue paramsValue = ScriptValue(ctx, JS_NULL); + + QJSFunction* callback = nullptr; + + auto* context = static_cast(JS_GetContextOpaque(ctx)); + + if (argc > 2 && !JS_IsNull(argv[2])) { + paramsValue = ScriptValue(ctx, argv[2]); + } + + if (argc > 3 && JS_IsFunction(ctx, argv[3])) { + callback = QJSFunction::create(ctx, argv[3]); + } + + ExceptionState exception; + ScriptValue result = ModuleManager::invokeModule(context, moduleName, methodValue, paramsValue, callback, &exception); + + if (exception.hasException()) { + return exception.toQuickJS(); + } + + return result.toQuickJS(); +} + +JSValue flushUICommand(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + + if (context->dartMethodPtr()->flushUICommand == nullptr) { + return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); + } + context->dartMethodPtr()->flushUICommand(); + return JS_NULL; +} + +void QJSModuleManager::installGlobalFunctions(JSContext* ctx) { + std::initializer_list functionConfig { + {"__kraken_module_listener__", krakenModuleListener, 1, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + {"__kraken_invoke_module__", krakenInvokeModule, 3, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + {"__kraken_flush_ui_command__", flushUICommand, 0, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + }; + + JSValue globalObject = JS_GetGlobalObject(ctx); + MemberInstaller::installFunctions(ctx, globalObject, functionConfig); + JS_FreeValue(ctx, globalObject); +} + +} diff --git a/bridge/bindings/qjs/qjs_module_manager.h b/bridge/bindings/qjs/qjs_module_manager.h new file mode 100644 index 0000000000..f2994b5402 --- /dev/null +++ b/bridge/bindings/qjs/qjs_module_manager.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_QJS_MODULE_MANAGER_H +#define KRAKENBRIDGE_QJS_MODULE_MANAGER_H + +#include + +namespace kraken { + +class QJSModuleManager final { + public: + static void installGlobalFunctions(JSContext* ctx); +}; + +} + +// +//void bindModuleManager(ExecutionContext* context); +//void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, NativeString* errmsg, NativeString* json); + +#endif // KRAKENBRIDGE_QJS_MODULE_MANAGER_H diff --git a/bridge/bindings/qjs/qjs_page.cc b/bridge/bindings/qjs/qjs_page.cc new file mode 100644 index 0000000000..d0eb6667bd --- /dev/null +++ b/bridge/bindings/qjs/qjs_page.cc @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "qjs_page.h" + +namespace kraken { + +void QJSPage::installGlobalFunctions(JSContext* ctx) { + +} + +} diff --git a/bridge/bindings/qjs/qjs_page.h b/bridge/bindings/qjs/qjs_page.h new file mode 100644 index 0000000000..30ad1fd62d --- /dev/null +++ b/bridge/bindings/qjs/qjs_page.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_QJS_PAGE_H +#define KRAKENBRIDGE_QJS_PAGE_H + +#include + +namespace kraken { + +class QJSPage final { + public: + static void installGlobalFunctions(JSContext* ctx); +}; + +} + +#endif // KRAKENBRIDGE_QJS_PAGE_H diff --git a/bridge/bindings/qjs/qjs_patch_test.cc b/bridge/bindings/qjs/qjs_patch_test.cc index 8ef564d28c..0ca9aa4ea9 100644 --- a/bridge/bindings/qjs/qjs_patch_test.cc +++ b/bridge/bindings/qjs/qjs_patch_test.cc @@ -3,9 +3,9 @@ * Author: Kraken Team. */ -#include "qjs_patch.h" #include #include "gtest/gtest.h" +#include "qjs_engine_patch.h" TEST(JS_ToUnicode, asciiWords) { JSRuntime* runtime = JS_NewRuntime(); diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 7a6a2b51dc..4a5231d57c 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -4,9 +4,36 @@ */ #include "script_value.h" +#include "native_string_utils.h" +#include "qjs_engine_patch.h" namespace kraken { + +ScriptValue ScriptValue::createErrorObject(JSContext* ctx, const char* errmsg) { + JS_ThrowInternalError(ctx, "%s", errmsg); + JSValue errorObject = JS_GetException(ctx); + ScriptValue result = ScriptValue(ctx, errorObject); + JS_FreeValue(ctx, errorObject); + return result; +} + +ScriptValue ScriptValue::createJSONObject(JSContext* ctx, const char* jsonString, size_t length) { + JSValue jsonValue = JS_ParseJSON(ctx, jsonString, length, ""); + ScriptValue result = ScriptValue(ctx, jsonValue); + JS_FreeValue(ctx, jsonValue); + return result; +} + +ScriptValue ScriptValue::fromNativeString(JSContext* ctx, NativeString* nativeString) { + JSValue result = JS_NewUnicodeString(JS_GetRuntime(ctx), ctx, nativeString->string, nativeString->length); + return ScriptValue(ctx, result); +} + +ScriptValue ScriptValue::Empty(JSContext* ctx) { + return ScriptValue(ctx); +} + bool ScriptValue::isEmpty() { return JS_IsNull(m_value); } @@ -15,6 +42,24 @@ JSValue ScriptValue::toQuickJS() { return m_value; } +ScriptValue ScriptValue::toJSONStringify(ExceptionState* exception) { + JSValue stringifyedValue = JS_JSONStringify(m_ctx, m_value, JS_NULL, JS_NULL); + ScriptValue result = ScriptValue(m_ctx); + // JS_JSONStringify may return JS_EXCEPTION if object is not valid. Return JS_EXCEPTION and let quickjs to handle it. + if (JS_IsException(stringifyedValue)) { + exception->throwException(m_ctx, stringifyedValue); + result = ScriptValue(m_ctx, stringifyedValue); + } else { + result = ScriptValue(m_ctx, stringifyedValue); + } + + return result; +} + +std::unique_ptr ScriptValue::toNativeString() { + return jsValueToNativeString(m_ctx, m_value); +} + bool ScriptValue::isException() { return JS_IsException(m_value); } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index ff6d87edbb..b16ddfbb04 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -6,22 +6,47 @@ #ifndef KRAKENBRIDGE_SCRIPT_VALUE_H #define KRAKENBRIDGE_SCRIPT_VALUE_H +#include #include #include "foundation/macros.h" +#include "foundation/native_string.h" +#include "exception_state.h" namespace kraken { -// ScriptValue is a QuickJS JSValue wrapper which hold all information to hide out QuickJS running details. +// ScriptValue is a stack allocate only QuickJS JSValue wrapper which hold all information to hide out QuickJS running details. class ScriptValue final { + // ScriptValue should only allocate at stack. KRAKEN_DISALLOW_NEW(); - public: + // Create an errorObject from string error message. + static ScriptValue createErrorObject(JSContext* ctx, const char* errmsg); + // Create an object from JSON string. + static ScriptValue createJSONObject(JSContext* ctx, const char* jsonString, size_t length); + // Create from NativeString + static ScriptValue fromNativeString(JSContext* ctx, NativeString* nativeString); + + // Create an empty ScriptValue; + static ScriptValue Empty(JSContext* ctx); + // Wrap an Quickjs JSValue to ScriptValue. explicit ScriptValue(JSContext* ctx, JSValue value) : m_ctx(ctx), m_value(JS_DupValue(ctx, value)){}; + explicit ScriptValue(JSContext* ctx): m_ctx(ctx) {}; + + ScriptValue& operator=(const ScriptValue& other) { + if (&other != this) { + m_value = JS_DupValue(m_ctx, other.m_value); + } + return *this; + }; + ~ScriptValue() { JS_FreeValue(m_ctx, m_value); } bool isEmpty(); JSValue toQuickJS(); + // Create a new ScriptValue from call JSON.stringify to current value. + ScriptValue toJSONStringify(ExceptionState* exception); + std::unique_ptr toNativeString(); bool isException(); diff --git a/bridge/bindings/qjs/visitor.h b/bridge/bindings/qjs/visitor.h deleted file mode 100644 index 73920a4ad7..0000000000 --- a/bridge/bindings/qjs/visitor.h +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_VISITOR_H -#define KRAKENBRIDGE_VISITOR_H - -#include - -namespace kraken { - -class Visitor final { - public: - explicit Visitor(JSRuntime* rt, JS_MarkFunc* markFunc): m_runtime(rt), m_markFunc(markFunc) {}; - - void trace(JSValue value); - - private: - JSRuntime* m_runtime{nullptr}; - JS_MarkFunc* m_markFunc{nullptr}; -}; - -} - -#endif // KRAKENBRIDGE_VISITOR_H diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 897f3d13cf..e97bada4b0 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -8,7 +8,7 @@ #include #include -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" namespace kraken { diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index ef1de832a9..fe7b2b5ccc 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -9,11 +9,15 @@ /// Functions implements at dart side, including timer, Rendering and module API. /// Communicate via Dart FFI. -#include "kraken_bridge.h" - #include #include +#include "foundation/native_string.h" +#include "core/frame/screen.h" + +namespace kraken { + + using AsyncCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg); using AsyncRAFCallback = void (*)(void* callbackContext, int32_t contextId, double result, const char* errmsg); using AsyncModuleCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json); @@ -56,7 +60,7 @@ struct MousePointer { using SimulatePointer = void (*)(MousePointer**, int32_t length, int32_t pointer); using SimulateInputText = void (*)(NativeString* nativeString); -namespace kraken { + struct DartMethodPointer { DartMethodPointer() = default; InvokeModule invokeModule{nullptr}; diff --git a/bridge/core/dom/events/custom_event.cc b/bridge/core/dom/events/custom_event.cc index e8fb237a79..00e66fd6ab 100644 --- a/bridge/core/dom/events/custom_event.cc +++ b/bridge/core/dom/events/custom_event.cc @@ -5,7 +5,7 @@ #include "custom_event.h" #include "bindings/qjs/native_value.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" #include diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index a8917d3669..b3aca4712b 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -4,7 +4,7 @@ */ #include "event.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" #include "custom_event.h" #include "event_target.h" diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 8fb3c6c864..6f6bacfd83 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -5,7 +5,7 @@ #include -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" #include "core/dom/node.h" #include "core/frame/window.h" #include "custom_event.h" diff --git a/bridge/core/dom/events/touch_event.cc b/bridge/core/dom/events/touch_event.cc index bd4c69fc07..f3d5096fa1 100644 --- a/bridge/core/dom/events/touch_event.cc +++ b/bridge/core/dom/events/touch_event.cc @@ -4,7 +4,7 @@ */ #include "touch_event.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" #include "page.h" namespace kraken { diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc index 378bc6923f..323096e47e 100644 --- a/bridge/core/dom/frame_request_callback_collection.cc +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -33,8 +33,8 @@ void FrameCallback::fire(double highResTimeStamp) { 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::trace(GCVisitor* visitor) const { + visitor->trace(m_callback); } void FrameCallback::dispose() const { diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index 843ac237f4..9e91a44047 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -22,7 +22,7 @@ class FrameCallback : public GarbageCollected { [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "FrameCallback"; } - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void trace(GCVisitor* visitor) const override; void dispose() const override; private: diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 1e5fbfc485..8dafea8391 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -4,7 +4,7 @@ */ #include "node.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" #include "comment.h" #include "document.h" #include "document_fragment.h" diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 62a1d2559c..807dc270d3 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -4,6 +4,7 @@ */ #include "executing_context.h" +#include "polyfill.h" namespace kraken { @@ -21,7 +22,7 @@ std::unique_ptr createJSContext(int32_t contextId, const JSExc static JSRuntime* m_runtime{nullptr}; -void ExecutionContextGCTracker::trace(Visitor* visitor) const { +void ExecutionContextGCTracker::trace(GCVisitor* visitor) const { auto* context = static_cast(JS_GetContextOpaque(m_ctx)); context->trace(visitor); } @@ -31,6 +32,14 @@ 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++) { +#if ENABLE_PROFILE + auto jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + 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 + // @FIXME: maybe contextId will larger than MAX_JS_CONTEXT valid_contexts[contextId] = true; if (contextId > running_context_list) @@ -63,6 +72,24 @@ ExecutionContext::ExecutionContext(int32_t contextId, const JSExceptionHandler& JS_DefinePropertyValueStr(m_ctx, globalObject, "_gc_tracker_", m_gcTracker->toQuickJS(), JS_PROP_NORMAL); runningContexts++; + + // Register all built-in native bindings. + installBindings(m_ctx); + +#if ENABLE_PROFILE + nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); + nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); +#endif + + initKrakenPolyFill(this); + + for (auto& p : pluginByteCode) { + evaluateByteCode(p.second.bytes, p.second.length); + } + +#if ENABLE_PROFILE + nativePerformance.mark(PERF_JS_POLYFILL_INIT_END); +#endif } ExecutionContext::~ExecutionContext() { @@ -236,12 +263,12 @@ void ExecutionContext::reportError(JSValueConst error) { messageLength += 4 + strlen(stack); char message[messageLength]; sprintf(message, "%s: %s\n%s", type, title, stack); - _handler(contextId, message); + _handler(this, message); } else { messageLength += 3; char message[messageLength]; sprintf(message, "%s: %s", type, title); - _handler(contextId, message); + _handler(this, message); } JS_FreeValue(m_ctx, errorTypeValue); @@ -362,6 +389,8 @@ void ExecutionContext::dispatchGlobalRejectionHandledEvent(ExecutionContext* con dispatchPromiseRejectionEvent("rejectionhandled", context, promise, error); } +std::unordered_map ExecutionContext::pluginByteCode{}; + 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. @@ -425,8 +454,17 @@ DOMTimerCoordinator* ExecutionContext::timers() { return &m_timers; } -void ExecutionContext::trace(Visitor* visitor) { +ModuleListenerContainer* ExecutionContext::moduleListeners() { + return &m_moduleListeners; +} + +ModuleCallbackCoordinator* ExecutionContext::moduleCallbacks() { + return &m_moduleCallbacks; +} + +void ExecutionContext::trace(GCVisitor* visitor) { m_timers.trace(visitor); + m_moduleListeners.trace(visitor); } void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01) { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 664a5729a3..b7bd9e712d 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -19,20 +19,28 @@ #include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/rejected_promises.h" #include "bindings/qjs/script_value.h" +#include "bindings/qjs/binding_initializer.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" - -using JSExceptionHandler = std::function; +#include "frame/module_listener_container.h" +#include "frame/module_callback_coordinator.h" namespace kraken { +struct NativeByteCode { + uint8_t* bytes; + int32_t length; +}; + class ExecutionContext; class Document; +using JSExceptionHandler = std::function; + std::string jsAtomToStdString(JSContext* ctx, JSAtom atom); static inline bool isNumberIndex(const std::string& name) { @@ -57,7 +65,7 @@ class ExecutionContextGCTracker : public GarbageCollected& dartMethodPtr() { return m_dartMethodPtr; } - void trace(Visitor* visitor); + void trace(GCVisitor* visitor); std::chrono::time_point timeOrigin; std::unordered_map constructorMap; @@ -116,6 +130,9 @@ class ExecutionContext { static void dispatchGlobalRejectionHandledEvent(ExecutionContext* context, JSValueConst promise, JSValueConst error); static void dispatchGlobalErrorEvent(ExecutionContext* context, JSValueConst error); + // Bytecodes which registered by kraken plugins. + static std::unordered_map pluginByteCode; + private: static void promiseRejectTracker(JSContext* ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void* opaque); @@ -127,6 +144,8 @@ class ExecutionContext { JSContext* m_ctx{nullptr}; Document* m_document{nullptr}; DOMTimerCoordinator m_timers; + ModuleListenerContainer m_moduleListeners; + ModuleCallbackCoordinator m_moduleCallbacks; ExecutionContextGCTracker* m_gcTracker{nullptr}; ExecutionContextData m_data{this}; UICommandBuffer m_commandBuffer{this}; diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index f021ede074..c07526948e 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -6,6 +6,9 @@ #include "gtest/gtest.h" #include "kraken_test_env.h" #include "page.h" +#include "include/kraken_bridge.h" + +using namespace kraken; TEST(Context, isValid) { auto bridge = TEST_init(); diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index 6ed91a101c..af60e3f282 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -5,7 +5,7 @@ #include "dom_timer.h" #include "bindings/qjs/garbage_collected.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" #include "core/executing_context.h" #if UNIT_TEST @@ -30,7 +30,7 @@ void DOMTimer::fire() { } } -void DOMTimer::trace(Visitor* visitor) const { +void DOMTimer::trace(GCVisitor* visitor) const { m_callback->trace(visitor); } diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index 336bab831f..595721a27d 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -25,7 +25,7 @@ class DOMTimer : public GarbageCollected { [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "DOMTimer"; } - void trace(Visitor* visitor) const override; + void trace(GCVisitor* visitor) const override; void dispose() const override; private: diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 44564109d3..e513667f43 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -34,8 +34,6 @@ static void handleTransientCallback(void* ptr, int32_t contextId, const char* er auto* timer = static_cast(ptr); auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); - if (!checkPage(contextId, context)) - return; if (!context->isValid()) return; @@ -66,7 +64,7 @@ DOMTimer* DOMTimerCoordinator::getTimerById(int32_t timerId) { return m_activeTimers[timerId]; } -void DOMTimerCoordinator::trace(Visitor* visitor) { +void DOMTimerCoordinator::trace(GCVisitor* visitor) { for (auto& timer : m_activeTimers) { visitor->trace(timer.second->toQuickJS()); } diff --git a/bridge/core/frame/dom_timer_coordinator.h b/bridge/core/frame/dom_timer_coordinator.h index 2ff4ec217c..be9e425a91 100644 --- a/bridge/core/frame/dom_timer_coordinator.h +++ b/bridge/core/frame/dom_timer_coordinator.h @@ -9,7 +9,7 @@ #include #include #include -#include "bindings/qjs/visitor.h" +#include "bindings/qjs/gc_visitor.h" namespace kraken { @@ -31,7 +31,7 @@ class DOMTimerCoordinator { void* removeTimeoutById(int32_t timerId); DOMTimer* getTimerById(int32_t timerId); - void trace(Visitor* visitor); + void trace(GCVisitor* visitor); private: std::unordered_map m_activeTimers; diff --git a/bridge/core/frame/module_callback.cc b/bridge/core/frame/module_callback.cc new file mode 100644 index 0000000000..e7a1403982 --- /dev/null +++ b/bridge/core/frame/module_callback.cc @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "module_callback.h" + +namespace kraken { + +ModuleCallback::ModuleCallback(QJSFunction* function): m_function(function) {} + +QJSFunction* ModuleCallback::value() { + return m_function; +} + +void ModuleCallback::trace(GCVisitor* visitor) const { + m_function->trace(visitor); +} + +void ModuleCallback::dispose() const { + m_function->dispose(); +} + +} diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h new file mode 100644 index 0000000000..ca2a123e4e --- /dev/null +++ b/bridge/core/frame/module_callback.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MODULE_CALLBACK_H +#define KRAKENBRIDGE_MODULE_CALLBACK_H + +#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/qjs_function.h" +#include + +namespace kraken { + +// ModuleCallback is an asynchronous callback function, usually from the 4th parameter of `kraken.invokeModule` function. +// When the asynchronous operation on the Dart side ends, the callback is will called and to return to the JS executing environment. +class ModuleCallback : public GarbageCollected { + public: + explicit ModuleCallback(QJSFunction* function); + + QJSFunction* value(); + + void trace(GCVisitor*visitor) const override; + void dispose() const override; + + list_head link; + +private: + QJSFunction* m_function{nullptr}; +}; + + + +} + +#endif // KRAKENBRIDGE_MODULE_CALLBACK_H diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc new file mode 100644 index 0000000000..6052b3cd20 --- /dev/null +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "module_callback_coordinator.h" + +namespace kraken { + +void ModuleCallbackCoordinator::addModuleCallbacks(ModuleCallback* callback) { + list_add_tail(&m_listeners, &callback->link); +} + +void ModuleCallbackCoordinator::removeModuleCallbacks(ModuleCallback* callback) { + list_del(&callback->link); +} + +ModuleCallbackCoordinator::ModuleCallbackCoordinator() { + init_list_head(&m_listeners); +} + +void ModuleCallbackCoordinator::trace(GCVisitor* visitor) { + { + struct list_head *el, *el1; + list_for_each_safe(el, el1, &m_listeners) { + auto* callback = list_entry(el, ModuleCallback, link); + visitor->trace(callback->toQuickJS()); + } + } +} + +} // namespace kraken diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_callback_coordinator.h new file mode 100644 index 0000000000..8e0c1375ee --- /dev/null +++ b/bridge/core/frame/module_callback_coordinator.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H +#define KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H + +#include +// Quickjs's linked-list are more efficient than STL forward_list. +#include +#include "module_manager.h" +#include "module_callback.h" + +namespace kraken { + +class ModuleCallbackCoordinator final { + public: + + ModuleCallbackCoordinator(); + + void addModuleCallbacks(ModuleCallback* callback); + void removeModuleCallbacks(ModuleCallback* callback); + + void trace(GCVisitor* visitor); + + private: + list_head m_listeners; +}; + +} + +#endif // KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H diff --git a/bridge/core/frame/module_listener.cc b/bridge/core/frame/module_listener.cc new file mode 100644 index 0000000000..1c9c2de22f --- /dev/null +++ b/bridge/core/frame/module_listener.cc @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "module_listener.h" + +namespace kraken { + +ModuleListener::ModuleListener(QJSFunction* function): m_function(function) {} + +void ModuleListener::trace(GCVisitor* visitor) const { + m_function->trace(visitor); +} + +void ModuleListener::dispose() const { + m_function->dispose(); +} + +} diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h new file mode 100644 index 0000000000..d85689a6de --- /dev/null +++ b/bridge/core/frame/module_listener.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MODULE_LISTENER_H +#define KRAKENBRIDGE_MODULE_LISTENER_H + +#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/qjs_function.h" + +namespace kraken { + +// ModuleListener is an persistent callback function. Registered from user with `kraken.addModuleListener` method. +// When module event triggered at dart side, All module listener will be invoked and let user to dispatch further operations. +class ModuleListener : public GarbageCollected { + public: + explicit ModuleListener(QJSFunction* function); + private: + + void trace(GCVisitor*visitor) const override; + void dispose() const override; + + QJSFunction* m_function{nullptr}; +}; + +} + +#endif // KRAKENBRIDGE_MODULE_LISTENER_H diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc new file mode 100644 index 0000000000..92ef768dc9 --- /dev/null +++ b/bridge/core/frame/module_listener_container.cc @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "module_listener_container.h" + +namespace kraken { + +void ModuleListenerContainer::addModuleListener(ModuleListener* listener) { + m_listeners.insert_after(m_listeners.end(), listener); +} + +void ModuleListenerContainer::trace(GCVisitor* visitor) { + for(auto& listener: m_listeners) { + visitor->trace(listener->toQuickJS()); + } +} + +} diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h new file mode 100644 index 0000000000..a385326868 --- /dev/null +++ b/bridge/core/frame/module_listener_container.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H +#define KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H + +#include "module_listener.h" +#include + +namespace kraken { + +class ModuleListenerContainer final { + public: + + void addModuleListener(ModuleListener* listener); + void trace(GCVisitor* visitor); + + private: + std::forward_list m_listeners; +}; + +} + +#endif // KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index b3dcc32f88..5592fac6c8 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -4,137 +4,89 @@ */ #include "module_manager.h" -#include "page.h" -#include "qjs_patch.h" +#include "module_callback.h" +#include "core/executing_context.h" namespace kraken { -JSValue krakenModuleListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': 1 parameter required, but only 0 present."); - } - - JSValue callbackValue = argv[0]; - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': parameter 1 (callback) must be a function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': parameter 1 (callback) must be a function."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - auto* link = new ModuleContext{JS_DupValue(ctx, callbackValue), context}; - list_add_tail(&link->link, &context->module_job_list); - - return JS_NULL; -} +struct ModuleContext { + ExecutionContext* context; + ModuleCallback* callback; +}; -void handleInvokeModuleTransientCallback(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json) { - auto* moduleContext = static_cast(callbackContext); +void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const char* errmsg, NativeString* json) { + auto* moduleContext = static_cast(ptr); ExecutionContext* context = moduleContext->context; - if (!checkPage(contextId, context)) - return; if (!context->isValid()) return; - if (JS_IsNull(moduleContext->callback)) { + if (moduleContext->callback == nullptr) { JSValue exception = JS_ThrowTypeError(moduleContext->context->ctx(), "Failed to execute '__kraken_invoke_module__': callback is null."); context->handleException(&exception); return; } JSContext* ctx = moduleContext->context->ctx(); - if (!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); + ScriptValue errorObject = ScriptValue::createErrorObject(ctx, errmsg); + ScriptValue arguments[] = { + errorObject + }; + ScriptValue returnValue = moduleContext->callback->value()->invoke(ctx, 1, arguments); + if (returnValue.isException()) { + context->handleException(&returnValue); + } } 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); + ScriptValue jsonObject = ScriptValue::createJSONObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); + ScriptValue arguments[] = { + jsonObject + }; + ScriptValue returnValue = moduleContext->callback->value()->invoke(ctx, 1, arguments); + if (returnValue.isException()) { + context->handleException(&returnValue); + } } context->drainPendingPromiseJobs(); - - context->handleException(&returnValue); - JS_FreeValue(ctx, moduleContext->callback); - JS_FreeValue(ctx, returnValue); - list_del(&moduleContext->link); + context->moduleCallbacks()->removeModuleCallbacks(moduleContext->callback); } void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json) { static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); } -JSValue krakenInvokeModule(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc < 2) { - return JS_ThrowTypeError(ctx, "Failed to execute 'kraken.invokeModule()': 2 arguments required."); - } - - JSValue moduleNameValue = argv[0]; - JSValue methodValue = argv[1]; - JSValue paramsValue = JS_NULL; - JSValue callbackValue = JS_NULL; - - auto* context = static_cast(JS_GetContextOpaque(ctx)); +ScriptValue ModuleManager::invokeModule(ExecutionContext* context, ScriptValue& moduleNameValue, ScriptValue& methodValue, ScriptValue& paramsValue, QJSFunction* callback, ExceptionState* exception) { + std::unique_ptr moduleName = moduleNameValue.toNativeString(); + std::unique_ptr method = methodValue.toNativeString(); + std::unique_ptr params; + if (!paramsValue.isEmpty()) { + ScriptValue stringifiedValue = paramsValue.toJSONStringify(exception); + if (exception->hasException()) { + return stringifiedValue; + } - if (argc > 2 && !JS_IsNull(argv[2])) { - paramsValue = argv[2]; + params = stringifiedValue.toNativeString(); } - if (argc > 3 && JS_IsObject(argv[3])) { - callbackValue = argv[3]; + if (context->dartMethodPtr()->invokeModule == nullptr) { + exception->throwException(context->ctx(), ErrorType::InternalError, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); + return ScriptValue(context->ctx()); } - 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); - } + auto* moduleCallback = makeGarbageCollected(callback); + context->moduleCallbacks()->addModuleCallbacks(moduleCallback); - if (getDartMethod()->invokeModule == nullptr) { -#if FLUTTER_BACKEND - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); -#else - return JS_NULL; -#endif - } - - ModuleContext* moduleContext; - if (JS_IsNull(callbackValue)) { - auto emptyFunction = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_NULL; }; - JSValue callbackFunc = JS_NewCFunction(ctx, emptyFunction, "_f", 0); - moduleContext = new ModuleContext{callbackFunc, context}; - } else { - moduleContext = new ModuleContext{JS_DupValue(ctx, callbackValue), context}; - } - list_add_tail(&moduleContext->link, &context->module_callback_job_list); + ModuleContext* moduleContext = new ModuleContext{context, moduleCallback}; NativeString* result; - - if (!JS_IsNull(callbackValue)) { - result = getDartMethod()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleTransientCallback); + if (callback != nullptr) { + result = context->dartMethodPtr()->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); + result = context->dartMethodPtr()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleUnexpectedCallback); } moduleName->free(); @@ -144,27 +96,20 @@ JSValue krakenInvokeModule(JSContext* ctx, JSValueConst this_val, int argc, JSVa } if (result == nullptr) { - return JS_NULL; + return ScriptValue::Empty(context->ctx()); } - JSValue resultString = JS_NewUnicodeString(context->runtime(), ctx, result->string, result->length); + ScriptValue resultString = ScriptValue::fromNativeString(context->ctx(), result); + + // Manual free returned result string; result->free(); return resultString; } -JSValue flushUICommand(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (getDartMethod()->flushUICommand == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); - } - getDartMethod()->flushUICommand(); - return JS_NULL; -} - -void bindModuleManager(ExecutionContext* context) { - QJS_GLOBAL_BINDING_FUNCTION(context, krakenModuleListener, "__kraken_module_listener__", 1); - QJS_GLOBAL_BINDING_FUNCTION(context, krakenInvokeModule, "__kraken_invoke_module__", 3); - QJS_GLOBAL_BINDING_FUNCTION(context, flushUICommand, "__kraken_flush_ui_command__", 0); +void ModuleManager::addModuleListener(ExecutionContext* context, QJSFunction* handler, ExceptionState* exception) { + auto* listener = makeGarbageCollected(handler); + context->moduleListeners()->addModuleListener(listener); } } // namespace kraken diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 2dfef7a6a5..11d66d38d6 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -6,18 +6,18 @@ #ifndef KRAKENBRIDGE_MODULE_MANAGER_H #define KRAKENBRIDGE_MODULE_MANAGER_H -#include "executing_context.h" +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/qjs_function.h" +#include "module_callback.h" namespace kraken { -struct ModuleContext { - JSValue callback; - ExecutionContext* context; - list_head link; +class ModuleManager { + public: + static ScriptValue invokeModule(ExecutionContext* context, ScriptValue& moduleName, ScriptValue& method, ScriptValue& params, QJSFunction* callback, ExceptionState* exception); + static void addModuleListener(ExecutionContext* context, QJSFunction* handler, ExceptionState* exception); }; -void bindModuleManager(ExecutionContext* context); -void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, NativeString* errmsg, NativeString* json); } // namespace kraken #endif // KRAKENBRIDGE_MODULE_MANAGER_H diff --git a/bridge/core/frame/screen.h b/bridge/core/frame/screen.h index 2ac23ec116..43afef226f 100644 --- a/bridge/core/frame/screen.h +++ b/bridge/core/frame/screen.h @@ -6,10 +6,6 @@ #ifndef KRAKENBRIDGE_SCREEN_H #define KRAKENBRIDGE_SCREEN_H -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/host_object.h" -#include "dart_methods.h" - namespace kraken { struct NativeScreen { @@ -17,16 +13,16 @@ struct NativeScreen { double height; }; -class Screen : public HostObject { - public: - explicit Screen(ExecutionContext* context) : HostObject(context, "Screen"){}; - - private: - DEFINE_READONLY_PROPERTY(width); - DEFINE_READONLY_PROPERTY(height); -}; +//class Screen : public HostObject { +// public: +// explicit Screen(ExecutionContext* context) : HostObject(context, "Screen"){}; +// +// private: +// DEFINE_READONLY_PROPERTY(width); +// DEFINE_READONLY_PROPERTY(height); +//}; -void bindScreen(ExecutionContext* context); +//void bindScreen(ExecutionContext* context); } // namespace kraken diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index fd04391060..7b9f1abcbb 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -7,7 +7,7 @@ #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 "bindings/qjs/qjs_engine_patch.h" #include "dart_methods.h" namespace kraken { diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 64235b926e..fe4c54a849 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -31,8 +31,6 @@ static void handleTransientCallback(void* ptr, int32_t contextId, const char* er auto* timer = static_cast(ptr); auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); - if (!checkPage(contextId, context)) - return; if (!context->isValid()) return; @@ -45,8 +43,6 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e auto* timer = static_cast(ptr); auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); - if (!checkPage(contextId, context)) - return; if (!context->isValid()) return; diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index 5beff41da8..0ebd59faee 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -5,7 +5,7 @@ #include "html_template_element.h" #include "bindings/qjs/dom/text_node.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" #include "page.h" namespace kraken { diff --git a/bridge/page.cc b/bridge/core/page.cc similarity index 80% rename from bridge/page.cc rename to bridge/core/page.cc index 6b7c7e968b..bb59f6b072 100644 --- a/bridge/page.cc +++ b/bridge/core/page.cc @@ -14,58 +14,30 @@ namespace kraken { -std::unordered_map KrakenPage::pluginByteCode{}; ConsoleMessageHandler KrakenPage::consoleMessageHandler{nullptr}; kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { -#if ENABLE_PROFILE - auto jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); -#endif m_context = new ExecutionContext( contextId, - [this](int32_t contextId, const char* message) { - if (m_context->dartMethodPtr()->onJsError != nullptr) { - m_context->dartMethodPtr()->onJsError(contextId, message); + [](ExecutionContext* context, const char* message) { + if (context->dartMethodPtr()->onJsError != nullptr) { + context->dartMethodPtr()->onJsError(context->getContextId(), message); } KRAKEN_LOG(ERROR) << message << std::endl; }, 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 - - installBindings(m_context->ctx()); - -#if ENABLE_PROFILE - nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); - nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); -#endif - - initKrakenPolyFill(this); - - for (auto& p : pluginByteCode) { - evaluateByteCode(p.second.bytes, p.second.length); - } - -#if ENABLE_PROFILE - nativePerformance.mark(PERF_JS_POLYFILL_INIT_END); -#endif } bool KrakenPage::parseHTML(const char* code, size_t length) { - // if (!m_context->isValid()) - // return false; - // JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); - // auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId)); - // HTMLParser::parseHTML(code, length, body); - // JS_FreeValue(m_context->ctx(), bodyValue); - // return true; + // if (!m_context->isValid()) + // return false; + // JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); + // auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId)); + // HTMLParser::parseHTML(code, length, body); + // JS_FreeValue(m_context->ctx(), bodyValue); + // return true; } void KrakenPage::invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* ptr, NativeString* extra) { @@ -196,7 +168,7 @@ KrakenPage::~KrakenPage() { } void KrakenPage::reportError(const char* errmsg) { - m_handler(m_context->getContextId(), errmsg); + m_handler(m_context, errmsg); } } // namespace kraken diff --git a/bridge/page.h b/bridge/core/page.h similarity index 93% rename from bridge/page.h rename to bridge/core/page.h index de15d7856c..6efed8969d 100644 --- a/bridge/page.h +++ b/bridge/core/page.h @@ -17,11 +17,6 @@ namespace kraken { -struct NativeByteCode { - uint8_t* bytes; - int32_t length; -}; - class KrakenPage; using JSBridgeDisposeCallback = void (*)(KrakenPage* bridge); using ConsoleMessageHandler = std::function; @@ -38,9 +33,6 @@ class KrakenPage final { KrakenPage(int32_t jsContext, const JSExceptionHandler& handler); ~KrakenPage(); - // Bytecodes which registered by kraken plugins. - static std::unordered_map pluginByteCode; - // evaluate JavaScript source codes in standard mode. void evaluateScript(const NativeString* script, const char* url, int startLine); void evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine); diff --git a/bridge/foundation/logging.cc b/bridge/foundation/logging.cc index 40a6f13d88..d5277fa060 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -7,7 +7,7 @@ #include #include "colors.h" -#include "page.h" +#include "core/page.h" #if defined(IS_ANDROID) #include diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 2e1c7d70d9..4098cf4eb1 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -4,7 +4,7 @@ */ #include "native_value.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/qjs_engine_patch.h" #include "core/executing_context.h" namespace kraken { diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index b6877af116..626ed309e2 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -13,7 +13,7 @@ #include "foundation/ui_command_buffer.h" #include "foundation/ui_task_queue.h" #include "include/kraken_bridge.h" -#include "page.h" +#include "core/page.h" #if defined(_WIN32) #define SYSTEM_NAME "windows" // Windows @@ -226,7 +226,7 @@ void registerContextDisposedCallbacks(int32_t contextId, Task task, void* data) } void registerPluginByteCode(uint8_t* bytes, int32_t length, const char* pluginName) { - kraken::KrakenPage::pluginByteCode[pluginName] = kraken::NativeByteCode{bytes, length}; + kraken::ExecutionContext::pluginByteCode[pluginName] = kraken::NativeByteCode{bytes, length}; } int32_t profileModeEnabled() { diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index 7ff425d4fa..5ae02b9729 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -6,27 +6,27 @@ #include "kraken_bridge_test.h" #include #include "bindings/qjs/native_string_utils.h" -#include "page_test.h" +#include "kraken_test_context.h" -std::unordered_map bridgeTestPool = std::unordered_map(); +std::unordered_map testContextPool = std::unordered_map(); void initTestFramework(int32_t contextId) { auto* page = static_cast(getPage(contextId)); - auto bridgeTest = new kraken::KrakenPageTest(page); - bridgeTestPool[contextId] = bridgeTest; + auto testContext = new kraken::KrakenTestContext(page->getContext()); + testContextPool[contextId] = testContext; } int8_t evaluateTestScripts(int32_t contextId, kraken::NativeString* code, const char* bundleFilename, int startLine) { - auto bridgeTest = bridgeTestPool[contextId]; - return bridgeTest->evaluateTestScripts(code->string, code->length, bundleFilename, startLine); + auto testContext = testContextPool[contextId]; + return testContext->evaluateTestScripts(code->string, 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(int32_t contextId, uint64_t* methodBytes, int32_t length) { - auto bridgeTest = bridgeTestPool[contextId]; - bridgeTest->registerTestEnvDartMethods(methodBytes, length); + auto testContext = testContextPool[contextId]; + testContext->registerTestEnvDartMethods(methodBytes, length); } diff --git a/bridge/page_test.h b/bridge/page_test.h deleted file mode 100644 index 219e4d060d..0000000000 --- a/bridge/page_test.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2020-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_PAGE_TEST_H -#define KRAKENBRIDGE_PAGE_TEST_H - -#include "core/executing_context.h" -#include "kraken_bridge_test.h" -#include "page.h" - -namespace kraken { - -struct ImageSnapShotContext { - JSValue callback; - ExecutionContext* context; - list_head link; -}; - -class KrakenPageTest final { - public: - explicit KrakenPageTest() = delete; - explicit KrakenPageTest(KrakenPage* bridge); - - ~KrakenPageTest() { - if (!JS_IsNull(executeTestCallback)) { - JS_FreeValue(m_page_context->ctx(), executeTestCallback); - } - if (!JS_IsNull(executeTestProxyObject)) { - JS_FreeValue(m_page_context->ctx(), executeTestProxyObject); - } - - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &image_link) { - auto* image = list_entry(el, ImageSnapShotContext, link); - JS_FreeValue(m_page_context->ctx(), image->callback); - } - } - } - - /// evaluete JavaScript source code with build-in test frameworks, use in test only. - bool evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); - bool parseTestHTML(const uint16_t* code, size_t codeLength); - void invokeExecuteTest(ExecuteCallback executeCallback); - void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); - - JSValue executeTestCallback{JS_NULL}; - JSValue executeTestProxyObject{JS_NULL}; - list_head image_link; - - private: - /// the pointer of bridge, ownership belongs to JSBridge - KrakenPage* m_page; - /// the pointer of JSContext, overship belongs to JSContext - ExecutionContext* m_page_context; -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_PAGE_TEST_H diff --git a/bridge/polyfill/scripts/js_to_c.js b/bridge/polyfill/scripts/js_to_c.js index a44151fc49..431ddbb032 100644 --- a/bridge/polyfill/scripts/js_to_c.js +++ b/bridge/polyfill/scripts/js_to_c.js @@ -34,13 +34,9 @@ const getPolyFillHeader = (outputName) => `/* #ifndef KRAKEN_${outputName.toUpperCase()}_H #define KRAKEN_${outputName.toUpperCase()}_H -#if KRAKEN_JSC_ENGINE -#include "bridge_jsc.h" -#elif KRAKEN_QUICK_JS_ENGINE -#include "page.h" -#endif +#include "core/executing_context.h" -void initKraken${outputName}(kraken::KrakenPage *page); +void initKraken${outputName}(kraken::ExecutionContext *context); #endif // KRAKEN_${outputName.toUpperCase()}_H `; @@ -55,7 +51,7 @@ uint8_t bytes[${uint8Array.length}] = {${uint8Array.join(',')}}; }`; }; const getPolyfillEvalCall = () => { - return 'page->evaluateByteCode(bytes, byteLength);'; + return 'context->evaluateByteCode(bytes, byteLength);'; } const getPolyFillSource = (source, outputName) => `/* @@ -67,7 +63,7 @@ const getPolyFillSource = (source, outputName) => `/* ${getPolyFillJavaScriptSource(source)} -void initKraken${outputName}(kraken::KrakenPage *page) { +void initKraken${outputName}(kraken::ExecutionContext *context) { ${getPolyfillEvalCall()} } `; diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index d32e7ee17b..0f917a535b 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -1,4 +1,3 @@ -import 'es6-promise/dist/es6-promise.auto'; // import './dom'; // import './query-selector'; import { console } from './console'; diff --git a/bridge/page_test.cc b/bridge/test/kraken_test_context.cc similarity index 66% rename from bridge/page_test.cc rename to bridge/test/kraken_test_context.cc index b93e033937..c036d119d4 100644 --- a/bridge/page_test.cc +++ b/bridge/test/kraken_test_context.cc @@ -3,22 +3,38 @@ * Author: Kraken Team. */ -#include "page_test.h" +#include "kraken_test_context.h" #include "testframework.h" namespace kraken { -bool KrakenPageTest::evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { - if (!m_page_context->isValid()) - return false; - return m_page_context->evaluateJavaScript(code, codeLength, sourceURL, startLine); +KrakenTestContext::KrakenTestContext(ExecutionContext* context) : m_context(context) { + // bridge->owner = this; + // bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast(bridge->owner); }; + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); + // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); + + // initKrakenTestFramework(bridge); + // init_list_head(&image_link); } -bool KrakenPageTest::parseTestHTML(const uint16_t* code, size_t codeLength) { - if (!m_page_context->isValid()) + +bool KrakenTestContext::evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { + if (!m_context->isValid()) return false; - std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), codeLength)); - return m_page->parseHTML(utf8Code.c_str(), utf8Code.length()); + return m_context->evaluateJavaScript(code, codeLength, sourceURL, startLine); +} + +bool KrakenTestContext::parseTestHTML(const uint16_t* code, size_t codeLength) { +// if (!m_page_context->isValid()) +// return false; +// std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), codeLength)); +// return m_page->parseHTML(utf8Code.c_str(), utf8Code.length()); } static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { @@ -32,9 +48,8 @@ static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSVa return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); } auto bridge = static_cast(context->getOwner()); - auto bridgeTest = static_cast(bridge->owner); - JS_DupValue(ctx, callback); - bridgeTest->executeTestCallback = callback; + auto bridgeTest = static_cast(bridge->owner); + bridgeTest->m_executeTestCallback = ScriptValue(ctx, callback); return JS_NULL; } @@ -112,99 +127,100 @@ static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSVa } static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - // if (getDartMethod()->simulatePointer == nullptr) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); - // } - // - // auto* context = static_cast(JS_GetContextOpaque(ctx)); - // - // JSValue inputArrayValue = argv[0]; - // if (!JS_IsObject(inputArrayValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); - // } - // - // JSValue pointerValue = argv[1]; - // if (!JS_IsNumber(pointerValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); - // } - // - // uint32_t length; - // JSValue lengthValue = JS_GetPropertyStr(ctx, inputArrayValue, "length"); - // JS_ToUint32(ctx, &length, lengthValue); - // JS_FreeValue(ctx, lengthValue); - // - // auto** mousePointerList = new MousePointer*[length]; - // - // for (int i = 0; i < length; i++) { - // auto mouse = new MousePointer(); - // JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); - // mouse->contextId = context->getContextId(); - // JSValue xValue = JS_GetPropertyUint32(ctx, params, 0); - // JSValue yValue = JS_GetPropertyUint32(ctx, params, 1); - // JSValue changeValue = JS_GetPropertyUint32(ctx, params, 2); - // - // double x; - // double y; - // double change; - // - // JS_ToFloat64(ctx, &x, xValue); - // JS_ToFloat64(ctx, &y, yValue); - // JS_ToFloat64(ctx, &change, changeValue); - // - // mouse->x = x; - // mouse->y = y; - // mouse->change = change; - // mousePointerList[i] = mouse; - // - // JS_FreeValue(ctx, params); - // JS_FreeValue(ctx, xValue); - // JS_FreeValue(ctx, yValue); - // JS_FreeValue(ctx, changeValue); - // } - // - // uint32_t pointer; - // JS_ToUint32(ctx, &pointer, pointerValue); - // - // getDartMethod()->simulatePointer(mousePointerList, length, pointer); - // - // delete[] mousePointerList; - // - // return JS_NULL; + auto* context = static_cast(JS_GetContextOpaque(ctx)); + if (context->dartMethodPtr()->simulatePointer == nullptr) { + return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); + } + + JSValue inputArrayValue = argv[0]; + if (!JS_IsObject(inputArrayValue)) { + return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); + } + + JSValue pointerValue = argv[1]; + if (!JS_IsNumber(pointerValue)) { + return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); + } + + uint32_t length; + JSValue lengthValue = JS_GetPropertyStr(ctx, inputArrayValue, "length"); + JS_ToUint32(ctx, &length, lengthValue); + JS_FreeValue(ctx, lengthValue); + + auto** mousePointerList = new MousePointer*[length]; + + for (int i = 0; i < length; i++) { + auto mouse = new MousePointer(); + JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); + mouse->contextId = context->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); + + context->dartMethodPtr()->simulatePointer(mousePointerList, length, pointer); + + delete[] mousePointerList; + + return JS_NULL; } -static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv){ - // if (getDartMethod()->simulateInputText == nullptr) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); - // } - // - // JSValue& charStringValue = argv[0]; - // - // if (!JS_IsString(charStringValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); - // } - // - // std::unique_ptr nativeString = kraken::jsValueToNativeString(ctx, charStringValue); - // getDartMethod()->simulateInputText(nativeString.get()); - // nativeString->free(); - // return JS_NULL; +static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* context = static_cast(JS_GetContextOpaque(ctx)); + if (context->dartMethodPtr()->simulateInputText == nullptr) { + return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); + } + + JSValue& charStringValue = argv[0]; + + if (!JS_IsString(charStringValue)) { + return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); + } + + std::unique_ptr nativeString = kraken::jsValueToNativeString(ctx, charStringValue); + void* p = static_cast(nativeString.get()); + context->dartMethodPtr()->simulateInputText(static_cast(p)); + nativeString->free(); + return JS_NULL; }; static JSValue parseHTML(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - // auto* context = static_cast(JS_GetContextOpaque(ctx)); - // - // if (argc == 1) { - // JSValue& html = argv[0]; - // - // std::string strHTML = jsValueToStdString(ctx, html); - // - // JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); - // auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); - // HTMLParser::parseHTML(strHTML, body); - // - // JS_FreeValue(ctx, bodyValue); - // } - // - // return JS_NULL; +// auto* context = static_cast(JS_GetContextOpaque(ctx)); +// +// if (argc == 1) { +// JSValue& html = argv[0]; +// +// std::string strHTML = jsValueToStdString(ctx, html); +// +// JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); +// auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); +// HTMLParser::parseHTML(strHTML, body); +// +// JS_FreeValue(ctx, bodyValue); +// } +// +// return JS_NULL; } static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { @@ -221,21 +237,6 @@ static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int arg return JS_NULL; } -KrakenPageTest::KrakenPageTest(KrakenPage* bridge) : m_page(bridge), m_page_context(bridge->getContext()) { - // bridge->owner = this; - // bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast(bridge->owner); }; - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); - - // initKrakenTestFramework(bridge); - // init_list_head(&image_link); -} - struct ExecuteCallbackContext { ExecuteCallbackContext() = delete; @@ -244,7 +245,7 @@ struct ExecuteCallbackContext { ExecutionContext* context; }; -void KrakenPageTest::invokeExecuteTest(ExecuteCallback executeCallback) { +void KrakenTestContext::invokeExecuteTest(ExecuteCallback executeCallback) { // if (JS_IsNull(executeTestCallback)) { // return; // } @@ -280,10 +281,10 @@ void KrakenPageTest::invokeExecuteTest(ExecuteCallback executeCallback) { // executeTestCallback = JS_NULL; } -void KrakenPageTest::registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { +void KrakenTestContext::registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { size_t i = 0; - auto& dartMethodPtr = m_page_context->dartMethodPtr(); + auto& dartMethodPtr = m_context->dartMethodPtr(); dartMethodPtr->onJsError = reinterpret_cast(methodBytes[i++]); dartMethodPtr->matchImageSnapshot = reinterpret_cast(methodBytes[i++]); diff --git a/bridge/test/kraken_test_context.h b/bridge/test/kraken_test_context.h new file mode 100644 index 0000000000..cdec91c57c --- /dev/null +++ b/bridge/test/kraken_test_context.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H +#define KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H + +#include "core/executing_context.h" +#include "core/page.h" +#include "bindings/qjs/qjs_function.h" +#include "kraken_bridge_test.h" + +namespace kraken { + +struct ImageSnapShotContext { + JSValue callback; + ExecutionContext* context; + list_head link; +}; + +class KrakenTestContext final { + public: + explicit KrakenTestContext() = delete; + explicit KrakenTestContext(ExecutionContext* 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); + + ScriptValue m_executeTestCallback{m_context->ctx()}; + ScriptValue m_executeTestProxyObject{m_context->ctx()}; + + private: + /// the pointer of JSContext, ownership belongs to JSContext + ExecutionContext* m_context{nullptr}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 8ab2a9474c..6af8ea3d5e 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -11,7 +11,7 @@ #include "foundation/native_string.h" #include "kraken_bridge_test.h" #include "kraken_test_env.h" -#include "page.h" +#include "core/page.h" #if defined(__linux__) || defined(__APPLE__) static int64_t get_time_ms(void) { @@ -28,6 +28,8 @@ static int64_t get_time_ms(void) { } #endif +namespace kraken { + typedef struct { struct list_head link; int64_t timeout; @@ -256,21 +258,21 @@ void TEST_runLoop(kraken::ExecutionContext* context) { void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { std::vector mockMethods{ - reinterpret_cast(TEST_invokeModule), - reinterpret_cast(TEST_requestBatchUpdate), - reinterpret_cast(TEST_reloadApp), - reinterpret_cast(TEST_setTimeout), - reinterpret_cast(TEST_setInterval), - reinterpret_cast(TEST_clearTimeout), - reinterpret_cast(TEST_requestAnimationFrame), - reinterpret_cast(TEST_cancelAnimationFrame), - reinterpret_cast(TEST_getScreen), - reinterpret_cast(TEST_devicePixelRatio), - reinterpret_cast(TEST_platformBrightness), - reinterpret_cast(TEST_toBlob), - reinterpret_cast(TEST_flushUICommand), - reinterpret_cast(TEST_initWindow), - reinterpret_cast(TEST_initDocument), + reinterpret_cast(TEST_invokeModule), + reinterpret_cast(TEST_requestBatchUpdate), + reinterpret_cast(TEST_reloadApp), + reinterpret_cast(TEST_setTimeout), + reinterpret_cast(TEST_setInterval), + reinterpret_cast(TEST_clearTimeout), + reinterpret_cast(TEST_requestAnimationFrame), + reinterpret_cast(TEST_cancelAnimationFrame), + reinterpret_cast(TEST_getScreen), + reinterpret_cast(TEST_devicePixelRatio), + reinterpret_cast(TEST_platformBrightness), + reinterpret_cast(TEST_toBlob), + reinterpret_cast(TEST_flushUICommand), + reinterpret_cast(TEST_initWindow), + reinterpret_cast(TEST_initDocument), }; #if ENABLE_PROFILE @@ -283,6 +285,8 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { registerDartMethods(contextId, mockMethods.data(), mockMethods.size()); } +} + // void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { // NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); // auto nativeEventType = stringToNativeString(type); diff --git a/bridge/test/kraken_test_env.h b/bridge/test/kraken_test_env.h index d30fcb74bc..533aed9729 100644 --- a/bridge/test/kraken_test_env.h +++ b/bridge/test/kraken_test_env.h @@ -9,21 +9,27 @@ #include #include "core/executing_context.h" #include "foundation/logging.h" -#include "page.h" +#include "core/page.h" +#include "core/dart_methods.h" // //// Trigger a callbacks before GC free the eventTargets. -// using TEST_OnEventTargetDisposed = void (*)(kraken::binding::qjs::EventTargetInstance* eventTargetInstance); +// using TEST_OnEventTargetDisposed = void (*)(binding::qjs::EventTargetInstance* eventTargetInstance); // struct UnitTestEnv { // TEST_OnEventTargetDisposed onEventTargetDisposed{nullptr}; //}; // //// Mock dart methods and add async timer to emulate kraken environment in C++ unit test. // -std::unique_ptr TEST_init(OnJSError onJsError); -std::unique_ptr TEST_init(); -std::unique_ptr TEST_allocateNewPage(); -void TEST_runLoop(kraken::ExecutionContext* context); + +namespace kraken { + +std::unique_ptr TEST_init(OnJSError onJsError); +std::unique_ptr TEST_init(); +std::unique_ptr TEST_allocateNewPage(); +void TEST_runLoop(ExecutionContext* context); void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); + +} // void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); // void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); // void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index b2d54f7cda..d04098c1cf 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -10,8 +10,8 @@ add_subdirectory(./third_party/googletest) add_subdirectory(./third_party/benchmark) list(APPEND KRAKEN_TEST_SOURCE - page_test.cc - page_test.h + test/kraken_test_context.cc + test/kraken_test_context.h ) list(APPEND KRAKEN_UNIT_TEST_SOURCE ./test/kraken_test_env.cc From 6dab9e7616f27a6c21beaae8531f358328f087cf Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Wed, 9 Feb 2022 12:48:43 +0800 Subject: [PATCH 017/498] fix: fix module api binding. --- bridge/bindings/qjs/binding_initializer.cc | 3 +++ bridge/bindings/qjs/qjs_module_manager.cc | 15 --------------- bridge/bindings/qjs/qjs_module_manager.h | 4 ---- bridge/core/frame/module_callback.h | 12 ++++++++++-- bridge/core/frame/module_callback_coordinator.cc | 8 ++++---- bridge/foundation/native_value.cc | 2 ++ 6 files changed, 19 insertions(+), 25 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index dde7f1260f..8bc9f8b20a 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -6,6 +6,7 @@ #include "binding_initializer.h" #include "qjs_window.h" +#include "qjs_module_manager.h" //#include "bindings/qjs/bom/blob.h" //#include "bindings/qjs/bom/console.h" @@ -45,6 +46,8 @@ namespace kraken { void installBindings(JSContext* ctx) { QJSWindow::installGlobalFunctions(ctx); + QJSModuleManager::installGlobalFunctions(ctx); + } } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_module_manager.cc b/bridge/bindings/qjs/qjs_module_manager.cc index cfdabd77e8..910b53e608 100644 --- a/bridge/bindings/qjs/qjs_module_manager.cc +++ b/bridge/bindings/qjs/qjs_module_manager.cc @@ -10,7 +10,6 @@ namespace kraken { - JSValue krakenModuleListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': 1 parameter required, but only 0 present."); @@ -35,9 +34,6 @@ JSValue krakenModuleListener(JSContext* ctx, JSValueConst this_val, int argc, JS return exception.toQuickJS(); } -// auto* link = new ModuleContext{JS_DupValue(ctx, callbackValue), context}; -// list_add_tail(&link->link, &context->module_job_list); - return JS_NULL; } @@ -72,21 +68,10 @@ JSValue krakenInvokeModule(JSContext* ctx, JSValueConst this_val, int argc, JSVa return result.toQuickJS(); } -JSValue flushUICommand(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - - if (context->dartMethodPtr()->flushUICommand == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); - } - context->dartMethodPtr()->flushUICommand(); - return JS_NULL; -} - void QJSModuleManager::installGlobalFunctions(JSContext* ctx) { std::initializer_list functionConfig { {"__kraken_module_listener__", krakenModuleListener, 1, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, {"__kraken_invoke_module__", krakenInvokeModule, 3, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, - {"__kraken_flush_ui_command__", flushUICommand, 0, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, }; JSValue globalObject = JS_GetGlobalObject(ctx); diff --git a/bridge/bindings/qjs/qjs_module_manager.h b/bridge/bindings/qjs/qjs_module_manager.h index f2994b5402..e13d058820 100644 --- a/bridge/bindings/qjs/qjs_module_manager.h +++ b/bridge/bindings/qjs/qjs_module_manager.h @@ -17,8 +17,4 @@ class QJSModuleManager final { } -// -//void bindModuleManager(ExecutionContext* context); -//void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, NativeString* errmsg, NativeString* json); - #endif // KRAKENBRIDGE_QJS_MODULE_MANAGER_H diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h index ca2a123e4e..d35a1d94c8 100644 --- a/bridge/core/frame/module_callback.h +++ b/bridge/core/frame/module_callback.h @@ -12,6 +12,15 @@ namespace kraken { +class ModuleCallback; + +// In C++ code, We can not use offsetof to access members of structures or classes that are not Plain Old Data Structures. +// So we use struct which support offsetof. +struct ModuleCallbackLinker { + ModuleCallback* ptr; + list_head link; +}; + // ModuleCallback is an asynchronous callback function, usually from the 4th parameter of `kraken.invokeModule` function. // When the asynchronous operation on the Dart side ends, the callback is will called and to return to the JS executing environment. class ModuleCallback : public GarbageCollected { @@ -23,14 +32,13 @@ class ModuleCallback : public GarbageCollected { void trace(GCVisitor*visitor) const override; void dispose() const override; - list_head link; + ModuleCallbackLinker linker{this}; private: QJSFunction* m_function{nullptr}; }; - } #endif // KRAKENBRIDGE_MODULE_CALLBACK_H diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc index 6052b3cd20..c5d36766c4 100644 --- a/bridge/core/frame/module_callback_coordinator.cc +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -8,11 +8,11 @@ namespace kraken { void ModuleCallbackCoordinator::addModuleCallbacks(ModuleCallback* callback) { - list_add_tail(&m_listeners, &callback->link); + list_add_tail(&m_listeners, &callback->linker.link); } void ModuleCallbackCoordinator::removeModuleCallbacks(ModuleCallback* callback) { - list_del(&callback->link); + list_del(&callback->linker.link); } ModuleCallbackCoordinator::ModuleCallbackCoordinator() { @@ -23,8 +23,8 @@ void ModuleCallbackCoordinator::trace(GCVisitor* visitor) { { struct list_head *el, *el1; list_for_each_safe(el, el1, &m_listeners) { - auto* callback = list_entry(el, ModuleCallback, link); - visitor->trace(callback->toQuickJS()); + auto* linker = list_entry(el, ModuleCallbackLinker, link); + visitor->trace(linker->ptr->toQuickJS()); } } } diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 4098cf4eb1..8b2347d3a5 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -156,6 +156,7 @@ static JSValue anonymousFunction(JSContext* ctx, JSValueConst this_val, int argc // JSValue returnValue = eventTarget->callNativeMethods(call_params.c_str(), argc, arguments); // delete[] arguments; // return returnValue; + return JS_NULL; } void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int32_t contextId, const char* errmsg) { @@ -215,6 +216,7 @@ static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int // delete[] arguments; // // return promise; + return JS_NULL; } JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { From adeb4222f2ed3de8b33e84daa74f6feed4a06981 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Wed, 9 Feb 2022 20:31:21 +0800 Subject: [PATCH 018/498] feat: add code generator. --- bridge/.gitignore | 1 + bridge/CMakeLists.txt | 16 ++-- bridge/bindings/qjs/binding_initializer.cc | 37 +------ bridge/bindings/qjs/script_value.cc | 8 ++ bridge/bindings/qjs/script_value.h | 2 + bridge/core/frame/console.cc | 29 ++---- bridge/core/frame/console.d.ts | 11 +++ bridge/core/frame/console.h | 14 ++- .../code_generator/bin/code_generator.js | 15 +-- bridge/scripts/code_generator/src/analyzer.ts | 35 ++++++- bridge/scripts/code_generator/src/blob.ts | 8 +- .../scripts/code_generator/src/declaration.ts | 12 ++- .../code_generator/src/generate_header.ts | 26 +++-- .../code_generator/src/genereate_source.ts | 96 +++++++++++++++++-- bridge/scripts/code_generator/src/utils.ts | 6 ++ bridge/test/kraken_test_context.cc | 26 ++--- bridge/test/kraken_test_context.h | 4 +- 17 files changed, 234 insertions(+), 112 deletions(-) create mode 100644 bridge/core/frame/console.d.ts 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 eebd39d119..546aff4ffa 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -34,15 +34,10 @@ execute_process( ) # g execute_process( - COMMAND bash "-c" "node bin/code_generator -s ../../bindings/qjs/dom/elements -d ../../bindings/qjs/dom/elements/.gen" + COMMAND bash "-c" "node bin/code_generator -s ../../core -d ../../out" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/scripts/code_generator ) # generate elements code -execute_process( - COMMAND bash "-c" "node bin/code_generator -s ../../bindings/qjs/dom/events -d ../../bindings/qjs/dom/events/.gen" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/scripts/code_generator -) # generate events code - execute_process( COMMAND bash "-c" "read dart_sdk < <(type -p dart) && echo $\{dart_sdk%/*\}/cache/dart-sdk/include | xargs" OUTPUT_VARIABLE DART_SDK @@ -142,6 +137,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 @@ -221,6 +217,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/executing_context_data.cc core/executing_context_data.h core/dart_methods.h + core/frame/console.cc + core/frame/console.h core/frame/dom_timer.cc core/frame/dom_timer.h core/frame/dom_timer_coordinator.cc @@ -255,6 +253,12 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") # core/dom/events/event_target.h ) + # Gen sources. + list(APPEND BRIDGE_SOURCE + out/qjs_console.cc + out/qjs_console.h + ) + # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 # https://stackoverflow.com/questions/14735010/how-do-you-get-gccs-builtin-frame-address-to-work-with-o2 add_compile_options(-fno-optimize-sibling-calls -fno-omit-frame-pointer) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 8bc9f8b20a..56f56109f4 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -7,47 +7,14 @@ #include "qjs_window.h" #include "qjs_module_manager.h" - -//#include "bindings/qjs/bom/blob.h" -//#include "bindings/qjs/bom/console.h" -//#include "bindings/qjs/bom/location.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/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" +#include "qjs_console.h" namespace kraken { void installBindings(JSContext* ctx) { QJSWindow::installGlobalFunctions(ctx); QJSModuleManager::installGlobalFunctions(ctx); - + QJSConsole::install(ctx); } } // namespace kraken diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 4a5231d57c..7b01063ce7 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -38,6 +38,10 @@ bool ScriptValue::isEmpty() { return JS_IsNull(m_value); } +bool ScriptValue::isString() { + return JS_IsString(m_value); +} + JSValue ScriptValue::toQuickJS() { return m_value; } @@ -60,6 +64,10 @@ std::unique_ptr ScriptValue::toNativeString() { return jsValueToNativeString(m_ctx, m_value); } +std::string ScriptValue::toCString() { + return jsValueToStdString(m_ctx, m_value); +} + bool ScriptValue::isException() { return JS_IsException(m_value); } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index b16ddfbb04..6c4ec5b570 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -43,10 +43,12 @@ class ScriptValue final { JS_FreeValue(m_ctx, m_value); } bool isEmpty(); + bool isString(); JSValue toQuickJS(); // Create a new ScriptValue from call JSON.stringify to current value. ScriptValue toJSONStringify(ExceptionState* exception); std::unique_ptr toNativeString(); + std::string toCString(); bool isException(); diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 17243e85e1..cc58a9b5bf 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -4,34 +4,19 @@ */ #include "console.h" +#include +#include "foundation/logging.h" namespace kraken { -JSValue print(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +void Console::__kraken_print__(ExecutionContext* context, ScriptValue& logValue, ScriptValue& levelValue, ExceptionState* exception) { 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); - } + std::string buffer = logValue.toCString(); + stream << buffer; - foundation::printLog(context->getContextId(), stream, logLevel, nullptr); - return JS_UNDEFINED; -} - -void bindConsole(ExecutionContext* context) { - QJS_GLOBAL_BINDING_FUNCTION(context, print, "__kraken_print__", 2); + std::string logLevel = levelValue.isEmpty() ? "info" : levelValue.toCString(); + printLog(context->getContextId(), stream, logLevel, nullptr); } } // namespace kraken diff --git a/bridge/core/frame/console.d.ts b/bridge/core/frame/console.d.ts new file mode 100644 index 0000000000..576c536d2c --- /dev/null +++ b/bridge/core/frame/console.d.ts @@ -0,0 +1,11 @@ +declare const __kraken_print__: (log: string, level?: string) => void; + +// @ts-ignore +class EventTarget { + +} + +// @ts-ignore +class Node extends EventTarget { + +} diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index e239662f09..6f9d93437b 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -3,15 +3,19 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_CONSOLE_H -#define KRAKENBRIDGE_CONSOLE_H +#ifndef KRAKE_CONSOLE_H +#define KRAKE_CONSOLE_H -#include "bindings/qjs/executing_context.h" +#include "bindings/qjs/script_value.h" +#include "core/executing_context.h" namespace kraken { -void bindConsole(ExecutionContext* context); +class Console final { + public: + static void __kraken_print__(ExecutionContext* context, ScriptValue& log, ScriptValue& level, ExceptionState* exception); +}; } -#endif // KRAKENBRIDGE_CONSOLE_H +#endif // KRAKE_CONSOLE_H diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index b0b7dbe531..bb4aff710d 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -30,19 +30,22 @@ let files = glob.sync("**/*.d.ts", { }); let blobs = files.map(file => { - let filename = file.replace('.d.ts', ''); - return new Blob(path.join(source, file), dist, filename); -}); + let filename = 'qjs_' + file.split('/').slice(-1)[0].replace('.d.ts', ''); + let implement = file.replace(path.join(__dirname, '../../')).replace('.d.ts', ''); + return new Blob(path.join(source, file), dist, filename, implement); +}).filter(blob => blob.filename === 'qjs_console'); for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; let result = analyzer(b); if (!fs.existsSync(b.dist)) { - fs.mkdirSync(b.dist); + fs.mkdirSync(b.dist, {recursive: true}); } - fs.writeFileSync(path.join(b.dist, b.filename) + '.h', result.header); - fs.writeFileSync(path.join(b.dist, b.filename) + '.cc', result.source); + let genFilePath = path.join(b.dist, b.filename); + + fs.writeFileSync(genFilePath + '.h', result.header); + fs.writeFileSync(genFilePath + '.cc', result.source); } diff --git a/bridge/scripts/code_generator/src/analyzer.ts b/bridge/scripts/code_generator/src/analyzer.ts index 2334d96629..5ae2fc9e57 100644 --- a/bridge/scripts/code_generator/src/analyzer.ts +++ b/bridge/scripts/code_generator/src/analyzer.ts @@ -1,19 +1,23 @@ -import ts, {HeritageClause, ScriptTarget} from 'typescript'; +import ts, {HeritageClause, ScriptTarget, VariableStatement} from 'typescript'; import {Blob} from './blob'; import { ClassObject, FunctionArguments, FunctionArgumentType, FunctionDeclaration, + FunctionObject, PropsDeclaration, - PropsDeclarationKind + PropsDeclarationKind, + ReturnType } 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[]; + blob.objects = sourceFile.statements.map(statement => walkProgram(statement)).filter(o => { + return o instanceof ClassObject || o instanceof FunctionObject; + }) as (FunctionObject | ClassObject)[]; return generatorSource(blob); } @@ -48,6 +52,14 @@ function getPropKind(type: ts.TypeNode): PropsDeclarationKind { return PropsDeclarationKind.object; } +function getFunctionReturnType(keyword: ts.TypeNode): ReturnType { + switch (keyword.kind) { + case ts.SyntaxKind.VoidKeyword: + return ReturnType.void; + } + return ReturnType.null; +} + function getPropName(propName: ts.PropertyName) { if (propName.kind == ts.SyntaxKind.Identifier) { return propName.escapedText.toString(); @@ -149,6 +161,23 @@ function walkProgram(statement: ts.Statement) { 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 = getFunctionReturnType((type as ts.FunctionTypeNode).type); + functionObject.declare.name = methodName.toString(); + } + + console.log(functionObject); + + return functionObject; + } } return null; diff --git a/bridge/scripts/code_generator/src/blob.ts b/bridge/scripts/code_generator/src/blob.ts index 77237f10b7..d42c3ea695 100644 --- a/bridge/scripts/code_generator/src/blob.ts +++ b/bridge/scripts/code_generator/src/blob.ts @@ -1,17 +1,19 @@ import fs from 'fs'; -import {ClassObject} from "./declaration"; +import {ClassObject, FunctionObject} from "./declaration"; export class Blob { raw: string; dist: string; source: string; filename: string; - objects: ClassObject[]; + implement: string; + objects: (ClassObject | FunctionObject)[]; - constructor(source: string, dist: string, filename: string) { + 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/declaration.ts b/bridge/scripts/code_generator/src/declaration.ts index a88a480846..a1d4d4f5fa 100644 --- a/bridge/scripts/code_generator/src/declaration.ts +++ b/bridge/scripts/code_generator/src/declaration.ts @@ -27,8 +27,14 @@ export class PropsDeclaration { readonly: boolean; } +export enum ReturnType { + void, + null +} + export class FunctionDeclaration extends PropsDeclaration { - args: FunctionArguments[] + args: FunctionArguments[]; + returnType: ReturnType; } export class ClassObject { @@ -37,3 +43,7 @@ export class ClassObject { props: PropsDeclaration[] = []; methods: FunctionDeclaration[] = []; } + +export class FunctionObject { + declare: FunctionDeclaration +} diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index 9648a38762..2a77f24272 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -1,7 +1,7 @@ -import {ClassObject, PropsDeclaration, PropsDeclarationKind} from "./declaration"; +import {ClassObject, FunctionObject, PropsDeclaration, PropsDeclarationKind} from "./declaration"; import {uniqBy} from "lodash"; import {Blob} from "./blob"; -import {addIndent} from "./utils"; +import {addIndent, getClassName} from "./utils"; function generatePropsHeader(object: ClassObject, type: PropType) { let propsDefine = ''; @@ -157,9 +157,14 @@ function generateObjectHeader(object: ClassObject) { return null; } -export function generateCppHeader(blob: Blob) { - let headers = blob.objects.map(o => generateObjectHeader(o)); +function generateFunctionHeader(blob: Blob, object: FunctionObject) { + return `class QJS${blob.filename[0].toUpperCase() + blob.filename.slice(1)} final { + public: + static void installGlobalFunctions(JSContext* ctx); +};`; +} +export function generateCppHeader(blob: Blob) { return `/* * Copyright (C) 2021 Alibaba Inc. All rights reserved. * Author: Kraken Team. @@ -168,10 +173,17 @@ export function generateCppHeader(blob: Blob) { #ifndef KRAKENBRIDGE_${blob.filename.toUpperCase()}_H #define KRAKENBRIDGE_${blob.filename.toUpperCase()}_H -#include "bindings/qjs/dom/element.h" +#include + +namespace kraken { + +class ${getClassName(blob)} final { + public: + static void install(JSContext* ctx); + private: + static void installGlobalFunctions(JSContext* ctx); +}; -namespace kraken::binding::qjs { -${headers.join('')} } #endif //KRAKENBRIDGE_${blob.filename.toUpperCase()}T_H diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index a05d753a24..6c5efa70b2 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -4,10 +4,12 @@ import { FunctionArguments, FunctionArgumentType, FunctionDeclaration, + FunctionObject, PropsDeclaration, - PropsDeclarationKind + PropsDeclarationKind, + ReturnType } from "./declaration"; -import {addIndent} from "./utils"; +import {addIndent, getClassName} from "./utils"; function generateHostObjectSource(object: ClassObject) { let propSource: string[] = generatePropsSource(object, PropType.hostObject); @@ -186,7 +188,7 @@ function generateArgumentsTypeCheck(index: number, argv: FunctionArguments, m: F return ''; } -function generateMethodArgumentsCheck(m: FunctionDeclaration, object: ClassObject) { +function generateMethodArgumentsCheck(m: FunctionDeclaration, object: ClassObject | FunctionObject) { if (m.args.length == 0) return ''; let requiredArgsCount = 0; @@ -200,7 +202,7 @@ function generateMethodArgumentsCheck(m: FunctionDeclaration, object: ClassObjec } return ` if (argc < ${requiredArgsCount}) { - return JS_ThrowTypeError(ctx, "Failed to execute '${m.name}' on '${object.name}': ${requiredArgsCount} argument required, but %d present.", argc); + return JS_ThrowTypeError(ctx, "Failed to execute '${m.name}' : ${requiredArgsCount} argument required, but %d present.", argc); } ${argsCheck.join('\n ')} `; @@ -448,18 +450,94 @@ function generateObjectSource(object: ClassObject) { return null; } +function generateFunctionValueInit(object: FunctionObject) { + return object.declare.args.map((a, i) => { + let head = `ScriptValue ${a.name} = `; + if (a.required) { + head += `ScriptValue(ctx, argv[${i}]);`; + } else { + head += `ScriptValue(ctx, JS_NULL); + if (argc > ${i}) { + ${a.name} = ScriptValue(ctx, argv[${i}]); +}`; + } + + return head; + }); +} + +function generateCoreModuleCall(blob: Blob, object: FunctionObject) { + let params = object.declare.args.map(a => `${a.name}`); + let coreClassName = blob.filename[4].toUpperCase() + blob.filename.slice(5); + let returnValue = ''; + + if (object.declare.returnType != ReturnType.void) { + returnValue = 'ScriptValue returnValue = ' + } + + return ` +auto context = static_cast(JS_GetContextOpaque(ctx)); +ExceptionState exception; + +${returnValue}${coreClassName}::${object.declare.name}(context, ${params.join(', ')}, &exception); + +if (exception.hasException()) { + return exception.toQuickJS(); +} + +${returnValue && 'return returnValue'} + + `; +} + +function generateFunctionSource(blob: Blob, object: FunctionObject) { + let paramCheck = generateMethodArgumentsCheck(object.declare, object); + let varInit = generateFunctionValueInit(object); + let moduleCall = generateCoreModuleCall(blob, object); + return `static JSValue ${object.declare.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +${paramCheck} +${varInit.join('\n')} +${moduleCall} +}`; +} + export function generateCppSource(blob: Blob) { - let sources = blob.objects.map(o => generateObjectSource(o)); + let installList: string[] = []; + + let sources = blob.objects.map(o => { + if (o instanceof FunctionObject) { + installList.push(` {"${o.declare.name}", ${o.declare.name}, ${o.declare.args.length}, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)},`); + return generateFunctionSource(blob, o); + } + return ''; + }); + return `/* * Copyright (C) 2021 Alibaba Inc. All rights reserved. * Author: Kraken Team. */ #include "${blob.filename}.h" -#include "page.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/member_installer.h" +#include "bindings/qjs/qjs_function.h" +#include "core/${blob.implement}.h" + +namespace kraken { + +${sources} + +void ${getClassName(blob)}::install(JSContext* ctx) { + installGlobalFunctions(ctx); +} + +void ${getClassName(blob)}::installGlobalFunctions(JSContext* ctx) { + std::initializer_list functionConfig { + ${installList.join(',\n')} + }; -namespace kraken::binding::qjs { - ${sources.join('')} + JSValue globalObject = JS_GetGlobalObject(ctx); + MemberInstaller::installFunctions(ctx, globalObject, functionConfig); + JS_FreeValue(ctx, globalObject); +} }`; } diff --git a/bridge/scripts/code_generator/src/utils.ts b/bridge/scripts/code_generator/src/utils.ts index 6e5524b06f..aefa5d13a1 100644 --- a/bridge/scripts/code_generator/src/utils.ts +++ b/bridge/scripts/code_generator/src/utils.ts @@ -1,3 +1,5 @@ +import {Blob} from './blob'; + export function addIndent(str: String, space: number) { let lines = str.split('\n'); lines = lines.map(l => { @@ -8,3 +10,7 @@ export function addIndent(str: String, space: number) { }); return lines.join('\n'); } + +export function getClassName(blob: Blob) { + return `QJS${blob.filename[4].toUpperCase() + blob.filename.slice(5)}`; +} diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index c036d119d4..3749e19cf9 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -38,19 +38,19 @@ bool KrakenTestContext::parseTestHTML(const uint16_t* code, size_t codeLength) { } static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - JSValue& callback = argv[0]; - auto context = static_cast(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(context->getOwner()); - auto bridgeTest = static_cast(bridge->owner); - bridgeTest->m_executeTestCallback = ScriptValue(ctx, callback); - return JS_NULL; +// JSValue& callback = argv[0]; +// auto context = static_cast(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(context->getOwner()); +// auto bridgeTest = static_cast(bridge->owner); +// bridgeTest->m_executeTestCallback = ScriptValue(ctx, callback); +// return JS_NULL; } static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { diff --git a/bridge/test/kraken_test_context.h b/bridge/test/kraken_test_context.h index cdec91c57c..d317648515 100644 --- a/bridge/test/kraken_test_context.h +++ b/bridge/test/kraken_test_context.h @@ -30,8 +30,8 @@ class KrakenTestContext final { void invokeExecuteTest(ExecuteCallback executeCallback); void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); - ScriptValue m_executeTestCallback{m_context->ctx()}; - ScriptValue m_executeTestProxyObject{m_context->ctx()}; +// ScriptValue m_executeTestCallback{m_context->ctx()}; +// ScriptValue m_executeTestProxyObject{m_context->ctx()}; private: /// the pointer of JSContext, ownership belongs to JSContext From 0254c47a42e649277ae61a0396449f20449bd06d Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Wed, 9 Feb 2022 12:32:12 +0000 Subject: [PATCH 019/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 4 +- bridge/bindings/qjs/exception_state.cc | 6 +- bridge/bindings/qjs/exception_state.h | 10 +- bridge/bindings/qjs/garbage_collected.h | 2 +- bridge/bindings/qjs/gc_visitor.cc | 2 +- bridge/bindings/qjs/gc_visitor.h | 4 +- bridge/bindings/qjs/member_installer.h | 13 +- bridge/bindings/qjs/qjs_function.cc | 4 +- bridge/bindings/qjs/qjs_function.h | 2 +- bridge/bindings/qjs/qjs_module_manager.cc | 10 +- bridge/bindings/qjs/qjs_module_manager.h | 2 +- bridge/bindings/qjs/qjs_page.cc | 6 +- bridge/bindings/qjs/qjs_page.h | 2 +- bridge/bindings/qjs/qjs_window.cc | 16 +-- bridge/bindings/qjs/qjs_window.h | 2 +- bridge/bindings/qjs/rejected_promises.cc | 2 +- bridge/bindings/qjs/rejected_promises.h | 2 +- bridge/bindings/qjs/script_value.cc | 3 +- bridge/bindings/qjs/script_value.h | 11 +- bridge/core/dart_methods.h | 4 +- bridge/core/executing_context.cc | 112 +++++++++--------- bridge/core/executing_context.h | 4 +- bridge/core/executing_context_test.cc | 2 +- bridge/core/frame/console.h | 2 +- bridge/core/frame/module_callback.cc | 4 +- bridge/core/frame/module_callback.h | 9 +- .../core/frame/module_callback_coordinator.h | 5 +- bridge/core/frame/module_listener.cc | 4 +- bridge/core/frame/module_listener.h | 6 +- .../core/frame/module_listener_container.cc | 4 +- bridge/core/frame/module_listener_container.h | 5 +- bridge/core/frame/module_manager.cc | 10 +- bridge/core/frame/module_manager_test.cc | 2 +- bridge/core/frame/screen.h | 4 +- .../frame/window_or_worker_global_scope.cc | 3 +- .../frame/window_or_worker_global_scope.h | 4 +- bridge/foundation/native_value.cc | 2 +- bridge/foundation/native_value.h | 2 +- bridge/kraken_bridge.cc | 2 +- bridge/test/kraken_test_context.cc | 65 +++++----- bridge/test/kraken_test_context.h | 6 +- bridge/test/kraken_test_env.cc | 34 +++--- bridge/test/kraken_test_env.h | 6 +- 43 files changed, 190 insertions(+), 214 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 56f56109f4..8fff1b8783 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -5,9 +5,9 @@ #include "binding_initializer.h" -#include "qjs_window.h" -#include "qjs_module_manager.h" #include "qjs_console.h" +#include "qjs_module_manager.h" +#include "qjs_window.h" namespace kraken { diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index 6c3c1ab97d..c7d09f8e81 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -8,11 +8,11 @@ namespace kraken { void ExceptionState::throwException(JSContext* ctx, ErrorType type, const char* message) { - switch(type) { + switch (type) { case ErrorType::TypeError: m_exception = JS_ThrowTypeError(ctx, "%s", message); break; - case InternalError : + case InternalError: m_exception = JS_ThrowInternalError(ctx, "%s", message); break; case RangeError: @@ -39,4 +39,4 @@ JSValue ExceptionState::toQuickJS() { return m_exception; } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index 235e98456d..e1c7cb442e 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -11,23 +11,19 @@ namespace kraken { -enum ErrorType { - TypeError, - InternalError, - RangeError, - ReferenceError, - SyntaxError -}; +enum ErrorType { TypeError, InternalError, RangeError, ReferenceError, SyntaxError }; // ExceptionState is a scope-like class and provides a way to store an exception. class ExceptionState { // ExceptionState should only allocate at stack. KRAKEN_DISALLOW_NEW(); + public: void throwException(JSContext* ctx, ErrorType type, const char* message); void throwException(JSContext* ctx, JSValue exception); bool hasException(); JSValue toQuickJS(); + private: JSValue m_exception{JS_NULL}; JSContext* m_ctx; diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index 99b11dde44..e3b69f36ab 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -82,7 +82,7 @@ class GarbageCollected { JSContext* m_ctx{nullptr}; JSRuntime* m_runtime{nullptr}; GarbageCollected(){}; - GarbageCollected(JSContext* ctx): m_runtime(JS_GetRuntime(ctx)), m_ctx(ctx) {}; + GarbageCollected(JSContext* ctx) : m_runtime(JS_GetRuntime(ctx)), m_ctx(ctx){}; friend class MakeGarbageCollectedTrait; }; diff --git a/bridge/bindings/qjs/gc_visitor.cc b/bridge/bindings/qjs/gc_visitor.cc index a8d8a65af2..b1313c967b 100644 --- a/bridge/bindings/qjs/gc_visitor.cc +++ b/bridge/bindings/qjs/gc_visitor.cc @@ -11,4 +11,4 @@ void GCVisitor::trace(JSValue value) { JS_MarkValue(m_runtime, value, m_markFunc); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/gc_visitor.h b/bridge/bindings/qjs/gc_visitor.h index 2146d08238..4864e62554 100644 --- a/bridge/bindings/qjs/gc_visitor.h +++ b/bridge/bindings/qjs/gc_visitor.h @@ -13,7 +13,7 @@ namespace kraken { // Use GCVisitor to keep track gc managed members in C++ class. class GCVisitor final { public: - explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc): m_runtime(rt), m_markFunc(markFunc) {}; + explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc) : m_runtime(rt), m_markFunc(markFunc){}; void trace(JSValue value); @@ -22,6 +22,6 @@ class GCVisitor final { JS_MarkFunc* m_markFunc{nullptr}; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_GC_VISITOR_H diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h index a00ad53484..df013bdeae 100644 --- a/bridge/bindings/qjs/member_installer.h +++ b/bridge/bindings/qjs/member_installer.h @@ -12,12 +12,7 @@ namespace kraken { // Flags for object properties. -enum JSPropFlag { - normal = JS_PROP_NORMAL, - writable = JS_PROP_WRITABLE, - enumerable = JS_PROP_ENUMERABLE, - configurable = JS_PROP_CONFIGURABLE -}; +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); @@ -30,7 +25,7 @@ class MemberInstaller { AttributeConfig& operator=(const AttributeConfig&) = delete; const char* name; JSValue value; - int flag; // Flags for object properties. + int flag; // Flags for object properties. }; struct FunctionConfig { @@ -38,13 +33,13 @@ class MemberInstaller { const char* name; JSCFunction* function; size_t length; - int flag; // Flags for object properties. + int flag; // Flags for object properties. }; static void installAttributes(JSContext* ctx, JSValue root, std::initializer_list); static void installFunctions(JSContext* ctx, JSValue root, std::initializer_list); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_MEMBER_INSTALLER_H diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index c15bdf1dfc..8bab223ac3 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -18,7 +18,7 @@ ScriptValue QJSFunction::invoke(JSContext* ctx, int32_t argc, ScriptValue* argum JSValue argv[std::max(1, argc)]; - for(int i = 0; i < argc; i ++) { + for (int i = 0; i < argc; i++) { argv[0 + i] = arguments[i].toQuickJS(); } @@ -41,4 +41,4 @@ void QJSFunction::trace(GCVisitor* visitor) const { void QJSFunction::dispose() const { JS_FreeValueRT(m_runtime, m_function); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 96f160a8de..5d5a27c0f1 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -15,7 +15,7 @@ namespace kraken { class QJSFunction : public GarbageCollected { public: static QJSFunction* create(JSContext* ctx, JSValue function) { return makeGarbageCollected(ctx, function); } - explicit QJSFunction(JSContext* ctx, JSValue function) : m_function(JS_DupValue(ctx, function)), GarbageCollected(ctx) {}; + explicit QJSFunction(JSContext* ctx, JSValue function) : m_function(JS_DupValue(ctx, function)), GarbageCollected(ctx){}; bool isFunction(JSContext* ctx); diff --git a/bridge/bindings/qjs/qjs_module_manager.cc b/bridge/bindings/qjs/qjs_module_manager.cc index 910b53e608..1a3d468a55 100644 --- a/bridge/bindings/qjs/qjs_module_manager.cc +++ b/bridge/bindings/qjs/qjs_module_manager.cc @@ -4,9 +4,9 @@ */ #include "qjs_module_manager.h" +#include "core/frame/module_manager.h" #include "member_installer.h" #include "qjs_function.h" -#include "core/frame/module_manager.h" namespace kraken { @@ -69,9 +69,9 @@ JSValue krakenInvokeModule(JSContext* ctx, JSValueConst this_val, int argc, JSVa } void QJSModuleManager::installGlobalFunctions(JSContext* ctx) { - std::initializer_list functionConfig { - {"__kraken_module_listener__", krakenModuleListener, 1, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, - {"__kraken_invoke_module__", krakenInvokeModule, 3, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + std::initializer_list functionConfig{ + {"__kraken_module_listener__", krakenModuleListener, 1, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + {"__kraken_invoke_module__", krakenInvokeModule, 3, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, }; JSValue globalObject = JS_GetGlobalObject(ctx); @@ -79,4 +79,4 @@ void QJSModuleManager::installGlobalFunctions(JSContext* ctx) { JS_FreeValue(ctx, globalObject); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_module_manager.h b/bridge/bindings/qjs/qjs_module_manager.h index e13d058820..9b029a9476 100644 --- a/bridge/bindings/qjs/qjs_module_manager.h +++ b/bridge/bindings/qjs/qjs_module_manager.h @@ -15,6 +15,6 @@ class QJSModuleManager final { static void installGlobalFunctions(JSContext* ctx); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_QJS_MODULE_MANAGER_H diff --git a/bridge/bindings/qjs/qjs_page.cc b/bridge/bindings/qjs/qjs_page.cc index d0eb6667bd..d44ebfaf0e 100644 --- a/bridge/bindings/qjs/qjs_page.cc +++ b/bridge/bindings/qjs/qjs_page.cc @@ -7,8 +7,6 @@ namespace kraken { -void QJSPage::installGlobalFunctions(JSContext* ctx) { +void QJSPage::installGlobalFunctions(JSContext* ctx) {} -} - -} +} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_page.h b/bridge/bindings/qjs/qjs_page.h index 30ad1fd62d..6b30df2d48 100644 --- a/bridge/bindings/qjs/qjs_page.h +++ b/bridge/bindings/qjs/qjs_page.h @@ -15,6 +15,6 @@ class QJSPage final { static void installGlobalFunctions(JSContext* ctx); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_QJS_PAGE_H diff --git a/bridge/bindings/qjs/qjs_window.cc b/bridge/bindings/qjs/qjs_window.cc index 207a4c978b..ec05a0faca 100644 --- a/bridge/bindings/qjs/qjs_window.cc +++ b/bridge/bindings/qjs/qjs_window.cc @@ -5,11 +5,11 @@ #include "qjs_window.h" #include -#include "qjs_function.h" -#include "exception_state.h" -#include "member_installer.h" #include "core/executing_context.h" #include "core/frame/window_or_worker_global_scope.h" +#include "exception_state.h" +#include "member_installer.h" +#include "qjs_function.h" namespace kraken { @@ -125,10 +125,10 @@ static JSValue clearTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSV } void QJSWindow::installGlobalFunctions(JSContext* ctx) { - std::initializer_list functionConfig { - {"setTimeout", setTimeout, 2, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, - {"setInterval", setInterval, 2, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, - {"clearTimeout", clearTimeout, 0, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + std::initializer_list functionConfig{ + {"setTimeout", setTimeout, 2, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + {"setInterval", setInterval, 2, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + {"clearTimeout", clearTimeout, 0, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, }; JSValue globalObject = JS_GetGlobalObject(ctx); @@ -136,4 +136,4 @@ void QJSWindow::installGlobalFunctions(JSContext* ctx) { JS_FreeValue(ctx, globalObject); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_window.h b/bridge/bindings/qjs/qjs_window.h index 8ca006e17a..385cfd0c24 100644 --- a/bridge/bindings/qjs/qjs_window.h +++ b/bridge/bindings/qjs/qjs_window.h @@ -15,6 +15,6 @@ class QJSWindow final { static void installGlobalFunctions(JSContext* ctx); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_QJS_WINDOW_H diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 3db40c5679..8b1717935b 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -64,4 +64,4 @@ void RejectedPromises::process(ExecutionContext* context) { } } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/bindings/qjs/rejected_promises.h b/bridge/bindings/qjs/rejected_promises.h index bbbc87ee31..e66bdfd1ee 100644 --- a/bridge/bindings/qjs/rejected_promises.h +++ b/bridge/bindings/qjs/rejected_promises.h @@ -39,6 +39,6 @@ class RejectedPromises { std::vector> m_reportHandledRejection; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_REJECTED_PROMISES_H_ diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 7b01063ce7..298c2f7052 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -9,7 +9,6 @@ namespace kraken { - ScriptValue ScriptValue::createErrorObject(JSContext* ctx, const char* errmsg) { JS_ThrowInternalError(ctx, "%s", errmsg); JSValue errorObject = JS_GetException(ctx); @@ -72,4 +71,4 @@ bool ScriptValue::isException() { return JS_IsException(m_value); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 6c4ec5b570..e6f8b5bfab 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -6,11 +6,11 @@ #ifndef KRAKENBRIDGE_SCRIPT_VALUE_H #define KRAKENBRIDGE_SCRIPT_VALUE_H -#include #include +#include +#include "exception_state.h" #include "foundation/macros.h" #include "foundation/native_string.h" -#include "exception_state.h" namespace kraken { @@ -18,6 +18,7 @@ namespace kraken { class ScriptValue final { // ScriptValue should only allocate at stack. KRAKEN_DISALLOW_NEW(); + public: // Create an errorObject from string error message. static ScriptValue createErrorObject(JSContext* ctx, const char* errmsg); @@ -30,7 +31,7 @@ class ScriptValue final { static ScriptValue Empty(JSContext* ctx); // Wrap an Quickjs JSValue to ScriptValue. explicit ScriptValue(JSContext* ctx, JSValue value) : m_ctx(ctx), m_value(JS_DupValue(ctx, value)){}; - explicit ScriptValue(JSContext* ctx): m_ctx(ctx) {}; + explicit ScriptValue(JSContext* ctx) : m_ctx(ctx){}; ScriptValue& operator=(const ScriptValue& other) { if (&other != this) { @@ -39,9 +40,7 @@ class ScriptValue final { return *this; }; - ~ScriptValue() { - JS_FreeValue(m_ctx, m_value); - } + ~ScriptValue() { JS_FreeValue(m_ctx, m_value); } bool isEmpty(); bool isString(); JSValue toQuickJS(); diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index fe7b2b5ccc..c0307c6010 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -12,12 +12,11 @@ #include #include -#include "foundation/native_string.h" #include "core/frame/screen.h" +#include "foundation/native_string.h" namespace kraken { - using AsyncCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg); using AsyncRAFCallback = void (*)(void* callbackContext, int32_t contextId, double result, const char* errmsg); using AsyncModuleCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json); @@ -60,7 +59,6 @@ struct MousePointer { using SimulatePointer = void (*)(MousePointer**, int32_t length, int32_t pointer); using SimulateInputText = void (*)(NativeString* nativeString); - struct DartMethodPointer { DartMethodPointer() = default; InvokeModule invokeModule{nullptr}; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 807dc270d3..6b8b35bb80 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -315,65 +315,65 @@ uint8_t* ExecutionContext::dumpByteCode(const char* code, uint32_t codeLength, c } void ExecutionContext::dispatchGlobalErrorEvent(ExecutionContext* context, JSValueConst error) { -// JSContext* ctx = context->ctx(); -// auto* window = static_cast(JS_GetOpaque(context->global(), Window::classId())); -// -// { -// JSValue ErrorEventValue = JS_GetPropertyStr(ctx, context->global(), "ErrorEvent"); -// JSValue errorType = JS_NewString(ctx, "error"); -// JSValue errorInit = JS_NewObject(ctx); -// JS_SetPropertyStr(ctx, errorInit, "error", JS_DupValue(ctx, error)); -// JS_SetPropertyStr(ctx, errorInit, "message", JS_GetPropertyStr(ctx, error, "message")); -// JS_SetPropertyStr(ctx, errorInit, "lineno", JS_GetPropertyStr(ctx, error, "lineNumber")); -// JS_SetPropertyStr(ctx, errorInit, "filename", JS_GetPropertyStr(ctx, error, "fileName")); -// JS_SetPropertyStr(ctx, errorInit, "colno", JS_NewUint32(ctx, 0)); -// JSValue arguments[] = {errorType, errorInit}; -// JSValue errorEventValue = JS_CallConstructor(context->ctx(), ErrorEventValue, 2, arguments); -// if (JS_IsException(errorEventValue)) { -// context->handleException(&errorEventValue); -// return; -// } -// -// auto* errorEvent = static_cast(JS_GetOpaque(errorEventValue, Event::kEventClassID)); -// window->dispatchEvent(errorEvent); -// -// JS_FreeValue(ctx, ErrorEventValue); -// JS_FreeValue(ctx, errorEventValue); -// JS_FreeValue(ctx, errorType); -// JS_FreeValue(ctx, errorInit); -// -// context->drainPendingPromiseJobs(); -// } + // JSContext* ctx = context->ctx(); + // auto* window = static_cast(JS_GetOpaque(context->global(), Window::classId())); + // + // { + // JSValue ErrorEventValue = JS_GetPropertyStr(ctx, context->global(), "ErrorEvent"); + // JSValue errorType = JS_NewString(ctx, "error"); + // JSValue errorInit = JS_NewObject(ctx); + // JS_SetPropertyStr(ctx, errorInit, "error", JS_DupValue(ctx, error)); + // JS_SetPropertyStr(ctx, errorInit, "message", JS_GetPropertyStr(ctx, error, "message")); + // JS_SetPropertyStr(ctx, errorInit, "lineno", JS_GetPropertyStr(ctx, error, "lineNumber")); + // JS_SetPropertyStr(ctx, errorInit, "filename", JS_GetPropertyStr(ctx, error, "fileName")); + // JS_SetPropertyStr(ctx, errorInit, "colno", JS_NewUint32(ctx, 0)); + // JSValue arguments[] = {errorType, errorInit}; + // JSValue errorEventValue = JS_CallConstructor(context->ctx(), ErrorEventValue, 2, arguments); + // if (JS_IsException(errorEventValue)) { + // context->handleException(&errorEventValue); + // return; + // } + // + // auto* errorEvent = static_cast(JS_GetOpaque(errorEventValue, Event::kEventClassID)); + // window->dispatchEvent(errorEvent); + // + // JS_FreeValue(ctx, ErrorEventValue); + // JS_FreeValue(ctx, errorEventValue); + // JS_FreeValue(ctx, errorType); + // JS_FreeValue(ctx, errorInit); + // + // context->drainPendingPromiseJobs(); + // } } static void dispatchPromiseRejectionEvent(const char* eventType, 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)); -// window->dispatchEvent(rejectEvent); -// -// JS_FreeValue(ctx, errorType); -// JS_FreeValue(ctx, errorInit); -// JS_FreeValue(ctx, rejectEventValue); -// JS_FreeValue(ctx, PromiseRejectionEventValue); -// -// context->drainPendingPromiseJobs(); -// } + // 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)); + // 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) { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index b7bd9e712d..570f63a2d2 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -16,18 +16,18 @@ #include #include #include +#include "bindings/qjs/binding_initializer.h" #include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/rejected_promises.h" #include "bindings/qjs/script_value.h" -#include "bindings/qjs/binding_initializer.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_listener_container.h" #include "frame/module_callback_coordinator.h" +#include "frame/module_listener_container.h" namespace kraken { diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index c07526948e..df6febcf84 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -4,9 +4,9 @@ */ #include "gtest/gtest.h" +#include "include/kraken_bridge.h" #include "kraken_test_env.h" #include "page.h" -#include "include/kraken_bridge.h" using namespace kraken; diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index 6f9d93437b..329dfd4394 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -16,6 +16,6 @@ class Console final { static void __kraken_print__(ExecutionContext* context, ScriptValue& log, ScriptValue& level, ExceptionState* exception); }; -} +} // namespace kraken #endif // KRAKE_CONSOLE_H diff --git a/bridge/core/frame/module_callback.cc b/bridge/core/frame/module_callback.cc index e7a1403982..63024a9c38 100644 --- a/bridge/core/frame/module_callback.cc +++ b/bridge/core/frame/module_callback.cc @@ -7,7 +7,7 @@ namespace kraken { -ModuleCallback::ModuleCallback(QJSFunction* function): m_function(function) {} +ModuleCallback::ModuleCallback(QJSFunction* function) : m_function(function) {} QJSFunction* ModuleCallback::value() { return m_function; @@ -21,4 +21,4 @@ void ModuleCallback::dispose() const { m_function->dispose(); } -} +} // namespace kraken diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h index d35a1d94c8..a19f765a71 100644 --- a/bridge/core/frame/module_callback.h +++ b/bridge/core/frame/module_callback.h @@ -6,9 +6,9 @@ #ifndef KRAKENBRIDGE_MODULE_CALLBACK_H #define KRAKENBRIDGE_MODULE_CALLBACK_H +#include #include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/qjs_function.h" -#include namespace kraken { @@ -29,16 +29,15 @@ class ModuleCallback : public GarbageCollected { QJSFunction* value(); - void trace(GCVisitor*visitor) const override; + void trace(GCVisitor* visitor) const override; void dispose() const override; ModuleCallbackLinker linker{this}; -private: + private: QJSFunction* m_function{nullptr}; }; - -} +} // namespace kraken #endif // KRAKENBRIDGE_MODULE_CALLBACK_H diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_callback_coordinator.h index 8e0c1375ee..0a13dc17ee 100644 --- a/bridge/core/frame/module_callback_coordinator.h +++ b/bridge/core/frame/module_callback_coordinator.h @@ -9,14 +9,13 @@ #include // Quickjs's linked-list are more efficient than STL forward_list. #include -#include "module_manager.h" #include "module_callback.h" +#include "module_manager.h" namespace kraken { class ModuleCallbackCoordinator final { public: - ModuleCallbackCoordinator(); void addModuleCallbacks(ModuleCallback* callback); @@ -28,6 +27,6 @@ class ModuleCallbackCoordinator final { list_head m_listeners; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H diff --git a/bridge/core/frame/module_listener.cc b/bridge/core/frame/module_listener.cc index 1c9c2de22f..98ceeb8db1 100644 --- a/bridge/core/frame/module_listener.cc +++ b/bridge/core/frame/module_listener.cc @@ -7,7 +7,7 @@ namespace kraken { -ModuleListener::ModuleListener(QJSFunction* function): m_function(function) {} +ModuleListener::ModuleListener(QJSFunction* function) : m_function(function) {} void ModuleListener::trace(GCVisitor* visitor) const { m_function->trace(visitor); @@ -17,4 +17,4 @@ void ModuleListener::dispose() const { m_function->dispose(); } -} +} // namespace kraken diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h index d85689a6de..b28d60df67 100644 --- a/bridge/core/frame/module_listener.h +++ b/bridge/core/frame/module_listener.h @@ -16,14 +16,14 @@ namespace kraken { class ModuleListener : public GarbageCollected { public: explicit ModuleListener(QJSFunction* function); - private: - void trace(GCVisitor*visitor) const override; + private: + void trace(GCVisitor* visitor) const override; void dispose() const override; QJSFunction* m_function{nullptr}; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_MODULE_LISTENER_H diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc index 92ef768dc9..8e7e7c750b 100644 --- a/bridge/core/frame/module_listener_container.cc +++ b/bridge/core/frame/module_listener_container.cc @@ -12,9 +12,9 @@ void ModuleListenerContainer::addModuleListener(ModuleListener* listener) { } void ModuleListenerContainer::trace(GCVisitor* visitor) { - for(auto& listener: m_listeners) { + for (auto& listener : m_listeners) { visitor->trace(listener->toQuickJS()); } } -} +} // namespace kraken diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h index a385326868..fe946e7d0d 100644 --- a/bridge/core/frame/module_listener_container.h +++ b/bridge/core/frame/module_listener_container.h @@ -6,14 +6,13 @@ #ifndef KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H #define KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H -#include "module_listener.h" #include +#include "module_listener.h" namespace kraken { class ModuleListenerContainer final { public: - void addModuleListener(ModuleListener* listener); void trace(GCVisitor* visitor); @@ -21,6 +20,6 @@ class ModuleListenerContainer final { std::forward_list m_listeners; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 5592fac6c8..e9813e9405 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -4,8 +4,8 @@ */ #include "module_manager.h" -#include "module_callback.h" #include "core/executing_context.h" +#include "module_callback.h" namespace kraken { @@ -31,9 +31,7 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha if (errmsg != nullptr) { ScriptValue errorObject = ScriptValue::createErrorObject(ctx, errmsg); - ScriptValue arguments[] = { - errorObject - }; + ScriptValue arguments[] = {errorObject}; ScriptValue returnValue = moduleContext->callback->value()->invoke(ctx, 1, arguments); if (returnValue.isException()) { context->handleException(&returnValue); @@ -42,9 +40,7 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha std::u16string argumentString = std::u16string(reinterpret_cast(json->string), json->length); std::string utf8Arguments = toUTF8(argumentString); ScriptValue jsonObject = ScriptValue::createJSONObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); - ScriptValue arguments[] = { - jsonObject - }; + ScriptValue arguments[] = {jsonObject}; ScriptValue returnValue = moduleContext->callback->value()->invoke(ctx, 1, arguments); if (returnValue.isException()) { context->handleException(&returnValue); diff --git a/bridge/core/frame/module_manager_test.cc b/bridge/core/frame/module_manager_test.cc index d401230776..5218360da0 100644 --- a/bridge/core/frame/module_manager_test.cc +++ b/bridge/core/frame/module_manager_test.cc @@ -71,4 +71,4 @@ f(); EXPECT_EQ(logCalled, true); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/core/frame/screen.h b/bridge/core/frame/screen.h index 43afef226f..98be8f7a82 100644 --- a/bridge/core/frame/screen.h +++ b/bridge/core/frame/screen.h @@ -13,7 +13,7 @@ struct NativeScreen { double height; }; -//class Screen : public HostObject { +// class Screen : public HostObject { // public: // explicit Screen(ExecutionContext* context) : HostObject(context, "Screen"){}; // @@ -22,7 +22,7 @@ struct NativeScreen { // DEFINE_READONLY_PROPERTY(height); //}; -//void bindScreen(ExecutionContext* context); +// void bindScreen(ExecutionContext* context); } // namespace kraken diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index fe4c54a849..7798a24f40 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -49,7 +49,6 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e handleTimerCallback(timer, errmsg); } - int WindowOrWorkerGlobalScope::setTimeout(ExecutionContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception) { #if FLUTTER_BACKEND if (context->dartMethodPtr()->setTimeout == nullptr) { @@ -100,4 +99,4 @@ void WindowOrWorkerGlobalScope::clearTimeout(ExecutionContext* context, int32_t context->timers()->removeTimeoutById(timerId); } -} +} // namespace kraken diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h index c134db2ed7..1d3e885dd5 100644 --- a/bridge/core/frame/window_or_worker_global_scope.h +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -6,9 +6,9 @@ #ifndef KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H #define KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H -#include "core/executing_context.h" -#include "bindings/qjs/qjs_function.h" #include "bindings/qjs/exception_state.h" +#include "bindings/qjs/qjs_function.h" +#include "core/executing_context.h" namespace kraken { diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 8b2347d3a5..83e027c87c 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -276,4 +276,4 @@ std::string nativeStringToStdString(NativeString* nativeString) { return toUTF8(u16EventType); } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index d0a88d5e99..77538834ae 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -69,6 +69,6 @@ JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value); std::string nativeStringToStdString(NativeString* nativeString); -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_NATIVE_VALUE_H diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 626ed309e2..4fa363fc45 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -8,12 +8,12 @@ #include #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" #include "include/kraken_bridge.h" -#include "core/page.h" #if defined(_WIN32) #define SYSTEM_NAME "windows" // Windows diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index 3749e19cf9..3a5e3da12f 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -23,7 +23,6 @@ KrakenTestContext::KrakenTestContext(ExecutionContext* context) : m_context(cont // init_list_head(&image_link); } - bool KrakenTestContext::evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { if (!m_context->isValid()) return false; @@ -31,26 +30,26 @@ bool KrakenTestContext::evaluateTestScripts(const uint16_t* code, size_t codeLen } bool KrakenTestContext::parseTestHTML(const uint16_t* code, size_t codeLength) { -// if (!m_page_context->isValid()) -// return false; -// std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), codeLength)); -// return m_page->parseHTML(utf8Code.c_str(), utf8Code.length()); + // if (!m_page_context->isValid()) + // return false; + // std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(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(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(context->getOwner()); -// auto bridgeTest = static_cast(bridge->owner); -// bridgeTest->m_executeTestCallback = ScriptValue(ctx, callback); -// return JS_NULL; + // JSValue& callback = argv[0]; + // auto context = static_cast(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(context->getOwner()); + // auto bridgeTest = static_cast(bridge->owner); + // bridgeTest->m_executeTestCallback = ScriptValue(ctx, callback); + // return JS_NULL; } static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { @@ -206,21 +205,21 @@ static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc }; static JSValue parseHTML(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { -// auto* context = static_cast(JS_GetContextOpaque(ctx)); -// -// if (argc == 1) { -// JSValue& html = argv[0]; -// -// std::string strHTML = jsValueToStdString(ctx, html); -// -// JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); -// auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); -// HTMLParser::parseHTML(strHTML, body); -// -// JS_FreeValue(ctx, bodyValue); -// } -// -// return JS_NULL; + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // + // if (argc == 1) { + // JSValue& html = argv[0]; + // + // std::string strHTML = jsValueToStdString(ctx, html); + // + // JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); + // auto* body = static_cast(JS_GetOpaque(bodyValue, Element::classId())); + // HTMLParser::parseHTML(strHTML, body); + // + // JS_FreeValue(ctx, bodyValue); + // } + // + // return JS_NULL; } static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { diff --git a/bridge/test/kraken_test_context.h b/bridge/test/kraken_test_context.h index d317648515..01ae8928f1 100644 --- a/bridge/test/kraken_test_context.h +++ b/bridge/test/kraken_test_context.h @@ -6,9 +6,9 @@ #ifndef KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H #define KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H +#include "bindings/qjs/qjs_function.h" #include "core/executing_context.h" #include "core/page.h" -#include "bindings/qjs/qjs_function.h" #include "kraken_bridge_test.h" namespace kraken { @@ -30,8 +30,8 @@ class KrakenTestContext final { void invokeExecuteTest(ExecuteCallback executeCallback); void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); -// ScriptValue m_executeTestCallback{m_context->ctx()}; -// ScriptValue m_executeTestProxyObject{m_context->ctx()}; + // ScriptValue m_executeTestCallback{m_context->ctx()}; + // ScriptValue m_executeTestProxyObject{m_context->ctx()}; private: /// the pointer of JSContext, ownership belongs to JSContext diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 6af8ea3d5e..8c4174fb7d 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -8,10 +8,10 @@ #include "core/dom/frame_request_callback_collection.h" #include "core/frame/dom_timer.h" +#include "core/page.h" #include "foundation/native_string.h" #include "kraken_bridge_test.h" #include "kraken_test_env.h" -#include "core/page.h" #if defined(__linux__) || defined(__APPLE__) static int64_t get_time_ms(void) { @@ -258,21 +258,21 @@ void TEST_runLoop(kraken::ExecutionContext* context) { void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { std::vector mockMethods{ - reinterpret_cast(TEST_invokeModule), - reinterpret_cast(TEST_requestBatchUpdate), - reinterpret_cast(TEST_reloadApp), - reinterpret_cast(TEST_setTimeout), - reinterpret_cast(TEST_setInterval), - reinterpret_cast(TEST_clearTimeout), - reinterpret_cast(TEST_requestAnimationFrame), - reinterpret_cast(TEST_cancelAnimationFrame), - reinterpret_cast(TEST_getScreen), - reinterpret_cast(TEST_devicePixelRatio), - reinterpret_cast(TEST_platformBrightness), - reinterpret_cast(TEST_toBlob), - reinterpret_cast(TEST_flushUICommand), - reinterpret_cast(TEST_initWindow), - reinterpret_cast(TEST_initDocument), + reinterpret_cast(TEST_invokeModule), + reinterpret_cast(TEST_requestBatchUpdate), + reinterpret_cast(TEST_reloadApp), + reinterpret_cast(TEST_setTimeout), + reinterpret_cast(TEST_setInterval), + reinterpret_cast(TEST_clearTimeout), + reinterpret_cast(TEST_requestAnimationFrame), + reinterpret_cast(TEST_cancelAnimationFrame), + reinterpret_cast(TEST_getScreen), + reinterpret_cast(TEST_devicePixelRatio), + reinterpret_cast(TEST_platformBrightness), + reinterpret_cast(TEST_toBlob), + reinterpret_cast(TEST_flushUICommand), + reinterpret_cast(TEST_initWindow), + reinterpret_cast(TEST_initDocument), }; #if ENABLE_PROFILE @@ -285,7 +285,7 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { registerDartMethods(contextId, mockMethods.data(), mockMethods.size()); } -} +} // namespace kraken // void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { // NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); diff --git a/bridge/test/kraken_test_env.h b/bridge/test/kraken_test_env.h index 533aed9729..4f47ae6dcc 100644 --- a/bridge/test/kraken_test_env.h +++ b/bridge/test/kraken_test_env.h @@ -7,10 +7,10 @@ #define KRAKENBRIDGE_TEST_KRAKEN_TEST_ENV_H_ #include +#include "core/dart_methods.h" #include "core/executing_context.h" -#include "foundation/logging.h" #include "core/page.h" -#include "core/dart_methods.h" +#include "foundation/logging.h" // //// Trigger a callbacks before GC free the eventTargets. // using TEST_OnEventTargetDisposed = void (*)(binding::qjs::EventTargetInstance* eventTargetInstance); @@ -29,7 +29,7 @@ std::unique_ptr TEST_allocateNewPage(); void TEST_runLoop(ExecutionContext* context); void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); -} +} // namespace kraken // void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); // void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); // void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); From bd0398bce7ee329293fa646c80076c518bb7969e Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 10 Feb 2022 18:01:18 +0800 Subject: [PATCH 020/498] feat: use code genereator to replace module manager. --- bridge/CMakeLists.txt | 4 +- bridge/bindings/qjs/binding_initializer.cc | 2 +- bridge/bindings/qjs/member_installer.cc | 2 - bridge/bindings/qjs/qjs_module_manager.cc | 82 ------------------- bridge/bindings/qjs/qjs_module_manager.h | 20 ----- bridge/core/executing_context.cc | 28 ------- bridge/core/frame/console.d.ts | 10 --- bridge/core/frame/console_test.cc | 3 +- bridge/core/frame/module_manager.cc | 4 +- bridge/core/frame/module_manager.d.ts | 2 + bridge/core/frame/module_manager.h | 4 +- bridge/polyfill/src/bridge.ts | 4 +- .../code_generator/bin/code_generator.js | 2 +- bridge/scripts/code_generator/src/analyzer.ts | 9 +- .../scripts/code_generator/src/declaration.ts | 1 + .../code_generator/src/generate_header.ts | 2 +- .../code_generator/src/genereate_source.ts | 60 +++++++++----- bridge/scripts/code_generator/src/utils.ts | 5 +- bridge/test/test.cmake | 4 +- 19 files changed, 70 insertions(+), 178 deletions(-) delete mode 100644 bridge/bindings/qjs/qjs_module_manager.cc delete mode 100644 bridge/bindings/qjs/qjs_module_manager.h create mode 100644 bridge/core/frame/module_manager.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 546aff4ffa..7b6399677e 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -206,8 +206,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/qjs_window.h bindings/qjs/qjs_page.cc bindings/qjs/qjs_page.h - bindings/qjs/qjs_module_manager.cc - bindings/qjs/qjs_module_manager.h # Core sources core/executing_context.cc @@ -257,6 +255,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") list(APPEND BRIDGE_SOURCE out/qjs_console.cc out/qjs_console.h + out/qjs_module_manager.cc + out/qjs_module_manager.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 8fff1b8783..33d27482f4 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -13,7 +13,7 @@ namespace kraken { void installBindings(JSContext* ctx) { QJSWindow::installGlobalFunctions(ctx); - QJSModuleManager::installGlobalFunctions(ctx); + QJSModuleManager::install(ctx); QJSConsole::install(ctx); } diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc index bc05ea884d..0e236c6778 100644 --- a/bridge/bindings/qjs/member_installer.cc +++ b/bridge/bindings/qjs/member_installer.cc @@ -20,8 +20,6 @@ void MemberInstaller::installAttributes(JSContext* ctx, JSValue root, std::initi } } -int compilePropFlags(); - void MemberInstaller::installFunctions(JSContext* ctx, JSValue root, std::initializer_list config) { for (auto& c : config) { JSValue function = JS_NewCFunction(ctx, c.function, c.name, c.length); diff --git a/bridge/bindings/qjs/qjs_module_manager.cc b/bridge/bindings/qjs/qjs_module_manager.cc deleted file mode 100644 index 1a3d468a55..0000000000 --- a/bridge/bindings/qjs/qjs_module_manager.cc +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "qjs_module_manager.h" -#include "core/frame/module_manager.h" -#include "member_installer.h" -#include "qjs_function.h" - -namespace kraken { - -JSValue krakenModuleListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': 1 parameter required, but only 0 present."); - } - - JSValue callbackValue = argv[0]; - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': parameter 1 (callback) must be a function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_module_listener__': parameter 1 (callback) must be a function."); - } - - auto context = static_cast(JS_GetContextOpaque(ctx)); - - QJSFunction* handler = QJSFunction::create(ctx, callbackValue); - ExceptionState exception; - - ModuleManager::addModuleListener(context, handler, &exception); - if (exception.hasException()) { - return exception.toQuickJS(); - } - - return JS_NULL; -} - -JSValue krakenInvokeModule(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - if (argc < 2) { - return JS_ThrowTypeError(ctx, "Failed to execute 'kraken.invokeModule()': 2 arguments required."); - } - - ScriptValue moduleName = ScriptValue(ctx, argv[0]); - ScriptValue methodValue = ScriptValue(ctx, argv[1]); - ScriptValue paramsValue = ScriptValue(ctx, JS_NULL); - - QJSFunction* callback = nullptr; - - auto* context = static_cast(JS_GetContextOpaque(ctx)); - - if (argc > 2 && !JS_IsNull(argv[2])) { - paramsValue = ScriptValue(ctx, argv[2]); - } - - if (argc > 3 && JS_IsFunction(ctx, argv[3])) { - callback = QJSFunction::create(ctx, argv[3]); - } - - ExceptionState exception; - ScriptValue result = ModuleManager::invokeModule(context, moduleName, methodValue, paramsValue, callback, &exception); - - if (exception.hasException()) { - return exception.toQuickJS(); - } - - return result.toQuickJS(); -} - -void QJSModuleManager::installGlobalFunctions(JSContext* ctx) { - std::initializer_list functionConfig{ - {"__kraken_module_listener__", krakenModuleListener, 1, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, - {"__kraken_invoke_module__", krakenInvokeModule, 3, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, - }; - - JSValue globalObject = JS_GetGlobalObject(ctx); - MemberInstaller::installFunctions(ctx, globalObject, functionConfig); - JS_FreeValue(ctx, globalObject); -} - -} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_module_manager.h b/bridge/bindings/qjs/qjs_module_manager.h deleted file mode 100644 index 9b029a9476..0000000000 --- a/bridge/bindings/qjs/qjs_module_manager.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_QJS_MODULE_MANAGER_H -#define KRAKENBRIDGE_QJS_MODULE_MANAGER_H - -#include - -namespace kraken { - -class QJSModuleManager final { - public: - static void installGlobalFunctions(JSContext* ctx); -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_QJS_MODULE_MANAGER_H diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 6b8b35bb80..b39c36e494 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -96,34 +96,6 @@ 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; diff --git a/bridge/core/frame/console.d.ts b/bridge/core/frame/console.d.ts index 576c536d2c..1e2d5a4472 100644 --- a/bridge/core/frame/console.d.ts +++ b/bridge/core/frame/console.d.ts @@ -1,11 +1 @@ declare const __kraken_print__: (log: string, level?: string) => void; - -// @ts-ignore -class EventTarget { - -} - -// @ts-ignore -class Node extends EventTarget { - -} diff --git a/bridge/core/frame/console_test.cc b/bridge/core/frame/console_test.cc index 0e986de4e6..013a7c8fef 100644 --- a/bridge/core/frame/console_test.cc +++ b/bridge/core/frame/console_test.cc @@ -6,7 +6,8 @@ #include "console.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" + +using namespace kraken; TEST(Console, rawPrintShouldWork) { static bool logExecuted = false; diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index e9813e9405..4eb8e72faf 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -55,7 +55,7 @@ void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t context static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); } -ScriptValue ModuleManager::invokeModule(ExecutionContext* context, ScriptValue& moduleNameValue, ScriptValue& methodValue, ScriptValue& paramsValue, QJSFunction* callback, ExceptionState* exception) { +ScriptValue ModuleManager::__kraken_invoke_module__(ExecutionContext* context, ScriptValue& moduleNameValue, ScriptValue& methodValue, ScriptValue& paramsValue, QJSFunction* callback, ExceptionState* exception) { std::unique_ptr moduleName = moduleNameValue.toNativeString(); std::unique_ptr method = methodValue.toNativeString(); std::unique_ptr params; @@ -103,7 +103,7 @@ ScriptValue ModuleManager::invokeModule(ExecutionContext* context, ScriptValue& return resultString; } -void ModuleManager::addModuleListener(ExecutionContext* context, QJSFunction* handler, ExceptionState* exception) { +void ModuleManager::__kraken_add_module_listener__(ExecutionContext* context, QJSFunction* handler, ExceptionState* exception) { auto* listener = makeGarbageCollected(handler); context->moduleListeners()->addModuleListener(listener); } diff --git a/bridge/core/frame/module_manager.d.ts b/bridge/core/frame/module_manager.d.ts new file mode 100644 index 0000000000..2386df2407 --- /dev/null +++ b/bridge/core/frame/module_manager.d.ts @@ -0,0 +1,2 @@ +declare const __kraken_invoke_module__: (moduleName: string, methodName: string, paramsValue?: string, callback?: Function) => string; +declare const __kraken_add_module_listener__: (callback?: Function) => void; diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 11d66d38d6..3eb966881d 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -14,8 +14,8 @@ namespace kraken { class ModuleManager { public: - static ScriptValue invokeModule(ExecutionContext* context, ScriptValue& moduleName, ScriptValue& method, ScriptValue& params, QJSFunction* callback, ExceptionState* exception); - static void addModuleListener(ExecutionContext* context, QJSFunction* handler, ExceptionState* exception); + static ScriptValue __kraken_invoke_module__(ExecutionContext* context, ScriptValue& moduleName, ScriptValue& method, ScriptValue& params, QJSFunction* callback, ExceptionState* exception); + static void __kraken_add_module_listener__(ExecutionContext* context, QJSFunction* handler, ExceptionState* exception); }; } // namespace kraken diff --git a/bridge/polyfill/src/bridge.ts b/bridge/polyfill/src/bridge.ts index 6d6e681b05..d9079f0743 100644 --- a/bridge/polyfill/src/bridge.ts +++ b/bridge/polyfill/src/bridge.ts @@ -19,8 +19,8 @@ export interface PrivateKraken { declare const __kraken_invoke_module__: (module: string, method: string, params?: Object | null, fn?: (err: Error, data: any) => void) => string; export const krakenInvokeModule = __kraken_invoke_module__; -declare const __kraken_module_listener__: (fn: (moduleName: string, event: Event, extra: string) => void) => void; -export const addKrakenModuleListener = __kraken_module_listener__; +declare const __kraken_add_module_listener__: (fn: (moduleName: string, event: Event, extra: string) => void) => void; +export const addKrakenModuleListener = __kraken_add_module_listener__; declare const __kraken_print__: (log: string, level?: string) => void; export const krakenPrint = __kraken_print__; diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index bb4aff710d..8dd3126025 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -33,7 +33,7 @@ let blobs = files.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 Blob(path.join(source, file), dist, filename, implement); -}).filter(blob => blob.filename === 'qjs_console'); +}).filter(blob => blob.filename === 'qjs_module_manager'); for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; diff --git a/bridge/scripts/code_generator/src/analyzer.ts b/bridge/scripts/code_generator/src/analyzer.ts index 5ae2fc9e57..c64e926c5c 100644 --- a/bridge/scripts/code_generator/src/analyzer.ts +++ b/bridge/scripts/code_generator/src/analyzer.ts @@ -85,6 +85,13 @@ function getParameterType(type: ts.TypeNode) { return FunctionArgumentType.number; } else if (type.kind === ts.SyntaxKind.BooleanKeyword) { return FunctionArgumentType.boolean; + } 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; + } } return FunctionArgumentType.union; } @@ -174,8 +181,6 @@ function walkProgram(statement: ts.Statement) { functionObject.declare.name = methodName.toString(); } - console.log(functionObject); - return functionObject; } } diff --git a/bridge/scripts/code_generator/src/declaration.ts b/bridge/scripts/code_generator/src/declaration.ts index a1d4d4f5fa..24f48c643f 100644 --- a/bridge/scripts/code_generator/src/declaration.ts +++ b/bridge/scripts/code_generator/src/declaration.ts @@ -2,6 +2,7 @@ export enum FunctionArgumentType { string, number, boolean, + function, union } diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index 2a77f24272..d7c6e1fa0c 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -177,7 +177,7 @@ export function generateCppHeader(blob: Blob) { namespace kraken { -class ${getClassName(blob)} final { +class QJS${getClassName(blob)} final { public: static void install(JSContext* ctx); private: diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index 6c5efa70b2..fbb5904337 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -10,6 +10,7 @@ import { ReturnType } from "./declaration"; import {addIndent, getClassName} from "./utils"; +import {capitalize, camelCase} from 'lodash'; function generateHostObjectSource(object: ClassObject) { let propSource: string[] = generatePropsSource(object, PropType.hostObject); @@ -183,6 +184,10 @@ function generateArgumentsTypeCheck(index: number, argv: FunctionArguments, m: F return `if (!JS_IsBool(argv[${index}])) { return JS_ThrowTypeError(ctx, "Failed to execute ${m.name}: ${index + 1}st arguments is not Boolean."); }` + } else if (argv.type === FunctionArgumentType.function) { + return `if (!JS_IsFunction(ctx, argv[${index}])) { + return JS_ThrowTypeError(ctx, "Failed to execute ${m.name}: ${index + 1}st arguments is not Function."); + }`; } return ''; @@ -451,31 +456,51 @@ function generateObjectSource(object: ClassObject) { } function generateFunctionValueInit(object: FunctionObject) { - return object.declare.args.map((a, i) => { - let head = `ScriptValue ${a.name} = `; - if (a.required) { - head += `ScriptValue(ctx, argv[${i}]);`; - } else { - head += `ScriptValue(ctx, JS_NULL); - if (argc > ${i}) { - ${a.name} = ScriptValue(ctx, argv[${i}]); + function generateValueInitHead(argument: FunctionArguments) { + if (argument.type === FunctionArgumentType.function) { + return `QJSFunction* ${argument.name} = `; + } + return `ScriptValue ${argument.name} = `; + } + + function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { + if (argument.type === FunctionArgumentType.function) { + return `QJSFunction::create(ctx, argv[${argsIndex}])`; + } + return `ScriptValue(ctx, argv[${argsIndex}]);`; + } + + function generateInitBody(argument: FunctionArguments, argsIndex: number) { + if (argument.type === FunctionArgumentType.function) { + return `nullptr; +if (argc > ${argsIndex} && JS_IsFunction(ctx, argv[${argsIndex}])) { + ${argument.name} = QJSFunction::create(ctx, argv[${argsIndex}]); }`; + } else { + return `ScriptValue(ctx, JS_NULL); +if (argc > ${argsIndex}) { + ${argument.name} = ScriptValue(ctx, argv[${argsIndex}]); +}` } + } - return head; + return object.declare.args.map((a, i) => { + let initHead = generateValueInitHead(a); + let initBody = a.required ? generateRequiredInitBody(a, i) : generateInitBody(a, i); + return addIndent(initHead + initBody, 2); }); } function generateCoreModuleCall(blob: Blob, object: FunctionObject) { let params = object.declare.args.map(a => `${a.name}`); - let coreClassName = blob.filename[4].toUpperCase() + blob.filename.slice(5); + let coreClassName = getClassName(blob); let returnValue = ''; if (object.declare.returnType != ReturnType.void) { returnValue = 'ScriptValue returnValue = ' } - return ` + return addIndent(` auto context = static_cast(JS_GetContextOpaque(ctx)); ExceptionState exception; @@ -484,10 +509,7 @@ ${returnValue}${coreClassName}::${object.declare.name}(context, ${params.join(', if (exception.hasException()) { return exception.toQuickJS(); } - -${returnValue && 'return returnValue'} - - `; +${returnValue && 'return returnValue.toQuickJS();'}`, 2); } function generateFunctionSource(blob: Blob, object: FunctionObject) { @@ -524,15 +546,15 @@ export function generateCppSource(blob: Blob) { namespace kraken { -${sources} +${sources.join('\n')} -void ${getClassName(blob)}::install(JSContext* ctx) { +void QJS${getClassName(blob)}::install(JSContext* ctx) { installGlobalFunctions(ctx); } -void ${getClassName(blob)}::installGlobalFunctions(JSContext* ctx) { +void QJS${getClassName(blob)}::installGlobalFunctions(JSContext* ctx) { std::initializer_list functionConfig { - ${installList.join(',\n')} + ${installList.join('\n')} }; JSValue globalObject = JS_GetGlobalObject(ctx); diff --git a/bridge/scripts/code_generator/src/utils.ts b/bridge/scripts/code_generator/src/utils.ts index aefa5d13a1..cf9a71d3e7 100644 --- a/bridge/scripts/code_generator/src/utils.ts +++ b/bridge/scripts/code_generator/src/utils.ts @@ -1,4 +1,5 @@ import {Blob} from './blob'; +import {camelCase} from 'lodash'; export function addIndent(str: String, space: number) { let lines = str.split('\n'); @@ -12,5 +13,7 @@ export function addIndent(str: String, space: number) { } export function getClassName(blob: Blob) { - return `QJS${blob.filename[4].toUpperCase() + blob.filename.slice(5)}`; + let raw = camelCase(blob.filename[4].toUpperCase() + blob.filename.slice(5)); + + return `${raw[0].toUpperCase() + raw.slice(1)}`; } diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index d04098c1cf..0858329d5d 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -17,8 +17,8 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./test/kraken_test_env.cc ./test/kraken_test_env.h ./core/executing_context_test.cc -# ./bindings/qjs/bom/timer_test.cc -# ./bindings/qjs/bom/console_test.cc + ./core/frame/console_test.cc + # ./bindings/qjs/bom/timer_test.cc # ./bindings/qjs/qjs_patch_test.cc # ./bindings/qjs/garbage_collected_test.cc # ./bindings/qjs/dom/event_target_test.cc From 6f68e555f01e03f40737e21ddd4aa08d00acd915 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 10 Feb 2022 21:12:17 +0800 Subject: [PATCH 021/498] feat: add class base property install template. --- bridge/CMakeLists.txt | 4 + bridge/bindings/qjs/binding_initializer.cc | 9 +- bridge/bindings/qjs/binding_initializer.h | 4 +- bridge/bindings/qjs/garbage_collected.h | 4 +- bridge/bindings/qjs/member_installer.cc | 53 ++++++- bridge/bindings/qjs/member_installer.h | 14 +- bridge/bindings/qjs/qjs_blob.cc | 130 ++++++++++++++++++ bridge/bindings/qjs/qjs_blob.h | 38 +++++ bridge/bindings/qjs/qjs_window.cc | 18 ++- bridge/bindings/qjs/qjs_window.h | 4 +- bridge/bindings/qjs/rejected_promises.cc | 8 +- bridge/bindings/qjs/rejected_promises.h | 10 +- .../dom/frame_request_callback_collection.cc | 2 +- bridge/core/executing_context.cc | 70 +++++----- bridge/core/executing_context.h | 68 ++------- bridge/core/executing_context_data.h | 6 +- bridge/core/fileapi/blob.cc | 44 +++--- bridge/core/fileapi/blob.d.ts | 10 ++ bridge/core/fileapi/blob.h | 69 +--------- bridge/core/frame/console.cc | 2 +- bridge/core/frame/console.h | 2 +- bridge/core/frame/dom_timer.cc | 2 +- bridge/core/frame/dom_timer_coordinator.cc | 6 +- bridge/core/frame/dom_timer_coordinator.h | 4 +- bridge/core/frame/module_manager.cc | 8 +- bridge/core/frame/module_manager.h | 4 +- .../frame/window_or_worker_global_scope.cc | 12 +- .../frame/window_or_worker_global_scope.h | 6 +- bridge/core/page.cc | 4 +- bridge/core/page.h | 4 +- bridge/foundation/native_value.cc | 8 +- bridge/foundation/native_value.h | 10 +- bridge/foundation/ui_command_buffer.cc | 2 +- bridge/foundation/ui_command_buffer.h | 6 +- bridge/kraken_bridge.cc | 2 +- .../code_generator/bin/code_generator.js | 2 +- .../code_generator/src/generate_header.ts | 6 +- .../code_generator/src/genereate_source.ts | 23 ++-- bridge/test/kraken_test_context.cc | 12 +- bridge/test/kraken_test_context.h | 6 +- bridge/test/kraken_test_env.cc | 10 +- bridge/test/kraken_test_env.h | 2 +- 42 files changed, 426 insertions(+), 282 deletions(-) create mode 100644 bridge/bindings/qjs/qjs_blob.cc create mode 100644 bridge/bindings/qjs/qjs_blob.h create mode 100644 bridge/core/fileapi/blob.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 7b6399677e..b3f822567d 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -206,6 +206,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/qjs_window.h bindings/qjs/qjs_page.cc bindings/qjs/qjs_page.h + bindings/qjs/qjs_blob.cc + bindings/qjs/qjs_blob.h # Core sources core/executing_context.cc @@ -215,6 +217,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/executing_context_data.cc core/executing_context_data.h core/dart_methods.h + core/fileapi/blob.h + core/fileapi/blob.cc core/frame/console.cc core/frame/console.h core/frame/dom_timer.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 33d27482f4..02e958457f 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -4,6 +4,7 @@ */ #include "binding_initializer.h" +#include "core/executing_context.h" #include "qjs_console.h" #include "qjs_module_manager.h" @@ -11,10 +12,10 @@ namespace kraken { -void installBindings(JSContext* ctx) { - QJSWindow::installGlobalFunctions(ctx); - QJSModuleManager::install(ctx); - QJSConsole::install(ctx); +void installBindings(ExecutingContext* context) { + QJSWindow::installGlobalFunctions(context); + QJSModuleManager::install(context); + QJSConsole::install(context); } } // namespace kraken diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h index 8af0324b22..46ab98e9a6 100644 --- a/bridge/bindings/qjs/binding_initializer.h +++ b/bridge/bindings/qjs/binding_initializer.h @@ -10,7 +10,9 @@ namespace kraken { -void installBindings(JSContext* ctx); +class ExecutingContext; + +void installBindings(ExecutingContext* context); } // namespace kraken diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index e3b69f36ab..045299c3a9 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -18,7 +18,7 @@ namespace kraken { template class MakeGarbageCollectedTrait; -class ExecutionContext; +class ExecutingContext; /** * Base class for GC managed objects. Only descendent types of `GarbageCollected` @@ -75,7 +75,7 @@ class GarbageCollected { FORCE_INLINE JSValue toQuickJS() { return jsObject; }; FORCE_INLINE JSContext* ctx() { return m_ctx; }; - FORCE_INLINE ExecutionContext* context() const { return static_cast(JS_GetContextOpaque(m_ctx)); }; + FORCE_INLINE ExecutingContext* context() const { return static_cast(JS_GetContextOpaque(m_ctx)); }; protected: JSValue jsObject{JS_NULL}; diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc index 0e236c6778..fa33bfd8d6 100644 --- a/bridge/bindings/qjs/member_installer.cc +++ b/bridge/bindings/qjs/member_installer.cc @@ -3,7 +3,10 @@ * Author: Kraken Team. */ +#include #include "member_installer.h" +#include "qjs_engine_patch.h" +#include "core/executing_context.h" namespace kraken { @@ -14,13 +17,57 @@ int combinePropFlags(JSPropFlag a, JSPropFlag b, JSPropFlag c) { return a | b | c; } -void MemberInstaller::installAttributes(JSContext* ctx, JSValue root, std::initializer_list config) { +// 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) { - JS_DefinePropertyValueStr(ctx, root, c.name, JS_DupValue(ctx, c.value), c.flag); + 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(JSContext* ctx, JSValue root, std::initializer_list config) { +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); diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h index df013bdeae..a78056228c 100644 --- a/bridge/bindings/qjs/member_installer.h +++ b/bridge/bindings/qjs/member_installer.h @@ -11,6 +11,8 @@ namespace kraken { +class ExecutingContext; + // Flags for object properties. enum JSPropFlag { normal = JS_PROP_NORMAL, writable = JS_PROP_WRITABLE, enumerable = JS_PROP_ENUMERABLE, configurable = JS_PROP_CONFIGURABLE }; @@ -24,8 +26,10 @@ class MemberInstaller { struct AttributeConfig { AttributeConfig& operator=(const AttributeConfig&) = delete; const char* name; - JSValue value; - int flag; // Flags for object properties. + JSCFunction* getter{nullptr}; + JSCFunction* setter{nullptr}; + JSValue value{JS_NULL}; + int flag{JS_PROP_C_W_E}; // Flags for object properties. }; struct FunctionConfig { @@ -33,11 +37,11 @@ class MemberInstaller { const char* name; JSCFunction* function; size_t length; - int flag; // Flags for object properties. + int flag{JS_PROP_C_W_E}; // Flags for object properties. }; - static void installAttributes(JSContext* ctx, JSValue root, std::initializer_list); - static void installFunctions(JSContext* ctx, JSValue root, std::initializer_list); + static void installAttributes(ExecutingContext* context, JSValue root, std::initializer_list); + static void installFunctions(ExecutingContext* context, JSValue root, std::initializer_list); }; } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_blob.cc b/bridge/bindings/qjs/qjs_blob.cc new file mode 100644 index 0000000000..9760ffae45 --- /dev/null +++ b/bridge/bindings/qjs/qjs_blob.cc @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "qjs_blob.h" +#include "member_installer.h" +#include "core/executing_context.h" + +namespace kraken { + +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) { + +} + +static JSValue sizeAttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + +} + +static JSValue sizeAttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + +} + +static JSValue typeAttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + +} + +static JSValue typeAttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + +} + + +JSValue QJSBlob::constructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { +// if (argc == 0) { +// auto* blob = Blob::create(ctx); +// return blob->toQuickJS(); +// } +// +// 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"); +// } +// +// auto* context = static_cast(JS_GetContextOpaque(ctx)); +// BlobBuilder builder; +// +// if (argc == 1 || JS_IsUndefined(optionValue)) { +// builder.append(*context, arrayValue); +// auto* blob = Blob::create(ctx, builder.finalize()); +// return blob->toQuickJS(); +// } +// +// 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(*context, mimeTypeValue); +// const char* cMineType = JS_ToCString(ctx, mimeTypeValue); +// std::string mimeType = std::string(cMineType); +// +// auto* blob = Blob::create(ctx, builder.finalize(), mimeType); +// +// JS_FreeValue(ctx, mimeTypeValue); +// JS_FreeCString(ctx, mimeType.c_str()); +// JS_FreeAtom(ctx, mimeTypeKey); +// +// return blob->toQuickJS(); +} + +void QJSBlob::install(ExecutingContext* context) { + installConstructor(context); + installPrototypeMethods(context); + installPrototypeProperties(context); +} + +void QJSBlob::installConstructor(ExecutingContext* context) { + const WrapperTypeInfo* wrapperTypeInfo = getWrapperTypeInfo(); + JSValue constructor = context->contextData()->constructorForType(wrapperTypeInfo); + + std::initializer_list attributeConfig { + {"Blob", nullptr, nullptr, constructor} + }; + MemberInstaller::installAttributes(context, context->global(), attributeConfig); +} + +void QJSBlob::installPrototypeMethods(ExecutingContext* context) { + const WrapperTypeInfo* wrapperTypeInfo = getWrapperTypeInfo(); + JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); + + std::initializer_list attributesConfig { + {"size", sizeAttributeGetCallback, sizeAttributeSetCallback}, + {"type", typeAttributeGetCallback, typeAttributeSetCallback} + }; + + MemberInstaller::installAttributes(context, prototype, attributesConfig); +} + +void QJSBlob::installPrototypeProperties(ExecutingContext* context) { + const WrapperTypeInfo* wrapperTypeInfo = getWrapperTypeInfo(); + JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); + + std::initializer_list functionConfig { + {"arrayBuffer", arrayBuffer, 0}, + {"slice", slice, 3}, + {"text", text, 0} + }; + + MemberInstaller::installFunctions(context, prototype, functionConfig); +} + +} diff --git a/bridge/bindings/qjs/qjs_blob.h b/bridge/bindings/qjs/qjs_blob.h new file mode 100644 index 0000000000..9e83eefaf1 --- /dev/null +++ b/bridge/bindings/qjs/qjs_blob.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_QJS_BLOB_H +#define KRAKENBRIDGE_QJS_BLOB_H + +#include +#include "wrapper_type_info.h" +#include "core/executing_context.h" + +namespace kraken { + +class ExecutingContext; + +class QJSBlob final { + public: + static void install(ExecutingContext* context); + + static const WrapperTypeInfo* getWrapperTypeInfo() { + return &m_wrapperTypeInfo; + } + + private: + static JSValue constructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); + constexpr static const WrapperTypeInfo m_wrapperTypeInfo = {"Blob", nullptr, constructorCallback}; + + static void installPrototypeMethods(ExecutingContext* context); + static void installPrototypeProperties(ExecutingContext* context); + static void installConstructor(ExecutingContext* context); +}; + +} + +class qjs_blob {}; + +#endif // KRAKENBRIDGE_QJS_BLOB_H diff --git a/bridge/bindings/qjs/qjs_window.cc b/bridge/bindings/qjs/qjs_window.cc index ec05a0faca..1cc164aec6 100644 --- a/bridge/bindings/qjs/qjs_window.cc +++ b/bridge/bindings/qjs/qjs_window.cc @@ -18,7 +18,7 @@ static JSValue setTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSVal return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': 1 argument required, but only 0 present."); } - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); JSValue callbackValue = argv[0]; JSValue timeoutValue = argv[1]; @@ -62,7 +62,7 @@ static JSValue setInterval(JSContext* ctx, JSValueConst this_val, int argc, JSVa return JS_ThrowTypeError(ctx, "Failed to execute 'setInterval': 1 argument required, but only 0 present."); } - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); JSValue callbackValue = argv[0]; JSValue timeoutValue = argv[1]; @@ -104,7 +104,7 @@ static JSValue clearTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSV return JS_ThrowTypeError(ctx, "Failed to execute 'clearTimeout': 1 argument required, but only 0 present."); } - auto context = static_cast(JS_GetContextOpaque(ctx)); + auto context = static_cast(JS_GetContextOpaque(ctx)); JSValue timeIdValue = argv[0]; if (!JS_IsNumber(timeIdValue)) { @@ -124,16 +124,14 @@ static JSValue clearTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSV return JS_NULL; } -void QJSWindow::installGlobalFunctions(JSContext* ctx) { +void QJSWindow::installGlobalFunctions(ExecutingContext* context) { std::initializer_list functionConfig{ - {"setTimeout", setTimeout, 2, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, - {"setInterval", setInterval, 2, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, - {"clearTimeout", clearTimeout, 0, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)}, + {"setTimeout", setTimeout, 2}, + {"setInterval", setInterval, 2}, + {"clearTimeout", clearTimeout, 0}, }; - JSValue globalObject = JS_GetGlobalObject(ctx); - MemberInstaller::installFunctions(ctx, globalObject, functionConfig); - JS_FreeValue(ctx, globalObject); + MemberInstaller::installFunctions(context, context->global(), functionConfig); } } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_window.h b/bridge/bindings/qjs/qjs_window.h index 385cfd0c24..6a9d52e2e2 100644 --- a/bridge/bindings/qjs/qjs_window.h +++ b/bridge/bindings/qjs/qjs_window.h @@ -10,9 +10,11 @@ namespace kraken { +class ExecutingContext; + class QJSWindow final { public: - static void installGlobalFunctions(JSContext* ctx); + static void installGlobalFunctions(ExecutingContext* ctx); }; } // namespace kraken diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 8b1717935b..2b194b0a7c 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -8,7 +8,7 @@ namespace kraken { -RejectedPromises::Message::Message(ExecutionContext* context, JSValue promise, JSValue reason) +RejectedPromises::Message::Message(ExecutingContext* 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() { @@ -16,7 +16,7 @@ RejectedPromises::Message::~Message() { 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); @@ -24,7 +24,7 @@ void RejectedPromises::trackUnhandledPromiseRejection(ExecutionContext* context, // 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. @@ -36,7 +36,7 @@ void RejectedPromises::trackHandledPromiseRejection(ExecutionContext* context, J } } -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) { diff --git a/bridge/bindings/qjs/rejected_promises.h b/bridge/bindings/qjs/rejected_promises.h index e66bdfd1ee..261f29f682 100644 --- a/bridge/bindings/qjs/rejected_promises.h +++ b/bridge/bindings/qjs/rejected_promises.h @@ -13,13 +13,13 @@ namespace kraken { -class ExecutionContext; +class ExecutingContext; class RejectedPromises { public: class Message { public: - Message(ExecutionContext* context, JSValue promise, JSValue reason); + Message(ExecutingContext* context, JSValue promise, JSValue reason); ~Message(); JSRuntime* m_runtime{nullptr}; @@ -28,11 +28,11 @@ 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; diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc index 323096e47e..fdfa239913 100644 --- a/bridge/core/dom/frame_request_callback_collection.cc +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -11,7 +11,7 @@ JSClassID FrameCallback::classId{0}; FrameCallback::FrameCallback(JSValue callback) : m_callback(callback) {} void FrameCallback::fire(double highResTimeStamp) { - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); + auto* context = static_cast(JS_GetContextOpaque(m_ctx)); if (!JS_IsFunction(m_ctx, m_callback)) return; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index b39c36e494..8b75fbb3d5 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -16,21 +16,21 @@ std::atomic runningContexts{0}; bool valid_contexts[MAX_JS_CONTEXT]; std::atomic running_context_list{0}; -std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) { - return std::make_unique(contextId, handler, owner); +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(GCVisitor* visitor) const { - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); + auto* context = static_cast(JS_GetContextOpaque(m_ctx)); context->trace(visitor); } void ExecutionContextGCTracker::dispose() const {} JSClassID ExecutionContextGCTracker::contextGcTrackerClassId{0}; -ExecutionContext::ExecutionContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) +ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) : contextId(contextId), _handler(handler), owner(owner), ctxInvalid_(false), uniqueId(context_unique_id++) { #if ENABLE_PROFILE auto jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); @@ -92,7 +92,7 @@ ExecutionContext::ExecutionContext(int32_t contextId, const JSExceptionHandler& #endif } -ExecutionContext::~ExecutionContext() { +ExecutingContext::~ExecutingContext() { valid_contexts[contextId] = false; ctxInvalid_ = true; @@ -139,7 +139,7 @@ ExecutionContext::~ExecutionContext() { m_ctx = nullptr; } -bool ExecutionContext::evaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { +bool ExecutingContext::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(); @@ -148,7 +148,7 @@ bool ExecutionContext::evaluateJavaScript(const uint16_t* code, size_t codeLengt return success; } -bool ExecutionContext::evaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine) { +bool ExecutingContext::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(); @@ -157,7 +157,7 @@ bool ExecutionContext::evaluateJavaScript(const char16_t* code, size_t length, c return success; } -bool ExecutionContext::evaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine) { +bool ExecutingContext::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); @@ -165,7 +165,7 @@ bool ExecutionContext::evaluateJavaScript(const char* code, size_t codeLength, c return success; } -bool ExecutionContext::evaluateByteCode(uint8_t* bytes, size_t byteLength) { +bool ExecutingContext::evaluateByteCode(uint8_t* bytes, size_t byteLength) { JSValue obj, val; obj = JS_ReadObject(m_ctx, bytes, byteLength, JS_READ_OBJ_BYTECODE); if (!handleException(&obj)) @@ -177,16 +177,16 @@ bool ExecutionContext::evaluateByteCode(uint8_t* bytes, size_t byteLength) { return true; } -bool ExecutionContext::isValid() const { +bool ExecutingContext::isValid() const { return !ctxInvalid_; } -void* ExecutionContext::getOwner() { +void* ExecutingContext::getOwner() { assert(!ctxInvalid_ && "context has been released"); return owner; } -bool ExecutionContext::handleException(JSValue* exc) { +bool ExecutingContext::handleException(JSValue* exc) { if (JS_IsException(*exc)) { JSValue error = JS_GetException(m_ctx); reportError(error); @@ -198,25 +198,25 @@ bool ExecutionContext::handleException(JSValue* exc) { return true; } -bool ExecutionContext::handleException(ScriptValue* exc) { +bool ExecutingContext::handleException(ScriptValue* exc) { JSValue value = exc->toQuickJS(); handleException(&value); } -JSValue ExecutionContext::global() { +JSValue ExecutingContext::global() { return globalObject; } -JSContext* ExecutionContext::ctx() { +JSContext* ExecutingContext::ctx() { assert(!ctxInvalid_ && "context has been released"); return m_ctx; } -JSRuntime* ExecutionContext::runtime() { +JSRuntime* ExecutingContext::runtime() { return m_runtime; } -void ExecutionContext::reportError(JSValueConst error) { +void ExecutingContext::reportError(JSValueConst error) { if (!JS_IsError(m_ctx, error)) return; @@ -251,7 +251,7 @@ void ExecutionContext::reportError(JSValueConst error) { JS_FreeCString(m_ctx, type); } -void ExecutionContext::drainPendingPromiseJobs() { +void ExecutingContext::drainPendingPromiseJobs() { // should executing pending promise jobs. JSContext* pctx; int finished = JS_ExecutePendingJob(runtime(), &pctx); @@ -266,17 +266,17 @@ void ExecutionContext::drainPendingPromiseJobs() { m_rejectedPromise.process(this); } -void ExecutionContext::defineGlobalProperty(const char* prop, JSValue value) { +void ExecutingContext::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); } -ExecutionContextData* ExecutionContext::contextData() { +ExecutionContextData* ExecutingContext::contextData() { return &m_data; } -uint8_t* ExecutionContext::dumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength) { +uint8_t* ExecutingContext::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) @@ -286,7 +286,7 @@ uint8_t* ExecutionContext::dumpByteCode(const char* code, uint32_t codeLength, c return bytes; } -void ExecutionContext::dispatchGlobalErrorEvent(ExecutionContext* context, JSValueConst error) { +void ExecutingContext::dispatchGlobalErrorEvent(ExecutingContext* context, JSValueConst error) { // JSContext* ctx = context->ctx(); // auto* window = static_cast(JS_GetOpaque(context->global(), Window::classId())); // @@ -318,7 +318,7 @@ void ExecutionContext::dispatchGlobalErrorEvent(ExecutionContext* context, JSVal // } } -static void dispatchPromiseRejectionEvent(const char* eventType, ExecutionContext* context, JSValueConst promise, JSValueConst error) { +static void dispatchPromiseRejectionEvent(const char* eventType, ExecutingContext* context, JSValueConst promise, JSValueConst error) { // JSContext* ctx = context->ctx(); // auto* window = static_cast(JS_GetOpaque(context->global(), Window::classId())); // @@ -348,7 +348,7 @@ static void dispatchPromiseRejectionEvent(const char* eventType, ExecutionContex // } } -void ExecutionContext::dispatchGlobalUnhandledRejectionEvent(ExecutionContext* context, JSValueConst promise, JSValueConst error) { +void ExecutingContext::dispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error) { // Trigger onerror event. dispatchGlobalErrorEvent(context, error); @@ -356,15 +356,15 @@ void ExecutionContext::dispatchGlobalUnhandledRejectionEvent(ExecutionContext* c dispatchPromiseRejectionEvent("unhandledrejection", context, promise, error); } -void ExecutionContext::dispatchGlobalRejectionHandledEvent(ExecutionContext* context, JSValue promise, JSValue error) { +void ExecutingContext::dispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValue promise, JSValue error) { // Trigger rejectionhandled event. dispatchPromiseRejectionEvent("rejectionhandled", context, promise, error); } -std::unordered_map ExecutionContext::pluginByteCode{}; +std::unordered_map ExecutingContext::pluginByteCode{}; -void ExecutionContext::promiseRejectTracker(JSContext* ctx, JSValue promise, JSValue reason, int is_handled, void* opaque) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); +void ExecutingContext::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. @@ -375,7 +375,7 @@ void ExecutionContext::promiseRejectTracker(JSContext* ctx, JSValue promise, JSV } } -void installFunctionProperty(ExecutionContext* context, JSValue thisObject, const char* functionName, JSCFunction function, int argc) { +void installFunctionProperty(ExecutingContext* context, JSValue 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); @@ -391,7 +391,7 @@ void installFunctionProperty(ExecutionContext* context, JSValue thisObject, cons JS_FreeAtom(context->ctx(), key); } -void installPropertyGetterSetter(ExecutionContext* context, JSValue thisObject, const char* property, JSCFunction getterFunction, JSCFunction setterFunction) { +void installPropertyGetterSetter(ExecutingContext* context, JSValue thisObject, const char* 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); @@ -411,7 +411,7 @@ void installPropertyGetterSetter(ExecutionContext* context, JSValue thisObject, JS_FreeValue(context->ctx(), setter); } -void installPropertyGetter(ExecutionContext* context, JSValue thisObject, const char* property, JSCFunction getterFunction) { +void installPropertyGetter(ExecutingContext* context, JSValue thisObject, const char* 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); @@ -422,19 +422,19 @@ void installPropertyGetter(ExecutionContext* context, JSValue thisObject, const JS_FreeValue(context->ctx(), getter); } -DOMTimerCoordinator* ExecutionContext::timers() { +DOMTimerCoordinator* ExecutingContext::timers() { return &m_timers; } -ModuleListenerContainer* ExecutionContext::moduleListeners() { +ModuleListenerContainer* ExecutingContext::moduleListeners() { return &m_moduleListeners; } -ModuleCallbackCoordinator* ExecutionContext::moduleCallbacks() { +ModuleCallbackCoordinator* ExecutingContext::moduleCallbacks() { return &m_moduleCallbacks; } -void ExecutionContext::trace(GCVisitor* visitor) { +void ExecutingContext::trace(GCVisitor* visitor) { m_timers.trace(visitor); m_moduleListeners.trace(visitor); } diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 570f63a2d2..3366019615 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -36,10 +36,10 @@ struct NativeByteCode { int32_t length; }; -class ExecutionContext; +class ExecutingContext; class Document; -using JSExceptionHandler = std::function; +using JSExceptionHandler = std::function; std::string jsAtomToStdString(JSContext* ctx, JSAtom atom); @@ -52,7 +52,7 @@ static inline bool isNumberIndex(const std::string& name) { struct PromiseContext { void* data; - ExecutionContext* context; + ExecutingContext* context; JSValue resolveFunc; JSValue rejectFunc; JSValue promise; @@ -74,11 +74,11 @@ class ExecutionContextGCTracker : public GarbageCollected pluginByteCode; @@ -153,26 +153,6 @@ class ExecutionContext { RejectedPromises m_rejectedPromise; }; -// The read object's method or properties via Proxy, we should redirect this_val from Proxy into target property of -// proxy object. -static JSValue handleCallThisOnProxy(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int data_len, JSValueConst* data) { - JSValue f = data[0]; - JSValue result; - if (JS_IsProxy(this_val)) { - result = JS_Call(ctx, f, JS_GetProxyTarget(this_val), argc, argv); - } else { - // If this_val is undefined or null, this_val should set to globalThis. - if (JS_IsUndefined(this_val) || JS_IsNull(this_val)) { - this_val = JS_GetGlobalObject(ctx); - result = JS_Call(ctx, f, this_val, argc, argv); - JS_FreeValue(ctx, this_val); - } else { - result = JS_Call(ctx, f, this_val, argc, argv); - } - } - return result; -} - class ObjectProperty { KRAKEN_DISALLOW_COPY_ASSIGN_AND_MOVE(ObjectProperty); @@ -180,7 +160,7 @@ class ObjectProperty { ObjectProperty() = delete; // Define an property on object with a JSValue. - explicit ObjectProperty(ExecutionContext* context, JSValueConst thisObject, const char* property, JSValue value) : m_value(value) { + explicit ObjectProperty(ExecutingContext* context, JSValueConst thisObject, const char* property, JSValue value) : m_value(value) { JS_DefinePropertyValueStr(context->ctx(), thisObject, property, value, JS_PROP_ENUMERABLE); } @@ -191,29 +171,11 @@ class ObjectProperty { }; // Property define helpers -void installFunctionProperty(ExecutionContext* context, JSValueConst thisObject, const char* functionName, JSCFunction function, int argc); -void installPropertyGetterSetter(ExecutionContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction, JSCFunction setterFunction); -void installPropertyGetter(ExecutionContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction); - -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}; -}; +void installFunctionProperty(ExecutingContext* context, JSValueConst thisObject, const char* functionName, JSCFunction function, int argc); +void installPropertyGetterSetter(ExecutingContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction, JSCFunction setterFunction); +void installPropertyGetter(ExecutingContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction); -std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); +std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01); diff --git a/bridge/core/executing_context_data.h b/bridge/core/executing_context_data.h index 90e612df4f..38eaaf150f 100644 --- a/bridge/core/executing_context_data.h +++ b/bridge/core/executing_context_data.h @@ -12,13 +12,13 @@ namespace kraken { -class ExecutionContext; +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(ExecutionContext* context) : m_context(context){}; + explicit ExecutionContextData(ExecutingContext* context) : m_context(context){}; ExecutionContextData(const ExecutionContextData&) = delete; ExecutionContextData& operator=(const ExecutionContextData&) = delete; @@ -32,7 +32,7 @@ class ExecutionContextData final { std::unordered_map m_constructorMap; std::unordered_map m_prototypeMap; - ExecutionContext* m_context; + ExecutingContext* m_context; }; } // namespace kraken diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index dbe8bee144..b5641d3979 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -4,30 +4,30 @@ */ #include "blob.h" -#include "dart_methods.h" +//#include "dart_methods.h" namespace kraken { -void bindBlob(std::unique_ptr& context) { - JSValue constructor = context->contextData()->constructorForType(&blobTypeInfo); - JSValue prototype = context->contextData()->prototypeForType(&blobTypeInfo); - - // Install methods on prototype. - INSTALL_FUNCTION(Blob, prototype, arrayBuffer, 0); - INSTALL_FUNCTION(Blob, prototype, slice, 3); - INSTALL_FUNCTION(Blob, prototype, text, 0); - - // Install readonly properties. - INSTALL_READONLY_PROPERTY(Blob, prototype, type); - INSTALL_READONLY_PROPERTY(Blob, prototype, size); - - context->defineGlobalProperty("Blob", constructor); -} +//void bindBlob(std::unique_ptr& context) { +// JSValue constructor = context->contextData()->constructorForType(&blobTypeInfo); +// JSValue prototype = context->contextData()->prototypeForType(&blobTypeInfo); +// +// // Install methods on prototype. +// INSTALL_FUNCTION(Blob, prototype, arrayBuffer, 0); +// INSTALL_FUNCTION(Blob, prototype, slice, 3); +// INSTALL_FUNCTION(Blob, prototype, text, 0); +// +// // Install readonly properties. +// INSTALL_READONLY_PROPERTY(Blob, prototype, type); +// INSTALL_READONLY_PROPERTY(Blob, prototype, size); +// +// context->defineGlobalProperty("Blob", constructor); +//} JSClassID Blob::classID{0}; Blob* Blob::create(JSContext* ctx) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); auto* blob = makeGarbageCollected()->initialize(ctx, &classID); JSValue prototype = context->contextData()->prototypeForType(&blobTypeInfo); @@ -43,11 +43,11 @@ Blob* Blob::create(JSContext* ctx, std::vector&& data, std::string& mim return create(ctx); } -JSValue Blob::constructor(ExecutionContext* context) { +JSValue Blob::constructor(ExecutingContext* context) { return context->contextData()->constructorForType(&blobTypeInfo); } -JSValue Blob::prototype(ExecutionContext* context) { +JSValue Blob::prototype(ExecutingContext* context) { return context->contextData()->prototypeForType(&blobTypeInfo); } @@ -183,13 +183,13 @@ IMPL_FUNCTION(Blob, text)(JSContext* ctx, JSValue this_val, int argc, JSValue* a return promise; } -void BlobBuilder::append(ExecutionContext& context, Blob* blob) { +void BlobBuilder::append(ExecutingContext& context, Blob* 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) { +void BlobBuilder::append(ExecutingContext& context, JSValue& value) { if (JS_IsString(value)) { const char* buffer = JS_ToCString(context.ctx(), value); std::string str = std::string(buffer); @@ -257,7 +257,7 @@ uint8_t* Blob::bytes() { return _data.data(); } -void Blob::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const {} +void Blob::trace(GCVisitor* visitor) const {} void Blob::dispose() const {} } // namespace kraken diff --git a/bridge/core/fileapi/blob.d.ts b/bridge/core/fileapi/blob.d.ts new file mode 100644 index 0000000000..9bf3128261 --- /dev/null +++ b/bridge/core/fileapi/blob.d.ts @@ -0,0 +1,10 @@ +interface Blob { + readonly size: number; + readonly type: string; + arrayBuffer(): Promise; + slice(start?: number, end?: number, contentType?: string): Blob; + text(): Promise; + + prototype: Blob; + new(blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; +} diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index cd63e0a5db..82e321376c 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -8,13 +8,12 @@ #include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/macros.h" +#include +#include namespace kraken { class BlobBuilder; -class BlobInstance; - -void bindBlob(ExecutionContext* context); class Blob : public GarbageCollected { public: @@ -22,17 +21,13 @@ class Blob : public GarbageCollected { static Blob* create(JSContext* ctx); static Blob* create(JSContext* ctx, std::vector&& data); static Blob* create(JSContext* ctx, std::vector&& data, std::string& mime); - static JSValue constructor(ExecutionContext* context); - static JSValue prototype(ExecutionContext* context); + static JSValue constructor(ExecutingContext* context); + static JSValue prototype(ExecutingContext* context); Blob(){}; Blob(std::vector&& data) : _size(data.size()), _data(std::move(data)){}; Blob(std::vector&& data, std::string& mime) : mimeType(mime), _size(data.size()), _data(std::move(data)){}; - DEFINE_FUNCTION(arrayBuffer); - DEFINE_FUNCTION(slice); - DEFINE_FUNCTION(text); - /// get an pointer of bytes data from JSBlob uint8_t* bytes(); /// get bytes data's length @@ -41,7 +36,7 @@ class Blob : public GarbageCollected { DEFINE_PROTOTYPE_READONLY_PROPERTY(type); DEFINE_PROTOTYPE_READONLY_PROPERTY(size); - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; + void trace(GCVisitor* visitor) const override; void dispose() const override; private: @@ -53,8 +48,8 @@ class Blob : public GarbageCollected { class BlobBuilder { public: - void append(ExecutionContext& context, JSValue& value); - void append(ExecutionContext& context, Blob* blob); + void append(ExecutingContext& context, JSValue& value); + void append(ExecutingContext& context, Blob* blob); std::vector finalize(); @@ -63,56 +58,6 @@ class BlobBuilder { std::vector _data; }; -auto blobCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { - if (argc == 0) { - auto* blob = Blob::create(ctx); - return blob->toQuickJS(); - } - - 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"); - } - - auto* context = static_cast(JS_GetContextOpaque(ctx)); - BlobBuilder builder; - - if (argc == 1 || JS_IsUndefined(optionValue)) { - builder.append(*context, arrayValue); - auto* blob = Blob::create(ctx, builder.finalize()); - return blob->toQuickJS(); - } - - 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(*context, mimeTypeValue); - const char* cMineType = JS_ToCString(ctx, mimeTypeValue); - std::string mimeType = std::string(cMineType); - - auto* blob = Blob::create(ctx, builder.finalize(), mimeType); - - JS_FreeValue(ctx, mimeTypeValue); - JS_FreeCString(ctx, mimeType.c_str()); - JS_FreeAtom(ctx, mimeTypeKey); - - return blob->toQuickJS(); -}; - -const WrapperTypeInfo blobTypeInfo = {"Blob", nullptr, blobCreator}; - } // namespace kraken #endif // KRAKENBRIDGE_BLOB_H diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index cc58a9b5bf..42dfea7845 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -9,7 +9,7 @@ namespace kraken { -void Console::__kraken_print__(ExecutionContext* context, ScriptValue& logValue, ScriptValue& levelValue, ExceptionState* exception) { +void Console::__kraken_print__(ExecutingContext* context, ScriptValue& logValue, ScriptValue& levelValue, ExceptionState* exception) { std::stringstream stream; std::string buffer = logValue.toCString(); diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index 329dfd4394..b63e8bbf59 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -13,7 +13,7 @@ namespace kraken { class Console final { public: - static void __kraken_print__(ExecutionContext* context, ScriptValue& log, ScriptValue& level, ExceptionState* exception); + static void __kraken_print__(ExecutingContext* context, ScriptValue& log, ScriptValue& level, ExceptionState* exception); }; } // namespace kraken diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index af60e3f282..01b9bd490f 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -19,7 +19,7 @@ DOMTimer::DOMTimer(QJSFunction* callback) : m_callback(callback) {} JSClassID DOMTimer::classId{0}; void DOMTimer::fire() { - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); + auto* context = static_cast(JS_GetContextOpaque(m_ctx)); if (!m_callback->isFunction(m_ctx)) return; diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index e513667f43..b1dfd85289 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -15,7 +15,7 @@ namespace kraken { static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); if (errmsg != nullptr) { JSValue exception = JS_ThrowTypeError(timer->ctx(), "%s", errmsg); @@ -32,7 +32,7 @@ static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { auto* timer = static_cast(ptr); - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); if (!context->isValid()) return; @@ -42,7 +42,7 @@ static void handleTransientCallback(void* ptr, int32_t contextId, const char* er context->timers()->removeTimeoutById(timer->timerId()); } -void DOMTimerCoordinator::installNewTimer(ExecutionContext* context, int32_t timerId, DOMTimer* timer) { +void DOMTimerCoordinator::installNewTimer(ExecutingContext* context, int32_t timerId, DOMTimer* timer) { m_activeTimers[timerId] = timer; } diff --git a/bridge/core/frame/dom_timer_coordinator.h b/bridge/core/frame/dom_timer_coordinator.h index be9e425a91..d3797b5ae5 100644 --- a/bridge/core/frame/dom_timer_coordinator.h +++ b/bridge/core/frame/dom_timer_coordinator.h @@ -14,7 +14,7 @@ namespace kraken { class DOMTimer; -class ExecutionContext; +class ExecutingContext; // Maintains a set of DOMTimers for a given page // DOMTimerCoordinator assigns IDs to timers; these IDs are @@ -24,7 +24,7 @@ class ExecutionContext; class DOMTimerCoordinator { public: // Creates and installs a new timer. Returns the assigned ID. - void installNewTimer(ExecutionContext* context, int32_t timerId, DOMTimer* timer); + void installNewTimer(ExecutingContext* context, int32_t timerId, DOMTimer* timer); // Removes and disposes the timer with the specified ID, if any. This may // destroy the timer. diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 4eb8e72faf..6b3b83e814 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -10,13 +10,13 @@ namespace kraken { struct ModuleContext { - ExecutionContext* context; + ExecutingContext* context; ModuleCallback* callback; }; void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const char* errmsg, NativeString* json) { auto* moduleContext = static_cast(ptr); - ExecutionContext* context = moduleContext->context; + ExecutingContext* context = moduleContext->context; if (!context->isValid()) return; @@ -55,7 +55,7 @@ void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t context static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); } -ScriptValue ModuleManager::__kraken_invoke_module__(ExecutionContext* context, ScriptValue& moduleNameValue, ScriptValue& methodValue, ScriptValue& paramsValue, QJSFunction* callback, ExceptionState* exception) { +ScriptValue ModuleManager::__kraken_invoke_module__(ExecutingContext* context, ScriptValue& moduleNameValue, ScriptValue& methodValue, ScriptValue& paramsValue, QJSFunction* callback, ExceptionState* exception) { std::unique_ptr moduleName = moduleNameValue.toNativeString(); std::unique_ptr method = methodValue.toNativeString(); std::unique_ptr params; @@ -103,7 +103,7 @@ ScriptValue ModuleManager::__kraken_invoke_module__(ExecutionContext* context, S return resultString; } -void ModuleManager::__kraken_add_module_listener__(ExecutionContext* context, QJSFunction* handler, ExceptionState* exception) { +void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, QJSFunction* handler, ExceptionState* exception) { auto* listener = makeGarbageCollected(handler); context->moduleListeners()->addModuleListener(listener); } diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 3eb966881d..0600aeeb20 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -14,8 +14,8 @@ namespace kraken { class ModuleManager { public: - static ScriptValue __kraken_invoke_module__(ExecutionContext* context, ScriptValue& moduleName, ScriptValue& method, ScriptValue& params, QJSFunction* callback, ExceptionState* exception); - static void __kraken_add_module_listener__(ExecutionContext* context, QJSFunction* handler, ExceptionState* exception); + static ScriptValue __kraken_invoke_module__(ExecutingContext* context, ScriptValue& moduleName, ScriptValue& method, ScriptValue& params, QJSFunction* callback, ExceptionState* exception); + static void __kraken_add_module_listener__(ExecutingContext* context, QJSFunction* handler, ExceptionState* exception); }; } // namespace kraken diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 7798a24f40..01d70c3b31 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -9,7 +9,7 @@ namespace kraken { static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); if (errmsg != nullptr) { JSValue exception = JS_ThrowTypeError(timer->ctx(), "%s", errmsg); @@ -29,7 +29,7 @@ static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { static void handleTransientCallback(void* ptr, int32_t contextId, const char* errmsg) { auto* timer = static_cast(ptr); - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); if (!context->isValid()) return; @@ -41,7 +41,7 @@ static void handleTransientCallback(void* ptr, int32_t contextId, const char* er static void handlePersistentCallback(void* ptr, int32_t contextId, const char* errmsg) { auto* timer = static_cast(ptr); - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); if (!context->isValid()) return; @@ -49,7 +49,7 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e handleTimerCallback(timer, errmsg); } -int WindowOrWorkerGlobalScope::setTimeout(ExecutionContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception) { +int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, 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."); @@ -70,7 +70,7 @@ int WindowOrWorkerGlobalScope::setTimeout(ExecutionContext* context, QJSFunction return timerId; } -int WindowOrWorkerGlobalScope::setInterval(ExecutionContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception) { +int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, 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; @@ -88,7 +88,7 @@ int WindowOrWorkerGlobalScope::setInterval(ExecutionContext* context, QJSFunctio return timerId; } -void WindowOrWorkerGlobalScope::clearTimeout(ExecutionContext* context, int32_t timerId, ExceptionState* exception) { +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; diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h index 1d3e885dd5..55d766ea29 100644 --- a/bridge/core/frame/window_or_worker_global_scope.h +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -14,9 +14,9 @@ namespace kraken { class WindowOrWorkerGlobalScope { public: - static int setTimeout(ExecutionContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception); - static int setInterval(ExecutionContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception); - static void clearTimeout(ExecutionContext* context, int32_t timerId, ExceptionState* exception); + static int setTimeout(ExecutingContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception); + static int setInterval(ExecutingContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception); + static void clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState* exception); }; } // namespace kraken diff --git a/bridge/core/page.cc b/bridge/core/page.cc index bb59f6b072..6914c045b4 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -19,9 +19,9 @@ ConsoleMessageHandler KrakenPage::consoleMessageHandler{nullptr}; kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { - m_context = new ExecutionContext( + m_context = new ExecutingContext( contextId, - [](ExecutionContext* context, const char* message) { + [](ExecutingContext* context, const char* message) { if (context->dartMethodPtr()->onJsError != nullptr) { context->dartMethodPtr()->onJsError(context->getContextId(), message); } diff --git a/bridge/core/page.h b/bridge/core/page.h index 6efed8969d..b01867ccd0 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -44,7 +44,7 @@ class KrakenPage final { void registerDartMethods(uint64_t* methodBytes, int32_t length); std::thread::id currentThread() const; - [[nodiscard]] ExecutionContext* getContext() const { return m_context; } + [[nodiscard]] ExecutingContext* getContext() const { return m_context; } void invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* event, NativeString* extra); void reportError(const char* errmsg); @@ -59,7 +59,7 @@ class KrakenPage final { const std::thread::id ownerThreadId; // 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. - ExecutionContext* m_context; + ExecutingContext* m_context; JSExceptionHandler m_handler; }; diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 83e027c87c..ca5a484acc 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -58,7 +58,7 @@ NativeValue Native_NewInt32(int32_t value) { }; } -NativeValue Native_NewJSON(ExecutionContext* context, JSValue& value) { +NativeValue Native_NewJSON(ExecutingContext* context, JSValue& value) { JSValue stringifiedValue = JS_JSONStringify(context->ctx(), value, JS_UNDEFINED, JS_UNDEFINED); if (JS_IsException(stringifiedValue)) return Native_NewNull(); @@ -116,7 +116,7 @@ NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { NativeString* string = jsValueToNativeString(ctx, value).release(); return Native_NewString(string); } else if (JS_IsFunction(ctx, value)) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); auto* functionContext = new NativeFunctionContext{context, value}; return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); } else if (JS_IsObject(value)) { @@ -132,7 +132,7 @@ NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { return Native_NewNull(); } -NativeFunctionContext::NativeFunctionContext(ExecutionContext* context, JSValue callback) : m_context(context), m_ctx(context->ctx()), m_callback(callback), call(call_native_function) { +NativeFunctionContext::NativeFunctionContext(ExecutingContext* 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); }; @@ -219,7 +219,7 @@ static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int return JS_NULL; } -JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { +JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { switch (value.tag) { case NativeTag::TAG_STRING: { auto* string = static_cast(value.u.ptr); diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 77538834ae..67db971556 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -28,7 +28,7 @@ enum NativeTag { enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeEventTarget = 4 }; -class ExecutionContext; +class ExecutingContext; // Exchange data struct between dart and C++ struct NativeValue { @@ -48,10 +48,10 @@ static void call_native_function(NativeFunctionContext* functionContext, int32_t 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; }; @@ -63,9 +63,9 @@ NativeValue Native_NewFloat64(double value); NativeValue Native_NewBool(bool value); NativeValue Native_NewInt32(int32_t value); NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr); -NativeValue Native_NewJSON(ExecutionContext* context, JSValue& value); +NativeValue Native_NewJSON(ExecutingContext* context, JSValue& value); NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value); -JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value); +JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value); std::string nativeStringToStdString(NativeString* nativeString); diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index b0046fa21b..7cda39332c 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -9,7 +9,7 @@ namespace kraken { -UICommandBuffer::UICommandBuffer(ExecutionContext* context) : m_context(context) {} +UICommandBuffer::UICommandBuffer(ExecutingContext* context) : m_context(context) {} void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate) { if (batchedUpdate) { diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 033b7991ab..b1ffb1843d 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -13,7 +13,7 @@ namespace kraken { -class ExecutionContext; +class ExecutingContext; enum UICommand { createElement, @@ -55,7 +55,7 @@ struct UICommandItem { class UICommandBuffer { public: UICommandBuffer() = delete; - explicit UICommandBuffer(ExecutionContext* context); + explicit UICommandBuffer(ExecutingContext* context); 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); @@ -65,7 +65,7 @@ class UICommandBuffer { void clear(); private: - ExecutionContext* m_context{nullptr}; + ExecutingContext* m_context{nullptr}; std::atomic update_batched{false}; std::vector queue; }; diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 4fa363fc45..73f02968d8 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -226,7 +226,7 @@ void registerContextDisposedCallbacks(int32_t contextId, Task task, void* data) } void registerPluginByteCode(uint8_t* bytes, int32_t length, const char* pluginName) { - kraken::ExecutionContext::pluginByteCode[pluginName] = kraken::NativeByteCode{bytes, length}; + kraken::ExecutingContext::pluginByteCode[pluginName] = kraken::NativeByteCode{bytes, length}; } int32_t profileModeEnabled() { diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 8dd3126025..bb4aff710d 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -33,7 +33,7 @@ let blobs = files.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 Blob(path.join(source, file), dist, filename, implement); -}).filter(blob => blob.filename === 'qjs_module_manager'); +}).filter(blob => blob.filename === 'qjs_console'); for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index d7c6e1fa0c..536126ffc7 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -177,11 +177,13 @@ export function generateCppHeader(blob: Blob) { namespace kraken { +class ExecutingContext; + class QJS${getClassName(blob)} final { public: - static void install(JSContext* ctx); + static void install(ExecutionContext* context); private: - static void installGlobalFunctions(JSContext* ctx); + static void installGlobalFunctions(ExecutionContext* context); }; } diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index fbb5904337..6d946ef6da 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -15,7 +15,7 @@ import {capitalize, camelCase} from 'lodash'; 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, + return `${object.name}::${object.name}(ExecutingContext *context, Native${object.name} *nativePtr) : HostObject(context, "${object.name}"), m_nativePtr(nativePtr) { } @@ -60,7 +60,7 @@ function getPropsVars(object: ClassObject, type: PropType) { let classId = ''; if (type == PropType.hostObject) { instanceName = 'object'; - classId = 'ExecutionContext::kHostObjectClassId'; + classId = 'ExecutingContext::kHostObjectClassId'; } else if (type == PropType.Element) { instanceName = 'element'; classId = 'Element::classId()'; @@ -427,11 +427,11 @@ function generateHostClassSource(object: ClassObject) { } return ` -${object.name}::${object.name}(ExecutionContext *context) : ${object.type}(context) { +${object.name}::${object.name}(ExecutingContext *context) : ${object.type}(context) { ${classInheritCode} } -void bind${object.name}(ExecutionContext* context) { +void bind${object.name}(ExecutingContext* context) { auto *constructor = ${object.name}::instance(context); context->defineGlobalProperty("${globalBindingName}", constructor->jsObject); ${specialBind} @@ -501,7 +501,7 @@ function generateCoreModuleCall(blob: Blob, object: FunctionObject) { } return addIndent(` -auto context = static_cast(JS_GetContextOpaque(ctx)); +auto context = static_cast(JS_GetContextOpaque(ctx)); ExceptionState exception; ${returnValue}${coreClassName}::${object.declare.name}(context, ${params.join(', ')}, &exception); @@ -528,7 +528,7 @@ export function generateCppSource(blob: Blob) { let sources = blob.objects.map(o => { if (o instanceof FunctionObject) { - installList.push(` {"${o.declare.name}", ${o.declare.name}, ${o.declare.args.length}, combinePropFlags(JSPropFlag::enumerable, JSPropFlag::writable, JSPropFlag::configurable)},`); + installList.push(` {"${o.declare.name}", ${o.declare.name}, ${o.declare.args.length}},`); return generateFunctionSource(blob, o); } return ''; @@ -542,24 +542,23 @@ export function generateCppSource(blob: Blob) { #include "${blob.filename}.h" #include "bindings/qjs/member_installer.h" #include "bindings/qjs/qjs_function.h" +#include "core/executing_context.h" #include "core/${blob.implement}.h" namespace kraken { ${sources.join('\n')} -void QJS${getClassName(blob)}::install(JSContext* ctx) { - installGlobalFunctions(ctx); +void QJS${getClassName(blob)}::install(ExecutingContext* context) { + installGlobalFunctions(context); } -void QJS${getClassName(blob)}::installGlobalFunctions(JSContext* ctx) { +void QJS${getClassName(blob)}::installGlobalFunctions(ExecutingContext* context) { std::initializer_list functionConfig { ${installList.join('\n')} }; - JSValue globalObject = JS_GetGlobalObject(ctx); - MemberInstaller::installFunctions(ctx, globalObject, functionConfig); - JS_FreeValue(ctx, globalObject); + MemberInstaller::installFunctions(context, context->global(), functionConfig); } }`; } diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index 3a5e3da12f..1b1700965f 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -8,7 +8,7 @@ namespace kraken { -KrakenTestContext::KrakenTestContext(ExecutionContext* context) : m_context(context) { +KrakenTestContext::KrakenTestContext(ExecutingContext* context) : m_context(context) { // bridge->owner = this; // bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast(bridge->owner); }; // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); @@ -126,7 +126,7 @@ static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSVa } static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); if (context->dartMethodPtr()->simulatePointer == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); } @@ -186,7 +186,7 @@ static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, } static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); if (context->dartMethodPtr()->simulateInputText == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); } @@ -223,7 +223,7 @@ static JSValue parseHTML(JSContext* ctx, JSValueConst this_val, int argc, JSValu } static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); + auto* context = static_cast(JS_GetContextOpaque(ctx)); JSValue globalErrorFunc = JS_GetPropertyStr(ctx, context->global(), "triggerGlobalError"); @@ -239,9 +239,9 @@ static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int arg struct ExecuteCallbackContext { ExecuteCallbackContext() = delete; - explicit ExecuteCallbackContext(ExecutionContext* context, ExecuteCallback executeCallback) : executeCallback(executeCallback), context(context){}; + explicit ExecuteCallbackContext(ExecutingContext* context, ExecuteCallback executeCallback) : executeCallback(executeCallback), context(context){}; ExecuteCallback executeCallback; - ExecutionContext* context; + ExecutingContext* context; }; void KrakenTestContext::invokeExecuteTest(ExecuteCallback executeCallback) { diff --git a/bridge/test/kraken_test_context.h b/bridge/test/kraken_test_context.h index 01ae8928f1..74a6517351 100644 --- a/bridge/test/kraken_test_context.h +++ b/bridge/test/kraken_test_context.h @@ -15,14 +15,14 @@ namespace kraken { struct ImageSnapShotContext { JSValue callback; - ExecutionContext* context; + ExecutingContext* context; list_head link; }; class KrakenTestContext final { public: explicit KrakenTestContext() = delete; - explicit KrakenTestContext(ExecutionContext* context); + explicit KrakenTestContext(ExecutingContext* context); /// Evaluate JavaScript source code with build-in test frameworks, use in test only. bool evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); @@ -35,7 +35,7 @@ class KrakenTestContext final { private: /// the pointer of JSContext, ownership belongs to JSContext - ExecutionContext* m_context{nullptr}; + ExecutingContext* m_context{nullptr}; }; } // namespace kraken diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 8c4174fb7d..e7a9ae6572 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -78,7 +78,7 @@ int32_t timerId = 0; int32_t TEST_setTimeout(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { JSRuntime* rt = JS_GetRuntime(timer->ctx()); - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); th->timeout = get_time_ms() + timeout; @@ -95,7 +95,7 @@ int32_t TEST_setTimeout(kraken::DOMTimer* timer, int32_t contextId, AsyncCallbac int32_t TEST_setInterval(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { JSRuntime* rt = JS_GetRuntime(timer->ctx()); - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); th->timeout = get_time_ms() + timeout; @@ -114,7 +114,7 @@ int32_t callbackId = 0; uint32_t TEST_requestAnimationFrame(kraken::FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { JSRuntime* rt = JS_GetRuntime(frameCallback->ctx()); - auto* context = static_cast(JS_GetContextOpaque(frameCallback->ctx())); + auto* context = static_cast(JS_GetContextOpaque(frameCallback->ctx())); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); JSFrameCallback* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); th->handler = handler; @@ -202,7 +202,7 @@ std::unique_ptr TEST_allocateNewPage() { return std::unique_ptr(static_cast(getPage(newContextId))); } -static bool jsPool(kraken::ExecutionContext* context) { +static bool jsPool(kraken::ExecutingContext* context) { JSRuntime* rt = context->runtime(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); int64_t cur_time, delay; @@ -248,7 +248,7 @@ static bool jsPool(kraken::ExecutionContext* context) { return false; } -void TEST_runLoop(kraken::ExecutionContext* context) { +void TEST_runLoop(kraken::ExecutingContext* context) { for (;;) { context->drainPendingPromiseJobs(); if (jsPool(context)) diff --git a/bridge/test/kraken_test_env.h b/bridge/test/kraken_test_env.h index 4f47ae6dcc..68ab9e730d 100644 --- a/bridge/test/kraken_test_env.h +++ b/bridge/test/kraken_test_env.h @@ -26,7 +26,7 @@ namespace kraken { std::unique_ptr TEST_init(OnJSError onJsError); std::unique_ptr TEST_init(); std::unique_ptr TEST_allocateNewPage(); -void TEST_runLoop(ExecutionContext* context); +void TEST_runLoop(ExecutingContext* context); void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); } // namespace kraken From c10009bb018379f374b93840919a8cda1cff187a Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 11 Feb 2022 20:17:09 +0800 Subject: [PATCH 022/498] feat: add scriptWrappeable. --- bridge/CMakeLists.txt | 2 + bridge/bindings/qjs/garbage_collected.h | 65 ------ bridge/bindings/qjs/qjs_blob.cc | 138 +++++++++++++ bridge/bindings/qjs/qjs_blob.h | 8 +- bridge/bindings/qjs/script_value.cc | 4 + bridge/bindings/qjs/script_value.h | 1 + bridge/bindings/qjs/script_wrappable.cc | 76 +++++++ bridge/bindings/qjs/script_wrappable.h | 61 ++++++ bridge/bindings/qjs/wrapper_type_info.h | 8 +- bridge/core/fileapi/blob.cc | 186 ++---------------- bridge/core/fileapi/blob.h | 24 ++- .../frame/window_or_worker_global_scope.cc | 2 +- 12 files changed, 313 insertions(+), 262 deletions(-) create mode 100644 bridge/bindings/qjs/script_wrappable.cc create mode 100644 bridge/bindings/qjs/script_wrappable.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index b3f822567d..2b4542e9de 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -186,6 +186,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/member_installer.cc bindings/qjs/member_installer.h bindings/qjs/garbage_collected.h + bindings/qjs/script_wrappable.cc + bindings/qjs/script_wrappable.h bindings/qjs/wrapper_type_info.h bindings/qjs/heap_hashmap.h bindings/qjs/native_string_utils.cc diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index 045299c3a9..356431be76 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -39,11 +39,6 @@ class GarbageCollected { public: using ParentMostGarbageCollectedType = T; - template - P* initialize(JSContext* ctx, JSClassID* classId); - template - P* initialize(JSContext* ctx, JSClassID* classId, JSClassExoticMethods* exoticMethods); - // Must use MakeGarbageCollected. void* operator new(size_t) = delete; void* operator new[](size_t) = delete; @@ -72,8 +67,6 @@ class GarbageCollected { */ [[nodiscard]] FORCE_INLINE virtual const char* getHumanReadableName() const { return ""; }; - FORCE_INLINE JSValue toQuickJS() { return jsObject; }; - FORCE_INLINE JSContext* ctx() { return m_ctx; }; FORCE_INLINE ExecutingContext* context() const { return static_cast(JS_GetContextOpaque(m_ctx)); }; @@ -98,64 +91,6 @@ class MakeGarbageCollectedTrait { friend GarbageCollected; }; -template -template -P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId, JSClassExoticMethods* exoticMethods) { - 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 toQuickJS 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))); - GCVisitor visitor{rt, mark_func}; - object->trace(&visitor); - }; - - /// Define custom behavior when call getProperty, setProperty on object. - if (exoticMethods != nullptr) { - def.exotic = exoticMethods; - } - - /// 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 -template -P* GarbageCollected::initialize(JSContext* ctx, JSClassID* classId) { - return initialize

(ctx, classId, nullptr); -} - template T* makeGarbageCollected(Args&&... args) { static_assert(std::is_base_of::value, diff --git a/bridge/bindings/qjs/qjs_blob.cc b/bridge/bindings/qjs/qjs_blob.cc index 9760ffae45..bcb76cad1c 100644 --- a/bridge/bindings/qjs/qjs_blob.cc +++ b/bridge/bindings/qjs/qjs_blob.cc @@ -6,9 +6,147 @@ #include "qjs_blob.h" #include "member_installer.h" #include "core/executing_context.h" +#include "core/fileapi/blob.h" namespace kraken { + +//IMPL_PROPERTY_GETTER(Blob, type)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); +// return JS_NewString(blob->m_ctx, blob->mimeType.empty() ? "" : blob->mimeType.c_str()); +//} +// +//IMPL_PROPERTY_GETTER(Blob, size)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); +// return JS_NewFloat64(blob->m_ctx, blob->_size); +//} +// +//IMPL_FUNCTION(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::classID)); +// +// JS_DupValue(ctx, blob->jsObject); +// +// auto* promiseContext = new PromiseContext{blob, blob->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->context()->promise_job_list); +// +// // TODO: remove setTimeout +// getDartMethod()->setTimeout(promiseContext, blob->context()->getContextId(), callback, 0); +// +// return promise; +//} +// +//IMPL_FUNCTION(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::classID)); +// 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 = Blob::create(ctx, std::move(blob->_data), mimeType); +// return newBlob->toQuickJS(); +// } +// 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 = Blob::create(ctx, std::move(newData), mimeType); +// return newBlob->toQuickJS(); +//} +// +//IMPL_FUNCTION(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::classID)); +// JS_DupValue(ctx, blob->jsObject); +// +// auto* promiseContext = new PromiseContext{blob, blob->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->context()->promise_job_list); +// +// getDartMethod()->setTimeout(promiseContext, blob->context()->getContextId(), callback, 0); +// +// return promise; +//} + +const WrapperTypeInfo& Blob::wrapper_type_info_ = QJSBlob::m_wrapperTypeInfo; + +//const WrapperTypeInfo Blob::wrapper_type_info_ = QJSBlob::m_wrapperTypeInfo; + static JSValue arrayBuffer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { } diff --git a/bridge/bindings/qjs/qjs_blob.h b/bridge/bindings/qjs/qjs_blob.h index 9e83eefaf1..7e762db4cd 100644 --- a/bridge/bindings/qjs/qjs_blob.h +++ b/bridge/bindings/qjs/qjs_blob.h @@ -18,8 +18,8 @@ class QJSBlob final { public: static void install(ExecutingContext* context); - static const WrapperTypeInfo* getWrapperTypeInfo() { - return &m_wrapperTypeInfo; + static WrapperTypeInfo* getWrapperTypeInfo() { + return const_cast(&m_wrapperTypeInfo); } private: @@ -29,10 +29,10 @@ class QJSBlob final { static void installPrototypeMethods(ExecutingContext* context); static void installPrototypeProperties(ExecutingContext* context); static void installConstructor(ExecutingContext* context); + + friend class Blob; }; } -class qjs_blob {}; - #endif // KRAKENBRIDGE_QJS_BLOB_H diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 298c2f7052..8751b40863 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -41,6 +41,10 @@ bool ScriptValue::isString() { return JS_IsString(m_value); } +bool ScriptValue::isArray() { + return JS_IsArray(m_ctx, m_value); +} + JSValue ScriptValue::toQuickJS() { return m_value; } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index e6f8b5bfab..ae82731de8 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -43,6 +43,7 @@ class ScriptValue final { ~ScriptValue() { JS_FreeValue(m_ctx, m_value); } bool isEmpty(); bool isString(); + bool isArray(); JSValue toQuickJS(); // Create a new ScriptValue from call JSON.stringify to current value. ScriptValue toJSONStringify(ExceptionState* exception); diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc new file mode 100644 index 0000000000..522dbc9f5f --- /dev/null +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_wrappable.h" +#include "core/executing_context.h" + +namespace kraken { + +ScriptWrappable::ScriptWrappable(JSContext* ctx): GarbageCollected(ctx) {} + +JSValue ScriptWrappable::toQuickJS() { + if (m_wrapped) { + return m_jsObject; + } + + // Initialize the corresponding quickjs object. + initializeQuickJSObject(); + + return m_jsObject; +} + +void ScriptWrappable::initializeQuickJSObject() { + auto* wrapperTypeInfo = const_cast(getWrapperTypeInfo()); + JSRuntime* runtime = m_runtime; + + /// 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 toQuickJS to make sure JSClassDef when this class are created at the first class. + if (wrapperTypeInfo->classId == 0 || !JS_HasClassId(runtime, wrapperTypeInfo->classId)) { + /// Allocate a new unique classID from QuickJS. + JS_NewClassID(&wrapperTypeInfo->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))); + GCVisitor visitor{rt, mark_func}; + object->trace(&visitor); + }; + + /// Define custom behavior when call getProperty, setProperty on object. + if (wrapperTypeInfo->exoticMethods != nullptr) { + def.exotic = wrapperTypeInfo->exoticMethods; + } + + /// 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, wrapperTypeInfo->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(m_ctx, wrapperTypeInfo->classId); + JS_SetOpaque(jsObject, this); + + // Let instance inherit EventTarget prototype methods. + JSValue prototype = context()->contextData()->prototypeForType(wrapperTypeInfo); + JS_SetPrototype(m_ctx, jsObject, prototype); + + m_wrapped = true; +} + +} diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h new file mode 100644 index 0000000000..0c4800b1a9 --- /dev/null +++ b/bridge/bindings/qjs/script_wrappable.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_SCRIPT_WRAPPABLE_H +#define KRAKENBRIDGE_SCRIPT_WRAPPABLE_H + +#include +#include "garbage_collected.h" +#include "wrapper_type_info.h" + +namespace kraken { + +// 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); + + // Returns the WrapperTypeInfo of the instance. + virtual const WrapperTypeInfo* getWrapperTypeInfo() const = 0; + + FORCE_INLINE JSValue toQuickJS(); + + private: + bool m_wrapped{false}; + void initializeQuickJSObject(); + JSValue m_jsObject{JS_NULL}; +}; + +inline ScriptWrappable* toScriptWrappable(JSValue object) { + return static_cast(JS_GetOpaque(object, JSValueGetClassId(object))); +} + +} + +#endif // KRAKENBRIDGE_SCRIPT_WRAPPABLE_H diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index e97bada4b0..370b67b64f 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -28,10 +28,10 @@ class WrapperTypeInfo final { return false; } - const char* className; - const WrapperTypeInfo* parent_class; - JSClassCall* callFunc; - + const char* className{nullptr}; + const WrapperTypeInfo* parent_class{nullptr}; + JSClassCall* callFunc{nullptr}; + JSClassExoticMethods *exoticMethods{nullptr}; JSClassID classId{0}; }; diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index b5641d3979..9c30732786 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -3,184 +3,19 @@ * Author: Kraken Team. */ +#include "bindings/qjs/qjs_blob.h" #include "blob.h" -//#include "dart_methods.h" namespace kraken { -//void bindBlob(std::unique_ptr& context) { -// JSValue constructor = context->contextData()->constructorForType(&blobTypeInfo); -// JSValue prototype = context->contextData()->prototypeForType(&blobTypeInfo); -// -// // Install methods on prototype. -// INSTALL_FUNCTION(Blob, prototype, arrayBuffer, 0); -// INSTALL_FUNCTION(Blob, prototype, slice, 3); -// INSTALL_FUNCTION(Blob, prototype, text, 0); -// -// // Install readonly properties. -// INSTALL_READONLY_PROPERTY(Blob, prototype, type); -// INSTALL_READONLY_PROPERTY(Blob, prototype, size); -// -// context->defineGlobalProperty("Blob", constructor); -//} - -JSClassID Blob::classID{0}; - Blob* Blob::create(JSContext* ctx) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - auto* blob = makeGarbageCollected()->initialize(ctx, &classID); - - JSValue prototype = context->contextData()->prototypeForType(&blobTypeInfo); - - // Let eventTarget instance inherit EventTarget prototype methods. - JS_SetPrototype(ctx, blob->toQuickJS(), prototype); - return blob; + return makeGarbageCollected(ctx); } Blob* Blob::create(JSContext* ctx, std::vector&& data) { - return create(ctx); + return makeGarbageCollected(ctx, std::forward>(data)); } Blob* Blob::create(JSContext* ctx, std::vector&& data, std::string& mime) { - return create(ctx); -} - -JSValue Blob::constructor(ExecutingContext* context) { - return context->contextData()->constructorForType(&blobTypeInfo); -} - -JSValue Blob::prototype(ExecutingContext* context) { - return context->contextData()->prototypeForType(&blobTypeInfo); -} - -IMPL_PROPERTY_GETTER(Blob, type)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); - return JS_NewString(blob->m_ctx, blob->mimeType.empty() ? "" : blob->mimeType.c_str()); -} - -IMPL_PROPERTY_GETTER(Blob, size)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); - return JS_NewFloat64(blob->m_ctx, blob->_size); -} - -IMPL_FUNCTION(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::classID)); - - JS_DupValue(ctx, blob->jsObject); - - auto* promiseContext = new PromiseContext{blob, blob->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->context()->promise_job_list); - - // TODO: remove setTimeout - getDartMethod()->setTimeout(promiseContext, blob->context()->getContextId(), callback, 0); - - return promise; -} - -IMPL_FUNCTION(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::classID)); - 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 = Blob::create(ctx, std::move(blob->_data), mimeType); - return newBlob->toQuickJS(); - } - 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 = Blob::create(ctx, std::move(newData), mimeType); - return newBlob->toQuickJS(); -} - -IMPL_FUNCTION(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::classID)); - JS_DupValue(ctx, blob->jsObject); - - auto* promiseContext = new PromiseContext{blob, blob->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->context()->promise_job_list); - - getDartMethod()->setTimeout(promiseContext, blob->context()->getContextId(), callback, 0); - - return promise; + return makeGarbageCollected(ctx, std::forward>(data), mime); } void BlobBuilder::append(ExecutingContext& context, Blob* blob) { @@ -189,15 +24,13 @@ void BlobBuilder::append(ExecutingContext& context, Blob* blob) { _data.insert(_data.end(), blobData.begin(), blobData.end()); } -void BlobBuilder::append(ExecutingContext& context, JSValue& value) { - if (JS_IsString(value)) { - const char* buffer = JS_ToCString(context.ctx(), value); - std::string str = std::string(buffer); +void BlobBuilder::append(ExecutingContext& context, ScriptValue& value) { + if (value.isString()) { + std::string str = value.toCString(); 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)) { + } else if (value.isArray()) { JSAtom lengthKey = JS_NewAtom(context.ctx(), "length"); JSValue lengthValue = JS_GetProperty(context.ctx(), value, lengthKey); uint32_t length; @@ -260,4 +93,7 @@ uint8_t* Blob::bytes() { void Blob::trace(GCVisitor* visitor) const {} void Blob::dispose() const {} +Blob::Blob(JSContext* ctx): ScriptWrappable(ctx) {} +Blob::Blob(JSContext* pContext, std::vector vector) {} + } // namespace kraken diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 82e321376c..b0a07fcd3e 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -6,36 +6,33 @@ #ifndef KRAKENBRIDGE_BLOB_H #define KRAKENBRIDGE_BLOB_H -#include "bindings/qjs/garbage_collected.h" -#include "bindings/qjs/macros.h" #include #include +#include "bindings/qjs/macros.h" +#include "bindings/qjs/qjs_blob.h" +#include "bindings/qjs/script_wrappable.h" namespace kraken { class BlobBuilder; -class Blob : public GarbageCollected { +class Blob : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); public: - static JSClassID classID; static Blob* create(JSContext* ctx); static Blob* create(JSContext* ctx, std::vector&& data); static Blob* create(JSContext* ctx, std::vector&& data, std::string& mime); - static JSValue constructor(ExecutingContext* context); - static JSValue prototype(ExecutingContext* context); - Blob(){}; - Blob(std::vector&& data) : _size(data.size()), _data(std::move(data)){}; - Blob(std::vector&& data, std::string& mime) : mimeType(mime), _size(data.size()), _data(std::move(data)){}; + Blob() = delete; + explicit Blob(JSContext* ctx); + explicit Blob(JSContext* ctx, std::vector&& data) : _size(data.size()), _data(std::move(data)), ScriptWrappable(ctx) {}; + explicit Blob(JSContext* ctx, std::vector&& data, std::string& mime) : mimeType(mime), _size(data.size()), _data(std::move(data)), ScriptWrappable(ctx){}; /// get an pointer of bytes data from JSBlob uint8_t* bytes(); /// get bytes data's length int32_t size(); - DEFINE_PROTOTYPE_READONLY_PROPERTY(type); - DEFINE_PROTOTYPE_READONLY_PROPERTY(size); - void trace(GCVisitor* visitor) const override; void dispose() const override; @@ -44,11 +41,12 @@ class Blob : public GarbageCollected { std::string mimeType; std::vector _data; friend BlobBuilder; + friend QJSBlob; }; class BlobBuilder { public: - void append(ExecutingContext& context, JSValue& value); + void append(ExecutingContext& context, ScriptValue& value); void append(ExecutingContext& context, Blob* blob); std::vector finalize(); diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 01d70c3b31..a19425111d 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -77,7 +77,7 @@ int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, QJSFunctio } // Create a timer object to keep track timer callback. - auto* timer = makeGarbageCollected(handler)->initialize(context->ctx(), &DOMTimer::classId); + auto* timer = makeGarbageCollected(handler)->initializeQuickJSObject(context->ctx(), &DOMTimer::classId); uint32_t timerId = context->dartMethodPtr()->setInterval(timer, context->getContextId(), handlePersistentCallback, timeout); From 128a22ea048b323253fa37f2d3c5e0ca08b7cb48 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 3 Mar 2022 21:20:16 +0800 Subject: [PATCH 023/498] refactor: add type traits and refactor to google code style. --- bridge/CMakeLists.txt | 13 +- bridge/bindings/qjs/atom_string.cc | 6 + bridge/bindings/qjs/atom_string.h | 46 +++++++ bridge/bindings/qjs/binding_initializer.cc | 2 +- bridge/bindings/qjs/binding_initializer.h | 2 +- bridge/bindings/qjs/converter.h | 34 +++++ bridge/bindings/qjs/converter_impl.h | 120 +++++++++++++++++ bridge/bindings/qjs/exception_state.cc | 24 ++-- bridge/bindings/qjs/exception_state.h | 12 +- bridge/bindings/qjs/garbage_collected.h | 15 +-- bridge/bindings/qjs/gc_visitor.cc | 4 +- bridge/bindings/qjs/gc_visitor.h | 8 +- bridge/bindings/qjs/heap_hashmap.h | 68 +++++----- bridge/bindings/qjs/qjs_blob.cc | 32 ++--- bridge/bindings/qjs/qjs_blob.h | 6 +- bridge/bindings/qjs/qjs_blob_property_bag.cc | 23 ++++ bridge/bindings/qjs/qjs_blob_property_bag.h | 26 ++++ bridge/bindings/qjs/qjs_engine_patch.cc | 4 +- bridge/bindings/qjs/qjs_engine_patch.h | 2 +- bridge/bindings/qjs/qjs_function.cc | 24 ++-- bridge/bindings/qjs/qjs_function.h | 17 +-- ...arraybuffer_arraybufferview_blob_string.cc | 14 ++ ..._arraybuffer_arraybufferview_blob_string.h | 41 ++++++ bridge/bindings/qjs/qjs_window.cc | 16 +-- bridge/bindings/qjs/script_value.cc | 26 +--- bridge/bindings/qjs/script_value.h | 15 ++- bridge/bindings/qjs/script_wrappable.cc | 40 +++--- bridge/bindings/qjs/script_wrappable.h | 18 ++- bridge/bindings/qjs/ts_type.h | 62 +++++++++ .../dom/frame_request_callback_collection.cc | 55 ++++---- .../dom/frame_request_callback_collection.h | 30 ++--- bridge/core/executing_context.cc | 67 ++-------- bridge/core/executing_context.h | 16 +-- bridge/core/fileapi/blob.cc | 124 ++++++++---------- bridge/core/fileapi/blob.h | 18 +-- bridge/core/fileapi/dom_array_buffer_view.cc | 6 + bridge/core/fileapi/dom_array_buffer_view.h | 38 ++++++ bridge/core/frame/console.cc | 4 +- bridge/core/frame/dom_timer.cc | 22 ++-- bridge/core/frame/dom_timer.h | 20 +-- bridge/core/frame/dom_timer_coordinator.cc | 4 +- bridge/core/frame/module_callback.cc | 12 +- bridge/core/frame/module_callback.h | 6 +- .../core/frame/module_callback_coordinator.cc | 14 +- .../core/frame/module_callback_coordinator.h | 12 +- bridge/core/frame/module_listener.cc | 8 +- bridge/core/frame/module_listener.h | 10 +- .../core/frame/module_listener_container.cc | 2 +- bridge/core/frame/module_listener_container.h | 1 + bridge/core/frame/module_manager.cc | 90 ++++++------- .../frame/window_or_worker_global_scope.cc | 9 +- bridge/core/page.h | 2 +- bridge/foundation/macros.h | 9 ++ bridge/foundation/native_value.cc | 12 +- .../code_generator/bin/code_generator.js | 2 +- .../code_generator/src/generate_header.ts | 4 +- 56 files changed, 833 insertions(+), 484 deletions(-) create mode 100644 bridge/bindings/qjs/atom_string.cc create mode 100644 bridge/bindings/qjs/atom_string.h create mode 100644 bridge/bindings/qjs/converter.h create mode 100644 bridge/bindings/qjs/converter_impl.h create mode 100644 bridge/bindings/qjs/qjs_blob_property_bag.cc create mode 100644 bridge/bindings/qjs/qjs_blob_property_bag.h create mode 100644 bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc create mode 100644 bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h create mode 100644 bridge/bindings/qjs/ts_type.h create mode 100644 bridge/core/fileapi/dom_array_buffer_view.cc create mode 100644 bridge/core/fileapi/dom_array_buffer_view.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 2b4542e9de..a9d83656ef 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -181,6 +181,9 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") list(APPEND BRIDGE_SOURCE # Binding files + bindings/qjs/ts_type.h + bindings/qjs/converter.h + bindings/qjs/converter_impl.h bindings/qjs/binding_initializer.cc bindings/qjs/binding_initializer.h bindings/qjs/member_installer.cc @@ -198,6 +201,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/qjs_function.h bindings/qjs/script_value.cc bindings/qjs/script_value.h + bindings/qjs/atom_string.cc + bindings/qjs/atom_string.h bindings/qjs/exception_state.cc bindings/qjs/exception_state.h bindings/qjs/gc_visitor.cc @@ -210,6 +215,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/qjs_page.h bindings/qjs/qjs_blob.cc bindings/qjs/qjs_blob.h + bindings/qjs/qjs_blob_property_bag.cc + bindings/qjs/qjs_blob_property_bag.h + bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h + bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc # Core sources core/executing_context.cc @@ -221,6 +230,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dart_methods.h core/fileapi/blob.h core/fileapi/blob.cc + core/fileapi/dom_array_buffer_view.cc + core/fileapi/dom_array_buffer_view.h core/frame/console.cc core/frame/console.h core/frame/dom_timer.cc @@ -261,8 +272,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") list(APPEND BRIDGE_SOURCE out/qjs_console.cc out/qjs_console.h - out/qjs_module_manager.cc - out/qjs_module_manager.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/atom_string.cc b/bridge/bindings/qjs/atom_string.cc new file mode 100644 index 0000000000..73ef074b15 --- /dev/null +++ b/bridge/bindings/qjs/atom_string.cc @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "atom_string.h" diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h new file mode 100644 index 0000000000..190ac78aa5 --- /dev/null +++ b/bridge/bindings/qjs/atom_string.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ + +#include +#include "foundation/macros.h" + +namespace kraken { + +// ScriptAtom is a stack allocate only QuickJS JSAtom wrapper. +class AtomString final { + // ScriptAtom should only allocate at stack. + KRAKEN_DISALLOW_NEW(); + public: + explicit AtomString(JSContext* ctx, const char* string): ctx_(ctx), atom_(JS_NewAtom(ctx, string)) {} + explicit AtomString(JSContext* ctx, JSAtom atom): ctx_(ctx), atom_(JS_DupAtom(ctx, atom)) {}; + + ~AtomString() { + JS_FreeAtom(ctx_, atom_); + } + + JSValue ToQuickJS() const { + return JS_AtomToValue(ctx_, atom_); + } + + AtomString& operator=(const AtomString& other) { + if (&other != this) { + atom_ = JS_DupAtom(ctx_, other.atom_); + } + return *this; + }; + + private: + AtomString() = delete; + JSContext* ctx_{nullptr}; + JSAtom atom_{JS_ATOM_NULL}; +}; + + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 02e958457f..0f3ea40fe3 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -12,7 +12,7 @@ namespace kraken { -void installBindings(ExecutingContext* context) { +void InstallBindings(ExecutingContext* context) { QJSWindow::installGlobalFunctions(context); QJSModuleManager::install(context); QJSConsole::install(context); diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h index 46ab98e9a6..fca2265bf1 100644 --- a/bridge/bindings/qjs/binding_initializer.h +++ b/bridge/bindings/qjs/binding_initializer.h @@ -12,7 +12,7 @@ namespace kraken { class ExecutingContext; -void installBindings(ExecutingContext* context); +void InstallBindings(ExecutingContext* context); } // namespace kraken diff --git a/bridge/bindings/qjs/converter.h b/bridge/bindings/qjs/converter.h new file mode 100644 index 0000000000..3178f28358 --- /dev/null +++ b/bridge/bindings/qjs/converter.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CONVERTER_H +#define KRAKENBRIDGE_CONVERTER_H + +#include + +#include + +#include "qjs_engine_patch.h" + +namespace kraken { + +// The template parameter |T| determines what kind of type conversion to perform. +// It is not supposed to be used directly: there needs to be a specialization for each type which represents +// a JavaScript type that will be converted to a C++ representation. +// Its main goal is to provide a standard interface for converting JS types +// into C++ ones. +template +struct Converter { + using ImplType = T; +}; + +template +struct ConverterBase { + using ImplType = typename T::ImplType; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CONVERTER_H diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h new file mode 100644 index 0000000000..0dbb3aa539 --- /dev/null +++ b/bridge/bindings/qjs/converter_impl.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ + +#include "converter.h" +#include "ts_type.h" + +namespace kraken { + +template +struct Converter, typename std::enable_if_t::ImplType>>> : 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); + } +}; + +// Any +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value) { + assert(!JS_IsException(value)); + return ScriptValue(ctx, value); + } + + static JSValue ToValue(JSContext* ctx, const ScriptValue& value) { return value.toQuickJS(); } +}; + +// Boolean +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value) { + 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) { + 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); } +}; + +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value) { + 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 std::unique_ptr FromValue(JSContext* ctx, JSValue value) { + assert(!JS_IsException(value)); + return jsValueToNativeString(ctx, value); + } + + static JSValue ToValue(JSContext* ctx, uint16_t* bytes, size_t length) { return JS_NewUnicodeString(ctx, bytes, length); } +}; + +template <> +struct Converter : public ConverterBase { + static AtomString FromValue(JSContext* ctx, JSValue value) { + assert(!JS_IsException(value)); + JSAtom atom = JS_ValueToAtom(ctx, value); + AtomString result = AtomString(ctx, atom); + JS_FreeAtom(ctx, atom); + return result; + } + + static JSValue ToValue(JSContext* ctx, const AtomString& atom_string) { return atom_string.ToQuickJS(); } +}; + +template +struct Converter> : public ConverterBase> { + using ImplType = typename TSSequence::ImplType; + + static ImplType FromValue(JSContext* ctx, JSValue value) { + assert(!JS_IsException(value)); + assert(JS_IsArray(ctx, value)); + + std::vector v; + uint32_t length = Converter::FromValue(ctx, JS_GetPropertyStr(ctx, value, "length")); + + v.reserve(length); + v.resize(length); + + for (uint32_t i = 0; i < length; i++) { + auto&& item = Converter::FromValue(ctx, JS_GetPropertyUint32(ctx, value, i)); + } + + return v; + } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index c7d09f8e81..abd8059a32 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -7,36 +7,36 @@ namespace kraken { -void ExceptionState::throwException(JSContext* ctx, ErrorType type, const char* message) { +void ExceptionState::ThrowException(JSContext* ctx, ErrorType type, const char* message) { switch (type) { case ErrorType::TypeError: - m_exception = JS_ThrowTypeError(ctx, "%s", message); + exception_ = JS_ThrowTypeError(ctx, "%s", message); break; case InternalError: - m_exception = JS_ThrowInternalError(ctx, "%s", message); + exception_ = JS_ThrowInternalError(ctx, "%s", message); break; case RangeError: - m_exception = JS_ThrowRangeError(ctx, "%s", message); + exception_ = JS_ThrowRangeError(ctx, "%s", message); break; case ReferenceError: - m_exception = JS_ThrowReferenceError(ctx, "%s", message); + exception_ = JS_ThrowReferenceError(ctx, "%s", message); break; case SyntaxError: - m_exception = JS_ThrowSyntaxError(ctx, "%s", message); + exception_ = JS_ThrowSyntaxError(ctx, "%s", message); break; } } -void ExceptionState::throwException(JSContext* ctx, JSValue exception) { - m_exception = JS_DupValue(ctx, exception); +void ExceptionState::ThrowException(JSContext* ctx, JSValue exception) { + exception_ = JS_DupValue(ctx, exception); } -bool ExceptionState::hasException() { - return !JS_IsNull(m_exception); +bool ExceptionState::HasException() { + return !JS_IsNull(exception_); } -JSValue ExceptionState::toQuickJS() { - return m_exception; +JSValue ExceptionState::ToQuickJS() { + return exception_; } } // namespace kraken diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index e1c7cb442e..fa6ef48230 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -19,14 +19,14 @@ class ExceptionState { KRAKEN_DISALLOW_NEW(); public: - void throwException(JSContext* ctx, ErrorType type, const char* message); - void throwException(JSContext* ctx, JSValue exception); - bool hasException(); - JSValue toQuickJS(); + void ThrowException(JSContext* ctx, ErrorType type, const char* message); + void ThrowException(JSContext* ctx, JSValue exception); + bool HasException(); + JSValue ToQuickJS(); private: - JSValue m_exception{JS_NULL}; - JSContext* m_ctx; + JSValue exception_{JS_NULL}; + JSContext* ctx_; }; } // namespace kraken diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index 356431be76..3d06d86af0 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -30,7 +30,7 @@ class ExecutingContext; * 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. + * // Trace all memory wants to collected by GC. * } * }; */ @@ -51,13 +51,13 @@ class GarbageCollected { * This Trace method must be override by objects inheriting from * GarbageCollected. */ - virtual void trace(GCVisitor* visitor) const = 0; + virtual void Trace(GCVisitor* visitor) 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; + virtual void Dispose() const = 0; /** * Specifies a name for the garbage-collected object. Such names will never @@ -65,17 +65,10 @@ class GarbageCollected { * * @returns a human readable name for the object. */ - [[nodiscard]] FORCE_INLINE virtual const char* getHumanReadableName() const { return ""; }; - - FORCE_INLINE JSContext* ctx() { return m_ctx; }; - FORCE_INLINE ExecutingContext* context() const { return static_cast(JS_GetContextOpaque(m_ctx)); }; + [[nodiscard]] FORCE_INLINE virtual const char* GetHumanReadableName() const { return ""; }; protected: - JSValue jsObject{JS_NULL}; - JSContext* m_ctx{nullptr}; - JSRuntime* m_runtime{nullptr}; GarbageCollected(){}; - GarbageCollected(JSContext* ctx) : m_runtime(JS_GetRuntime(ctx)), m_ctx(ctx){}; friend class MakeGarbageCollectedTrait; }; diff --git a/bridge/bindings/qjs/gc_visitor.cc b/bridge/bindings/qjs/gc_visitor.cc index b1313c967b..50820cfc2d 100644 --- a/bridge/bindings/qjs/gc_visitor.cc +++ b/bridge/bindings/qjs/gc_visitor.cc @@ -7,8 +7,8 @@ namespace kraken { -void GCVisitor::trace(JSValue value) { - JS_MarkValue(m_runtime, value, m_markFunc); +void GCVisitor::Trace(JSValue value) { + JS_MarkValue(runtime_, value, markFunc_); } } // namespace kraken diff --git a/bridge/bindings/qjs/gc_visitor.h b/bridge/bindings/qjs/gc_visitor.h index 4864e62554..b782e4ca03 100644 --- a/bridge/bindings/qjs/gc_visitor.h +++ b/bridge/bindings/qjs/gc_visitor.h @@ -13,13 +13,13 @@ namespace kraken { // Use GCVisitor to keep track gc managed members in C++ class. class GCVisitor final { public: - explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc) : m_runtime(rt), m_markFunc(markFunc){}; + explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc) : runtime_(rt), markFunc_(markFunc){}; - void trace(JSValue value); + void Trace(JSValue value); private: - JSRuntime* m_runtime{nullptr}; - JS_MarkFunc* m_markFunc{nullptr}; + JSRuntime* runtime_{nullptr}; + JS_MarkFunc* markFunc_{nullptr}; }; } // namespace kraken diff --git a/bridge/bindings/qjs/heap_hashmap.h b/bridge/bindings/qjs/heap_hashmap.h index 8449fbe152..1a700c28bc 100644 --- a/bridge/bindings/qjs/heap_hashmap.h +++ b/bridge/bindings/qjs/heap_hashmap.h @@ -18,82 +18,82 @@ class HeapHashMap { explicit HeapHashMap(JSContext* ctx); ~HeapHashMap(); - bool contains(K key); - JSValue getProperty(K key); - void setProperty(K key, JSValue value); - void copyWith(HeapHashMap* newValue); - void erase(K key); + bool Contains(K key); + JSValue GetProperty(K key); + void SetProperty(K key, JSValue value); + void CopyWith(HeapHashMap* newValue); + void Erase(K key); - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const; + void Trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const; private: - JSRuntime* m_runtime{nullptr}; - JSContext* m_ctx{nullptr}; - std::unordered_map m_entries; + JSRuntime* runtime_{nullptr}; + JSContext* ctx_{nullptr}; + std::unordered_map entries_; }; template -HeapHashMap::HeapHashMap(JSContext* ctx) : m_runtime(JS_GetRuntime(ctx)), m_ctx(ctx) {} +HeapHashMap::HeapHashMap(JSContext* ctx) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx) {} template HeapHashMap::~HeapHashMap() { - for (auto& entry : m_entries) { - JS_FreeAtomRT(m_runtime, entry.first); - JS_FreeValueRT(m_runtime, entry.second); + for (auto& entry : entries_) { + JS_FreeAtomRT(runtime_, entry.first); + JS_FreeValueRT(runtime_, entry.second); } } template -bool HeapHashMap::contains(K key) { - return m_entries.count(key) > 0; +bool HeapHashMap::Contains(K key) { + return entries_.count(key) > 0; } template -JSValue HeapHashMap::getProperty(K key) { - if (m_entries.count(key) == 0) +JSValue HeapHashMap::GetProperty(K key) { + if (entries_.count(key) == 0) return JS_NULL; - return m_entries[key]; + return entries_[key]; } template -void HeapHashMap::setProperty(K key, JSValue value) { +void HeapHashMap::SetProperty(K key, JSValue value) { // GC can't track the value if key had been override. // Should free the value if exist on m_properties. - if (m_entries.count(key) > 0) { - JS_FreeAtom(m_ctx, key); - JS_FreeValue(m_ctx, m_entries[key]); + if (entries_.count(key) > 0) { + JS_FreeAtom(ctx_, key); + JS_FreeValue(ctx_, entries_[key]); } - m_entries[key] = value; + entries_[key] = value; } template -void HeapHashMap::copyWith(HeapHashMap* newValue) { - for (auto& entry : m_entries) { +void HeapHashMap::CopyWith(HeapHashMap* newValue) { + for (auto& entry : entries_) { // We should also dup atom if K is JSAtom. if (std::is_same::value) { - JS_DupAtom(m_ctx, entry.first); + JS_DupAtom(ctx_, entry.first); } - newValue->m_entries[entry.first] = JS_DupValue(m_ctx, entry.second); + newValue->entries_[entry.first] = JS_DupValue(ctx_, entry.second); } } template -void HeapHashMap::erase(K key) { - if (m_entries.count(key) == 0) +void HeapHashMap::Erase(K key) { + if (entries_.count(key) == 0) return; // We should also free atom if K is JSAtom. if (std::is_same::value) { - JS_FreeAtomRT(m_runtime, key); + JS_FreeAtomRT(runtime_, key); } - JS_FreeValueRT(m_runtime, m_entries[key]); - m_entries.erase(key); + JS_FreeValueRT(runtime_, entries_[key]); + entries_.erase(key); } template -void HeapHashMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - for (auto& entry : m_entries) { +void HeapHashMap::Trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { + for (auto& entry : entries_) { JS_MarkValue(rt, entry.second, mark_func); } } diff --git a/bridge/bindings/qjs/qjs_blob.cc b/bridge/bindings/qjs/qjs_blob.cc index bcb76cad1c..856133aecf 100644 --- a/bridge/bindings/qjs/qjs_blob.cc +++ b/bridge/bindings/qjs/qjs_blob.cc @@ -7,6 +7,7 @@ #include "member_installer.h" #include "core/executing_context.h" #include "core/fileapi/blob.h" +#include "converter.h" namespace kraken { @@ -176,13 +177,15 @@ static JSValue typeAttributeSetCallback(JSContext* ctx, JSValueConst this_val, i } -JSValue QJSBlob::constructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { -// if (argc == 0) { -// auto* blob = Blob::create(ctx); -// return blob->toQuickJS(); -// } -// -// JSValue arrayValue = argv[0]; +JSValue QJSBlob::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { + if (argc == 0) { + auto* blob = Blob::create(ctx); + return blob->ToQuickJS(); + } + + + +// JSValue arrayValue = argv[0]; // JSValue optionValue = JS_UNDEFINED; // // if (argc > 1) { @@ -193,11 +196,11 @@ JSValue QJSBlob::constructorCallback(JSContext* ctx, JSValue func_obj, JSValue t // return JS_ThrowTypeError(ctx, "Failed to construct 'Blob': The provided value cannot be converted to a sequence"); // } // -// auto* context = static_cast(JS_GetContextOpaque(ctx)); +// auto* context = static_cast(JS_GetContextOpaque(ctx)); // BlobBuilder builder; // // if (argc == 1 || JS_IsUndefined(optionValue)) { -// builder.append(*context, arrayValue); +// builder.append(*context, ScriptValue(ctx, arrayValue)); // auto* blob = Blob::create(ctx, builder.finalize()); // return blob->toQuickJS(); // } @@ -207,9 +210,8 @@ JSValue QJSBlob::constructorCallback(JSContext* ctx, JSValue func_obj, JSValue t // "Failed to construct 'Blob': parameter 2 ('options') " // "is not an object"); // } -// -// JSAtom mimeTypeKey = JS_NewAtom(ctx, "type"); -// + +// ScriptAtom mineType = ScriptAtom(ctx, "type"); // JSValue mimeTypeValue = JS_GetProperty(ctx, optionValue, mimeTypeKey); // builder.append(*context, mimeTypeValue); // const char* cMineType = JS_ToCString(ctx, mimeTypeValue); @@ -231,7 +233,7 @@ void QJSBlob::install(ExecutingContext* context) { } void QJSBlob::installConstructor(ExecutingContext* context) { - const WrapperTypeInfo* wrapperTypeInfo = getWrapperTypeInfo(); + const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); JSValue constructor = context->contextData()->constructorForType(wrapperTypeInfo); std::initializer_list attributeConfig { @@ -241,7 +243,7 @@ void QJSBlob::installConstructor(ExecutingContext* context) { } void QJSBlob::installPrototypeMethods(ExecutingContext* context) { - const WrapperTypeInfo* wrapperTypeInfo = getWrapperTypeInfo(); + const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); std::initializer_list attributesConfig { @@ -253,7 +255,7 @@ void QJSBlob::installPrototypeMethods(ExecutingContext* context) { } void QJSBlob::installPrototypeProperties(ExecutingContext* context) { - const WrapperTypeInfo* wrapperTypeInfo = getWrapperTypeInfo(); + const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); std::initializer_list functionConfig { diff --git a/bridge/bindings/qjs/qjs_blob.h b/bridge/bindings/qjs/qjs_blob.h index 7e762db4cd..79b54eab0c 100644 --- a/bridge/bindings/qjs/qjs_blob.h +++ b/bridge/bindings/qjs/qjs_blob.h @@ -18,13 +18,13 @@ class QJSBlob final { public: static void install(ExecutingContext* context); - static WrapperTypeInfo* getWrapperTypeInfo() { + static WrapperTypeInfo* GetWrapperTypeInfo() { return const_cast(&m_wrapperTypeInfo); } private: - static JSValue constructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); - constexpr static const WrapperTypeInfo m_wrapperTypeInfo = {"Blob", nullptr, constructorCallback}; + static JSValue ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); + constexpr static const WrapperTypeInfo m_wrapperTypeInfo = {"Blob", nullptr, ConstructorCallback}; static void installPrototypeMethods(ExecutingContext* context); static void installPrototypeProperties(ExecutingContext* context); diff --git a/bridge/bindings/qjs/qjs_blob_property_bag.cc b/bridge/bindings/qjs/qjs_blob_property_bag.cc new file mode 100644 index 0000000000..9afb2e4cc2 --- /dev/null +++ b/bridge/bindings/qjs/qjs_blob_property_bag.cc @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "qjs_blob_property_bag.h" + +namespace kraken { + +BlobPropertyBag* BlobPropertyBag::create(ExecutingContext* context, JSValue value, ExceptionState* exceptionState) { + BlobPropertyBag* dictionary = new BlobPropertyBag(); + + if (JS_IsUndefined(value)) { + + } + return nullptr; +} + +void BlobPropertyBag::fillMemberFromQuickjsObject(ExecutingContext* context, JSValue value, ExceptionState* exceptionState) { + +} + +} diff --git a/bridge/bindings/qjs/qjs_blob_property_bag.h b/bridge/bindings/qjs/qjs_blob_property_bag.h new file mode 100644 index 0000000000..3c6e925e0f --- /dev/null +++ b/bridge/bindings/qjs/qjs_blob_property_bag.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_QJS_BLOB_PROPERTY_BAG_H +#define KRAKENBRIDGE_QJS_BLOB_PROPERTY_BAG_H + +#include "core/executing_context.h" + +namespace kraken { + +class BlobPropertyBag final { + public: + static BlobPropertyBag* create(ExecutingContext* context, JSValue value, ExceptionState* exceptionState); + + const std::string& type() const { return m_type; } + + private: + void fillMemberFromQuickjsObject(ExecutingContext* context, JSValue value, ExceptionState* exceptionState); + std::string m_type; +}; + +} + +#endif // KRAKENBRIDGE_QJS_BLOB_PROPERTY_BAG_H diff --git a/bridge/bindings/qjs/qjs_engine_patch.cc b/bridge/bindings/qjs/qjs_engine_patch.cc index 7975a189e6..3658d76ba2 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.cc +++ b/bridge/bindings/qjs/qjs_engine_patch.cc @@ -296,9 +296,9 @@ static JSString* js_alloc_string(JSRuntime* runtime, JSContext* ctx, int max_len return p; } -JSValue JS_NewUnicodeString(JSRuntime* runtime, JSContext* ctx, const uint16_t* code, uint32_t length) { +JSValue JS_NewUnicodeString(JSContext* ctx, const uint16_t* code, uint32_t length) { JSString* str; - str = js_alloc_string(runtime, ctx, length, 1); + str = js_alloc_string(JS_GetRuntime(ctx), ctx, length, 1); if (!str) return JS_EXCEPTION; memcpy(str->u.str16, code, length * 2); diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h index de9597d7c3..9669d70e8b 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -100,7 +100,7 @@ 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); +JSValue JS_NewUnicodeString(JSContext* ctx, const uint16_t* code, uint32_t length); JSClassID JSValueGetClassId(JSValue); bool JS_IsProxy(JSValue value); bool JS_HasClassId(JSRuntime* runtime, JSClassID classId); diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 8bab223ac3..5755d85185 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -8,37 +8,37 @@ namespace kraken { -bool QJSFunction::isFunction(JSContext* ctx) { - return JS_IsFunction(ctx, m_function); +bool QJSFunction::IsFunction(JSContext* ctx) { + return JS_IsFunction(ctx, function_); } -ScriptValue QJSFunction::invoke(JSContext* ctx, int32_t argc, ScriptValue* arguments) { +ScriptValue QJSFunction::Invoke(JSContext* ctx, 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, m_function); + JS_DupValue(ctx, function_); JSValue argv[std::max(1, argc)]; for (int i = 0; i < argc; i++) { - argv[0 + i] = arguments[i].toQuickJS(); + argv[0 + i] = arguments[i].ToQuickJS(); } - JSValue returnValue = JS_Call(ctx, m_function, JS_UNDEFINED, argc, argv); + JSValue returnValue = JS_Call(ctx, function_, JS_UNDEFINED, argc, argv); // Free the previous duplicated function. - JS_FreeValue(m_ctx, m_function); + JS_FreeValue(ctx, function_); return ScriptValue(ctx, returnValue); } -const char* QJSFunction::getHumanReadableName() const { +const char* QJSFunction::GetHumanReadableName() const { return "QJSFunction"; } -void QJSFunction::trace(GCVisitor* visitor) const { - visitor->trace(m_function); +void QJSFunction::Trace(GCVisitor* visitor) const { + visitor->Trace(function_); } -void QJSFunction::dispose() const { - JS_FreeValueRT(m_runtime, m_function); +void QJSFunction::Dispose() const { + JS_FreeValueRT(JS_GetRuntime(ctx_), function_); } } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 5d5a27c0f1..4581526171 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -14,21 +14,22 @@ namespace kraken { // https://webidl.spec.whatwg.org/#dfn-callback-interface class QJSFunction : public GarbageCollected { public: - static QJSFunction* create(JSContext* ctx, JSValue function) { return makeGarbageCollected(ctx, function); } - explicit QJSFunction(JSContext* ctx, JSValue function) : m_function(JS_DupValue(ctx, function)), GarbageCollected(ctx){}; + static QJSFunction* Create(JSContext* ctx, JSValue function) { return makeGarbageCollected(ctx, function); } + explicit QJSFunction(JSContext* ctx, JSValue function) : function_(JS_DupValue(ctx, function)) {}; - bool isFunction(JSContext* ctx); + bool IsFunction(JSContext* ctx); // Performs "invoke". // https://webidl.spec.whatwg.org/#invoke-a-callback-function - ScriptValue invoke(JSContext* ctx, int32_t argc, ScriptValue* arguments); + ScriptValue Invoke(JSContext* ctx, int32_t argc, ScriptValue* arguments); - const char* getHumanReadableName() const override; - void trace(GCVisitor* visitor) const override; - void dispose() const override; + const char* GetHumanReadableName() const override; + void Trace(GCVisitor* visitor) const override; + void Dispose() const override; private: - JSValue m_function{JS_NULL}; + JSContext* ctx_{nullptr}; + JSValue function_{JS_NULL}; }; } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc b/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc new file mode 100644 index 0000000000..775b6fb2b0 --- /dev/null +++ b/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "qjs_union_arraybuffer_arraybufferview_blob_string.h" + +namespace kraken { + +QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString* QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + return nullptr; +} + +} diff --git a/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h b/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h new file mode 100644 index 0000000000..6d8740ea27 --- /dev/null +++ b/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_QJS_UNION_ARRAYBUFFER_ARRAYBUFFERVIEW_BLOB_STRING_H +#define KRAKENBRIDGE_QJS_UNION_ARRAYBUFFER_ARRAYBUFFERVIEW_BLOB_STRING_H + +#include + +#include "core/fileapi/blob.h" +#include "exception_state.h" + +namespace kraken { + +class QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString { + public: + enum class ContentType { + kArrayBuffer, kArrayBufferView, kBlob, kString + }; + + static QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString* Create( + JSContext* ctx, + JSValue value, + ExceptionState& exception_state); + + explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, uint8_t* arrayBuffer, uint32_t length): content_type_(ContentType::kArrayBuffer) {}; + explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, const std::string& value): content_type_(ContentType::kString) {}; + explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, Blob* blob): content_type_(ContentType::kBlob) {}; + +private: + ContentType content_type_; + std::string member_string_; + uint32_t* bytes; +}; + +} + +class qjs_union_arraybuffer_arraybufferview_blob_string {}; + +#endif // KRAKENBRIDGE_QJS_UNION_ARRAYBUFFER_ARRAYBUFFERVIEW_BLOB_STRING_H diff --git a/bridge/bindings/qjs/qjs_window.cc b/bridge/bindings/qjs/qjs_window.cc index 1cc164aec6..1a4c856ae6 100644 --- a/bridge/bindings/qjs/qjs_window.cc +++ b/bridge/bindings/qjs/qjs_window.cc @@ -40,13 +40,13 @@ static JSValue setTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSVal return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); } - QJSFunction* handler = QJSFunction::create(ctx, callbackValue); + QJSFunction* handler = QJSFunction::Create(ctx, callbackValue); ExceptionState exceptionState; int32_t timerId = WindowOrWorkerGlobalScope::setTimeout(context, handler, timeout, &exceptionState); - if (exceptionState.hasException()) { - return exceptionState.toQuickJS(); + if (exceptionState.HasException()) { + return exceptionState.ToQuickJS(); } // `-1` represents ffi error occurred. @@ -84,12 +84,12 @@ static JSValue setInterval(JSContext* ctx, JSValueConst this_val, int argc, JSVa return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); } - QJSFunction* handler = QJSFunction::create(ctx, callbackValue); + QJSFunction* handler = QJSFunction::Create(ctx, callbackValue); ExceptionState exception; int32_t timerId = WindowOrWorkerGlobalScope::setInterval(context, handler, timeout, &exception); - if (exception.hasException()) { - return exception.toQuickJS(); + if (exception.HasException()) { + return exception.ToQuickJS(); } if (timerId == -1) { @@ -117,8 +117,8 @@ static JSValue clearTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSV ExceptionState exception; WindowOrWorkerGlobalScope::clearTimeout(context, id, &exception); - if (exception.hasException()) { - return exception.toQuickJS(); + if (exception.HasException()) { + return exception.ToQuickJS(); } return JS_NULL; diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 8751b40863..3d08ef4182 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -6,6 +6,8 @@ #include "script_value.h" #include "native_string_utils.h" #include "qjs_engine_patch.h" +#include +#include "core/executing_context.h" namespace kraken { @@ -24,38 +26,20 @@ ScriptValue ScriptValue::createJSONObject(JSContext* ctx, const char* jsonString return result; } -ScriptValue ScriptValue::fromNativeString(JSContext* ctx, NativeString* nativeString) { - JSValue result = JS_NewUnicodeString(JS_GetRuntime(ctx), ctx, nativeString->string, nativeString->length); - return ScriptValue(ctx, result); -} - ScriptValue ScriptValue::Empty(JSContext* ctx) { return ScriptValue(ctx); } -bool ScriptValue::isEmpty() { - return JS_IsNull(m_value); -} - -bool ScriptValue::isString() { - return JS_IsString(m_value); -} - -bool ScriptValue::isArray() { - return JS_IsArray(m_ctx, m_value); -} - -JSValue ScriptValue::toQuickJS() { +JSValue ScriptValue::ToQuickJS() const { return m_value; } -ScriptValue ScriptValue::toJSONStringify(ExceptionState* exception) { +ScriptValue ScriptValue::ToJSONStringify(ExceptionState* exception) { JSValue stringifyedValue = JS_JSONStringify(m_ctx, m_value, JS_NULL, JS_NULL); ScriptValue result = ScriptValue(m_ctx); // JS_JSONStringify may return JS_EXCEPTION if object is not valid. Return JS_EXCEPTION and let quickjs to handle it. if (JS_IsException(stringifyedValue)) { - exception->throwException(m_ctx, stringifyedValue); - result = ScriptValue(m_ctx, stringifyedValue); + exception->ThrowException(m_ctx, stringifyedValue); } else { result = ScriptValue(m_ctx, stringifyedValue); } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index ae82731de8..3eeda9fa34 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -8,13 +8,17 @@ #include #include + #include "exception_state.h" #include "foundation/macros.h" #include "foundation/native_string.h" namespace kraken { -// ScriptValue is a stack allocate only QuickJS JSValue wrapper which hold all information to hide out QuickJS running details. +class ExecutingContext; +class WrapperTypeInfo; + +// ScriptValue is a stack allocate only QuickJS JSValue wrapper ScriptValuewhich hold all information to hide out QuickJS running details. class ScriptValue final { // ScriptValue should only allocate at stack. KRAKEN_DISALLOW_NEW(); @@ -39,14 +43,11 @@ class ScriptValue final { } return *this; }; + ~ScriptValue() { JS_FreeValue(m_ctx, m_value); }; - ~ScriptValue() { JS_FreeValue(m_ctx, m_value); } - bool isEmpty(); - bool isString(); - bool isArray(); - JSValue toQuickJS(); + JSValue ToQuickJS() const; // Create a new ScriptValue from call JSON.stringify to current value. - ScriptValue toJSONStringify(ExceptionState* exception); + ScriptValue ToJSONStringify(ExceptionState* exception); std::unique_ptr toNativeString(); std::string toCString(); diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 522dbc9f5f..b118781fa5 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -8,43 +8,43 @@ namespace kraken { -ScriptWrappable::ScriptWrappable(JSContext* ctx): GarbageCollected(ctx) {} +ScriptWrappable::ScriptWrappable(JSContext* ctx): ctx_(ctx), runtime_(JS_GetRuntime(ctx)) {} -JSValue ScriptWrappable::toQuickJS() { - if (m_wrapped) { - return m_jsObject; +JSValue ScriptWrappable::ToQuickJS() { + if (wrapped_) { + return jsObject_; } // Initialize the corresponding quickjs object. - initializeQuickJSObject(); + InitializeQuickJSObject(); - return m_jsObject; + return jsObject_; } -void ScriptWrappable::initializeQuickJSObject() { - auto* wrapperTypeInfo = const_cast(getWrapperTypeInfo()); - JSRuntime* runtime = m_runtime; +void ScriptWrappable::InitializeQuickJSObject() { + auto* wrapperTypeInfo = const_cast(GetWrapperTypeInfo()); + JSRuntime* runtime = runtime_; - /// 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 toQuickJS to make sure JSClassDef when this class are created at the first class. + /// 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 ToQuickJS to make sure JSClassDef when this class are created at the first class. if (wrapperTypeInfo->classId == 0 || !JS_HasClassId(runtime, wrapperTypeInfo->classId)) { /// Allocate a new unique classID from QuickJS. JS_NewClassID(&wrapperTypeInfo->classId); /// Basic template to describe the behavior about this class. JSClassDef def{}; - def.class_name = getHumanReadableName(); + 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 + /// 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))); GCVisitor visitor{rt, mark_func}; - object->trace(&visitor); + object->Trace(&visitor); }; - /// Define custom behavior when call getProperty, setProperty on object. + /// Define custom behavior when call GetProperty, SetProperty on object. if (wrapperTypeInfo->exoticMethods != nullptr) { def.exotic = wrapperTypeInfo->exoticMethods; } @@ -53,7 +53,7 @@ void ScriptWrappable::initializeQuickJSObject() { /// 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(); + object->Dispose(); free(object); }; @@ -63,14 +63,14 @@ void ScriptWrappable::initializeQuickJSObject() { /// 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(m_ctx, wrapperTypeInfo->classId); - JS_SetOpaque(jsObject, this); + jsObject_ = JS_NewObjectClass(ctx_, wrapperTypeInfo->classId); + JS_SetOpaque(jsObject_, this); // Let instance inherit EventTarget prototype methods. JSValue prototype = context()->contextData()->prototypeForType(wrapperTypeInfo); - JS_SetPrototype(m_ctx, jsObject, prototype); + JS_SetPrototype(ctx_, jsObject_, prototype); - m_wrapped = true; + wrapped_ = true; } } diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 0c4800b1a9..3efc390b4e 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -21,10 +21,10 @@ namespace kraken { // class has a corresponding .idl file. #define DEFINE_WRAPPERTYPEINFO() \ public: \ - const WrapperTypeInfo* getWrapperTypeInfo() const override { \ + const WrapperTypeInfo* GetWrapperTypeInfo() const override { \ return &wrapper_type_info_; \ } \ - static const WrapperTypeInfo* getStaticWrapperTypeInfo() { \ + static const WrapperTypeInfo* GetStaticWrapperTypeInfo() { \ return &wrapper_type_info_; \ } \ \ @@ -42,14 +42,18 @@ class ScriptWrappable : public GarbageCollected { explicit ScriptWrappable(JSContext* ctx); // Returns the WrapperTypeInfo of the instance. - virtual const WrapperTypeInfo* getWrapperTypeInfo() const = 0; + virtual const WrapperTypeInfo* GetWrapperTypeInfo() const = 0; - FORCE_INLINE JSValue toQuickJS(); + JSValue ToQuickJS(); + FORCE_INLINE ExecutingContext* context() const { return static_cast(JS_GetContextOpaque(ctx_)); }; + FORCE_INLINE JSContext* ctx() const { return ctx_; } private: - bool m_wrapped{false}; - void initializeQuickJSObject(); - JSValue m_jsObject{JS_NULL}; + bool wrapped_{false}; + void InitializeQuickJSObject(); + JSValue jsObject_{JS_NULL}; + JSContext* ctx_{nullptr}; + JSRuntime* runtime_{nullptr}; }; inline ScriptWrappable* toScriptWrappable(JSValue object) { diff --git a/bridge/bindings/qjs/ts_type.h b/bridge/bindings/qjs/ts_type.h new file mode 100644 index 0000000000..a2061a4bd7 --- /dev/null +++ b/bridge/bindings/qjs/ts_type.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ + +#include +#include "foundation/native_string.h" +#include "converter.h" +#include "script_value.h" +#include "atom_string.h" +#include "qjs_union_arraybuffer_arraybufferview_blob_string.h" + +namespace kraken { + +struct TSTypeBase { + using ImplType = void; +}; + +template +struct TSTypeBaseHelper { + using ImplType = T; +}; + +// Any +struct TSAny final : public TSTypeBaseHelper {}; + +template +struct TSOptional final : public TSTypeBase { + using ImplType = typename Converter::ImplType; +}; + +// Bool +struct TSBoolean final : public TSTypeBaseHelper {}; + +// Primitive types +struct TSUint32 final : public TSTypeBaseHelper {}; +struct TSDouble final : public TSTypeBaseHelper {}; + +// DOMString is UTF-16 strings. +// https://stackoverflow.com/questions/35123890/what-is-a-domstring-really +struct TSDOMString final : public TSTypeBaseHelper {}; + +struct TSAtomString final : public TSTypeBaseHelper {}; + +// https://developer.mozilla.org/en-US/docs/Web/API/USVString +struct TSUSVString final : public TSTypeBaseHelper {}; + +// Object +struct TSObject : public TSTypeBaseHelper {}; + +// Sequence +template +struct TSSequence final : public TSTypeBase { + using ImplType = typename std::vector; +}; + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc index fdfa239913..63d629d560 100644 --- a/bridge/core/dom/frame_request_callback_collection.cc +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -7,67 +7,66 @@ namespace kraken { -JSClassID FrameCallback::classId{0}; -FrameCallback::FrameCallback(JSValue callback) : m_callback(callback) {} +FrameCallback::FrameCallback(JSContext* ctx, JSValue callback) : callback_(callback), ScriptWrappable(ctx) {} -void FrameCallback::fire(double highResTimeStamp) { - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); - if (!JS_IsFunction(m_ctx, m_callback)) +void FrameCallback::Fire(double highResTimeStamp) { + auto* context = static_cast(JS_GetContextOpaque(ctx())); + if (!JS_IsFunction(ctx(), 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); + JS_DupValue(ctx(), callback_); - JSValue arguments[] = {JS_NewFloat64(m_ctx, highResTimeStamp)}; + JSValue arguments[] = {JS_NewFloat64(ctx(), highResTimeStamp)}; - JSValue returnValue = JS_Call(m_ctx, m_callback, JS_UNDEFINED, 1, arguments); + JSValue returnValue = JS_Call(ctx(), callback_, JS_UNDEFINED, 1, arguments); context->drainPendingPromiseJobs(); - JS_FreeValue(m_ctx, m_callback); + JS_FreeValue(ctx(), callback_); if (JS_IsException(returnValue)) { context->handleException(&returnValue); } - JS_FreeValue(m_ctx, returnValue); + JS_FreeValue(ctx(), returnValue); } -void FrameCallback::trace(GCVisitor* visitor) const { - visitor->trace(m_callback); +void FrameCallback::Trace(GCVisitor* visitor) const { + visitor->Trace(callback_); } -void FrameCallback::dispose() const { - JS_FreeValueRT(m_runtime, m_callback); +void FrameCallback::Dispose() const { + JS_FreeValueRT(JS_GetRuntime(ctx()), callback_); } -void FrameRequestCallbackCollection::registerFrameCallback(uint32_t callbackId, FrameCallback* frameCallback) { - m_frameCallbacks[callbackId] = frameCallback; +void FrameRequestCallbackCollection::RegisterFrameCallback(uint32_t callbackId, FrameCallback* frameCallback) { + frameCallbacks_[callbackId] = frameCallback; } -void FrameRequestCallbackCollection::cancelFrameCallback(uint32_t callbackId) { - if (m_frameCallbacks.count(callbackId) == 0) +void FrameRequestCallbackCollection::CancelFrameCallback(uint32_t callbackId) { + if (frameCallbacks_.count(callbackId) == 0) return; - FrameCallback* callback = m_frameCallbacks[callbackId]; + FrameCallback* callback = frameCallbacks_[callbackId]; // Push this timer to abandoned list to mark this timer is deprecated. - m_abandonedCallbacks.emplace_back(callback); + abandonedCallbacks_.emplace_back(callback); - m_frameCallbacks.erase(callbackId); + 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); +void FrameRequestCallbackCollection::Trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { + for (auto& callback : 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); + if (!abandonedCallbacks_.empty()) { + for (auto& callback : abandonedCallbacks_) { + JS_MarkValue(rt, callback->ToQuickJS(), mark_func); } // All abandoned timers should be freed at the sweep stage. - m_abandonedCallbacks.clear(); + abandonedCallbacks_.clear(); } } diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index 9e91a44047..28a7192e09 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -7,38 +7,38 @@ #define KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ #include "core/executing_context.h" +#include "bindings/qjs/script_wrappable.h" namespace kraken { // |FrameCallback| is an interface type which generalizes callbacks which are // invoked when a script-based animation needs to be resampled. -class FrameCallback : public GarbageCollected { +class FrameCallback : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); public: - static JSClassID classId; + FrameCallback(JSContext* ctx, JSValue callback); - FrameCallback(JSValue callback); + void Fire(double highResTimeStamp); - void fire(double highResTimeStamp); + [[nodiscard]] FORCE_INLINE const char* GetHumanReadableName() const override { return "FrameCallback"; } - [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "FrameCallback"; } - - void trace(GCVisitor* visitor) const override; - void dispose() const override; + void Trace(GCVisitor* visitor) const override; + void Dispose() const override; private: - JSValue m_callback{JS_NULL}; - int32_t m_callbackId{-1}; + JSValue callback_{JS_NULL}; + int32_t 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); + 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; + std::unordered_map frameCallbacks_; + std::vector abandonedCallbacks_; }; } // namespace kraken diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 8b75fbb3d5..aa27adab17 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -22,13 +22,13 @@ std::unique_ptr createJSContext(int32_t contextId, const JSExc static JSRuntime* m_runtime{nullptr}; -void ExecutionContextGCTracker::trace(GCVisitor* visitor) const { - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); +ExecutionContextGCTracker::ExecutionContextGCTracker(JSContext* ctx): ScriptWrappable(ctx) {} + +void ExecutionContextGCTracker::Trace(GCVisitor* visitor) const { + auto* context = static_cast(JS_GetContextOpaque(ctx())); context->trace(visitor); } -void ExecutionContextGCTracker::dispose() const {} - -JSClassID ExecutionContextGCTracker::contextGcTrackerClassId{0}; +void ExecutionContextGCTracker::Dispose() const {} ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) : contextId(contextId), _handler(handler), owner(owner), ctxInvalid_(false), uniqueId(context_unique_id++) { @@ -68,13 +68,13 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& 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); + m_gcTracker = makeGarbageCollected(ctx()); + JS_DefinePropertyValueStr(m_ctx, globalObject, "_gc_tracker_", m_gcTracker->ToQuickJS(), JS_PROP_NORMAL); runningContexts++; // Register all built-in native bindings. - installBindings(m_ctx); + InstallBindings(this); #if ENABLE_PROFILE nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); @@ -121,7 +121,7 @@ ExecutingContext::~ExecutingContext() { if (JS_IsObject(exception) || JS_IsException(exception)) { // There must be bugs in native functions from call stack frame. Someone needs to fix it if throws. reportError(exception); - assert_m(false, "Unhandled exception found when dispose JSContext."); + assert_m(false, "Unhandled exception found when Dispose JSContext."); } JS_FreeValue(m_ctx, globalObject); @@ -199,7 +199,7 @@ bool ExecutingContext::handleException(JSValue* exc) { } bool ExecutingContext::handleException(ScriptValue* exc) { - JSValue value = exc->toQuickJS(); + JSValue value = exc->ToQuickJS(); handleException(&value); } @@ -375,53 +375,6 @@ void ExecutingContext::promiseRejectTracker(JSContext* ctx, JSValue promise, JSV } } -void installFunctionProperty(ExecutingContext* context, JSValue 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); -} - -void installPropertyGetterSetter(ExecutingContext* context, JSValue thisObject, const char* 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); - 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); -} - -void installPropertyGetter(ExecutingContext* context, JSValue thisObject, const char* 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); - 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); -} - DOMTimerCoordinator* ExecutingContext::timers() { return &m_timers; } diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 3366019615..44c30cb6ab 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -17,7 +17,7 @@ #include #include #include "bindings/qjs/binding_initializer.h" -#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/rejected_promises.h" #include "bindings/qjs/script_value.h" #include "foundation/macros.h" @@ -61,12 +61,13 @@ struct PromiseContext { bool isContextValid(int32_t contextId); -class ExecutionContextGCTracker : public GarbageCollected { +class ExecutionContextGCTracker : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); public: - static JSClassID contextGcTrackerClassId; + explicit ExecutionContextGCTracker(JSContext* ctx); - void trace(GCVisitor* visitor) const override; - void dispose() const override; + void Trace(GCVisitor* visitor) const override; + void Dispose() const override; private: }; @@ -170,11 +171,6 @@ class ObjectProperty { JSValue m_value{JS_NULL}; }; -// Property define helpers -void installFunctionProperty(ExecutingContext* context, JSValueConst thisObject, const char* functionName, JSCFunction function, int argc); -void installPropertyGetterSetter(ExecutingContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction, JSCFunction setterFunction); -void installPropertyGetter(ExecutingContext* context, JSValueConst thisObject, const char* property, JSCFunction getterFunction); - std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01); diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 9c30732786..f49f49d120 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -17,70 +17,59 @@ Blob* Blob::create(JSContext* ctx, std::vector&& data) { Blob* Blob::create(JSContext* ctx, std::vector&& data, std::string& mime) { return makeGarbageCollected(ctx, std::forward>(data), mime); } - -void BlobBuilder::append(ExecutingContext& context, Blob* blob) { - std::vector blobData = blob->_data; - _data.reserve(_data.size() + blobData.size()); - _data.insert(_data.end(), blobData.begin(), blobData.end()); -} - -void BlobBuilder::append(ExecutingContext& context, ScriptValue& value) { - if (value.isString()) { - std::string str = value.toCString(); - std::vector strArr(str.begin(), str.end()); - _data.reserve(_data.size() + strArr.size()); - _data.insert(_data.end(), strArr.begin(), strArr.end()); - } else if (value.isArray()) { - 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::constructor(&context))) { - auto blob = static_cast(JS_GetOpaque(value, Blob::classID)); - if (blob == nullptr) - return; - if (std::string(blob->getHumanReadableName()) == "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); -} +// +//void BlobBuilder::append(ExecutingContext& context, Blob* blob) { +// std::vector blobData = blob->_data; +// _data.reserve(_data.size() + blobData.size()); +// _data.insert(_data.end(), blobData.begin(), blobData.end()); +//} +// +//void BlobBuilder::append(ExecutingContext& context, const std::string& value) { +// std::vector strArr(value.begin(), value.end()); +// _data.reserve(_data.size() + strArr.size()); +// _data.insert(_data.end(), strArr.begin(), strArr.end()); +//} +// +//void BlobBuilder::append(ExecutingContext& context, ScriptValue value) { +// if (value.isString()) { +// +// } else if (value.isArray()) { +// std::vector array = createArrayFromQuickJSArraySlow(&context, value); +// for (auto &i : array) { +// append(context, i); +// } +// } else if (value.isObject()) { +// context.contextData()->constructorForType(Blob::getStaticWrapperTypeInfo()); +// if (value.isInstanceOf(Blob::getStaticWrapperTypeInfo())) { +// auto blob = static_cast(toScriptWrappable(value.toQuickJS())); +// if (blob == nullptr) +// return; +// if (std::string(blob->getHumanReadableName()) == "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; +// if (!value.isArrayBuffer(&buffer, &length)) { +// size_t byte_offset; +// size_t byte_length; +// size_t byte_per_element; +// ExceptionState exceptionState; +// value.getTypedArrayBuffer(&buffer, &length, &byte_offset, &byte_length, &byte_per_element, &exceptionState); +// } +// +// for (size_t i = 0; i < length; i++) { +// _data.emplace_back(buffer[i]); +// } +// } +// } +//} +// +//std::vector BlobBuilder::finalize() { +// return std::move(_data); +//} int32_t Blob::size() { return _data.size(); @@ -90,10 +79,7 @@ uint8_t* Blob::bytes() { return _data.data(); } -void Blob::trace(GCVisitor* visitor) const {} -void Blob::dispose() const {} - -Blob::Blob(JSContext* ctx): ScriptWrappable(ctx) {} -Blob::Blob(JSContext* pContext, std::vector vector) {} +void Blob::Trace(GCVisitor* visitor) const {} +void Blob::Dispose() const {} } // namespace kraken diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index b0a07fcd3e..5698882a6b 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -24,7 +24,7 @@ class Blob : public ScriptWrappable { static Blob* create(JSContext* ctx, std::vector&& data, std::string& mime); Blob() = delete; - explicit Blob(JSContext* ctx); + explicit Blob(JSContext* ctx): ScriptWrappable(ctx) {}; explicit Blob(JSContext* ctx, std::vector&& data) : _size(data.size()), _data(std::move(data)), ScriptWrappable(ctx) {}; explicit Blob(JSContext* ctx, std::vector&& data, std::string& mime) : mimeType(mime), _size(data.size()), _data(std::move(data)), ScriptWrappable(ctx){}; @@ -33,8 +33,8 @@ class Blob : public ScriptWrappable { /// get bytes data's length int32_t size(); - void trace(GCVisitor* visitor) const override; - void dispose() const override; + void Trace(GCVisitor* visitor) const override; + void Dispose() const override; private: size_t _size; @@ -44,18 +44,6 @@ class Blob : public ScriptWrappable { friend QJSBlob; }; -class BlobBuilder { - public: - void append(ExecutingContext& context, ScriptValue& value); - void append(ExecutingContext& context, Blob* blob); - - std::vector finalize(); - - private: - friend Blob; - std::vector _data; -}; - } // namespace kraken #endif // KRAKENBRIDGE_BLOB_H diff --git a/bridge/core/fileapi/dom_array_buffer_view.cc b/bridge/core/fileapi/dom_array_buffer_view.cc new file mode 100644 index 0000000000..ea40b43a3c --- /dev/null +++ b/bridge/core/fileapi/dom_array_buffer_view.cc @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "dom_array_buffer_view.h" diff --git a/bridge/core/fileapi/dom_array_buffer_view.h b/bridge/core/fileapi/dom_array_buffer_view.h new file mode 100644 index 0000000000..b29b1af25c --- /dev/null +++ b/bridge/core/fileapi/dom_array_buffer_view.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_DOM_ARRAY_BUFFER_VIEW_H +#define KRAKENBRIDGE_DOM_ARRAY_BUFFER_VIEW_H + +#include + +namespace kraken { + +class DOMArrayBufferView { + public: + enum ViewType { + kTypeInt8, + kTypeUint8, + kTypeUint8Clamped, + kTypeInt16, + kTypeUint16, + kTypeInt32, + kTypeUint32, + kTypeFloat32, + kTypeFloat64, + kTypeBigInt64, + kTypeBigUint64, + kTypeDataView + }; + + private: + uint8_t* buffer_; + size_t length_; + ViewType view_type_; +}; + +} + +#endif // KRAKENBRIDGE_DOM_ARRAY_BUFFER_VIEW_H diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 42dfea7845..2b11ea52bd 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -15,8 +15,8 @@ void Console::__kraken_print__(ExecutingContext* context, ScriptValue& logValue, std::string buffer = logValue.toCString(); stream << buffer; - std::string logLevel = levelValue.isEmpty() ? "info" : levelValue.toCString(); - printLog(context->getContextId(), stream, logLevel, nullptr); +// std::string logLevel = levelValue.isEmpty() ? "info" : levelValue.toCString(); +// printLog(context->getContextId(), stream, logLevel, nullptr); } } // namespace kraken diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index 01b9bd490f..204e6edbf4 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -14,36 +14,34 @@ namespace kraken { -DOMTimer::DOMTimer(QJSFunction* callback) : m_callback(callback) {} - -JSClassID DOMTimer::classId{0}; +DOMTimer::DOMTimer(JSContext* ctx, QJSFunction* callback) : callback_(callback), ScriptWrappable(ctx) {} void DOMTimer::fire() { - auto* context = static_cast(JS_GetContextOpaque(m_ctx)); - if (!m_callback->isFunction(m_ctx)) + auto* context = static_cast(JS_GetContextOpaque(ctx())); + if (!callback_->IsFunction(ctx())) return; - ScriptValue returnValue = m_callback->invoke(m_ctx, 0, nullptr); + ScriptValue returnValue = callback_->Invoke(ctx(), 0, nullptr); if (returnValue.isException()) { context->handleException(&returnValue); } } -void DOMTimer::trace(GCVisitor* visitor) const { - m_callback->trace(visitor); +void DOMTimer::Trace(GCVisitor* visitor) const { + callback_->Trace(visitor); } -void DOMTimer::dispose() const { - m_callback->dispose(); +void DOMTimer::Dispose() const { + callback_->Dispose(); } int32_t DOMTimer::timerId() { - return m_timerId; + return timerId_; } void DOMTimer::setTimerId(int32_t timerId) { - m_timerId = timerId; + timerId_ = timerId; } } // namespace kraken diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index 595721a27d..c4ef82e179 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -6,16 +6,16 @@ #ifndef KRAKENBRIDGE_DOM_TIMER_H #define KRAKENBRIDGE_DOM_TIMER_H -#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/qjs_function.h" #include "dom_timer_coordinator.h" namespace kraken { -class DOMTimer : public GarbageCollected { +class DOMTimer : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); public: - static JSClassID classId; - DOMTimer(QJSFunction* callback); + DOMTimer(JSContext* ctx, QJSFunction* callback); // Trigger timer callback. void fire(); @@ -23,15 +23,15 @@ class DOMTimer : public GarbageCollected { int32_t timerId(); void setTimerId(int32_t timerId); - [[nodiscard]] FORCE_INLINE const char* getHumanReadableName() const override { return "DOMTimer"; } + [[nodiscard]] FORCE_INLINE const char* GetHumanReadableName() const override { return "DOMTimer"; } - void trace(GCVisitor* visitor) const override; - void dispose() const override; + void Trace(GCVisitor* visitor) const override; + void Dispose() const override; private: - int32_t m_timerId{-1}; - int32_t m_isInterval{false}; - QJSFunction* m_callback; + int32_t timerId_{-1}; + int32_t isInterval_{false}; + QJSFunction* callback_; }; } // namespace kraken diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index b1dfd85289..f8cb3b9243 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -66,13 +66,13 @@ DOMTimer* DOMTimerCoordinator::getTimerById(int32_t timerId) { void DOMTimerCoordinator::trace(GCVisitor* visitor) { for (auto& timer : m_activeTimers) { - visitor->trace(timer.second->toQuickJS()); + visitor->Trace(timer.second->ToQuickJS()); } // Recycle all abandoned timers. if (!m_abandonedTimers.empty()) { for (auto& timer : m_abandonedTimers) { - visitor->trace(timer->toQuickJS()); + visitor->Trace(timer->ToQuickJS()); } // All abandoned timers should be freed at the sweep stage. m_abandonedTimers.clear(); diff --git a/bridge/core/frame/module_callback.cc b/bridge/core/frame/module_callback.cc index 63024a9c38..9638a535e8 100644 --- a/bridge/core/frame/module_callback.cc +++ b/bridge/core/frame/module_callback.cc @@ -7,18 +7,18 @@ namespace kraken { -ModuleCallback::ModuleCallback(QJSFunction* function) : m_function(function) {} +ModuleCallback::ModuleCallback(QJSFunction* function) : function_(function) {} QJSFunction* ModuleCallback::value() { - return m_function; + return function_; } -void ModuleCallback::trace(GCVisitor* visitor) const { - m_function->trace(visitor); +void ModuleCallback::Trace(GCVisitor* visitor) const { + function_->Trace(visitor); } -void ModuleCallback::dispose() const { - m_function->dispose(); +void ModuleCallback::Dispose() const { + function_->Dispose(); } } // namespace kraken diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h index a19f765a71..068620b880 100644 --- a/bridge/core/frame/module_callback.h +++ b/bridge/core/frame/module_callback.h @@ -29,13 +29,13 @@ class ModuleCallback : public GarbageCollected { QJSFunction* value(); - void trace(GCVisitor* visitor) const override; - void dispose() const override; + void Trace(GCVisitor* visitor) const override; + void Dispose() const override; ModuleCallbackLinker linker{this}; private: - QJSFunction* m_function{nullptr}; + QJSFunction* function_{nullptr}; }; } // namespace kraken diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc index c5d36766c4..ca9a09ccfd 100644 --- a/bridge/core/frame/module_callback_coordinator.cc +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -7,24 +7,24 @@ namespace kraken { -void ModuleCallbackCoordinator::addModuleCallbacks(ModuleCallback* callback) { - list_add_tail(&m_listeners, &callback->linker.link); +void ModuleCallbackCoordinator::AddModuleCallbacks(ModuleCallback* callback) { + list_add_tail(&listeners_, &callback->linker.link); } -void ModuleCallbackCoordinator::removeModuleCallbacks(ModuleCallback* callback) { +void ModuleCallbackCoordinator::RemoveModuleCallbacks(ModuleCallback* callback) { list_del(&callback->linker.link); } ModuleCallbackCoordinator::ModuleCallbackCoordinator() { - init_list_head(&m_listeners); + init_list_head(&listeners_); } -void ModuleCallbackCoordinator::trace(GCVisitor* visitor) { +void ModuleCallbackCoordinator::Trace(GCVisitor* visitor) { { struct list_head *el, *el1; - list_for_each_safe(el, el1, &m_listeners) { + list_for_each_safe(el, el1, &listeners_) { auto* linker = list_entry(el, ModuleCallbackLinker, link); - visitor->trace(linker->ptr->toQuickJS()); + linker->ptr->Trace(visitor); } } } diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_callback_coordinator.h index 0a13dc17ee..c6d5814ad3 100644 --- a/bridge/core/frame/module_callback_coordinator.h +++ b/bridge/core/frame/module_callback_coordinator.h @@ -14,17 +14,21 @@ namespace kraken { +class ModuleListener; + class ModuleCallbackCoordinator final { public: ModuleCallbackCoordinator(); - void addModuleCallbacks(ModuleCallback* callback); - void removeModuleCallbacks(ModuleCallback* callback); + void AddModuleCallbacks(ModuleCallback* callback); + void RemoveModuleCallbacks(ModuleCallback* callback); - void trace(GCVisitor* visitor); + void Trace(GCVisitor* visitor); private: - list_head m_listeners; + list_head listeners_; + + friend ModuleListener; }; } // namespace kraken diff --git a/bridge/core/frame/module_listener.cc b/bridge/core/frame/module_listener.cc index 98ceeb8db1..bfa4a1a565 100644 --- a/bridge/core/frame/module_listener.cc +++ b/bridge/core/frame/module_listener.cc @@ -9,12 +9,12 @@ namespace kraken { ModuleListener::ModuleListener(QJSFunction* function) : m_function(function) {} -void ModuleListener::trace(GCVisitor* visitor) const { - m_function->trace(visitor); +void ModuleListener::Trace(GCVisitor* visitor) const { + m_function->Trace(visitor); } -void ModuleListener::dispose() const { - m_function->dispose(); +void ModuleListener::Dispose() const { + m_function->Dispose(); } } // namespace kraken diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h index b28d60df67..7f6aed5616 100644 --- a/bridge/core/frame/module_listener.h +++ b/bridge/core/frame/module_listener.h @@ -11,6 +11,9 @@ namespace kraken { +class ModuleCallbackCoordinator; +class ModuleListenerContainer; + // ModuleListener is an persistent callback function. Registered from user with `kraken.addModuleListener` method. // When module event triggered at dart side, All module listener will be invoked and let user to dispatch further operations. class ModuleListener : public GarbageCollected { @@ -18,10 +21,13 @@ class ModuleListener : public GarbageCollected { explicit ModuleListener(QJSFunction* function); private: - void trace(GCVisitor* visitor) const override; - void dispose() const override; + void Trace(GCVisitor* visitor) const override; + void Dispose() const override; QJSFunction* m_function{nullptr}; + + friend ModuleListenerContainer; + friend ModuleCallbackCoordinator; }; } // namespace kraken diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc index 8e7e7c750b..7c135ff55c 100644 --- a/bridge/core/frame/module_listener_container.cc +++ b/bridge/core/frame/module_listener_container.cc @@ -13,7 +13,7 @@ void ModuleListenerContainer::addModuleListener(ModuleListener* listener) { void ModuleListenerContainer::trace(GCVisitor* visitor) { for (auto& listener : m_listeners) { - visitor->trace(listener->toQuickJS()); + listener->m_function->Trace(visitor); } } diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h index fe946e7d0d..1b1d11798b 100644 --- a/bridge/core/frame/module_listener_container.h +++ b/bridge/core/frame/module_listener_container.h @@ -18,6 +18,7 @@ class ModuleListenerContainer final { private: std::forward_list m_listeners; + friend ModuleListener; }; } // namespace kraken diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 6b3b83e814..8550b5fcd4 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -32,7 +32,7 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha if (errmsg != nullptr) { ScriptValue errorObject = ScriptValue::createErrorObject(ctx, errmsg); ScriptValue arguments[] = {errorObject}; - ScriptValue returnValue = moduleContext->callback->value()->invoke(ctx, 1, arguments); + ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); if (returnValue.isException()) { context->handleException(&returnValue); } @@ -41,14 +41,14 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha std::string utf8Arguments = toUTF8(argumentString); ScriptValue jsonObject = ScriptValue::createJSONObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); ScriptValue arguments[] = {jsonObject}; - ScriptValue returnValue = moduleContext->callback->value()->invoke(ctx, 1, arguments); + ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); if (returnValue.isException()) { context->handleException(&returnValue); } } context->drainPendingPromiseJobs(); - context->moduleCallbacks()->removeModuleCallbacks(moduleContext->callback); + context->moduleCallbacks()->RemoveModuleCallbacks(moduleContext->callback); } void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json) { @@ -59,48 +59,48 @@ ScriptValue ModuleManager::__kraken_invoke_module__(ExecutingContext* context, S std::unique_ptr moduleName = moduleNameValue.toNativeString(); std::unique_ptr method = methodValue.toNativeString(); std::unique_ptr params; - if (!paramsValue.isEmpty()) { - ScriptValue stringifiedValue = paramsValue.toJSONStringify(exception); - if (exception->hasException()) { - return stringifiedValue; - } - - params = stringifiedValue.toNativeString(); - } - - if (context->dartMethodPtr()->invokeModule == nullptr) { - exception->throwException(context->ctx(), ErrorType::InternalError, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); - return ScriptValue(context->ctx()); - } - - auto* moduleCallback = makeGarbageCollected(callback); - context->moduleCallbacks()->addModuleCallbacks(moduleCallback); - - ModuleContext* moduleContext = new ModuleContext{context, moduleCallback}; - - NativeString* result; - if (callback != nullptr) { - result = context->dartMethodPtr()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleTransientCallback); - } else { - result = context->dartMethodPtr()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleUnexpectedCallback); - } - - moduleName->free(); - method->free(); - if (params != nullptr) { - params->free(); - } - - if (result == nullptr) { - return ScriptValue::Empty(context->ctx()); - } - - ScriptValue resultString = ScriptValue::fromNativeString(context->ctx(), result); - - // Manual free returned result string; - result->free(); - - return resultString; +// if (!paramsValue.isEmpty()) { +// ScriptValue stringifiedValue = paramsValue.ToJSONStringify(exception); +// if (exception->HasException()) { +// return stringifiedValue; +// } +// +// params = stringifiedValue.toNativeString(); +// } +// +// if (context->dartMethodPtr()->invokeModule == nullptr) { +// exception->ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); +// return ScriptValue(context->ctx()); +// } +// +// auto* moduleCallback = makeGarbageCollected(callback); +// context->moduleCallbacks()->AddModuleCallbacks(moduleCallback); +// +// ModuleContext* moduleContext = new ModuleContext{context, moduleCallback}; +// +// NativeString* result; +// if (callback != nullptr) { +// result = context->dartMethodPtr()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleTransientCallback); +// } else { +// result = context->dartMethodPtr()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleUnexpectedCallback); +// } +// +// moduleName->free(); +// method->free(); +// if (params != nullptr) { +// params->free(); +// } +// +// if (result == nullptr) { +// return ScriptValue::Empty(context->ctx()); +// } +// +// ScriptValue resultString = ScriptValue::fromNativeString(context->ctx(), result); +// +// // Manual free returned result string; +// result->free(); + +// return resultString; } void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, QJSFunction* handler, ExceptionState* exception) { diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index a19425111d..c61114bb64 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -58,8 +58,7 @@ int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, QJSFunction #endif // Create a timer object to keep track timer callback. - auto* timer = makeGarbageCollected(handler)->initialize(context->ctx(), &DOMTimer::classId); - + auto* timer = makeGarbageCollected(context->ctx(), handler); auto timerId = context->dartMethodPtr()->setTimeout(timer, context->getContextId(), handleTransientCallback, timeout); // Register timerId. @@ -72,12 +71,12 @@ int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, QJSFunction int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, 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."); + 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 = makeGarbageCollected(handler)->initializeQuickJSObject(context->ctx(), &DOMTimer::classId); + auto* timer = makeGarbageCollected(context->ctx(), handler); uint32_t timerId = context->dartMethodPtr()->setInterval(timer, context->getContextId(), handlePersistentCallback, timeout); @@ -90,7 +89,7 @@ int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, QJSFunctio 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."); + exception->ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); return; } diff --git a/bridge/core/page.h b/bridge/core/page.h index b01867ccd0..b1dd343f28 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -21,7 +21,7 @@ class KrakenPage; using JSBridgeDisposeCallback = void (*)(KrakenPage* bridge); using ConsoleMessageHandler = std::function; -/// KrakenPage is class which manage all js objects create by flutter widget. +/// KrakenPage is class which manage all js objects Create by flutter widget. /// Every flutter widgets have a corresponding KrakenPage, and all objects created by JavaScript are stored here, /// and there is no data sharing between objects between different KrakenPages. /// It's safe to allocate many KrakenPages at the same times on one thread, but not safe for multi-threads, only one thread can enter to KrakenPage at the same time. diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h index 47ad3e7c15..e123a54031 100644 --- a/bridge/foundation/macros.h +++ b/bridge/foundation/macros.h @@ -6,6 +6,8 @@ #ifndef KRAKENBRIDGE_MACROS_H #define KRAKENBRIDGE_MACROS_H +#include + #if defined(__GNUC__) || defined(__clang__) #define LIKELY(x) __builtin_expect(!!(x), 1) #define UNLIKELY(x) __builtin_expect(!!(x), 0) @@ -26,6 +28,13 @@ TypeName(TypeName&&) = delete; \ TypeName& operator=(TypeName&&) = delete +#define KRAKEN_STATIC_ONLY(Type) \ + Type() = delete; \ + Type(const Type&) = delete; \ + Type& operator=(const Type&) = delete; \ + void* operator new(size_t) = delete; \ + void* operator new(size_t, void*) = delete + // KRAKEN_DISALLOW_NEW(): Cannot be allocated with new operators but can be a // part of object, a value object in collections or stack allocated. If it has // Members you need a trace method and the containing object needs to call that diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index ca5a484acc..d3f3fde31d 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -222,12 +222,12 @@ static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { switch (value.tag) { case NativeTag::TAG_STRING: { - auto* string = static_cast(value.u.ptr); - if (string == nullptr) - return JS_NULL; - JSValue returnedValue = JS_NewUnicodeString(context->runtime(), context->ctx(), string->string, string->length); - string->free(); - return returnedValue; +// auto* string = static_cast(value.u.ptr); +// if (string == nullptr) +// return JS_NULL; +// JSValue returnedValue = JS_NewUnicodeString(context->runtime(), context->ctx(), string->string, string->length); +// string->free(); +// return returnedValue; } case NativeTag::TAG_INT: { return JS_NewUint32(context->ctx(), value.u.int64); diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index bb4aff710d..8dd3126025 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -33,7 +33,7 @@ let blobs = files.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 Blob(path.join(source, file), dist, filename, implement); -}).filter(blob => blob.filename === 'qjs_console'); +}).filter(blob => blob.filename === 'qjs_module_manager'); for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index 536126ffc7..15b2aff622 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -181,9 +181,9 @@ class ExecutingContext; class QJS${getClassName(blob)} final { public: - static void install(ExecutionContext* context); + static void install(ExecutingContext* context); private: - static void installGlobalFunctions(ExecutionContext* context); + static void installGlobalFunctions(ExecutingContext* context); }; } From dd909879029ede368afe83efd1694c230ea4caf9 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 11 Mar 2022 15:59:22 +0800 Subject: [PATCH 024/498] refactor: fix compile and rename vars. --- bridge/CMakeLists.txt | 2 + bridge/bindings/qjs/binding_initializer.cc | 4 +- bridge/bindings/qjs/converter_impl.h | 1 + bridge/bindings/qjs/member_installer.cc | 4 +- bridge/bindings/qjs/member_installer.h | 4 +- bridge/bindings/qjs/native_string_utils.cc | 7 - bridge/bindings/qjs/qjs_blob.cc | 6 +- bridge/bindings/qjs/qjs_dom_timer.cc | 5 + bridge/bindings/qjs/qjs_dom_timer.h | 10 + bridge/bindings/qjs/qjs_window.cc | 2 +- bridge/bindings/qjs/rejected_promises.cc | 6 +- .../dom/frame_request_callback_collection.cc | 27 +-- .../dom/frame_request_callback_collection.h | 10 +- bridge/core/executing_context.cc | 223 +++++++++--------- bridge/core/executing_context.h | 92 ++++---- bridge/core/executing_context_data.cc | 2 +- bridge/core/executing_context_test.cc | 6 +- bridge/core/frame/dom_timer.cc | 15 +- bridge/core/frame/dom_timer.h | 12 +- bridge/core/frame/dom_timer_coordinator.cc | 20 +- bridge/core/frame/module_manager.cc | 14 +- .../frame/window_or_worker_global_scope.cc | 38 +-- bridge/core/page.cc | 22 +- bridge/foundation/native_value.cc | 22 +- bridge/foundation/ui_command_buffer.cc | 2 +- .../code_generator/src/generate_header.ts | 4 +- .../code_generator/src/genereate_source.ts | 18 +- bridge/test/benchmark/create_element.cc | 4 +- bridge/test/kraken_test_context.cc | 12 +- bridge/test/kraken_test_env.cc | 16 +- bridge/test/run_integration_test.cc | 2 +- 31 files changed, 306 insertions(+), 306 deletions(-) create mode 100644 bridge/bindings/qjs/qjs_dom_timer.cc create mode 100644 bridge/bindings/qjs/qjs_dom_timer.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index a9d83656ef..719f582ec2 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -272,6 +272,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") list(APPEND BRIDGE_SOURCE out/qjs_console.cc out/qjs_console.h + out/qjs_module_manager.cc + out/qjs_module_manager.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 0f3ea40fe3..80a1c18c29 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -14,8 +14,8 @@ namespace kraken { void InstallBindings(ExecutingContext* context) { QJSWindow::installGlobalFunctions(context); - QJSModuleManager::install(context); - QJSConsole::install(context); + QJSModuleManager::Install(context); + QJSConsole::Install(context); } } // namespace kraken diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 0dbb3aa539..8116bef53e 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -109,6 +109,7 @@ struct Converter> : public ConverterBase> { for (uint32_t i = 0; i < length; i++) { auto&& item = Converter::FromValue(ctx, JS_GetPropertyUint32(ctx, value, i)); + v.emplace_back(item); } return v; diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc index fa33bfd8d6..b02054b850 100644 --- a/bridge/bindings/qjs/member_installer.cc +++ b/bridge/bindings/qjs/member_installer.cc @@ -38,7 +38,7 @@ static JSValue handleCallThisOnProxy(JSContext* ctx, JSValueConst this_val, int } -void MemberInstaller::installAttributes(ExecutingContext* context, JSValue root, std::initializer_list config) { +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); @@ -66,7 +66,7 @@ void MemberInstaller::installAttributes(ExecutingContext* context, JSValue root, } } -void MemberInstaller::installFunctions(ExecutingContext* context, JSValue root, std::initializer_list config) { +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); diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h index a78056228c..3c4e26cb10 100644 --- a/bridge/bindings/qjs/member_installer.h +++ b/bridge/bindings/qjs/member_installer.h @@ -40,8 +40,8 @@ class MemberInstaller { int flag{JS_PROP_C_W_E}; // Flags for object properties. }; - static void installAttributes(ExecutingContext* context, JSValue root, std::initializer_list); - static void installFunctions(ExecutingContext* context, JSValue root, std::initializer_list); + static void InstallAttributes(ExecutingContext* context, JSValue root, std::initializer_list config); + static void InstallFunctions(ExecutingContext* context, JSValue root, std::initializer_list config); }; } // namespace kraken diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index bcfef06d40..d8205503fb 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -53,11 +53,4 @@ std::string jsValueToStdString(JSContext* ctx, JSValue& value) { 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; -} - } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_blob.cc b/bridge/bindings/qjs/qjs_blob.cc index 856133aecf..8f17eec955 100644 --- a/bridge/bindings/qjs/qjs_blob.cc +++ b/bridge/bindings/qjs/qjs_blob.cc @@ -239,7 +239,7 @@ void QJSBlob::installConstructor(ExecutingContext* context) { std::initializer_list attributeConfig { {"Blob", nullptr, nullptr, constructor} }; - MemberInstaller::installAttributes(context, context->global(), attributeConfig); + MemberInstaller::InstallAttributes(context, context->Global(), attributeConfig); } void QJSBlob::installPrototypeMethods(ExecutingContext* context) { @@ -251,7 +251,7 @@ void QJSBlob::installPrototypeMethods(ExecutingContext* context) { {"type", typeAttributeGetCallback, typeAttributeSetCallback} }; - MemberInstaller::installAttributes(context, prototype, attributesConfig); + MemberInstaller::InstallAttributes(context, prototype, attributesConfig); } void QJSBlob::installPrototypeProperties(ExecutingContext* context) { @@ -264,7 +264,7 @@ void QJSBlob::installPrototypeProperties(ExecutingContext* context) { {"text", text, 0} }; - MemberInstaller::installFunctions(context, prototype, functionConfig); + MemberInstaller::InstallFunctions(context, prototype, functionConfig); } } diff --git a/bridge/bindings/qjs/qjs_dom_timer.cc b/bridge/bindings/qjs/qjs_dom_timer.cc new file mode 100644 index 0000000000..9c438d91af --- /dev/null +++ b/bridge/bindings/qjs/qjs_dom_timer.cc @@ -0,0 +1,5 @@ +// +// Created by andycall on 2022/3/4. +// + +#include "qjs_dom_timer.h" diff --git a/bridge/bindings/qjs/qjs_dom_timer.h b/bridge/bindings/qjs/qjs_dom_timer.h new file mode 100644 index 0000000000..b434982b65 --- /dev/null +++ b/bridge/bindings/qjs/qjs_dom_timer.h @@ -0,0 +1,10 @@ +// +// Created by andycall on 2022/3/4. +// + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_QJS_DOM_TIMER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_QJS_DOM_TIMER_H_ + +class qjs_dom_timer {}; + +#endif // KRAKENBRIDGE_BINDINGS_QJS_QJS_DOM_TIMER_H_ diff --git a/bridge/bindings/qjs/qjs_window.cc b/bridge/bindings/qjs/qjs_window.cc index 1a4c856ae6..357d68e412 100644 --- a/bridge/bindings/qjs/qjs_window.cc +++ b/bridge/bindings/qjs/qjs_window.cc @@ -131,7 +131,7 @@ void QJSWindow::installGlobalFunctions(ExecutingContext* context) { {"clearTimeout", clearTimeout, 0}, }; - MemberInstaller::installFunctions(context, context->global(), functionConfig); + MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); } } // namespace kraken diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 2b194b0a7c..84f4a8856e 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -54,13 +54,13 @@ void RejectedPromises::process(ExecutingContext* 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); } } diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc index 63d629d560..9e34dee84f 100644 --- a/bridge/core/dom/frame_request_callback_collection.cc +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -7,29 +7,28 @@ namespace kraken { -FrameCallback::FrameCallback(JSContext* ctx, JSValue callback) : callback_(callback), ScriptWrappable(ctx) {} +FrameCallback::FrameCallback(ExecutingContext* context, JSValue callback) : context_(context), callback_(callback) {} void FrameCallback::Fire(double highResTimeStamp) { - auto* context = static_cast(JS_GetContextOpaque(ctx())); - if (!JS_IsFunction(ctx(), callback_)) + if (!JS_IsFunction(context_->ctx(), callback_)) return; /* 'callback' might be destroyed when calling itself (if it frees the handler), so must take extra care */ - JS_DupValue(ctx(), callback_); + JS_DupValue(context_->ctx(), callback_); - JSValue arguments[] = {JS_NewFloat64(ctx(), highResTimeStamp)}; + JSValue arguments[] = {JS_NewFloat64(context_->ctx(), highResTimeStamp)}; - JSValue returnValue = JS_Call(ctx(), callback_, JS_UNDEFINED, 1, arguments); + JSValue returnValue = JS_Call(context_->ctx(), callback_, JS_UNDEFINED, 1, arguments); - context->drainPendingPromiseJobs(); - JS_FreeValue(ctx(), callback_); + context_->DrainPendingPromiseJobs(); + JS_FreeValue(context_->ctx(), callback_); if (JS_IsException(returnValue)) { - context->handleException(&returnValue); + context_->HandleException(&returnValue); } - JS_FreeValue(ctx(), returnValue); + JS_FreeValue(context_->ctx(), returnValue); } void FrameCallback::Trace(GCVisitor* visitor) const { @@ -37,7 +36,7 @@ void FrameCallback::Trace(GCVisitor* visitor) const { } void FrameCallback::Dispose() const { - JS_FreeValueRT(JS_GetRuntime(ctx()), callback_); + JS_FreeValueRT(JS_GetRuntime(context_->ctx()), callback_); } void FrameRequestCallbackCollection::RegisterFrameCallback(uint32_t callbackId, FrameCallback* frameCallback) { @@ -55,15 +54,15 @@ void FrameRequestCallbackCollection::CancelFrameCallback(uint32_t callbackId) { frameCallbacks_.erase(callbackId); } -void FrameRequestCallbackCollection::Trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { +void FrameRequestCallbackCollection::Trace(GCVisitor* visitor) { for (auto& callback : frameCallbacks_) { - JS_MarkValue(rt, callback.second->ToQuickJS(), mark_func); + callback.second->Trace(visitor); } // Recycle all abandoned callbacks. if (!abandonedCallbacks_.empty()) { for (auto& callback : abandonedCallbacks_) { - JS_MarkValue(rt, callback->ToQuickJS(), mark_func); + callback->Trace(visitor); } // All abandoned timers should be freed at the sweep stage. abandonedCallbacks_.clear(); diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index 28a7192e09..6204042c3d 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -13,13 +13,14 @@ namespace kraken { // |FrameCallback| is an interface type which generalizes callbacks which are // invoked when a script-based animation needs to be resampled. -class FrameCallback : public ScriptWrappable { - DEFINE_WRAPPERTYPEINFO(); +class FrameCallback : public GarbageCollected { public: - FrameCallback(JSContext* ctx, JSValue callback); + FrameCallback(ExecutingContext* context, JSValue callback); void Fire(double highResTimeStamp); + ExecutingContext* context() { return context_; }; + [[nodiscard]] FORCE_INLINE const char* GetHumanReadableName() const override { return "FrameCallback"; } void Trace(GCVisitor* visitor) const override; @@ -28,11 +29,12 @@ class FrameCallback : public ScriptWrappable { private: JSValue callback_{JS_NULL}; int32_t callbackId_{-1}; + ExecutingContext* context_{nullptr}; }; class FrameRequestCallbackCollection final { public: - void Trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func); + void Trace(GCVisitor* visitor); void RegisterFrameCallback(uint32_t callbackId, FrameCallback* frameCallback); void CancelFrameCallback(uint32_t callbackId); diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index aa27adab17..e4519ab7e0 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -20,18 +20,19 @@ std::unique_ptr createJSContext(int32_t contextId, const JSExc return std::make_unique(contextId, handler, owner); } -static JSRuntime* m_runtime{nullptr}; +static JSRuntime* runtime_{nullptr}; ExecutionContextGCTracker::ExecutionContextGCTracker(JSContext* ctx): ScriptWrappable(ctx) {} +const WrapperTypeInfo& ExecutionContextGCTracker::wrapper_type_info_{"GCTracker"}; void ExecutionContextGCTracker::Trace(GCVisitor* visitor) const { auto* context = static_cast(JS_GetContextOpaque(ctx())); - context->trace(visitor); + context->Trace(visitor); } void ExecutionContextGCTracker::Dispose() const {} ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) - : contextId(contextId), _handler(handler), owner(owner), ctxInvalid_(false), uniqueId(context_unique_id++) { + : context_id_(contextId), handler_(handler), owner_(owner), ctx_invalid_(false), unique_id_(context_unique_id++) { #if ENABLE_PROFILE auto jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; @@ -51,25 +52,25 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& init_list_head(&promise_job_list); init_list_head(&native_function_job_list); - if (m_runtime == nullptr) { - m_runtime = JS_NewRuntime(); + if (runtime_ == nullptr) { + runtime_ = JS_NewRuntime(); } // Avoid stack overflow when running in multiple threads. - JS_UpdateStackTop(m_runtime); - m_ctx = JS_NewContext(m_runtime); + JS_UpdateStackTop(runtime_); + ctx_ = JS_NewContext(runtime_); - timeOrigin = std::chrono::system_clock::now(); - globalObject = JS_GetGlobalObject(m_ctx); + time_origin_ = std::chrono::system_clock::now(); + global_object_ = JS_GetGlobalObject(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); + ctx_, [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_GetGlobalObject(ctx); }, "get", 0); + JSAtom windowKey = JS_NewAtom(ctx_, "window"); + JS_DefinePropertyGetSet(ctx_, global_object_, windowKey, windowGetter, JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); + JS_FreeAtom(ctx_, windowKey); + JS_SetContextOpaque(ctx_, this); + JS_SetHostPromiseRejectionTracker(runtime_, promiseRejectTracker, nullptr); - m_gcTracker = makeGarbageCollected(ctx()); - JS_DefinePropertyValueStr(m_ctx, globalObject, "_gc_tracker_", m_gcTracker->ToQuickJS(), JS_PROP_NORMAL); + gc_tracker_ = makeGarbageCollected(ctx()); + JS_DefinePropertyValueStr(ctx_, global_object_, "_gc_tracker_", gc_tracker_->ToQuickJS(), JS_PROP_NORMAL); runningContexts++; @@ -84,7 +85,7 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& initKrakenPolyFill(this); for (auto& p : pluginByteCode) { - evaluateByteCode(p.second.bytes, p.second.length); + EvaluateByteCode(p.second.bytes, p.second.length); } #if ENABLE_PROFILE @@ -93,16 +94,16 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& } ExecutingContext::~ExecutingContext() { - valid_contexts[contextId] = false; - ctxInvalid_ = true; + valid_contexts[context_id_] = false; + ctx_invalid_ = true; // 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); + JS_FreeValue(ctx_, promiseContext->resolveFunc); + JS_FreeValue(ctx_, promiseContext->rejectFunc); delete promiseContext; } } @@ -117,117 +118,117 @@ ExecutingContext::~ExecutingContext() { } // Check if current context have unhandled exceptions. - JSValue exception = JS_GetException(m_ctx); + JSValue exception = JS_GetException(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); + ReportError(exception); assert_m(false, "Unhandled exception found when Dispose JSContext."); } - JS_FreeValue(m_ctx, globalObject); - JS_FreeContext(m_ctx); + JS_FreeValue(ctx_, global_object_); + JS_FreeContext(ctx_); // Run GC to clean up remaining objects about m_ctx; - JS_RunGC(m_runtime); + JS_RunGC(runtime_); #if DUMP_LEAKS if (--runningContexts == 0) { - JS_FreeRuntime(m_runtime); - m_runtime = nullptr; + JS_FreeRuntime(runtime_); + runtime_ = nullptr; } #endif - m_ctx = nullptr; + ctx_ = nullptr; } -bool ExecutingContext::evaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { +bool ExecutingContext::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); + JSValue result = JS_Eval(ctx_, utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); + DrainPendingPromiseJobs(); + bool success = HandleException(&result); + JS_FreeValue(ctx_, result); return success; } -bool ExecutingContext::evaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine) { +bool ExecutingContext::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); + JSValue result = JS_Eval(ctx_, utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); + DrainPendingPromiseJobs(); + bool success = HandleException(&result); + JS_FreeValue(ctx_, result); return success; } -bool ExecutingContext::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); +bool ExecutingContext::EvaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine) { + JSValue result = JS_Eval(ctx_, code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL); + DrainPendingPromiseJobs(); + bool success = HandleException(&result); + JS_FreeValue(ctx_, result); return success; } -bool ExecutingContext::evaluateByteCode(uint8_t* bytes, size_t byteLength) { +bool ExecutingContext::EvaluateByteCode(uint8_t* bytes, size_t byteLength) { JSValue obj, val; - obj = JS_ReadObject(m_ctx, bytes, byteLength, JS_READ_OBJ_BYTECODE); - if (!handleException(&obj)) + obj = JS_ReadObject(ctx_, bytes, byteLength, JS_READ_OBJ_BYTECODE); + if (!HandleException(&obj)) return false; - val = JS_EvalFunction(m_ctx, obj); - if (!handleException(&val)) + val = JS_EvalFunction(ctx_, obj); + if (!HandleException(&val)) return false; - JS_FreeValue(m_ctx, val); + JS_FreeValue(ctx_, val); return true; } -bool ExecutingContext::isValid() const { - return !ctxInvalid_; +bool ExecutingContext::IsValid() const { + return !ctx_invalid_; } -void* ExecutingContext::getOwner() { - assert(!ctxInvalid_ && "context has been released"); - return owner; +void* ExecutingContext::owner() { + assert(!ctx_invalid_ && "context has been released"); + return owner_; } -bool ExecutingContext::handleException(JSValue* exc) { +bool ExecutingContext::HandleException(JSValue* exc) { if (JS_IsException(*exc)) { - JSValue error = JS_GetException(m_ctx); - reportError(error); - dispatchGlobalErrorEvent(this, error); - JS_FreeValue(m_ctx, error); + JSValue error = JS_GetException(ctx_); + ReportError(error); + DispatchGlobalErrorEvent(this, error); + JS_FreeValue(ctx_, error); return false; } return true; } -bool ExecutingContext::handleException(ScriptValue* exc) { +bool ExecutingContext::HandleException(ScriptValue* exc) { JSValue value = exc->ToQuickJS(); - handleException(&value); + HandleException(&value); } -JSValue ExecutingContext::global() { - return globalObject; +JSValue ExecutingContext::Global() { + return global_object_; } JSContext* ExecutingContext::ctx() { - assert(!ctxInvalid_ && "context has been released"); - return m_ctx; + assert(!ctx_invalid_ && "context has been released"); + return ctx_; } JSRuntime* ExecutingContext::runtime() { - return m_runtime; + return runtime_; } -void ExecutingContext::reportError(JSValueConst error) { - if (!JS_IsError(m_ctx, error)) +void ExecutingContext::ReportError(JSValueConst error) { + if (!JS_IsError(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); + 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(m_ctx, error, "stack"); + JSValue stackValue = JS_GetPropertyStr(ctx_, error, "stack"); if (!JS_IsUndefined(stackValue)) { - stack = JS_ToCString(m_ctx, stackValue); + stack = JS_ToCString(ctx_, stackValue); } uint32_t messageLength = strlen(type) + strlen(title); @@ -235,23 +236,23 @@ void ExecutingContext::reportError(JSValueConst error) { messageLength += 4 + strlen(stack); char message[messageLength]; sprintf(message, "%s: %s\n%s", type, title, stack); - _handler(this, message); + handler_(this, message); } else { messageLength += 3; char message[messageLength]; sprintf(message, "%s: %s", type, title); - _handler(this, message); + handler_(this, 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); + 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() { +void ExecutingContext::DrainPendingPromiseJobs() { // should executing pending promise jobs. JSContext* pctx; int finished = JS_ExecutePendingJob(runtime(), &pctx); @@ -263,30 +264,30 @@ void ExecutingContext::drainPendingPromiseJobs() { } // Throw error when promise are not handled. - m_rejectedPromise.process(this); + rejected_promises_.process(this); } -void ExecutingContext::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); +void ExecutingContext::DefineGlobalProperty(const char* prop, JSValue value) { + JSAtom atom = JS_NewAtom(ctx_, prop); + JS_SetProperty(ctx_, global_object_, atom, value); + JS_FreeAtom(ctx_, atom); } ExecutionContextData* ExecutingContext::contextData() { - return &m_data; + return &context_data_; } -uint8_t* ExecutingContext::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); +uint8_t* ExecutingContext::DumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength) { + JSValue object = JS_Eval(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); + uint8_t* bytes = JS_WriteObject(ctx_, bytecodeLength, object, JS_WRITE_OBJ_BYTECODE); + JS_FreeValue(ctx_, object); return bytes; } -void ExecutingContext::dispatchGlobalErrorEvent(ExecutingContext* context, JSValueConst error) { +void ExecutingContext::DispatchGlobalErrorEvent(ExecutingContext* context, JSValueConst error) { // JSContext* ctx = context->ctx(); // auto* window = static_cast(JS_GetOpaque(context->global(), Window::classId())); // @@ -348,15 +349,15 @@ static void dispatchPromiseRejectionEvent(const char* eventType, ExecutingContex // } } -void ExecutingContext::dispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error) { +void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error) { // Trigger onerror event. - dispatchGlobalErrorEvent(context, error); + DispatchGlobalErrorEvent(context, error); // Trigger unhandledRejection event. dispatchPromiseRejectionEvent("unhandledrejection", context, promise, error); } -void ExecutingContext::dispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValue promise, JSValue error) { +void ExecutingContext::DispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValue promise, JSValue error) { // Trigger rejectionhandled event. dispatchPromiseRejectionEvent("rejectionhandled", context, promise, error); } @@ -369,27 +370,27 @@ void ExecutingContext::promiseRejectTracker(JSContext* ctx, JSValue promise, JSV // 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); + context->rejected_promises_.trackHandledPromiseRejection(context, promise, reason); } else { - context->m_rejectedPromise.trackUnhandledPromiseRejection(context, promise, reason); + context->rejected_promises_.trackUnhandledPromiseRejection(context, promise, reason); } } -DOMTimerCoordinator* ExecutingContext::timers() { - return &m_timers; +DOMTimerCoordinator* ExecutingContext::Timers() { + return &timers_; } -ModuleListenerContainer* ExecutingContext::moduleListeners() { - return &m_moduleListeners; +ModuleListenerContainer* ExecutingContext::ModuleListeners() { + return &module_listener_container_; } -ModuleCallbackCoordinator* ExecutingContext::moduleCallbacks() { - return &m_moduleCallbacks; +ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { + return &module_callbacks_; } -void ExecutingContext::trace(GCVisitor* visitor) { - m_timers.trace(visitor); - m_moduleListeners.trace(visitor); +void ExecutingContext::Trace(GCVisitor* visitor) { + timers_.trace(visitor); + module_listener_container_.trace(visitor); } void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01) { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 44c30cb6ab..c22891ab68 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -41,15 +41,6 @@ class Document; using JSExceptionHandler = std::function; -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; ExecutingContext* context; @@ -81,55 +72,54 @@ class ExecutingContext { ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); ~ExecutingContext(); - 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(); + 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(); - FORCE_INLINE int32_t getContextId() const { return contextId; }; - void* getOwner(); - bool handleException(JSValue* exc); - bool handleException(ScriptValue* exc); - void reportError(JSValueConst error); - void drainPendingPromiseJobs(); - void defineGlobalProperty(const char* prop, JSValueConst value); + FORCE_INLINE int32_t contextid() const { return context_id_; }; + void* owner(); + bool HandleException(JSValue* exc); + bool HandleException(ScriptValue* exc); + void ReportError(JSValueConst error); + void DrainPendingPromiseJobs(); + void DefineGlobalProperty(const char* prop, JSValueConst value); ExecutionContextData* contextData(); - uint8_t* dumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength); + 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(); + DOMTimerCoordinator* Timers(); // Gets the ModuleListeners which registered by `kraken.addModuleListener API`. - ModuleListenerContainer* moduleListeners(); + ModuleListenerContainer* ModuleListeners(); // Gets the ModuleCallbacks which from the 4th parameter of `kraken.invokeModule` function. - ModuleCallbackCoordinator* moduleCallbacks(); + ModuleCallbackCoordinator* ModuleCallbacks(); - FORCE_INLINE Document* document() { return m_document; }; - FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &m_commandBuffer; }; - FORCE_INLINE std::unique_ptr& dartMethodPtr() { return m_dartMethodPtr; } + FORCE_INLINE Document* document() { return document_; }; + FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; + FORCE_INLINE std::unique_ptr& dartMethodPtr() { return dart_method_ptr_; } - void trace(GCVisitor* visitor); + void Trace(GCVisitor* visitor); - std::chrono::time_point timeOrigin; - std::unordered_map constructorMap; + std::chrono::time_point time_origin_; - int32_t uniqueId; + int32_t unique_id_; 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 void dispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error); - static void dispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error); - static void dispatchGlobalErrorEvent(ExecutingContext* context, JSValueConst error); + static void DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error); + static void DispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error); + static void DispatchGlobalErrorEvent(ExecutingContext* context, JSValueConst error); // Bytecodes which registered by kraken plugins. static std::unordered_map pluginByteCode; @@ -137,21 +127,21 @@ class ExecutingContext { 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}; - Document* m_document{nullptr}; - DOMTimerCoordinator m_timers; - ModuleListenerContainer m_moduleListeners; - ModuleCallbackCoordinator m_moduleCallbacks; - ExecutionContextGCTracker* m_gcTracker{nullptr}; - ExecutionContextData m_data{this}; - UICommandBuffer m_commandBuffer{this}; - std::unique_ptr m_dartMethodPtr = std::make_unique(); - RejectedPromises m_rejectedPromise; + int32_t context_id_; + JSExceptionHandler handler_; + void* owner_; + JSValue global_object_{JS_NULL}; + bool ctx_invalid_{false}; + JSContext* ctx_{nullptr}; + Document* document_{nullptr}; + DOMTimerCoordinator timers_; + ModuleListenerContainer module_listener_container_; + ModuleCallbackCoordinator module_callbacks_; + ExecutionContextGCTracker* gc_tracker_{nullptr}; + ExecutionContextData context_data_{this}; + UICommandBuffer ui_command_buffer_{this}; + std::unique_ptr dart_method_ptr_ = std::make_unique(); + RejectedPromises rejected_promises_; }; class ObjectProperty { diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc index 949e5c5e18..7799b5b745 100644 --- a/bridge/core/executing_context_data.cc +++ b/bridge/core/executing_context_data.cc @@ -45,7 +45,7 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty JSValue prototypeObject = m_prototypeMap[type] = JS_NewObject(m_context->ctx()); // Make constructor function inherit to Function.prototype - JSValue functionConstructor = JS_GetPropertyStr(ctx, m_context->global(), "Function"); + 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); diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index df6febcf84..7727ce30bd 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -12,7 +12,7 @@ using namespace kraken; TEST(Context, isValid) { auto bridge = TEST_init(); - EXPECT_EQ(bridge->getContext()->isValid(), true); + EXPECT_EQ(bridge->getContext()->IsValid(), true); } TEST(Context, evalWithError) { @@ -232,7 +232,7 @@ TEST(Context, accessGetUICommandItemsAfterDisposed) { int32_t contextId; { auto bridge = TEST_init(); - contextId = bridge->getContext()->getContextId(); + contextId = bridge->getContext()->contextid(); } EXPECT_EQ(getUICommandItems(contextId), nullptr); @@ -245,7 +245,7 @@ TEST(Context, disposeContext) { auto bridge = static_cast(getPage(contextId)); static bool disposed = false; bridge->disposeCallback = [](kraken::KrakenPage* bridge) { disposed = true; }; - disposePage(bridge->getContext()->getContextId()); + disposePage(bridge->getContext()->contextid()); EXPECT_EQ(disposed, true); } diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index 204e6edbf4..96d56ef767 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -14,17 +14,16 @@ namespace kraken { -DOMTimer::DOMTimer(JSContext* ctx, QJSFunction* callback) : callback_(callback), ScriptWrappable(ctx) {} +DOMTimer::DOMTimer(ExecutingContext* context, QJSFunction* callback) : context_(context), callback_(callback) {} -void DOMTimer::fire() { - auto* context = static_cast(JS_GetContextOpaque(ctx())); - if (!callback_->IsFunction(ctx())) +void DOMTimer::Fire() { + if (!callback_->IsFunction(context_->ctx())) return; - ScriptValue returnValue = callback_->Invoke(ctx(), 0, nullptr); + ScriptValue returnValue = callback_->Invoke(context_->ctx(), 0, nullptr); if (returnValue.isException()) { - context->handleException(&returnValue); + context_->HandleException(&returnValue); } } @@ -36,10 +35,6 @@ void DOMTimer::Dispose() const { callback_->Dispose(); } -int32_t DOMTimer::timerId() { - return timerId_; -} - void DOMTimer::setTimerId(int32_t timerId) { timerId_ = timerId; } diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index c4ef82e179..fac52a15b3 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -12,23 +12,25 @@ namespace kraken { -class DOMTimer : public ScriptWrappable { - DEFINE_WRAPPERTYPEINFO(); +class DOMTimer : public GarbageCollected { public: - DOMTimer(JSContext* ctx, QJSFunction* callback); + DOMTimer(ExecutingContext* context, QJSFunction* callback); // Trigger timer callback. - void fire(); + void Fire(); - int32_t timerId(); + int32_t timerId() const { return timerId_; }; void setTimerId(int32_t timerId); + ExecutingContext* context() { return context_; } + [[nodiscard]] FORCE_INLINE const char* GetHumanReadableName() const override { return "DOMTimer"; } void Trace(GCVisitor* visitor) const override; void Dispose() const override; private: + ExecutingContext* context_{nullptr}; int32_t timerId_{-1}; int32_t isInterval_{false}; QJSFunction* callback_; diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index f8cb3b9243..fb478e56c8 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -15,31 +15,31 @@ namespace kraken { static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + auto* context = timer->context(); if (errmsg != nullptr) { - JSValue exception = JS_ThrowTypeError(timer->ctx(), "%s", errmsg); - context->handleException(&exception); + JSValue exception = JS_ThrowTypeError(timer->context()->ctx(), "%s", errmsg); + context->HandleException(&exception); return; } // Trigger timer callbacks. - timer->fire(); + timer->Fire(); // Executing pending async jobs. - context->drainPendingPromiseJobs(); + 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())); + auto* context = timer->context(); - if (!context->isValid()) + if (!context->IsValid()) return; handleTimerCallback(timer, errmsg); - context->timers()->removeTimeoutById(timer->timerId()); + context->Timers()->removeTimeoutById(timer->timerId()); } void DOMTimerCoordinator::installNewTimer(ExecutingContext* context, int32_t timerId, DOMTimer* timer) { @@ -66,13 +66,13 @@ DOMTimer* DOMTimerCoordinator::getTimerById(int32_t timerId) { void DOMTimerCoordinator::trace(GCVisitor* visitor) { for (auto& timer : m_activeTimers) { - visitor->Trace(timer.second->ToQuickJS()); + timer.second->Trace(visitor); } // Recycle all abandoned timers. if (!m_abandonedTimers.empty()) { for (auto& timer : m_abandonedTimers) { - visitor->Trace(timer->ToQuickJS()); + timer->Trace(visitor); } // All abandoned timers should be freed at the sweep stage. m_abandonedTimers.clear(); diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 8550b5fcd4..9dfef78f58 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -18,12 +18,12 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha auto* moduleContext = static_cast(ptr); ExecutingContext* context = moduleContext->context; - if (!context->isValid()) + if (!context->IsValid()) return; if (moduleContext->callback == nullptr) { JSValue exception = JS_ThrowTypeError(moduleContext->context->ctx(), "Failed to execute '__kraken_invoke_module__': callback is null."); - context->handleException(&exception); + context->HandleException(&exception); return; } @@ -34,7 +34,7 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha ScriptValue arguments[] = {errorObject}; ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); if (returnValue.isException()) { - context->handleException(&returnValue); + context->HandleException(&returnValue); } } else { std::u16string argumentString = std::u16string(reinterpret_cast(json->string), json->length); @@ -43,12 +43,12 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha ScriptValue arguments[] = {jsonObject}; ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); if (returnValue.isException()) { - context->handleException(&returnValue); + context->HandleException(&returnValue); } } - context->drainPendingPromiseJobs(); - context->moduleCallbacks()->RemoveModuleCallbacks(moduleContext->callback); + context->DrainPendingPromiseJobs(); + context->ModuleCallbacks()->RemoveModuleCallbacks(moduleContext->callback); } void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json) { @@ -105,7 +105,7 @@ ScriptValue ModuleManager::__kraken_invoke_module__(ExecutingContext* context, S void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, QJSFunction* handler, ExceptionState* exception) { auto* listener = makeGarbageCollected(handler); - context->moduleListeners()->addModuleListener(listener); + context->ModuleListeners()->addModuleListener(listener); } } // namespace kraken diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index c61114bb64..039b543252 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -9,41 +9,41 @@ namespace kraken { static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + auto* context = timer->context(); if (errmsg != nullptr) { - JSValue exception = JS_ThrowTypeError(timer->ctx(), "%s", errmsg); - context->handleException(&exception); + JSValue exception = JS_ThrowTypeError(context->ctx(), "%s", errmsg); + context->HandleException(&exception); return; } - if (context->timers()->getTimerById(timer->timerId()) == nullptr) + if (context->Timers()->getTimerById(timer->timerId()) == nullptr) return; // Trigger timer callbacks. - timer->fire(); + timer->Fire(); // Executing pending async jobs. - context->drainPendingPromiseJobs(); + 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())); + auto* context = timer->context(); - if (!context->isValid()) + if (!context->IsValid()) return; handleTimerCallback(timer, errmsg); - context->timers()->removeTimeoutById(timer->timerId()); + 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())); + auto* context = timer->context(); - if (!context->isValid()) + if (!context->IsValid()) return; handleTimerCallback(timer, errmsg); @@ -58,13 +58,13 @@ int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, QJSFunction #endif // Create a timer object to keep track timer callback. - auto* timer = makeGarbageCollected(context->ctx(), handler); - auto timerId = context->dartMethodPtr()->setTimeout(timer, context->getContextId(), handleTransientCallback, timeout); + auto* timer = makeGarbageCollected(context, handler); + auto timerId = context->dartMethodPtr()->setTimeout(timer, context->contextid(), handleTransientCallback, timeout); // Register timerId. timer->setTimerId(timerId); - context->timers()->installNewTimer(context, timerId, timer); + context->Timers()->installNewTimer(context, timerId, timer); return timerId; } @@ -76,13 +76,13 @@ int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, QJSFunctio } // Create a timer object to keep track timer callback. - auto* timer = makeGarbageCollected(context->ctx(), handler); + auto* timer = makeGarbageCollected(context, handler); - uint32_t timerId = context->dartMethodPtr()->setInterval(timer, context->getContextId(), handlePersistentCallback, timeout); + uint32_t timerId = context->dartMethodPtr()->setInterval(timer, context->contextid(), handlePersistentCallback, timeout); // Register timerId. timer->setTimerId(timerId); - context->timers()->installNewTimer(context, timerId, timer); + context->Timers()->installNewTimer(context, timerId, timer); return timerId; } @@ -93,9 +93,9 @@ void WindowOrWorkerGlobalScope::clearTimeout(ExecutingContext* context, int32_t return; } - context->dartMethodPtr()->clearTimeout(context->getContextId(), timerId); + context->dartMethodPtr()->clearTimeout(context->contextid(), timerId); - context->timers()->removeTimeoutById(timerId); + context->Timers()->removeTimeoutById(timerId); } } // namespace kraken diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 6914c045b4..07b8e341ad 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -23,7 +23,7 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c contextId, [](ExecutingContext* context, const char* message) { if (context->dartMethodPtr()->onJsError != nullptr) { - context->dartMethodPtr()->onJsError(context->getContextId(), message); + context->dartMethodPtr()->onJsError(context->contextid(), message); } KRAKEN_LOG(ERROR) << message << std::endl; }, @@ -84,7 +84,7 @@ void KrakenPage::invokeModuleEvent(const NativeString* moduleName, const char* e } void KrakenPage::evaluateScript(const NativeString* script, const char* url, int startLine) { - if (!m_context->isValid()) + if (!m_context->IsValid()) return; #if ENABLE_PROFILE @@ -93,32 +93,32 @@ void KrakenPage::evaluateScript(const NativeString* script, const char* url, int std::u16string patchedCode = std::u16string(u"performance.mark('js_parse_time_end');") + std::u16string(reinterpret_cast(script->string), script->length); m_context->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); #else - m_context->evaluateJavaScript(script->string, script->length, url, startLine); + m_context->EvaluateJavaScript(script->string, script->length, url, startLine); #endif } void KrakenPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { - if (!m_context->isValid()) + if (!m_context->IsValid()) return; - m_context->evaluateJavaScript(script, length, url, startLine); + m_context->EvaluateJavaScript(script, length, url, startLine); } void KrakenPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { - if (!m_context->isValid()) + if (!m_context->IsValid()) return; - m_context->evaluateJavaScript(script, length, url, startLine); + m_context->EvaluateJavaScript(script, length, url, startLine); } uint8_t* KrakenPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { - if (!m_context->isValid()) + if (!m_context->IsValid()) return nullptr; - return m_context->dumpByteCode(script, length, url, byteLength); + return m_context->DumpByteCode(script, length, url, byteLength); } void KrakenPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { - if (!m_context->isValid()) + if (!m_context->IsValid()) return; - m_context->evaluateByteCode(bytes, byteLength); + m_context->EvaluateByteCode(bytes, byteLength); } void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index d3f3fde31d..f499b81966 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -80,9 +80,9 @@ void call_native_function(NativeFunctionContext* functionContext, int32_t 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)) { + JSValue result = JS_Call(context->ctx(), functionContext->m_callback, context->Global(), argc, arguments); + context->DrainPendingPromiseJobs(); + if (context->HandleException(&result)) { *returnValue = jsValueToNativeValue(context->ctx(), result); } @@ -161,26 +161,26 @@ static JSValue anonymousFunction(JSContext* ctx, JSValueConst this_val, int argc void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int32_t contextId, const char* errmsg) { auto* promiseContext = static_cast(callbackContext); - if (!promiseContext->context->isValid()) + if (!promiseContext->context->IsValid()) return; - if (promiseContext->context->getContextId() != contextId) + if (promiseContext->context->contextid() != contextId) return; auto* context = promiseContext->context; if (nativeValue != nullptr) { JSValue value = nativeValueToJSValue(promiseContext->context, *nativeValue); - JSValue returnValue = JS_Call(context->ctx(), promiseContext->resolveFunc, context->global(), 1, &value); - context->drainPendingPromiseJobs(); - context->handleException(&returnValue); + 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); + 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); } diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 7cda39332c..d821a661eb 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -13,7 +13,7 @@ UICommandBuffer::UICommandBuffer(ExecutingContext* context) : m_context(context) void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate) { if (batchedUpdate) { - m_context->dartMethodPtr()->requestBatchUpdate(m_context->getContextId()); + m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextid()); update_batched = true; } diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index 15b2aff622..f08ff6f05b 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -181,9 +181,9 @@ class ExecutingContext; class QJS${getClassName(blob)} final { public: - static void install(ExecutingContext* context); + static void Install(ExecutingContext* context); private: - static void installGlobalFunctions(ExecutingContext* context); + static void InstallGlobalFunctions(ExecutingContext* context); }; } diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index 6d946ef6da..c00113be58 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -465,7 +465,7 @@ function generateFunctionValueInit(object: FunctionObject) { function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { if (argument.type === FunctionArgumentType.function) { - return `QJSFunction::create(ctx, argv[${argsIndex}])`; + return `QJSFunction::Create(ctx, argv[${argsIndex}])`; } return `ScriptValue(ctx, argv[${argsIndex}]);`; } @@ -474,7 +474,7 @@ function generateFunctionValueInit(object: FunctionObject) { if (argument.type === FunctionArgumentType.function) { return `nullptr; if (argc > ${argsIndex} && JS_IsFunction(ctx, argv[${argsIndex}])) { - ${argument.name} = QJSFunction::create(ctx, argv[${argsIndex}]); + ${argument.name} = QJSFunction::Create(ctx, argv[${argsIndex}]); }`; } else { return `ScriptValue(ctx, JS_NULL); @@ -506,10 +506,10 @@ ExceptionState exception; ${returnValue}${coreClassName}::${object.declare.name}(context, ${params.join(', ')}, &exception); -if (exception.hasException()) { - return exception.toQuickJS(); +if (exception.HasException()) { + return exception.ToQuickJS(); } -${returnValue && 'return returnValue.toQuickJS();'}`, 2); +${returnValue ? 'return returnValue.ToQuickJS();' : 'return JS_NULL; '}`, 2); } function generateFunctionSource(blob: Blob, object: FunctionObject) { @@ -549,16 +549,16 @@ namespace kraken { ${sources.join('\n')} -void QJS${getClassName(blob)}::install(ExecutingContext* context) { - installGlobalFunctions(context); +void QJS${getClassName(blob)}::Install(ExecutingContext* context) { + InstallGlobalFunctions(context); } -void QJS${getClassName(blob)}::installGlobalFunctions(ExecutingContext* context) { +void QJS${getClassName(blob)}::InstallGlobalFunctions(ExecutingContext* context) { std::initializer_list functionConfig { ${installList.join('\n')} }; - MemberInstaller::installFunctions(context, context->global(), functionConfig); + MemberInstaller::InstallFunctions(context, context->global(), functionConfig); } }`; } diff --git a/bridge/test/benchmark/create_element.cc b/bridge/test/benchmark/create_element.cc index ad7b150014..87e87719f7 100644 --- a/bridge/test/benchmark/create_element.cc +++ b/bridge/test/benchmark/create_element.cc @@ -14,7 +14,7 @@ static void CreateRawJavaScriptObjects(benchmark::State& state) { std::string code = "var a = {}"; // Perform setup here for (auto _ : state) { - context->evaluateJavaScript(code.c_str(), code.size(), "internal://", 0); + context->EvaluateJavaScript(code.c_str(), code.size(), "internal://", 0); } } @@ -23,7 +23,7 @@ static void CreateDivElement(benchmark::State& state) { std::string code = "var a = document.createElement('div');"; // Perform setup here for (auto _ : state) { - context->evaluateJavaScript(code.c_str(), code.size(), "internal://", 0); + context->EvaluateJavaScript(code.c_str(), code.size(), "internal://", 0); } } diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index 1b1700965f..8ef4d602e5 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -24,9 +24,9 @@ KrakenTestContext::KrakenTestContext(ExecutingContext* context) : m_context(cont } bool KrakenTestContext::evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { - if (!m_context->isValid()) + if (!m_context->IsValid()) return false; - return m_context->evaluateJavaScript(code, codeLength, sourceURL, startLine); + return m_context->EvaluateJavaScript(code, codeLength, sourceURL, startLine); } bool KrakenTestContext::parseTestHTML(const uint16_t* code, size_t codeLength) { @@ -151,7 +151,7 @@ static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, for (int i = 0; i < length; i++) { auto mouse = new MousePointer(); JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); - mouse->contextId = context->getContextId(); + 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); @@ -225,11 +225,11 @@ static JSValue parseHTML(JSContext* ctx, JSValueConst this_val, int argc, JSValu static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* context = static_cast(JS_GetContextOpaque(ctx)); - JSValue globalErrorFunc = JS_GetPropertyStr(ctx, context->global(), "triggerGlobalError"); + 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); + JSValue exception = JS_Call(ctx, globalErrorFunc, context->Global(), 0, nullptr); + context->HandleException(&exception); JS_FreeValue(ctx, globalErrorFunc); } diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index e7a9ae6572..6abe3d8189 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -77,8 +77,8 @@ void TEST_reloadApp(int32_t contextId) {} int32_t timerId = 0; int32_t TEST_setTimeout(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { - JSRuntime* rt = JS_GetRuntime(timer->ctx()); - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + JSRuntime* rt = timer->context()->runtime(); + auto* context = timer->context(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); th->timeout = get_time_ms() + timeout; @@ -94,8 +94,8 @@ int32_t TEST_setTimeout(kraken::DOMTimer* timer, int32_t contextId, AsyncCallbac } int32_t TEST_setInterval(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { - JSRuntime* rt = JS_GetRuntime(timer->ctx()); - auto* context = static_cast(JS_GetContextOpaque(timer->ctx())); + JSRuntime* rt = timer->context()->runtime(); + auto* context = timer->context(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); th->timeout = get_time_ms() + timeout; @@ -113,13 +113,13 @@ int32_t TEST_setInterval(kraken::DOMTimer* timer, int32_t contextId, AsyncCallba int32_t callbackId = 0; uint32_t TEST_requestAnimationFrame(kraken::FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { - JSRuntime* rt = JS_GetRuntime(frameCallback->ctx()); - auto* context = static_cast(JS_GetContextOpaque(frameCallback->ctx())); + JSRuntime* rt = frameCallback->context()->runtime(); + auto* context = frameCallback->context(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); JSFrameCallback* th = static_cast(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; @@ -250,7 +250,7 @@ static bool jsPool(kraken::ExecutingContext* context) { void TEST_runLoop(kraken::ExecutingContext* context) { for (;;) { - context->drainPendingPromiseJobs(); + context->DrainPendingPromiseJobs(); if (jsPool(context)) break; } diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index 5ae3929e14..e9b2b3e5a2 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -37,7 +37,7 @@ TEST(IntegrationTest, runSpecs) { std::string code = readTestSpec(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - executeTest(context->getContextId(), [](int32_t contextId, NativeString* status) -> void* { + executeTest(context->contextid(), [](int32_t contextId, NativeString* status) -> void* { KRAKEN_LOG(VERBOSE) << "done"; return nullptr; }); From 43a68c72d8a57878db6e115a425390718c70e3f5 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Tue, 15 Mar 2022 21:34:08 +0800 Subject: [PATCH 025/498] feat: add more type convertions and refactor base class. --- bridge/CMakeLists.txt | 2 - bridge/bindings/qjs/converter.h | 4 - bridge/bindings/qjs/converter_impl.h | 105 +++- bridge/bindings/qjs/garbage_collected.h | 18 +- bridge/bindings/qjs/qjs_blob.cc | 57 ++- bridge/bindings/qjs/qjs_blob.h | 8 +- bridge/bindings/qjs/qjs_function.cc | 4 - bridge/bindings/qjs/qjs_function.h | 9 +- ...arraybuffer_arraybufferview_blob_string.cc | 19 +- ..._arraybuffer_arraybufferview_blob_string.h | 28 +- bridge/bindings/qjs/qjs_window.cc | 4 +- bridge/bindings/qjs/script_value.cc | 6 +- bridge/bindings/qjs/script_value.h | 3 +- bridge/bindings/qjs/script_wrappable.cc | 2 +- bridge/bindings/qjs/script_wrappable.h | 4 +- bridge/bindings/qjs/ts_type.h | 12 +- .../dom/frame_request_callback_collection.h | 8 +- bridge/core/executing_context.cc | 1 + bridge/core/executing_context.h | 1 + bridge/core/executing_context_data.cc | 26 +- bridge/core/executing_context_data.h | 4 +- bridge/core/fileapi/blob.cc | 12 + bridge/core/fileapi/blob.h | 4 + bridge/core/fileapi/dom_array_buffer_view.cc | 6 - bridge/core/fileapi/dom_array_buffer_view.h | 38 -- bridge/core/frame/dom_timer.cc | 8 +- bridge/core/frame/dom_timer.h | 13 +- bridge/core/frame/dom_timer_coordinator.cc | 6 +- bridge/core/frame/dom_timer_coordinator.h | 8 +- bridge/core/frame/module_callback.cc | 8 +- bridge/core/frame/module_callback.h | 13 +- .../core/frame/module_callback_coordinator.cc | 17 +- .../core/frame/module_callback_coordinator.h | 7 +- bridge/core/frame/module_listener.cc | 10 +- bridge/core/frame/module_listener.h | 11 +- .../core/frame/module_listener_container.cc | 4 +- bridge/core/frame/module_listener_container.h | 4 +- bridge/core/frame/module_manager.cc | 50 +- bridge/core/frame/module_manager.d.ts | 2 +- bridge/core/frame/module_manager.h | 9 +- .../frame/window_or_worker_global_scope.cc | 12 +- .../frame/window_or_worker_global_scope.h | 4 +- bridge/core/page.h | 2 +- bridge/kraken_bridge.cc | 2 +- bridge/polyfill/scripts/js_to_c.js | 6 +- bridge/scripts/code_generator/src/analyzer.ts | 20 +- .../scripts/code_generator/src/declaration.ts | 9 +- .../code_generator/src/genereate_source.ts | 483 ++---------------- bridge/scripts/code_generator/tsconfig.json | 2 +- 49 files changed, 409 insertions(+), 686 deletions(-) delete mode 100644 bridge/core/fileapi/dom_array_buffer_view.cc delete mode 100644 bridge/core/fileapi/dom_array_buffer_view.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 719f582ec2..710602a038 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -230,8 +230,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dart_methods.h core/fileapi/blob.h core/fileapi/blob.cc - core/fileapi/dom_array_buffer_view.cc - core/fileapi/dom_array_buffer_view.h core/frame/console.cc core/frame/console.h core/frame/dom_timer.cc diff --git a/bridge/bindings/qjs/converter.h b/bridge/bindings/qjs/converter.h index 3178f28358..1d76de01c6 100644 --- a/bridge/bindings/qjs/converter.h +++ b/bridge/bindings/qjs/converter.h @@ -6,12 +6,8 @@ #ifndef KRAKENBRIDGE_CONVERTER_H #define KRAKENBRIDGE_CONVERTER_H -#include - #include -#include "qjs_engine_patch.h" - namespace kraken { // The template parameter |T| determines what kind of type conversion to perform. diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 8116bef53e..440f0c35f7 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -6,38 +6,97 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ #define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ +#include +#include "atom_string.h" #include "converter.h" +#include "native_string_utils.h" #include "ts_type.h" namespace kraken { +// Optional value for pointer value. template -struct Converter, typename std::enable_if_t::ImplType>>> : public ConverterBase> { +struct Converter, std::enable_if_t::ImplType>::value>> : public ConverterBase> { using ImplType = typename Converter::ImplType; - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState* exception) { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { if (JS_IsUndefined(value)) { return nullptr; } - return Converter::FromValue(ctx, value); + return Converter::FromValue(ctx, value, exception); + } +}; + +// Optional value for ScriptValue +template +struct Converter, std::enable_if_t::ImplType, ScriptValue>>> : public ConverterBase> { + using ImplType = typename Converter::ImplType; + + static ScriptValue FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { + if (JS_IsUndefined(value)) { + return ScriptValue::Empty(ctx); + } + return Converter::FromValue(ctx, value, exception); + } +}; + +// Optional value for TSCallback +template +struct Converter, std::enable_if_t::ImplType, TSCallback::ImplType>>> : 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); + } +}; + +// Macro to generate optional template for smart pointer +#define DEFINE_OPTIONAL_SMART_POINTER_TEMPLATE(SMART_POINTER) \ + template \ + struct Converter, std::enable_if_t::ImplType, std::SMART_POINTER>>> : 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); \ + } \ + }; + +DEFINE_OPTIONAL_SMART_POINTER_TEMPLATE(unique_ptr); +DEFINE_OPTIONAL_SMART_POINTER_TEMPLATE(shared_ptr); + +// 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); } }; // Any template <> struct Converter : public ConverterBase { - static ImplType FromValue(JSContext* ctx, JSValue value) { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return ScriptValue(ctx, value); } - static JSValue ToValue(JSContext* ctx, const ScriptValue& value) { return value.toQuickJS(); } + static JSValue ToValue(JSContext* ctx, const ScriptValue& value) { return value.ToQuickJS(); } }; // Boolean template <> struct Converter : public ConverterBase { - static ImplType FromValue(JSContext* ctx, JSValue value) { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return JS_ToBool(ctx, value); }; @@ -48,7 +107,7 @@ struct Converter : public ConverterBase { // Uint32 template <> struct Converter : public ConverterBase { - static ImplType FromValue(JSContext* ctx, JSValue value) { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); uint32_t v; JS_ToUint32(ctx, &v, value); @@ -60,7 +119,7 @@ struct Converter : public ConverterBase { template <> struct Converter : public ConverterBase { - static ImplType FromValue(JSContext* ctx, JSValue value) { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); double v; JS_ToFloat64(ctx, &v, value); @@ -72,7 +131,7 @@ struct Converter : public ConverterBase { template <> struct Converter : public ConverterBase { - static std::unique_ptr FromValue(JSContext* ctx, JSValue value) { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return jsValueToNativeString(ctx, value); } @@ -82,7 +141,7 @@ struct Converter : public ConverterBase { template <> struct Converter : public ConverterBase { - static AtomString FromValue(JSContext* ctx, JSValue value) { + static AtomString FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); JSAtom atom = JS_ValueToAtom(ctx, value); AtomString result = AtomString(ctx, atom); @@ -95,20 +154,24 @@ struct Converter : public ConverterBase { template struct Converter> : public ConverterBase> { - using ImplType = typename TSSequence::ImplType; + using ImplType = typename TSSequence::ImplType>::ImplType; - static ImplType FromValue(JSContext* ctx, JSValue value) { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); assert(JS_IsArray(ctx, value)); - std::vector v; - uint32_t length = Converter::FromValue(ctx, JS_GetPropertyStr(ctx, value, "length")); + ImplType v; + uint32_t length = Converter::FromValue(ctx, JS_GetPropertyStr(ctx, value, "length"), exception_state); v.reserve(length); v.resize(length); for (uint32_t i = 0; i < length; i++) { - auto&& item = Converter::FromValue(ctx, JS_GetPropertyUint32(ctx, value, i)); + auto&& item = Converter::FromValue(ctx, JS_GetPropertyUint32(ctx, value, i), exception_state); + if (exception_state.HasException()) { + return {}; + } + v.emplace_back(item); } @@ -116,6 +179,18 @@ struct Converter> : public ConverterBase> { } }; +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); + } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index 3d06d86af0..f9ee7eb334 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -21,18 +21,12 @@ class MakeGarbageCollectedTrait; class ExecutingContext; /** + * 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. - * - * \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 { @@ -65,7 +59,7 @@ class GarbageCollected { * * @returns a human readable name for the object. */ - [[nodiscard]] FORCE_INLINE virtual const char* GetHumanReadableName() const { return ""; }; + [[nodiscard]] FORCE_INLINE virtual const char* GetHumanReadableName() const = 0; protected: GarbageCollected(){}; @@ -76,7 +70,7 @@ template class MakeGarbageCollectedTrait { public: template - static T* allocate(Args&&... args) { + static T* Allocate(Args&&... args) { T* object = ::new T(std::forward(args)...); return object; } @@ -89,7 +83,7 @@ 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)...); + return MakeGarbageCollectedTrait::Allocate(std::forward(args)...); } } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_blob.cc b/bridge/bindings/qjs/qjs_blob.cc index 8f17eec955..3d8c9eeb25 100644 --- a/bridge/bindings/qjs/qjs_blob.cc +++ b/bridge/bindings/qjs/qjs_blob.cc @@ -7,7 +7,8 @@ #include "member_installer.h" #include "core/executing_context.h" #include "core/fileapi/blob.h" -#include "converter.h" +#include "converter_impl.h" +#include "qjs_union_arraybuffer_arraybufferview_blob_string.h" namespace kraken { @@ -149,31 +150,55 @@ const WrapperTypeInfo& Blob::wrapper_type_info_ = QJSBlob::m_wrapperTypeInfo; //const WrapperTypeInfo Blob::wrapper_type_info_ = QJSBlob::m_wrapperTypeInfo; static JSValue arrayBuffer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - + return JS_NULL; } static JSValue slice(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* blob = toScriptWrappable(this_val); + Blob* return_value; + ExceptionState exception_state; + + do { + if (argc == 0) { + return_value = blob->Slice(&exception_state); + break; + } + double args_start = Converter>::FromValue(ctx, argv[0], exception_state); + if (exception_state.HasException()) { + return exception_state.ToQuickJS(); + } + + if (argc <= 1) { + return_value = blob->Slice(args_start, &exception_state); + } + + } while (false); + + if (exception_state.HasException()) { + return exception_state.ToQuickJS(); + } + return return_value->ToQuickJS(); } static JSValue text(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - + return JS_NULL; } static JSValue sizeAttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - + return JS_NULL; } static JSValue sizeAttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - + return JS_NULL; } static JSValue typeAttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - + return JS_NULL; } static JSValue typeAttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - + return JS_NULL; } @@ -183,7 +208,9 @@ JSValue QJSBlob::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue t return blob->ToQuickJS(); } - + ExceptionState exception_state; + std::vector> a = + Converter>::FromValue(ctx, argv[0], exception_state); // JSValue arrayValue = argv[0]; // JSValue optionValue = JS_UNDEFINED; @@ -226,13 +253,13 @@ JSValue QJSBlob::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue t // return blob->toQuickJS(); } -void QJSBlob::install(ExecutingContext* context) { - installConstructor(context); - installPrototypeMethods(context); - installPrototypeProperties(context); +void QJSBlob::Install(ExecutingContext* context) { + InstallConstructor(context); + InstallPrototypeMethods(context); + InstallPrototypeProperties(context); } -void QJSBlob::installConstructor(ExecutingContext* context) { +void QJSBlob::InstallConstructor(ExecutingContext* context) { const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); JSValue constructor = context->contextData()->constructorForType(wrapperTypeInfo); @@ -242,7 +269,7 @@ void QJSBlob::installConstructor(ExecutingContext* context) { MemberInstaller::InstallAttributes(context, context->Global(), attributeConfig); } -void QJSBlob::installPrototypeMethods(ExecutingContext* context) { +void QJSBlob::InstallPrototypeMethods(ExecutingContext* context) { const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); @@ -254,7 +281,7 @@ void QJSBlob::installPrototypeMethods(ExecutingContext* context) { MemberInstaller::InstallAttributes(context, prototype, attributesConfig); } -void QJSBlob::installPrototypeProperties(ExecutingContext* context) { +void QJSBlob::InstallPrototypeProperties(ExecutingContext* context) { const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); diff --git a/bridge/bindings/qjs/qjs_blob.h b/bridge/bindings/qjs/qjs_blob.h index 79b54eab0c..ec0a8c1bac 100644 --- a/bridge/bindings/qjs/qjs_blob.h +++ b/bridge/bindings/qjs/qjs_blob.h @@ -16,7 +16,7 @@ class ExecutingContext; class QJSBlob final { public: - static void install(ExecutingContext* context); + static void Install(ExecutingContext* context); static WrapperTypeInfo* GetWrapperTypeInfo() { return const_cast(&m_wrapperTypeInfo); @@ -26,9 +26,9 @@ class QJSBlob final { static JSValue ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); constexpr static const WrapperTypeInfo m_wrapperTypeInfo = {"Blob", nullptr, ConstructorCallback}; - static void installPrototypeMethods(ExecutingContext* context); - static void installPrototypeProperties(ExecutingContext* context); - static void installConstructor(ExecutingContext* context); + static void InstallPrototypeMethods(ExecutingContext* context); + static void InstallPrototypeProperties(ExecutingContext* context); + static void InstallConstructor(ExecutingContext* context); friend class Blob; }; diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 5755d85185..a1cfcc47b4 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -30,10 +30,6 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, int32_t argc, ScriptValue* argum return ScriptValue(ctx, returnValue); } -const char* QJSFunction::GetHumanReadableName() const { - return "QJSFunction"; -} - void QJSFunction::Trace(GCVisitor* visitor) const { visitor->Trace(function_); } diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 4581526171..db1da00c72 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -12,9 +12,9 @@ namespace kraken { // https://webidl.spec.whatwg.org/#dfn-callback-interface -class QJSFunction : public GarbageCollected { +class QJSFunction { public: - static QJSFunction* Create(JSContext* ctx, JSValue function) { return makeGarbageCollected(ctx, function); } + static std::shared_ptr Create(JSContext* ctx, JSValue function) { return std::make_shared(ctx, function); } explicit QJSFunction(JSContext* ctx, JSValue function) : function_(JS_DupValue(ctx, function)) {}; bool IsFunction(JSContext* ctx); @@ -23,9 +23,8 @@ class QJSFunction : public GarbageCollected { // https://webidl.spec.whatwg.org/#invoke-a-callback-function ScriptValue Invoke(JSContext* ctx, int32_t argc, ScriptValue* arguments); - const char* GetHumanReadableName() const override; - void Trace(GCVisitor* visitor) const override; - void Dispose() const override; + void Trace(GCVisitor* visitor) const; + void Dispose() const; private: JSContext* ctx_{nullptr}; diff --git a/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc b/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc index 775b6fb2b0..147bea69e8 100644 --- a/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc +++ b/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc @@ -7,8 +7,25 @@ namespace kraken { -QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString* QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { +std::shared_ptr QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsString(value)) { + const char* buffer = JS_ToCString(ctx, value); + auto result = std::make_shared(ctx, buffer); + JS_FreeCString(ctx, buffer); + return result; + } + return nullptr; } +JSValue QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString::ToQuickJS(JSContext* ctx) const{ + switch(content_type_) { + case ContentType::kString: { + return JS_NewString(ctx, member_string_.c_str()); + } + case ContentType::kBlob: { + } + } +} + } diff --git a/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h b/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h index 6d8740ea27..9c0dc6d24d 100644 --- a/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h +++ b/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h @@ -7,9 +7,12 @@ #define KRAKENBRIDGE_QJS_UNION_ARRAYBUFFER_ARRAYBUFFERVIEW_BLOB_STRING_H #include +#include #include "core/fileapi/blob.h" #include "exception_state.h" +#include "ts_type.h" +#include "converter_impl.h" namespace kraken { @@ -19,19 +22,38 @@ class QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString { kArrayBuffer, kArrayBufferView, kBlob, kString }; - static QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString* Create( + static std::shared_ptr Create( JSContext* ctx, JSValue value, ExceptionState& exception_state); + JSValue ToQuickJS(JSContext* ctx) const; + explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, uint8_t* arrayBuffer, uint32_t length): content_type_(ContentType::kArrayBuffer) {}; - explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, const std::string& value): content_type_(ContentType::kString) {}; + explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, uint8_t* buffer, size_t byte_offset, size_t byte_length, size_t byte_per_element, uint32_t length): content_type_(ContentType::kArrayBufferView) {}; + explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, const std::string& value): content_type_(ContentType::kString), member_string_(value) {}; explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, Blob* blob): content_type_(ContentType::kBlob) {}; private: ContentType content_type_; std::string member_string_; - uint32_t* bytes; + uint32_t* bytes{nullptr}; +}; + +// Special types +struct TSUnionArrayBufferOrArrayBufferViewOrBlobOrString : public TSTypeBaseHelper> { + using ImplType = typename std::shared_ptr; +}; + +template <> +struct Converter : public ConverterBase { + using ImplType = TSUnionArrayBufferOrArrayBufferViewOrBlobOrString::ImplType; + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString::Create(ctx, value, exception_state); + } + + static JSValue ToValue(JSContext* ctx, QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString* data) { return data->ToQuickJS(ctx); } }; } diff --git a/bridge/bindings/qjs/qjs_window.cc b/bridge/bindings/qjs/qjs_window.cc index 357d68e412..1794afdbec 100644 --- a/bridge/bindings/qjs/qjs_window.cc +++ b/bridge/bindings/qjs/qjs_window.cc @@ -40,7 +40,7 @@ static JSValue setTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSVal return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); } - QJSFunction* handler = QJSFunction::Create(ctx, callbackValue); + auto handler = QJSFunction::Create(ctx, callbackValue); ExceptionState exceptionState; int32_t timerId = WindowOrWorkerGlobalScope::setTimeout(context, handler, timeout, &exceptionState); @@ -84,7 +84,7 @@ static JSValue setInterval(JSContext* ctx, JSValueConst this_val, int argc, JSVa return JS_ThrowTypeError(ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); } - QJSFunction* handler = QJSFunction::Create(ctx, callbackValue); + auto handler = QJSFunction::Create(ctx, callbackValue); ExceptionState exception; int32_t timerId = WindowOrWorkerGlobalScope::setInterval(context, handler, timeout, &exception); diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 3d08ef4182..a42409de76 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -55,8 +55,12 @@ std::string ScriptValue::toCString() { return jsValueToStdString(m_ctx, m_value); } -bool ScriptValue::isException() { +bool ScriptValue::IsException() { return JS_IsException(m_value); } +bool ScriptValue::IsEmpty() { + return JS_IsNull(m_value) || JS_IsUndefined(m_value); +} + } // namespace kraken diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 3eeda9fa34..53f307dc19 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -51,7 +51,8 @@ class ScriptValue final { std::unique_ptr toNativeString(); std::string toCString(); - bool isException(); + bool IsException(); + bool IsEmpty(); private: JSContext* m_ctx{nullptr}; diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index b118781fa5..9f9e0d8231 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -12,7 +12,7 @@ ScriptWrappable::ScriptWrappable(JSContext* ctx): ctx_(ctx), runtime_(JS_GetRunt JSValue ScriptWrappable::ToQuickJS() { if (wrapped_) { - return jsObject_; + return JS_DupValue(ctx_, jsObject_); } // Initialize the corresponding quickjs object. diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 3efc390b4e..bc553bdd91 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -32,7 +32,7 @@ namespace kraken { 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 +// 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 { @@ -56,6 +56,8 @@ class ScriptWrappable : public GarbageCollected { JSRuntime* runtime_{nullptr}; }; +// Converts a QuickJS object back to a ScriptWrappable. +template inline ScriptWrappable* toScriptWrappable(JSValue object) { return static_cast(JS_GetOpaque(object, JSValueGetClassId(object))); } diff --git a/bridge/bindings/qjs/ts_type.h b/bridge/bindings/qjs/ts_type.h index a2061a4bd7..e8c3fe6f18 100644 --- a/bridge/bindings/qjs/ts_type.h +++ b/bridge/bindings/qjs/ts_type.h @@ -7,11 +7,7 @@ #define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ #include -#include "foundation/native_string.h" #include "converter.h" -#include "script_value.h" -#include "atom_string.h" -#include "qjs_union_arraybuffer_arraybufferview_blob_string.h" namespace kraken { @@ -41,8 +37,9 @@ struct TSDouble final : public TSTypeBaseHelper {}; // DOMString is UTF-16 strings. // https://stackoverflow.com/questions/35123890/what-is-a-domstring-really -struct TSDOMString final : public TSTypeBaseHelper {}; +struct TSDOMString final : public TSTypeBaseHelper> {}; +class AtomString; struct TSAtomString final : public TSTypeBaseHelper {}; // https://developer.mozilla.org/en-US/docs/Web/API/USVString @@ -51,6 +48,11 @@ struct TSUSVString final : public TSTypeBaseHelper {}; // Object struct TSObject : public TSTypeBaseHelper {}; +// Function callback +struct TSCallback : public TSTypeBaseHelper> { + using ImplType = typename Converter>::ImplType; +}; + // Sequence template struct TSSequence final : public TSTypeBase { diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index 6204042c3d..3d5c5df23d 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -13,7 +13,7 @@ namespace kraken { // |FrameCallback| is an interface type which generalizes callbacks which are // invoked when a script-based animation needs to be resampled. -class FrameCallback : public GarbageCollected { +class FrameCallback { public: FrameCallback(ExecutingContext* context, JSValue callback); @@ -21,10 +21,8 @@ class FrameCallback : public GarbageCollected { ExecutingContext* context() { return context_; }; - [[nodiscard]] FORCE_INLINE const char* GetHumanReadableName() const override { return "FrameCallback"; } - - void Trace(GCVisitor* visitor) const override; - void Dispose() const override; + void Trace(GCVisitor* visitor) const; + void Dispose() const; private: JSValue callback_{JS_NULL}; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index e4519ab7e0..b3b0c767cc 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -30,6 +30,7 @@ void ExecutionContextGCTracker::Trace(GCVisitor* visitor) const { context->Trace(visitor); } void ExecutionContextGCTracker::Dispose() const {} +const char * ExecutionContextGCTracker::GetHumanReadableName() const { return "GCTracker"; } ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) : context_id_(contextId), handler_(handler), owner_(owner), ctx_invalid_(false), unique_id_(context_unique_id++) { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index c22891ab68..43800dde10 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -59,6 +59,7 @@ class ExecutionContextGCTracker : public ScriptWrappable { void Trace(GCVisitor* visitor) const override; void Dispose() const override; + const char* GetHumanReadableName() const override; private: }; diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc index 7799b5b745..3a33ef59e6 100644 --- a/bridge/core/executing_context_data.cc +++ b/bridge/core/executing_context_data.cc @@ -9,30 +9,28 @@ namespace kraken { JSValue ExecutionContextData::constructorForType(const WrapperTypeInfo* type) { - auto it = m_constructorMap.find(type); - return it != m_constructorMap.end() ? it->second : constructorForIdSlowCase(type); + auto it = constructor_map_.find(type); + return it != constructor_map_.end() ? it->second : constructorForIdSlowCase(type); } JSValue ExecutionContextData::prototypeForType(const WrapperTypeInfo* type) { - auto it = m_prototypeMap.find(type); + auto it = prototype_map_.find(type); // Constructor not initialized, create it. - if (it == m_prototypeMap.end()) { + if (it == prototype_map_.end()) { constructorForIdSlowCase(type); - it = m_prototypeMap.find(type); + it = prototype_map_.find(type); } - return it != m_prototypeMap.end() ? it->second : JS_NULL; + return it != prototype_map_.end() ? it->second : JS_NULL; } JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* type) { - JSRuntime* runtime = m_context->runtime(); JSContext* ctx = m_context->ctx(); - assert(type->classId == 0 || !JS_HasClassId(runtime, type->classId)); - + JSClassID class_id; // Allocate a new unique classID from QuickJS. - JS_NewClassID(const_cast(&type->classId)); + JS_NewClassID(&class_id); // Create class template for behavior. JSClassDef def{}; @@ -41,8 +39,8 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty JS_NewClass(m_context->runtime(), type->classId, &def); // Create class object and prototype object. - JSValue classObject = m_constructorMap[type] = JS_NewObjectClass(m_context->ctx(), type->classId); - JSValue prototypeObject = m_prototypeMap[type] = JS_NewObject(m_context->ctx()); + 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"); @@ -58,8 +56,8 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty // Inherit to parentClass. if (type->parent_class != nullptr) { - assert(m_prototypeMap.count(type->parent_class) > 0); - JS_SetPrototype(m_context->ctx(), prototypeObject, m_prototypeMap[type->parent_class]); + 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. diff --git a/bridge/core/executing_context_data.h b/bridge/core/executing_context_data.h index 38eaaf150f..90177d800f 100644 --- a/bridge/core/executing_context_data.h +++ b/bridge/core/executing_context_data.h @@ -29,8 +29,8 @@ class ExecutionContextData final { private: JSValue constructorForIdSlowCase(const WrapperTypeInfo* type); - std::unordered_map m_constructorMap; - std::unordered_map m_prototypeMap; + std::unordered_map constructor_map_; + std::unordered_map prototype_map_; ExecutingContext* m_context; }; diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index f49f49d120..f952146f59 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -79,7 +79,19 @@ uint8_t* Blob::bytes() { return _data.data(); } +const char * Blob::GetHumanReadableName() const { + return "Blob"; +} void Blob::Trace(GCVisitor* visitor) const {} void Blob::Dispose() const {} +Blob* Blob::Slice(ExceptionState* exception_state) { + return nullptr; +} +Blob * Blob::Slice(int64_t start, ExceptionState* exception_state) { + return nullptr; +} + + + } // namespace kraken diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 5698882a6b..bffb4efe4e 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -33,6 +33,10 @@ class Blob : public ScriptWrappable { /// get bytes data's length int32_t size(); + Blob* Slice(ExceptionState* exception_state); + Blob* Slice(int64_t start, ExceptionState* exception_state); + + const char* GetHumanReadableName() const override; void Trace(GCVisitor* visitor) const override; void Dispose() const override; diff --git a/bridge/core/fileapi/dom_array_buffer_view.cc b/bridge/core/fileapi/dom_array_buffer_view.cc deleted file mode 100644 index ea40b43a3c..0000000000 --- a/bridge/core/fileapi/dom_array_buffer_view.cc +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "dom_array_buffer_view.h" diff --git a/bridge/core/fileapi/dom_array_buffer_view.h b/bridge/core/fileapi/dom_array_buffer_view.h deleted file mode 100644 index b29b1af25c..0000000000 --- a/bridge/core/fileapi/dom_array_buffer_view.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_DOM_ARRAY_BUFFER_VIEW_H -#define KRAKENBRIDGE_DOM_ARRAY_BUFFER_VIEW_H - -#include - -namespace kraken { - -class DOMArrayBufferView { - public: - enum ViewType { - kTypeInt8, - kTypeUint8, - kTypeUint8Clamped, - kTypeInt16, - kTypeUint16, - kTypeInt32, - kTypeUint32, - kTypeFloat32, - kTypeFloat64, - kTypeBigInt64, - kTypeBigUint64, - kTypeDataView - }; - - private: - uint8_t* buffer_; - size_t length_; - ViewType view_type_; -}; - -} - -#endif // KRAKENBRIDGE_DOM_ARRAY_BUFFER_VIEW_H diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index 96d56ef767..a49f16310f 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -14,7 +14,11 @@ namespace kraken { -DOMTimer::DOMTimer(ExecutingContext* context, QJSFunction* callback) : context_(context), callback_(callback) {} +std::shared_ptr DOMTimer::create(ExecutingContext* context, std::shared_ptr callback) { + return std::make_shared(context, callback); +} + +DOMTimer::DOMTimer(ExecutingContext* context, std::shared_ptr callback) : context_(context), callback_(callback) {} void DOMTimer::Fire() { if (!callback_->IsFunction(context_->ctx())) @@ -22,7 +26,7 @@ void DOMTimer::Fire() { ScriptValue returnValue = callback_->Invoke(context_->ctx(), 0, nullptr); - if (returnValue.isException()) { + if (returnValue.IsException()) { context_->HandleException(&returnValue); } } diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index fac52a15b3..3ec5eecda1 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -12,9 +12,10 @@ namespace kraken { -class DOMTimer : public GarbageCollected { +class DOMTimer { public: - DOMTimer(ExecutingContext* context, QJSFunction* callback); + static std::shared_ptr create(ExecutingContext* context, std::shared_ptr callback); + DOMTimer(ExecutingContext* context, std::shared_ptr callback); // Trigger timer callback. void Fire(); @@ -24,16 +25,14 @@ class DOMTimer : public GarbageCollected { ExecutingContext* context() { return context_; } - [[nodiscard]] FORCE_INLINE const char* GetHumanReadableName() const override { return "DOMTimer"; } - - void Trace(GCVisitor* visitor) const override; - void Dispose() const override; + void Trace(GCVisitor* visitor) const; + void Dispose() const; private: ExecutingContext* context_{nullptr}; int32_t timerId_{-1}; int32_t isInterval_{false}; - QJSFunction* callback_; + std::shared_ptr callback_; }; } // namespace kraken diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index fb478e56c8..5129996c13 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -42,14 +42,14 @@ static void handleTransientCallback(void* ptr, int32_t contextId, const char* er context->Timers()->removeTimeoutById(timer->timerId()); } -void DOMTimerCoordinator::installNewTimer(ExecutingContext* context, int32_t timerId, DOMTimer* timer) { +void DOMTimerCoordinator::installNewTimer(ExecutingContext* context, int32_t timerId, std::shared_ptr timer) { m_activeTimers[timerId] = timer; } void* DOMTimerCoordinator::removeTimeoutById(int32_t timerId) { if (m_activeTimers.count(timerId) == 0) return nullptr; - DOMTimer* timer = m_activeTimers[timerId]; + auto timer = m_activeTimers[timerId]; // Push this timer to abandoned list to mark this timer is deprecated. m_abandonedTimers.emplace_back(timer); @@ -58,7 +58,7 @@ void* DOMTimerCoordinator::removeTimeoutById(int32_t timerId) { return nullptr; } -DOMTimer* DOMTimerCoordinator::getTimerById(int32_t timerId) { +std::shared_ptr DOMTimerCoordinator::getTimerById(int32_t timerId) { if (m_activeTimers.count(timerId) == 0) return nullptr; return m_activeTimers[timerId]; diff --git a/bridge/core/frame/dom_timer_coordinator.h b/bridge/core/frame/dom_timer_coordinator.h index d3797b5ae5..f008adc511 100644 --- a/bridge/core/frame/dom_timer_coordinator.h +++ b/bridge/core/frame/dom_timer_coordinator.h @@ -24,18 +24,18 @@ class ExecutingContext; class DOMTimerCoordinator { public: // Creates and installs a new timer. Returns the assigned ID. - void installNewTimer(ExecutingContext* context, int32_t timerId, DOMTimer* timer); + void installNewTimer(ExecutingContext* context, int32_t timerId, std::shared_ptr 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); + std::shared_ptr getTimerById(int32_t timerId); void trace(GCVisitor* visitor); private: - std::unordered_map m_activeTimers; - std::vector m_abandonedTimers; + std::unordered_map> m_activeTimers; + std::vector> m_abandonedTimers; }; } // namespace kraken diff --git a/bridge/core/frame/module_callback.cc b/bridge/core/frame/module_callback.cc index 9638a535e8..fa604218f3 100644 --- a/bridge/core/frame/module_callback.cc +++ b/bridge/core/frame/module_callback.cc @@ -7,9 +7,13 @@ namespace kraken { -ModuleCallback::ModuleCallback(QJSFunction* function) : function_(function) {} +std::shared_ptr ModuleCallback::Create(std::shared_ptr function) { + return std::make_shared(function); +} + +ModuleCallback::ModuleCallback(std::shared_ptr function) : function_(function) {} -QJSFunction* ModuleCallback::value() { +std::shared_ptr ModuleCallback::value() { return function_; } diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h index 068620b880..585d31156a 100644 --- a/bridge/core/frame/module_callback.h +++ b/bridge/core/frame/module_callback.h @@ -23,19 +23,20 @@ struct ModuleCallbackLinker { // ModuleCallback is an asynchronous callback function, usually from the 4th parameter of `kraken.invokeModule` function. // When the asynchronous operation on the Dart side ends, the callback is will called and to return to the JS executing environment. -class ModuleCallback : public GarbageCollected { +class ModuleCallback { public: - explicit ModuleCallback(QJSFunction* function); + static std::shared_ptr Create(std::shared_ptr function); + explicit ModuleCallback(std::shared_ptr function); - QJSFunction* value(); + std::shared_ptr value(); - void Trace(GCVisitor* visitor) const override; - void Dispose() const override; + void Trace(GCVisitor* visitor) const; + void Dispose() const; ModuleCallbackLinker linker{this}; private: - QJSFunction* function_{nullptr}; + std::shared_ptr function_{nullptr}; }; } // namespace kraken diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc index ca9a09ccfd..0049f76210 100644 --- a/bridge/core/frame/module_callback_coordinator.cc +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -7,25 +7,20 @@ namespace kraken { -void ModuleCallbackCoordinator::AddModuleCallbacks(ModuleCallback* callback) { - list_add_tail(&listeners_, &callback->linker.link); +void ModuleCallbackCoordinator::AddModuleCallbacks(std::shared_ptr callback) { + listeners_.push_front(callback); } -void ModuleCallbackCoordinator::RemoveModuleCallbacks(ModuleCallback* callback) { - list_del(&callback->linker.link); +void ModuleCallbackCoordinator::RemoveModuleCallbacks(std::shared_ptr callback) { + listeners_.remove(callback); } ModuleCallbackCoordinator::ModuleCallbackCoordinator() { - init_list_head(&listeners_); } void ModuleCallbackCoordinator::Trace(GCVisitor* visitor) { - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &listeners_) { - auto* linker = list_entry(el, ModuleCallbackLinker, link); - linker->ptr->Trace(visitor); - } + for(auto& listener: listeners_) { + listener->Trace(visitor); } } diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_callback_coordinator.h index c6d5814ad3..6e59ce59bb 100644 --- a/bridge/core/frame/module_callback_coordinator.h +++ b/bridge/core/frame/module_callback_coordinator.h @@ -20,14 +20,13 @@ class ModuleCallbackCoordinator final { public: ModuleCallbackCoordinator(); - void AddModuleCallbacks(ModuleCallback* callback); - void RemoveModuleCallbacks(ModuleCallback* callback); + void AddModuleCallbacks(std::shared_ptr callback); + void RemoveModuleCallbacks(std::shared_ptr callback); void Trace(GCVisitor* visitor); private: - list_head listeners_; - + std::forward_list> listeners_; friend ModuleListener; }; diff --git a/bridge/core/frame/module_listener.cc b/bridge/core/frame/module_listener.cc index bfa4a1a565..18ed76b318 100644 --- a/bridge/core/frame/module_listener.cc +++ b/bridge/core/frame/module_listener.cc @@ -7,14 +7,18 @@ namespace kraken { -ModuleListener::ModuleListener(QJSFunction* function) : m_function(function) {} +std::shared_ptr ModuleListener::Create(std::shared_ptr function) { + return std::make_shared(function); +} + +ModuleListener::ModuleListener(std::shared_ptr function) : function_(function) {} void ModuleListener::Trace(GCVisitor* visitor) const { - m_function->Trace(visitor); + function_->Trace(visitor); } void ModuleListener::Dispose() const { - m_function->Dispose(); + function_->Dispose(); } } // namespace kraken diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h index 7f6aed5616..0918c5e136 100644 --- a/bridge/core/frame/module_listener.h +++ b/bridge/core/frame/module_listener.h @@ -16,15 +16,16 @@ class ModuleListenerContainer; // ModuleListener is an persistent callback function. Registered from user with `kraken.addModuleListener` method. // When module event triggered at dart side, All module listener will be invoked and let user to dispatch further operations. -class ModuleListener : public GarbageCollected { +class ModuleListener { public: - explicit ModuleListener(QJSFunction* function); + static std::shared_ptr Create(std::shared_ptr function); + explicit ModuleListener(std::shared_ptr function); private: - void Trace(GCVisitor* visitor) const override; - void Dispose() const override; + void Trace(GCVisitor* visitor) const; + void Dispose() const; - QJSFunction* m_function{nullptr}; + std::shared_ptr function_{nullptr}; friend ModuleListenerContainer; friend ModuleCallbackCoordinator; diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc index 7c135ff55c..a9e7fe895c 100644 --- a/bridge/core/frame/module_listener_container.cc +++ b/bridge/core/frame/module_listener_container.cc @@ -7,13 +7,13 @@ namespace kraken { -void ModuleListenerContainer::addModuleListener(ModuleListener* listener) { +void ModuleListenerContainer::addModuleListener(std::shared_ptr listener) { m_listeners.insert_after(m_listeners.end(), listener); } void ModuleListenerContainer::trace(GCVisitor* visitor) { for (auto& listener : m_listeners) { - listener->m_function->Trace(visitor); + listener->function_->Trace(visitor); } } diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h index 1b1d11798b..8165c1d310 100644 --- a/bridge/core/frame/module_listener_container.h +++ b/bridge/core/frame/module_listener_container.h @@ -13,11 +13,11 @@ namespace kraken { class ModuleListenerContainer final { public: - void addModuleListener(ModuleListener* listener); + void addModuleListener(std::shared_ptr listener); void trace(GCVisitor* visitor); private: - std::forward_list m_listeners; + std::forward_list> m_listeners; friend ModuleListener; }; diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 9dfef78f58..da96bb8e91 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -11,7 +11,7 @@ namespace kraken { struct ModuleContext { ExecutingContext* context; - ModuleCallback* callback; + std::shared_ptr callback; }; void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const char* errmsg, NativeString* json) { @@ -33,7 +33,7 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha ScriptValue errorObject = ScriptValue::createErrorObject(ctx, errmsg); ScriptValue arguments[] = {errorObject}; ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); - if (returnValue.isException()) { + if (returnValue.IsException()) { context->HandleException(&returnValue); } } else { @@ -42,7 +42,7 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha ScriptValue jsonObject = ScriptValue::createJSONObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); ScriptValue arguments[] = {jsonObject}; ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); - if (returnValue.isException()) { + if (returnValue.IsException()) { context->HandleException(&returnValue); } } @@ -55,26 +55,28 @@ void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t context static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); } -ScriptValue ModuleManager::__kraken_invoke_module__(ExecutingContext* context, ScriptValue& moduleNameValue, ScriptValue& methodValue, ScriptValue& paramsValue, QJSFunction* callback, ExceptionState* exception) { - std::unique_ptr moduleName = moduleNameValue.toNativeString(); - std::unique_ptr method = methodValue.toNativeString(); +ScriptValue ModuleManager::__kraken_invoke_module__(ExecutingContext* context, + std::unique_ptr &moduleName, + std::unique_ptr &method, + ScriptValue& paramsValue, + std::shared_ptr callback, + ExceptionState& exception) { + std::unique_ptr params; -// if (!paramsValue.isEmpty()) { -// ScriptValue stringifiedValue = paramsValue.ToJSONStringify(exception); -// if (exception->HasException()) { -// return stringifiedValue; -// } -// -// params = stringifiedValue.toNativeString(); -// } -// -// if (context->dartMethodPtr()->invokeModule == nullptr) { -// exception->ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); -// return ScriptValue(context->ctx()); -// } -// -// auto* moduleCallback = makeGarbageCollected(callback); -// context->moduleCallbacks()->AddModuleCallbacks(moduleCallback); + if (!paramsValue.IsEmpty()) { + params = paramsValue.ToJSONStringify(&exception).toNativeString(); + if (exception.HasException()) { + return ScriptValue::Empty(context->ctx()); + } + } + + if (context->dartMethodPtr()->invokeModule == nullptr) { + exception.ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); + return ScriptValue::Empty(context->ctx()); + } + + auto moduleCallback = ModuleCallback::Create(callback); + context->ModuleCallbacks()->AddModuleCallbacks(moduleCallback); // // ModuleContext* moduleContext = new ModuleContext{context, moduleCallback}; // @@ -103,8 +105,8 @@ ScriptValue ModuleManager::__kraken_invoke_module__(ExecutingContext* context, S // return resultString; } -void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, QJSFunction* handler, ExceptionState* exception) { - auto* listener = makeGarbageCollected(handler); +void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, std::shared_ptr handler, ExceptionState& exception) { + auto listener = ModuleListener::Create(handler); context->ModuleListeners()->addModuleListener(listener); } diff --git a/bridge/core/frame/module_manager.d.ts b/bridge/core/frame/module_manager.d.ts index 2386df2407..6d6c920a07 100644 --- a/bridge/core/frame/module_manager.d.ts +++ b/bridge/core/frame/module_manager.d.ts @@ -1,2 +1,2 @@ -declare const __kraken_invoke_module__: (moduleName: string, methodName: string, paramsValue?: string, callback?: Function) => string; +declare const __kraken_invoke_module__: (moduleName: string, methodName: string, paramsValue?: any, callback?: Function) => string; declare const __kraken_add_module_listener__: (callback?: Function) => void; diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 0600aeeb20..4060ed0eff 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -14,8 +14,13 @@ namespace kraken { class ModuleManager { public: - static ScriptValue __kraken_invoke_module__(ExecutingContext* context, ScriptValue& moduleName, ScriptValue& method, ScriptValue& params, QJSFunction* callback, ExceptionState* exception); - static void __kraken_add_module_listener__(ExecutingContext* context, QJSFunction* handler, ExceptionState* exception); + static ScriptValue __kraken_invoke_module__(ExecutingContext* context, + std::unique_ptr &moduleName, + std::unique_ptr &method, + ScriptValue& params, + std::shared_ptr callback, + ExceptionState& exception); + static void __kraken_add_module_listener__(ExecutingContext* context, std::shared_ptr handler, ExceptionState& exception); }; } // namespace kraken diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 039b543252..9e613d09c7 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -49,7 +49,7 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e handleTimerCallback(timer, errmsg); } -int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception) { +int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState* exception) { #if FLUTTER_BACKEND if (context->dartMethodPtr()->setTimeout == nullptr) { exception->throwException(context->ctx(), ErrorType::InternalError, "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); @@ -58,8 +58,8 @@ int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, QJSFunction #endif // Create a timer object to keep track timer callback. - auto* timer = makeGarbageCollected(context, handler); - auto timerId = context->dartMethodPtr()->setTimeout(timer, context->contextid(), handleTransientCallback, timeout); + auto timer = DOMTimer::create(context, handler); + auto timerId = context->dartMethodPtr()->setTimeout(timer.get(), context->contextid(), handleTransientCallback, timeout); // Register timerId. timer->setTimerId(timerId); @@ -69,16 +69,16 @@ int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, QJSFunction return timerId; } -int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception) { +int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState* exception) { if (context->dartMethodPtr()->setInterval == nullptr) { exception->ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute 'setInterval': dart method (setInterval) is not registered."); return -1; } // Create a timer object to keep track timer callback. - auto* timer = makeGarbageCollected(context, handler); + auto timer = DOMTimer::create(context, handler); - uint32_t timerId = context->dartMethodPtr()->setInterval(timer, context->contextid(), handlePersistentCallback, timeout); + uint32_t timerId = context->dartMethodPtr()->setInterval(timer.get(), context->contextid(), handlePersistentCallback, timeout); // Register timerId. timer->setTimerId(timerId); diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h index 55d766ea29..a3c56fb08b 100644 --- a/bridge/core/frame/window_or_worker_global_scope.h +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -14,8 +14,8 @@ namespace kraken { class WindowOrWorkerGlobalScope { public: - static int setTimeout(ExecutingContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception); - static int setInterval(ExecutingContext* context, QJSFunction* handler, int32_t timeout, ExceptionState* exception); + static int setTimeout(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState* exception); + static int setInterval(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState* exception); static void clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState* exception); }; diff --git a/bridge/core/page.h b/bridge/core/page.h index b1dd343f28..ccc32e5341 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -24,7 +24,7 @@ using ConsoleMessageHandler = std::function flutter widget. /// Every flutter widgets have a corresponding KrakenPage, and all objects created by JavaScript are stored here, /// and there is no data sharing between objects between different KrakenPages. -/// It's safe to allocate many KrakenPages at the same times on one thread, but not safe for multi-threads, only one thread can enter to KrakenPage at the same time. +/// It's safe to Allocate many KrakenPages at the same times on one thread, but not safe for multi-threads, only one thread can enter to KrakenPage at the same time. class KrakenPage final { public: static kraken::KrakenPage** pageContextPool; diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 73f02968d8..6a208fc1aa 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -99,7 +99,7 @@ int32_t allocateNewPage(int32_t targetContextId) { } assert(kraken::KrakenPage::pageContextPool[targetContextId] == nullptr && - (std::string("can not allocate page at index") + std::to_string(targetContextId) + std::string(": page have already exist.")).c_str()); + (std::string("can not Allocate page at index") + std::to_string(targetContextId) + std::string(": page have already exist.")).c_str()); auto* page = new kraken::KrakenPage(targetContextId, nullptr); kraken::KrakenPage::pageContextPool[targetContextId] = page; return targetContextId; diff --git a/bridge/polyfill/scripts/js_to_c.js b/bridge/polyfill/scripts/js_to_c.js index 431ddbb032..7f144d8232 100644 --- a/bridge/polyfill/scripts/js_to_c.js +++ b/bridge/polyfill/scripts/js_to_c.js @@ -36,7 +36,7 @@ const getPolyFillHeader = (outputName) => `/* #include "core/executing_context.h" -void initKraken${outputName}(kraken::ExecutionContext *context); +void initKraken${outputName}(kraken::ExecutingContext *context); #endif // KRAKEN_${outputName.toUpperCase()}_H `; @@ -51,7 +51,7 @@ uint8_t bytes[${uint8Array.length}] = {${uint8Array.join(',')}}; }`; }; const getPolyfillEvalCall = () => { - return 'context->evaluateByteCode(bytes, byteLength);'; + return 'context->EvaluateByteCode(bytes, byteLength);'; } const getPolyFillSource = (source, outputName) => `/* @@ -63,7 +63,7 @@ const getPolyFillSource = (source, outputName) => `/* ${getPolyFillJavaScriptSource(source)} -void initKraken${outputName}(kraken::ExecutionContext *context) { +void initKraken${outputName}(kraken::ExecutingContext *context) { ${getPolyfillEvalCall()} } `; diff --git a/bridge/scripts/code_generator/src/analyzer.ts b/bridge/scripts/code_generator/src/analyzer.ts index c64e926c5c..c5e8777e35 100644 --- a/bridge/scripts/code_generator/src/analyzer.ts +++ b/bridge/scripts/code_generator/src/analyzer.ts @@ -78,22 +78,34 @@ function getParameterName(name: ts.BindingName) : string { return ''; } -function getParameterType(type: ts.TypeNode) { - if (type.kind === ts.SyntaxKind.StringKeyword) { +function getParameterType(type: ts.TypeNode): FunctionArgumentType | FunctionArgumentType[] { + if (type.kind == ts.SyntaxKind.ArrayType) { + let arrayType = type.kind as unknown as ts.ArrayTypeNode; + return [getParameterType(arrayType) as FunctionArgumentType]; + } else if (type.kind === ts.SyntaxKind.StringKeyword) { return FunctionArgumentType.string; } else if (type.kind === ts.SyntaxKind.NumberKeyword) { - return FunctionArgumentType.number; + 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; } else if (type.kind === ts.SyntaxKind.TypeReference) { let typeReference: ts.TypeReference = type as unknown as ts.TypeReference; // @ts-ignore let identifier = (typeReference.typeName as ts.Identifier).text; if (identifier === 'Function') { return FunctionArgumentType.function; + } else if (identifier === 'int32') { + return FunctionArgumentType.int32; + } else if (identifier === 'double') { + return FunctionArgumentType.double; } } - return FunctionArgumentType.union; + + return FunctionArgumentType.any; } function paramsNodeToArguments(parameter: ts.ParameterDeclaration): FunctionArguments { diff --git a/bridge/scripts/code_generator/src/declaration.ts b/bridge/scripts/code_generator/src/declaration.ts index 24f48c643f..24d114d41e 100644 --- a/bridge/scripts/code_generator/src/declaration.ts +++ b/bridge/scripts/code_generator/src/declaration.ts @@ -1,14 +1,17 @@ export enum FunctionArgumentType { + // Basic types string, - number, + object, + int32, + double, boolean, function, - union + any, } export class FunctionArguments { name: string; - type: FunctionArgumentType; + type: FunctionArgumentType | FunctionArgumentType[]; required: boolean; } diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index c00113be58..2177962952 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -5,43 +5,9 @@ import { FunctionArgumentType, FunctionDeclaration, FunctionObject, - PropsDeclaration, - PropsDeclarationKind, ReturnType } from "./declaration"; import {addIndent, getClassName} from "./utils"; -import {capitalize, camelCase} from 'lodash'; - -function generateHostObjectSource(object: ClassObject) { - let propSource: string[] = generatePropsSource(object, PropType.hostObject); - let methodsSource: string[] = generateMethodsSource(object, PropType.hostObject); - return `${object.name}::${object.name}(ExecutingContext *context, - Native${object.name} *nativePtr) - : HostObject(context, "${object.name}"), m_nativePtr(nativePtr) { -} -JSValue ${object.name}::callNativeMethods(const char *method, int32_t argc, - NativeValue *argv) { - if (m_nativePtr->callNativeMethods == nullptr) { - return JS_ThrowTypeError(m_ctx, "Failed to call native dart methods: callNativeMethods not initialized."); - } - - std::u16string methodString; - fromUTF8(method, methodString); - - NativeString m{ - reinterpret_cast(methodString.c_str()), - static_cast(methodString.size()) - }; - - NativeValue nativeValue{}; - m_nativePtr->callNativeMethods(m_nativePtr, &nativeValue, &m, argc, argv); - JSValue returnValue = nativeValueToJSValue(m_context, nativeValue); - return returnValue; -} -${propSource.join('\n')} -${methodsSource.join('\n')} -`; -} enum PropType { hostObject, @@ -49,150 +15,6 @@ enum PropType { 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 = 'ExecutingContext::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(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(event->nativeEvent); - ${qjsCallFunc};`; - } else if (object.type === 'HostObject') { - getterCode = `auto *${instanceName} = static_cast<${classSubFix} *>(JS_GetOpaque(this_val, ${classId})); - return ${instanceName}->callNativeMethods("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}->getNativeProperty("${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 = ''; - if (object.type == 'Element') { - setterCode = `std::string key = "${p.name}"; - std::unique_ptr args_01 = stringToNativeString(key); - std::unique_ptr args_02 = jsValueToNativeString(ctx, argv[0]); - element->m_context->uiCommandBuffer() - ->addCommand(${instanceName}->m_eventTargetId, UICommand::setProperty, *args_01, *args_02, nullptr); - return JS_NULL;`; - } else { - setterCode = `NativeValue arguments[] = { - jsValueToNativeValue(ctx, argv[0]) - }; - return ${instanceName}->callNativeMethods("set${p.name[0].toUpperCase() + p.name.substring(1)}", 1, arguments);`; - } - - - 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."); - }` - } else if (argv.type === FunctionArgumentType.function) { - return `if (!JS_IsFunction(ctx, argv[${index}])) { - return JS_ThrowTypeError(ctx, "Failed to execute ${m.name}: ${index + 1}st arguments is not Function."); - }`; - } - - return ''; -} - function generateMethodArgumentsCheck(m: FunctionDeclaration, object: ClassObject | FunctionObject) { if (m.args.length == 0) return ''; @@ -201,298 +23,64 @@ function generateMethodArgumentsCheck(m: FunctionDeclaration, object: ClassObjec 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}' : ${requiredArgsCount} argument required, but %d present.", argc); } - ${argsCheck.join('\n ')} `; } -function generateDefaultNativeValue(m: FunctionArguments, index: number) { - switch(m.type) { +function generateTypeConverter(type: FunctionArgumentType | FunctionArgumentType[]): string { + if (Array.isArray(type)) { + return `TSSequence<${generateTypeConverter(type[0])}>`; + } + + switch(type) { + case FunctionArgumentType.int32: + return `TSInt32`; + case FunctionArgumentType.double: + return `TSDouble`; + case FunctionArgumentType.function: + return `TSCallback`; case FunctionArgumentType.boolean: - return `NativeValue argv${index} = Native_NewBool(false);`; - case FunctionArgumentType.number: - return `NativeValue argv${index} = Native_NewFloat64(NAN);`; + return `TSBoolean`; case FunctionArgumentType.string: - return `NativeValue argv${index} = Native_NewCString("");`; + return `TSDOMString`; + case FunctionArgumentType.object: + return `TSObject`; 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}->callNativeMethods("${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]; + case FunctionArgumentType.any: + return `TSAny`; } - - auto *nativeEvent = new Native${object.name}(); - nativeEvent->nativeEvent.type = jsValueToNativeString(ctx, eventTypeValue).release(); - - ${generateEventInstanceConstructorCode(object)} - - auto event = new ${object.name}Instance(this, reinterpret_cast(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(&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 '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}(ExecutingContext *context) : ${object.type}(context) { - ${classInheritCode} -} - -void bind${object.name}(ExecutingContext* 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; } function generateFunctionValueInit(object: FunctionObject) { - function generateValueInitHead(argument: FunctionArguments) { - if (argument.type === FunctionArgumentType.function) { - return `QJSFunction* ${argument.name} = `; - } - return `ScriptValue ${argument.name} = `; - } - function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { - if (argument.type === FunctionArgumentType.function) { - return `QJSFunction::Create(ctx, argv[${argsIndex}])`; - } - return `ScriptValue(ctx, argv[${argsIndex}]);`; + let type = generateTypeConverter(argument.type); + return `auto&& args_${argument.name} = Converter<${type}>::FromValue(ctx, argv[${argsIndex}], exception_state);`; } function generateInitBody(argument: FunctionArguments, argsIndex: number) { - if (argument.type === FunctionArgumentType.function) { - return `nullptr; -if (argc > ${argsIndex} && JS_IsFunction(ctx, argv[${argsIndex}])) { - ${argument.name} = QJSFunction::Create(ctx, argv[${argsIndex}]); -}`; - } else { - return `ScriptValue(ctx, JS_NULL); + function generateInitParams(type: FunctionArgumentType | FunctionArgumentType[]) { + if (type == FunctionArgumentType.any) { + return 'ctx'; + } + return ''; + } + + return `Converter>::ImplType args_${argument.name}{${generateInitParams(argument.type)}}; if (argc > ${argsIndex}) { - ${argument.name} = ScriptValue(ctx, argv[${argsIndex}]); + args_${argument.name} = Converter>::FromValue(ctx, argv[${argsIndex}], exception_state); }` - } } return object.declare.args.map((a, i) => { - let initHead = generateValueInitHead(a); - let initBody = a.required ? generateRequiredInitBody(a, i) : generateInitBody(a, i); - return addIndent(initHead + initBody, 2); + let body = a.required ? generateRequiredInitBody(a, i) : generateInitBody(a, i); + return addIndent(body, 2); }); } function generateCoreModuleCall(blob: Blob, object: FunctionObject) { - let params = object.declare.args.map(a => `${a.name}`); + let params = object.declare.args.map(a => `args_${a.name}`); let coreClassName = getClassName(blob); let returnValue = ''; @@ -504,7 +92,7 @@ function generateCoreModuleCall(blob: Blob, object: FunctionObject) { auto context = static_cast(JS_GetContextOpaque(ctx)); ExceptionState exception; -${returnValue}${coreClassName}::${object.declare.name}(context, ${params.join(', ')}, &exception); +${returnValue}${coreClassName}::${object.declare.name}(context, ${params.join(', ')}, exception); if (exception.HasException()) { return exception.ToQuickJS(); @@ -518,6 +106,8 @@ function generateFunctionSource(blob: Blob, object: FunctionObject) { let moduleCall = generateCoreModuleCall(blob, object); return `static JSValue ${object.declare.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { ${paramCheck} + ExceptionState exception_state; + ${varInit.join('\n')} ${moduleCall} }`; @@ -542,6 +132,7 @@ export function generateCppSource(blob: Blob) { #include "${blob.filename}.h" #include "bindings/qjs/member_installer.h" #include "bindings/qjs/qjs_function.h" +#include "bindings/qjs/converter_impl.h" #include "core/executing_context.h" #include "core/${blob.implement}.h" @@ -558,7 +149,7 @@ void QJS${getClassName(blob)}::InstallGlobalFunctions(ExecutingContext* context) ${installList.join('\n')} }; - MemberInstaller::InstallFunctions(context, context->global(), functionConfig); + MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); } }`; } diff --git a/bridge/scripts/code_generator/tsconfig.json b/bridge/scripts/code_generator/tsconfig.json index 781a19a406..4b8c8add00 100644 --- a/bridge/scripts/code_generator/tsconfig.json +++ b/bridge/scripts/code_generator/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2020", + "target": "es6", "lib": [ "es6", "es7", From 85bfe7d9aadeefffeff9752d6ca561c3c9cc0e7b Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Wed, 16 Mar 2022 16:44:52 +0800 Subject: [PATCH 026/498] fix: basic test pass. --- bridge/bindings/qjs/converter_impl.h | 24 ++++++++----------- bridge/core/executing_context.cc | 3 ++- bridge/core/executing_context.h | 2 +- bridge/core/executing_context_data.cc | 14 +++++++++-- bridge/core/executing_context_data.h | 2 ++ bridge/core/executing_context_test.cc | 4 ++-- bridge/core/frame/console.cc | 9 ++++--- bridge/core/frame/console.h | 2 +- .../frame/window_or_worker_global_scope.cc | 6 ++--- bridge/core/page.cc | 2 +- bridge/foundation/native_value.cc | 2 +- bridge/foundation/ui_command_buffer.cc | 2 +- .../code_generator/bin/code_generator.js | 2 +- bridge/test/kraken_test_context.cc | 2 +- bridge/test/kraken_test_env.cc | 2 +- bridge/test/run_integration_test.cc | 2 +- 16 files changed, 44 insertions(+), 36 deletions(-) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 440f0c35f7..cfea8ce31d 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -53,21 +53,17 @@ struct Converter, std::enable_if_t \ - struct Converter, std::enable_if_t::ImplType, std::SMART_POINTER>>> : 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); \ - } \ - }; +template +struct Converter, std::enable_if_t::ImplType, TSDOMString::ImplType>>> : public ConverterBase> { + using ImplType = typename Converter::ImplType; -DEFINE_OPTIONAL_SMART_POINTER_TEMPLATE(unique_ptr); -DEFINE_OPTIONAL_SMART_POINTER_TEMPLATE(shared_ptr); + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { + if (JS_IsUndefined(value)) { + return nullptr; + } + return Converter::FromValue(ctx, value, exception); + } +}; // Optional value for arithmetic value template diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index b3b0c767cc..d9daf5cf98 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -72,6 +72,7 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& gc_tracker_ = makeGarbageCollected(ctx()); JS_DefinePropertyValueStr(ctx_, global_object_, "_gc_tracker_", gc_tracker_->ToQuickJS(), JS_PROP_NORMAL); + DefineGlobalProperty("__GC_Tracker__", contextData()->constructorForType(ExecutionContextGCTracker::GetStaticWrapperTypeInfo())); runningContexts++; @@ -202,7 +203,7 @@ bool ExecutingContext::HandleException(JSValue* exc) { bool ExecutingContext::HandleException(ScriptValue* exc) { JSValue value = exc->ToQuickJS(); - HandleException(&value); + return HandleException(&value); } JSValue ExecutingContext::Global() { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 43800dde10..eb19cafc4a 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -81,7 +81,7 @@ class ExecutingContext { JSValue Global(); JSContext* ctx(); static JSRuntime* runtime(); - FORCE_INLINE int32_t contextid() const { return context_id_; }; + FORCE_INLINE int32_t contextId() const { return context_id_; }; void* owner(); bool HandleException(JSValue* exc); bool HandleException(ScriptValue* exc); diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc index 3a33ef59e6..17f9f0086d 100644 --- a/bridge/core/executing_context_data.cc +++ b/bridge/core/executing_context_data.cc @@ -28,7 +28,7 @@ JSValue ExecutionContextData::prototypeForType(const WrapperTypeInfo* type) { JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* type) { JSContext* ctx = m_context->ctx(); - JSClassID class_id; + JSClassID class_id{0}; // Allocate a new unique classID from QuickJS. JS_NewClassID(&class_id); @@ -36,7 +36,7 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty JSClassDef def{}; def.class_name = type->className; def.call = type->callFunc; - JS_NewClass(m_context->runtime(), type->classId, &def); + JS_NewClass(m_context->runtime(), class_id, &def); // Create class object and prototype object. JSValue classObject = constructor_map_[type] = JS_NewObjectClass(m_context->ctx(), class_id); @@ -69,4 +69,14 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty return classObject; } +void ExecutionContextData::Dispose() { + for(auto& entry: prototype_map_) { + JS_FreeValueRT(m_context->runtime(), entry.second); + } + + for(auto& entry: constructor_map_) { + JS_FreeValueRT(m_context->runtime(), entry.second); + } +} + } // namespace kraken diff --git a/bridge/core/executing_context_data.h b/bridge/core/executing_context_data.h index 90177d800f..ad88b2dcd0 100644 --- a/bridge/core/executing_context_data.h +++ b/bridge/core/executing_context_data.h @@ -27,6 +27,8 @@ class ExecutionContextData final { // Returns the prototype object that is appropriately initialized. JSValue prototypeForType(const WrapperTypeInfo* type); + void Dispose(); + private: JSValue constructorForIdSlowCase(const WrapperTypeInfo* type); std::unordered_map constructor_map_; diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 7727ce30bd..77780b22d8 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -232,7 +232,7 @@ TEST(Context, accessGetUICommandItemsAfterDisposed) { int32_t contextId; { auto bridge = TEST_init(); - contextId = bridge->getContext()->contextid(); + contextId = bridge->getContext()->contextId(); } EXPECT_EQ(getUICommandItems(contextId), nullptr); @@ -245,7 +245,7 @@ TEST(Context, disposeContext) { auto bridge = static_cast(getPage(contextId)); static bool disposed = false; bridge->disposeCallback = [](kraken::KrakenPage* bridge) { disposed = true; }; - disposePage(bridge->getContext()->contextid()); + disposePage(bridge->getContext()->contextId()); EXPECT_EQ(disposed, true); } diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 2b11ea52bd..6fd9f78115 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -9,14 +9,13 @@ namespace kraken { -void Console::__kraken_print__(ExecutingContext* context, ScriptValue& logValue, ScriptValue& levelValue, ExceptionState* exception) { +void Console::__kraken_print__(ExecutingContext* context, std::unique_ptr& log, std::unique_ptr& level, ExceptionState& exception) { std::stringstream stream; - - std::string buffer = logValue.toCString(); + std::string buffer = nativeStringToStdString(log.get()); stream << buffer; -// std::string logLevel = levelValue.isEmpty() ? "info" : levelValue.toCString(); -// printLog(context->getContextId(), stream, logLevel, nullptr); + std::string logLevel = level == nullptr ? "info" : nativeStringToStdString(level.get()); + printLog(context->contextId(), stream, logLevel, nullptr); } } // namespace kraken diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index b63e8bbf59..b7fef671e9 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -13,7 +13,7 @@ namespace kraken { class Console final { public: - static void __kraken_print__(ExecutingContext* context, ScriptValue& log, ScriptValue& level, ExceptionState* exception); + static void __kraken_print__(ExecutingContext* context, std::unique_ptr& log, std::unique_ptr& level, ExceptionState& exception); }; } // namespace kraken diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 9e613d09c7..fcdb1d2c49 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -59,7 +59,7 @@ int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, std::shared // Create a timer object to keep track timer callback. auto timer = DOMTimer::create(context, handler); - auto timerId = context->dartMethodPtr()->setTimeout(timer.get(), context->contextid(), handleTransientCallback, timeout); + auto timerId = context->dartMethodPtr()->setTimeout(timer.get(), context->contextId(), handleTransientCallback, timeout); // Register timerId. timer->setTimerId(timerId); @@ -78,7 +78,7 @@ int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, std::share // Create a timer object to keep track timer callback. auto timer = DOMTimer::create(context, handler); - uint32_t timerId = context->dartMethodPtr()->setInterval(timer.get(), context->contextid(), handlePersistentCallback, timeout); + uint32_t timerId = context->dartMethodPtr()->setInterval(timer.get(), context->contextId(), handlePersistentCallback, timeout); // Register timerId. timer->setTimerId(timerId); @@ -93,7 +93,7 @@ void WindowOrWorkerGlobalScope::clearTimeout(ExecutingContext* context, int32_t return; } - context->dartMethodPtr()->clearTimeout(context->contextid(), timerId); + context->dartMethodPtr()->clearTimeout(context->contextId(), timerId); context->Timers()->removeTimeoutById(timerId); } diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 07b8e341ad..15bd1b1574 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -23,7 +23,7 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : c contextId, [](ExecutingContext* context, const char* message) { if (context->dartMethodPtr()->onJsError != nullptr) { - context->dartMethodPtr()->onJsError(context->contextid(), message); + context->dartMethodPtr()->onJsError(context->contextId(), message); } KRAKEN_LOG(ERROR) << message << std::endl; }, diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index f499b81966..3144aff934 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -163,7 +163,7 @@ void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int auto* promiseContext = static_cast(callbackContext); if (!promiseContext->context->IsValid()) return; - if (promiseContext->context->contextid() != contextId) + if (promiseContext->context->contextId() != contextId) return; auto* context = promiseContext->context; diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index d821a661eb..d72eaba618 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -13,7 +13,7 @@ UICommandBuffer::UICommandBuffer(ExecutingContext* context) : m_context(context) void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate) { if (batchedUpdate) { - m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextid()); + m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); update_batched = true; } diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 8dd3126025..bb4aff710d 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -33,7 +33,7 @@ let blobs = files.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 Blob(path.join(source, file), dist, filename, implement); -}).filter(blob => blob.filename === 'qjs_module_manager'); +}).filter(blob => blob.filename === 'qjs_console'); for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index 8ef4d602e5..1a38f8ff62 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -151,7 +151,7 @@ static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, for (int i = 0; i < length; i++) { auto mouse = new MousePointer(); JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); - mouse->contextId = context->contextid(); + 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); diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 6abe3d8189..a3f480019b 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -119,7 +119,7 @@ uint32_t TEST_requestAnimationFrame(kraken::FrameCallback* frameCallback, int32_ JSFrameCallback* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); th->handler = handler; th->callback = frameCallback; - th->contextId = context->contextid(); + th->contextId = context->contextId(); int32_t id = callbackId++; th->callbackId = id; diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index e9b2b3e5a2..da748017a6 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -37,7 +37,7 @@ TEST(IntegrationTest, runSpecs) { std::string code = readTestSpec(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - executeTest(context->contextid(), [](int32_t contextId, NativeString* status) -> void* { + executeTest(context->contextId(), [](int32_t contextId, NativeString* status) -> void* { KRAKEN_LOG(VERBOSE) << "done"; return nullptr; }); From 565c6daeb0d0bc26978dfd2b98947f97d6f0fc76 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 17 Mar 2022 16:47:10 +0800 Subject: [PATCH 027/498] feat: regenerate blob code. --- bridge/CMakeLists.txt | 16 +- bridge/bindings/qjs/converter_impl.h | 98 +++--- bridge/bindings/qjs/{ts_type.h => idl_type.h} | 29 +- bridge/bindings/qjs/qjs_blob.cc | 297 ------------------ bridge/bindings/qjs/qjs_blob.h | 38 --- bridge/bindings/qjs/qjs_blob_property_bag.cc | 23 -- ...arraybuffer_arraybufferview_blob_string.cc | 31 -- ..._arraybuffer_arraybufferview_blob_string.h | 63 ---- bridge/bindings/qjs/script_promise.cc | 6 + bridge/bindings/qjs/script_promise.h | 23 ++ bridge/core/fileapi/blob.cc | 30 +- bridge/core/fileapi/blob.d.ts | 2 - bridge/core/fileapi/blob.h | 26 +- bridge/core/fileapi/blob_part.cc | 31 ++ bridge/core/fileapi/blob_part.h | 47 +++ bridge/core/fileapi/blob_property_bag.cc | 14 + .../fileapi/blob_property_bag.h} | 13 +- .../code_generator/bin/code_generator.js | 2 +- bridge/scripts/code_generator/src/analyzer.ts | 53 ++-- .../scripts/code_generator/src/declaration.ts | 19 +- .../code_generator/src/generate_header.ts | 178 ++--------- .../code_generator/src/genereate_source.ts | 235 +++++++++++--- bridge/scripts/code_generator/src/utils.ts | 4 + 23 files changed, 496 insertions(+), 782 deletions(-) rename bridge/bindings/qjs/{ts_type.h => idl_type.h} (52%) delete mode 100644 bridge/bindings/qjs/qjs_blob.cc delete mode 100644 bridge/bindings/qjs/qjs_blob.h delete mode 100644 bridge/bindings/qjs/qjs_blob_property_bag.cc delete mode 100644 bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc delete mode 100644 bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h create mode 100644 bridge/bindings/qjs/script_promise.cc create mode 100644 bridge/bindings/qjs/script_promise.h create mode 100644 bridge/core/fileapi/blob_part.cc create mode 100644 bridge/core/fileapi/blob_part.h create mode 100644 bridge/core/fileapi/blob_property_bag.cc rename bridge/{bindings/qjs/qjs_blob_property_bag.h => core/fileapi/blob_property_bag.h} (50%) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 710602a038..d2798d75ae 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -181,7 +181,7 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") list(APPEND BRIDGE_SOURCE # Binding files - bindings/qjs/ts_type.h + bindings/qjs/idl_type.h bindings/qjs/converter.h bindings/qjs/converter_impl.h bindings/qjs/binding_initializer.cc @@ -201,6 +201,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/qjs_function.h bindings/qjs/script_value.cc bindings/qjs/script_value.h + bindings/qjs/script_promise.cc + bindings/qjs/script_promise.h bindings/qjs/atom_string.cc bindings/qjs/atom_string.h bindings/qjs/exception_state.cc @@ -213,12 +215,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/qjs_window.h bindings/qjs/qjs_page.cc bindings/qjs/qjs_page.h - bindings/qjs/qjs_blob.cc - bindings/qjs/qjs_blob.h - bindings/qjs/qjs_blob_property_bag.cc - bindings/qjs/qjs_blob_property_bag.h - bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h - bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc # Core sources core/executing_context.cc @@ -230,6 +226,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dart_methods.h core/fileapi/blob.h core/fileapi/blob.cc + core/fileapi/blob_part.cc + core/fileapi/blob_part.h + core/fileapi/blob_property_bag.cc + core/fileapi/blob_property_bag.h core/frame/console.cc core/frame/console.h core/frame/dom_timer.cc @@ -272,6 +272,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_console.h out/qjs_module_manager.cc out/qjs_module_manager.h + out/qjs_blob.cc + out/qjs_blob.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index cfea8ce31d..89268f47de 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -9,40 +9,21 @@ #include #include "atom_string.h" #include "converter.h" +#include "core/fileapi/blob_part.h" +#include "core/fileapi/blob_property_bag.h" +#include "idl_type.h" #include "native_string_utils.h" -#include "ts_type.h" namespace kraken { -// 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); - } -}; - -// Optional value for ScriptValue +struct is_shared_ptr : std::false_type {}; template -struct Converter, std::enable_if_t::ImplType, ScriptValue>>> : public ConverterBase> { - using ImplType = typename Converter::ImplType; +struct is_shared_ptr> : std::true_type {}; - static ScriptValue FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { - if (JS_IsUndefined(value)) { - return ScriptValue::Empty(ctx); - } - return Converter::FromValue(ctx, value, exception); - } -}; - -// Optional value for TSCallback +// Optional value for pointer value. template -struct Converter, std::enable_if_t::ImplType, TSCallback::ImplType>>> : public ConverterBase> { +struct Converter, std::enable_if_t::ImplType>::value>> : public ConverterBase> { using ImplType = typename Converter::ImplType; static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { @@ -54,7 +35,7 @@ struct Converter, std::enable_if_t -struct Converter, std::enable_if_t::ImplType, TSDOMString::ImplType>>> : public ConverterBase> { +struct Converter, std::enable_if_t::ImplType>::value>> : public ConverterBase> { using ImplType = typename Converter::ImplType; static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { @@ -67,7 +48,7 @@ struct Converter, std::enable_if_t -struct Converter, std::enable_if_t::ImplType>::value>> : public ConverterBase> { +struct Converter, std::enable_if_t::ImplType>::value>> : public ConverterBase> { using ImplType = typename Converter::ImplType; static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { @@ -80,7 +61,7 @@ struct Converter, std::enable_if_t -struct Converter : public ConverterBase { +struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return ScriptValue(ctx, value); @@ -88,10 +69,17 @@ struct Converter : public ConverterBase { static JSValue ToValue(JSContext* ctx, const ScriptValue& value) { return value.ToQuickJS(); } }; +template<> +struct Converter> : public ConverterBase> { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return ScriptValue(ctx, value); + } +}; // Boolean template <> -struct Converter : public ConverterBase { +struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return JS_ToBool(ctx, value); @@ -102,7 +90,7 @@ struct Converter : public ConverterBase { // Uint32 template <> -struct Converter : public ConverterBase { +struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); uint32_t v; @@ -114,7 +102,7 @@ struct Converter : public ConverterBase { }; template <> -struct Converter : public ConverterBase { +struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); double v; @@ -126,17 +114,18 @@ struct Converter : public ConverterBase { }; template <> -struct Converter : public ConverterBase { +struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return jsValueToNativeString(ctx, value); } 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 { +struct Converter : public ConverterBase { static AtomString FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); JSAtom atom = JS_ValueToAtom(ctx, value); @@ -149,18 +138,17 @@ struct Converter : public ConverterBase { }; template -struct Converter> : public ConverterBase> { - using ImplType = typename TSSequence::ImplType>::ImplType; +struct Converter> : public ConverterBase> { + using ImplType = typename IDLSequence::ImplType>::ImplType; static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); assert(JS_IsArray(ctx, value)); ImplType v; - uint32_t length = Converter::FromValue(ctx, JS_GetPropertyStr(ctx, value, "length"), exception_state); + uint32_t length = Converter::FromValue(ctx, JS_GetPropertyStr(ctx, value, "length"), exception_state); v.reserve(length); - v.resize(length); for (uint32_t i = 0; i < length; i++) { auto&& item = Converter::FromValue(ctx, JS_GetPropertyUint32(ctx, value, i), exception_state); @@ -175,8 +163,20 @@ struct Converter> : public ConverterBase> { } }; +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 { +struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); if (!JS_IsFunction(ctx, value)) { @@ -187,6 +187,26 @@ struct Converter : public ConverterBase { } }; +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); + } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/ts_type.h b/bridge/bindings/qjs/idl_type.h similarity index 52% rename from bridge/bindings/qjs/ts_type.h rename to bridge/bindings/qjs/idl_type.h index e8c3fe6f18..eef61d846e 100644 --- a/bridge/bindings/qjs/ts_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -11,51 +11,54 @@ namespace kraken { -struct TSTypeBase { +struct IDLTypeBase { using ImplType = void; }; template -struct TSTypeBaseHelper { +struct IDLTypeBaseHelper { using ImplType = T; }; +class ScriptValue; // Any -struct TSAny final : public TSTypeBaseHelper {}; +struct IDLAny final : public IDLTypeBaseHelper {}; template -struct TSOptional final : public TSTypeBase { +struct IDLOptional final : public IDLTypeBase { using ImplType = typename Converter::ImplType; }; // Bool -struct TSBoolean final : public TSTypeBaseHelper {}; +struct IDLBoolean final : public IDLTypeBaseHelper {}; // Primitive types -struct TSUint32 final : public TSTypeBaseHelper {}; -struct TSDouble final : public TSTypeBaseHelper {}; +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 TSDOMString final : public TSTypeBaseHelper> {}; +struct IDLDOMString final : public IDLTypeBaseHelper> {}; class AtomString; -struct TSAtomString final : public TSTypeBaseHelper {}; +struct IDLAtomString final : public IDLTypeBaseHelper {}; // https://developer.mozilla.org/en-US/docs/Web/API/USVString -struct TSUSVString final : public TSTypeBaseHelper {}; +struct IDLUSVString final : public IDLTypeBaseHelper {}; // Object -struct TSObject : public TSTypeBaseHelper {}; +struct IDLObject : public IDLTypeBaseHelper {}; +class QJSFunction; // Function callback -struct TSCallback : public TSTypeBaseHelper> { +struct IDLCallback : public IDLTypeBaseHelper> { using ImplType = typename Converter>::ImplType; }; // Sequence template -struct TSSequence final : public TSTypeBase { +struct IDLSequence final : public IDLTypeBase { using ImplType = typename std::vector; }; diff --git a/bridge/bindings/qjs/qjs_blob.cc b/bridge/bindings/qjs/qjs_blob.cc deleted file mode 100644 index 3d8c9eeb25..0000000000 --- a/bridge/bindings/qjs/qjs_blob.cc +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "qjs_blob.h" -#include "member_installer.h" -#include "core/executing_context.h" -#include "core/fileapi/blob.h" -#include "converter_impl.h" -#include "qjs_union_arraybuffer_arraybufferview_blob_string.h" - -namespace kraken { - - -//IMPL_PROPERTY_GETTER(Blob, type)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// auto* blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); -// return JS_NewString(blob->m_ctx, blob->mimeType.empty() ? "" : blob->mimeType.c_str()); -//} -// -//IMPL_PROPERTY_GETTER(Blob, size)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// auto* blob = static_cast(JS_GetOpaque(this_val, Blob::classID)); -// return JS_NewFloat64(blob->m_ctx, blob->_size); -//} -// -//IMPL_FUNCTION(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::classID)); -// -// JS_DupValue(ctx, blob->jsObject); -// -// auto* promiseContext = new PromiseContext{blob, blob->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->context()->promise_job_list); -// -// // TODO: remove setTimeout -// getDartMethod()->setTimeout(promiseContext, blob->context()->getContextId(), callback, 0); -// -// return promise; -//} -// -//IMPL_FUNCTION(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::classID)); -// 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 = Blob::create(ctx, std::move(blob->_data), mimeType); -// return newBlob->toQuickJS(); -// } -// 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 = Blob::create(ctx, std::move(newData), mimeType); -// return newBlob->toQuickJS(); -//} -// -//IMPL_FUNCTION(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::classID)); -// JS_DupValue(ctx, blob->jsObject); -// -// auto* promiseContext = new PromiseContext{blob, blob->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->context()->promise_job_list); -// -// getDartMethod()->setTimeout(promiseContext, blob->context()->getContextId(), callback, 0); -// -// return promise; -//} - -const WrapperTypeInfo& Blob::wrapper_type_info_ = QJSBlob::m_wrapperTypeInfo; - -//const WrapperTypeInfo Blob::wrapper_type_info_ = QJSBlob::m_wrapperTypeInfo; - -static JSValue arrayBuffer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - return JS_NULL; -} - -static JSValue slice(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* blob = toScriptWrappable(this_val); - Blob* return_value; - ExceptionState exception_state; - - do { - if (argc == 0) { - return_value = blob->Slice(&exception_state); - break; - } - double args_start = Converter>::FromValue(ctx, argv[0], exception_state); - if (exception_state.HasException()) { - return exception_state.ToQuickJS(); - } - - if (argc <= 1) { - return_value = blob->Slice(args_start, &exception_state); - } - - } while (false); - - if (exception_state.HasException()) { - return exception_state.ToQuickJS(); - } - - return return_value->ToQuickJS(); -} - -static JSValue text(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - return JS_NULL; -} - -static JSValue sizeAttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - return JS_NULL; -} - -static JSValue sizeAttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - return JS_NULL; -} - -static JSValue typeAttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - return JS_NULL; -} - -static JSValue typeAttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - return JS_NULL; -} - - -JSValue QJSBlob::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { - if (argc == 0) { - auto* blob = Blob::create(ctx); - return blob->ToQuickJS(); - } - - ExceptionState exception_state; - std::vector> a = - Converter>::FromValue(ctx, argv[0], exception_state); - -// 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"); -// } -// -// auto* context = static_cast(JS_GetContextOpaque(ctx)); -// BlobBuilder builder; -// -// if (argc == 1 || JS_IsUndefined(optionValue)) { -// builder.append(*context, ScriptValue(ctx, arrayValue)); -// auto* blob = Blob::create(ctx, builder.finalize()); -// return blob->toQuickJS(); -// } -// -// if (!JS_IsObject(optionValue)) { -// return JS_ThrowTypeError(ctx, -// "Failed to construct 'Blob': parameter 2 ('options') " -// "is not an object"); -// } - -// ScriptAtom mineType = ScriptAtom(ctx, "type"); -// JSValue mimeTypeValue = JS_GetProperty(ctx, optionValue, mimeTypeKey); -// builder.append(*context, mimeTypeValue); -// const char* cMineType = JS_ToCString(ctx, mimeTypeValue); -// std::string mimeType = std::string(cMineType); -// -// auto* blob = Blob::create(ctx, builder.finalize(), mimeType); -// -// JS_FreeValue(ctx, mimeTypeValue); -// JS_FreeCString(ctx, mimeType.c_str()); -// JS_FreeAtom(ctx, mimeTypeKey); -// -// return blob->toQuickJS(); -} - -void QJSBlob::Install(ExecutingContext* context) { - InstallConstructor(context); - InstallPrototypeMethods(context); - InstallPrototypeProperties(context); -} - -void QJSBlob::InstallConstructor(ExecutingContext* context) { - const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); - JSValue constructor = context->contextData()->constructorForType(wrapperTypeInfo); - - std::initializer_list attributeConfig { - {"Blob", nullptr, nullptr, constructor} - }; - MemberInstaller::InstallAttributes(context, context->Global(), attributeConfig); -} - -void QJSBlob::InstallPrototypeMethods(ExecutingContext* context) { - const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); - JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); - - std::initializer_list attributesConfig { - {"size", sizeAttributeGetCallback, sizeAttributeSetCallback}, - {"type", typeAttributeGetCallback, typeAttributeSetCallback} - }; - - MemberInstaller::InstallAttributes(context, prototype, attributesConfig); -} - -void QJSBlob::InstallPrototypeProperties(ExecutingContext* context) { - const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); - JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); - - std::initializer_list functionConfig { - {"arrayBuffer", arrayBuffer, 0}, - {"slice", slice, 3}, - {"text", text, 0} - }; - - MemberInstaller::InstallFunctions(context, prototype, functionConfig); -} - -} diff --git a/bridge/bindings/qjs/qjs_blob.h b/bridge/bindings/qjs/qjs_blob.h deleted file mode 100644 index ec0a8c1bac..0000000000 --- a/bridge/bindings/qjs/qjs_blob.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_QJS_BLOB_H -#define KRAKENBRIDGE_QJS_BLOB_H - -#include -#include "wrapper_type_info.h" -#include "core/executing_context.h" - -namespace kraken { - -class ExecutingContext; - -class QJSBlob final { - public: - static void Install(ExecutingContext* context); - - static WrapperTypeInfo* GetWrapperTypeInfo() { - return const_cast(&m_wrapperTypeInfo); - } - - private: - static JSValue ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); - constexpr static const WrapperTypeInfo m_wrapperTypeInfo = {"Blob", nullptr, ConstructorCallback}; - - static void InstallPrototypeMethods(ExecutingContext* context); - static void InstallPrototypeProperties(ExecutingContext* context); - static void InstallConstructor(ExecutingContext* context); - - friend class Blob; -}; - -} - -#endif // KRAKENBRIDGE_QJS_BLOB_H diff --git a/bridge/bindings/qjs/qjs_blob_property_bag.cc b/bridge/bindings/qjs/qjs_blob_property_bag.cc deleted file mode 100644 index 9afb2e4cc2..0000000000 --- a/bridge/bindings/qjs/qjs_blob_property_bag.cc +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "qjs_blob_property_bag.h" - -namespace kraken { - -BlobPropertyBag* BlobPropertyBag::create(ExecutingContext* context, JSValue value, ExceptionState* exceptionState) { - BlobPropertyBag* dictionary = new BlobPropertyBag(); - - if (JS_IsUndefined(value)) { - - } - return nullptr; -} - -void BlobPropertyBag::fillMemberFromQuickjsObject(ExecutingContext* context, JSValue value, ExceptionState* exceptionState) { - -} - -} diff --git a/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc b/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc deleted file mode 100644 index 147bea69e8..0000000000 --- a/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.cc +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "qjs_union_arraybuffer_arraybufferview_blob_string.h" - -namespace kraken { - -std::shared_ptr QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - if (JS_IsString(value)) { - const char* buffer = JS_ToCString(ctx, value); - auto result = std::make_shared(ctx, buffer); - JS_FreeCString(ctx, buffer); - return result; - } - - return nullptr; -} - -JSValue QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString::ToQuickJS(JSContext* ctx) const{ - switch(content_type_) { - case ContentType::kString: { - return JS_NewString(ctx, member_string_.c_str()); - } - case ContentType::kBlob: { - } - } -} - -} diff --git a/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h b/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h deleted file mode 100644 index 9c0dc6d24d..0000000000 --- a/bridge/bindings/qjs/qjs_union_arraybuffer_arraybufferview_blob_string.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_QJS_UNION_ARRAYBUFFER_ARRAYBUFFERVIEW_BLOB_STRING_H -#define KRAKENBRIDGE_QJS_UNION_ARRAYBUFFER_ARRAYBUFFERVIEW_BLOB_STRING_H - -#include -#include - -#include "core/fileapi/blob.h" -#include "exception_state.h" -#include "ts_type.h" -#include "converter_impl.h" - -namespace kraken { - -class QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString { - public: - enum class ContentType { - kArrayBuffer, kArrayBufferView, kBlob, kString - }; - - static std::shared_ptr Create( - JSContext* ctx, - JSValue value, - ExceptionState& exception_state); - - JSValue ToQuickJS(JSContext* ctx) const; - - explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, uint8_t* arrayBuffer, uint32_t length): content_type_(ContentType::kArrayBuffer) {}; - explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, uint8_t* buffer, size_t byte_offset, size_t byte_length, size_t byte_per_element, uint32_t length): content_type_(ContentType::kArrayBufferView) {}; - explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, const std::string& value): content_type_(ContentType::kString), member_string_(value) {}; - explicit QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString(JSContext* ctx, Blob* blob): content_type_(ContentType::kBlob) {}; - -private: - ContentType content_type_; - std::string member_string_; - uint32_t* bytes{nullptr}; -}; - -// Special types -struct TSUnionArrayBufferOrArrayBufferViewOrBlobOrString : public TSTypeBaseHelper> { - using ImplType = typename std::shared_ptr; -}; - -template <> -struct Converter : public ConverterBase { - using ImplType = TSUnionArrayBufferOrArrayBufferViewOrBlobOrString::ImplType; - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - assert(!JS_IsException(value)); - return QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString::Create(ctx, value, exception_state); - } - - static JSValue ToValue(JSContext* ctx, QJSUnionArrayBufferOrArrayBufferViewOrBlobOrString* data) { return data->ToQuickJS(ctx); } -}; - -} - -class qjs_union_arraybuffer_arraybufferview_blob_string {}; - -#endif // KRAKENBRIDGE_QJS_UNION_ARRAYBUFFER_ARRAYBUFFERVIEW_BLOB_STRING_H diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc new file mode 100644 index 0000000000..5c60699dc1 --- /dev/null +++ b/bridge/bindings/qjs/script_promise.cc @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_promise.h" diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h new file mode 100644 index 0000000000..37cc7a54be --- /dev/null +++ b/bridge/bindings/qjs/script_promise.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ + +#include "foundation/macros.h" + +namespace kraken { + +// ScriptPromise is the class for representing Promise values in C++ world. +// ScriptPromise holds a Promise. +// So holding a ScriptPromise as a member variable in DOM object causes +// memory leaks since it has a reference from C++ to QuickJS. +class ScriptPromise final { + KRAKEN_DISALLOW_NEW(); +}; + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index f952146f59..fe8869c4e7 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -3,34 +3,28 @@ * Author: Kraken Team. */ -#include "bindings/qjs/qjs_blob.h" #include "blob.h" +#include "bindings/qjs/qjs_blob.h" namespace kraken { -Blob* Blob::create(JSContext* ctx) { - return makeGarbageCollected(ctx); -} -Blob* Blob::create(JSContext* ctx, std::vector&& data) { - return makeGarbageCollected(ctx, std::forward>(data)); -} -Blob* Blob::create(JSContext* ctx, std::vector&& data, std::string& mime) { - return makeGarbageCollected(ctx, std::forward>(data), mime); +Blob* Blob::Create(ExecutingContext* context, std::vector> data, std::shared_ptr property, ExceptionState& exception_state) { + // return makeGarbageCollected(ctx, std::forward>(data), mime); } // -//void BlobBuilder::append(ExecutingContext& context, Blob* blob) { +// void BlobBuilder::append(ExecutingContext& context, Blob* blob) { // std::vector blobData = blob->_data; // _data.reserve(_data.size() + blobData.size()); // _data.insert(_data.end(), blobData.begin(), blobData.end()); //} // -//void BlobBuilder::append(ExecutingContext& context, const std::string& value) { +// void BlobBuilder::append(ExecutingContext& context, const std::string& value) { // std::vector strArr(value.begin(), value.end()); // _data.reserve(_data.size() + strArr.size()); // _data.insert(_data.end(), strArr.begin(), strArr.end()); //} // -//void BlobBuilder::append(ExecutingContext& context, ScriptValue value) { +// void BlobBuilder::append(ExecutingContext& context, ScriptValue value) { // if (value.isString()) { // // } else if (value.isArray()) { @@ -67,7 +61,7 @@ Blob* Blob::create(JSContext* ctx, std::vector&& data, std::string& mim // } //} // -//std::vector BlobBuilder::finalize() { +// std::vector BlobBuilder::finalize() { // return std::move(_data); //} @@ -79,19 +73,21 @@ uint8_t* Blob::bytes() { return _data.data(); } -const char * Blob::GetHumanReadableName() const { +const char* Blob::GetHumanReadableName() const { return "Blob"; } void Blob::Trace(GCVisitor* visitor) const {} void Blob::Dispose() const {} -Blob* Blob::Slice(ExceptionState* exception_state) { +Blob* Blob::slice(ExceptionState& exception_state) { return nullptr; } -Blob * Blob::Slice(int64_t start, ExceptionState* exception_state) { +Blob* Blob::slice(int64_t start, ExceptionState* exception_state) { return nullptr; } - +std::string Blob::type() { + return mime_type_; +} } // namespace kraken diff --git a/bridge/core/fileapi/blob.d.ts b/bridge/core/fileapi/blob.d.ts index 9bf3128261..71e084df19 100644 --- a/bridge/core/fileapi/blob.d.ts +++ b/bridge/core/fileapi/blob.d.ts @@ -4,7 +4,5 @@ interface Blob { arrayBuffer(): Promise; slice(start?: number, end?: number, contentType?: string): Blob; text(): Promise; - - prototype: Blob; new(blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; } diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index bffb4efe4e..e1e6d7faa1 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -11,30 +11,33 @@ #include "bindings/qjs/macros.h" #include "bindings/qjs/qjs_blob.h" #include "bindings/qjs/script_wrappable.h" +#include "blob_part.h" +#include "blob_property_bag.h" namespace kraken { -class BlobBuilder; - class Blob : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); + public: - static Blob* create(JSContext* ctx); - static Blob* create(JSContext* ctx, std::vector&& data); - static Blob* create(JSContext* ctx, std::vector&& data, std::string& mime); + static Blob* Create(ExecutingContext* context, + std::vector> data, + std::shared_ptr property, + ExceptionState& exception_state); Blob() = delete; - explicit Blob(JSContext* ctx): ScriptWrappable(ctx) {}; - explicit Blob(JSContext* ctx, std::vector&& data) : _size(data.size()), _data(std::move(data)), ScriptWrappable(ctx) {}; - explicit Blob(JSContext* ctx, std::vector&& data, std::string& mime) : mimeType(mime), _size(data.size()), _data(std::move(data)), ScriptWrappable(ctx){}; + explicit Blob(JSContext* ctx) : ScriptWrappable(ctx){}; + explicit Blob(JSContext* ctx, std::vector&& data) : _size(data.size()), _data(std::move(data)), ScriptWrappable(ctx){}; + explicit Blob(JSContext* ctx, std::vector&& data, std::string& mime) : mime_type_(mime), _size(data.size()), _data(std::move(data)), ScriptWrappable(ctx){}; /// get an pointer of bytes data from JSBlob uint8_t* bytes(); /// get bytes data's length int32_t size(); + std::string type(); - Blob* Slice(ExceptionState* exception_state); - Blob* Slice(int64_t start, ExceptionState* exception_state); + Blob* slice(ExceptionState& exception_state); + Blob* slice(int64_t start, ExceptionState* exception_state); const char* GetHumanReadableName() const override; void Trace(GCVisitor* visitor) const override; @@ -42,9 +45,8 @@ class Blob : public ScriptWrappable { private: size_t _size; - std::string mimeType; + std::string mime_type_; std::vector _data; - friend BlobBuilder; friend QJSBlob; }; diff --git a/bridge/core/fileapi/blob_part.cc b/bridge/core/fileapi/blob_part.cc new file mode 100644 index 0000000000..0348486847 --- /dev/null +++ b/bridge/core/fileapi/blob_part.cc @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "blob_part.h" + +namespace kraken { + +std::shared_ptr BlobPart::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsString(value)) { + const char* buffer = JS_ToCString(ctx, value); + auto result = std::make_shared(ctx, buffer); + JS_FreeCString(ctx, buffer); + return result; + } + + 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: { +// } +// } +} + +} diff --git a/bridge/core/fileapi/blob_part.h b/bridge/core/fileapi/blob_part.h new file mode 100644 index 0000000000..45d4fc7d3d --- /dev/null +++ b/bridge/core/fileapi/blob_part.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ +#define KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ + +#include +#include +#include +#include +#include "bindings/qjs/exception_state.h" + +namespace kraken { + +class Blob; + +class BlobPart { + public: + using ImplType = std::shared_ptr; + + enum class ContentType { + kArrayBuffer, kArrayBufferView, kBlob, kString + }; + + static std::shared_ptr Create( + JSContext* ctx, + JSValue value, + ExceptionState& exception_state); + + JSValue ToQuickJS(JSContext* ctx) const; + + explicit BlobPart(JSContext* ctx, uint8_t* arrayBuffer, uint32_t length): content_type_(ContentType::kArrayBuffer) {}; + explicit BlobPart(JSContext* ctx, uint8_t* buffer, size_t byte_offset, size_t byte_length, size_t byte_per_element, uint32_t length): content_type_(ContentType::kArrayBufferView) {}; + 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) {}; + + private: + ContentType content_type_; + std::string member_string_; + uint32_t* bytes{nullptr}; +}; + +} + +#endif // KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ diff --git a/bridge/core/fileapi/blob_property_bag.cc b/bridge/core/fileapi/blob_property_bag.cc new file mode 100644 index 0000000000..ba98c5698b --- /dev/null +++ b/bridge/core/fileapi/blob_property_bag.cc @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "blob_property_bag.h" + +namespace kraken { + +std::shared_ptr BlobPropertyBag::Create(JSContext* ctx, JSValue value, ExceptionState& exceptionState) { + return nullptr; +} + +} diff --git a/bridge/bindings/qjs/qjs_blob_property_bag.h b/bridge/core/fileapi/blob_property_bag.h similarity index 50% rename from bridge/bindings/qjs/qjs_blob_property_bag.h rename to bridge/core/fileapi/blob_property_bag.h index 3c6e925e0f..0bba922690 100644 --- a/bridge/bindings/qjs/qjs_blob_property_bag.h +++ b/bridge/core/fileapi/blob_property_bag.h @@ -3,16 +3,20 @@ * Author: Kraken Team. */ -#ifndef KRAKENBRIDGE_QJS_BLOB_PROPERTY_BAG_H -#define KRAKENBRIDGE_QJS_BLOB_PROPERTY_BAG_H +#ifndef KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ +#define KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ +#include +#include #include "core/executing_context.h" namespace kraken { class BlobPropertyBag final { public: - static BlobPropertyBag* create(ExecutingContext* context, JSValue value, ExceptionState* exceptionState); + using ImplType = std::shared_ptr; + + static std::shared_ptr Create(JSContext* ctx, JSValue value, ExceptionState& exceptionState); const std::string& type() const { return m_type; } @@ -23,4 +27,5 @@ class BlobPropertyBag final { } -#endif // KRAKENBRIDGE_QJS_BLOB_PROPERTY_BAG_H + +#endif // KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index bb4aff710d..123de3d06b 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -33,7 +33,7 @@ let blobs = files.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 Blob(path.join(source, file), dist, filename, implement); -}).filter(blob => blob.filename === 'qjs_console'); +}).filter(blob => blob.filename === 'qjs_blob'); for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; diff --git a/bridge/scripts/code_generator/src/analyzer.ts b/bridge/scripts/code_generator/src/analyzer.ts index c5e8777e35..254d693e1c 100644 --- a/bridge/scripts/code_generator/src/analyzer.ts +++ b/bridge/scripts/code_generator/src/analyzer.ts @@ -7,7 +7,6 @@ import { FunctionDeclaration, FunctionObject, PropsDeclaration, - PropsDeclarationKind, ReturnType } from './declaration'; import {generatorSource} from './generator'; @@ -33,25 +32,6 @@ function getHeritageType(heritage: HeritageClause) { 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 getFunctionReturnType(keyword: ts.TypeNode): ReturnType { switch (keyword.kind) { case ts.SyntaxKind.VoidKeyword: @@ -78,10 +58,12 @@ function getParameterName(name: ts.BindingName) : string { return ''; } -function getParameterType(type: ts.TypeNode): FunctionArgumentType | FunctionArgumentType[] { +export type ParameterType = FunctionArgumentType | string; + +function getParameterType(type: ts.TypeNode): ParameterType | ParameterType[] { if (type.kind == ts.SyntaxKind.ArrayType) { - let arrayType = type.kind as unknown as ts.ArrayTypeNode; - return [getParameterType(arrayType) as FunctionArgumentType]; + let arrayType = type as unknown as ts.ArrayTypeNode; + return [getParameterType(arrayType.elementType) as FunctionArgumentType]; } else if (type.kind === ts.SyntaxKind.StringKeyword) { return FunctionArgumentType.string; } else if (type.kind === ts.SyntaxKind.NumberKeyword) { @@ -92,6 +74,7 @@ function getParameterType(type: ts.TypeNode): FunctionArgumentType | FunctionArg return FunctionArgumentType.any; } else if (type.kind === ts.SyntaxKind.ObjectKeyword) { return FunctionArgumentType.object; + // @ts-ignore } else if (type.kind === ts.SyntaxKind.TypeReference) { let typeReference: ts.TypeReference = type as unknown as ts.TypeReference; // @ts-ignore @@ -103,6 +86,8 @@ function getParameterType(type: ts.TypeNode): FunctionArgumentType | FunctionArg } else if (identifier === 'double') { return FunctionArgumentType.double; } + + return identifier; } return FunctionArgumentType.any; @@ -125,13 +110,12 @@ 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(); + if (heritageType) obj.parent = heritageType.toString(); } obj.name = s.name.escapedText.toString(); @@ -146,8 +130,8 @@ function walkProgram(statement: ts.Statement) { let propKind = m.type; if (propKind) { - prop.kind = getPropKind(propKind); - if (prop.kind === PropsDeclarationKind.function) { + prop.type = getParameterType(propKind); + if (prop.type === FunctionArgumentType.function) { let f = (m.type as ts.FunctionTypeNode); let functionProps = prop as FunctionDeclaration; functionProps.args = []; @@ -160,20 +144,31 @@ function walkProgram(statement: ts.Statement) { 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); + 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); + }); + obj.construct = c; + break; } } }); diff --git a/bridge/scripts/code_generator/src/declaration.ts b/bridge/scripts/code_generator/src/declaration.ts index 24d114d41e..f0c1a481b4 100644 --- a/bridge/scripts/code_generator/src/declaration.ts +++ b/bridge/scripts/code_generator/src/declaration.ts @@ -1,3 +1,5 @@ +import {ParameterType} from "./analyzer"; + export enum FunctionArgumentType { // Basic types string, @@ -11,22 +13,12 @@ export enum FunctionArgumentType { export class FunctionArguments { name: string; - type: FunctionArgumentType | FunctionArgumentType[]; + type: ParameterType | ParameterType[]; required: boolean; } -export enum PropsDeclarationKind { - none, - string, - double, - int64, - boolean, - object, - function -} - export class PropsDeclaration { - kind: PropsDeclarationKind; + type: ParameterType | ParameterType[]; name: string; readonly: boolean; } @@ -43,9 +35,10 @@ export class FunctionDeclaration extends PropsDeclaration { export class ClassObject { name: string; - type: string; + parent: string; props: PropsDeclaration[] = []; methods: FunctionDeclaration[] = []; + construct: FunctionDeclaration; } export class FunctionObject { diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index f08ff6f05b..5d00bf6a4a 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -1,170 +1,35 @@ -import {ClassObject, FunctionObject, PropsDeclaration, PropsDeclarationKind} from "./declaration"; +import {ClassObject, FunctionObject, PropsDeclaration} from "./declaration"; import {uniqBy} from "lodash"; import {Blob} from "./blob"; import {addIndent, getClassName} 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} { - CallNativeMethods callNativeMethods{nullptr}; -}; - -class ${object.name} : public ${object.type} { -public: - ${object.name}() = delete; - explicit ${object.name}(ExecutionContext *context, Native${object.name} *nativePtr); - - JSValue callNativeMethods(const char* method, int32_t argc, - NativeValue *argv); - - - ${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)} -};`; +function generateInterfaceAdditionalHeader(object: any): [string, string, string] { + if (!(object instanceof ClassObject)) { + return ['', '', '']; } - 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 wrapperTypeInfo = `static WrapperTypeInfo* GetWrapperTypeInfo() { + return const_cast(&m_wrapperTypeInfo); + }`; - 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}; -}; + let wrapperTypeDefine = `static JSValue ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); + constexpr static const WrapperTypeInfo m_wrapperTypeInfo = {"Blob", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ConstructorCallback}; `; - return constructorHeader + '\n' + instanceHeaders; -} + let installFunctions = `static void InstallPrototypeMethods(ExecutingContext* context); + static void InstallPrototypeProperties(ExecutingContext* context); + static void InstallConstructor(ExecutingContext* context);`; -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; -} - -function generateFunctionHeader(blob: Blob, object: FunctionObject) { - return `class QJS${blob.filename[0].toUpperCase() + blob.filename.slice(1)} final { - public: - static void installGlobalFunctions(JSContext* ctx); -};`; + return [ + wrapperTypeInfo, + wrapperTypeDefine, + installFunctions + ]; } export function generateCppHeader(blob: Blob) { + let classObject = blob.objects.find(object => object instanceof ClassObject); + let interfaceDefines = generateInterfaceAdditionalHeader(classObject); return `/* * Copyright (C) 2021 Alibaba Inc. All rights reserved. * Author: Kraken Team. @@ -174,6 +39,7 @@ export function generateCppHeader(blob: Blob) { #define KRAKENBRIDGE_${blob.filename.toUpperCase()}_H #include +#include "bindings/qjs/wrapper_type_info.h" namespace kraken { @@ -182,8 +48,12 @@ class ExecutingContext; class QJS${getClassName(blob)} final { public: static void Install(ExecutingContext* context); + + ${interfaceDefines[0]} + ${interfaceDefines[1]} private: static void InstallGlobalFunctions(ExecutingContext* context); + ${interfaceDefines[2]} }; } diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index 2177962952..a22fb3971b 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -4,10 +4,11 @@ import { FunctionArguments, FunctionArgumentType, FunctionDeclaration, - FunctionObject, + FunctionObject, PropsDeclaration, ReturnType } from "./declaration"; -import {addIndent, getClassName} from "./utils"; +import {addIndent, getClassName, getMethodName} from "./utils"; +import {ParameterType} from "./analyzer"; enum PropType { hostObject, @@ -15,7 +16,7 @@ enum PropType { Event } -function generateMethodArgumentsCheck(m: FunctionDeclaration, object: ClassObject | FunctionObject) { +function generateMethodArgumentsCheck(m: FunctionDeclaration) { if (m.args.length == 0) return ''; let requiredArgsCount = 0; @@ -29,81 +30,102 @@ function generateMethodArgumentsCheck(m: FunctionDeclaration, object: ClassObjec `; } -function generateTypeConverter(type: FunctionArgumentType | FunctionArgumentType[]): string { +function generateTypeConverter(type: ParameterType | ParameterType[]): string { if (Array.isArray(type)) { - return `TSSequence<${generateTypeConverter(type[0])}>`; + return `IDLSequence<${generateTypeConverter(type[0])}>`; + } + + if (typeof type === 'string') { + return type; } switch(type) { case FunctionArgumentType.int32: - return `TSInt32`; + return `IDLInt32`; case FunctionArgumentType.double: - return `TSDouble`; + return `IDLDouble`; case FunctionArgumentType.function: - return `TSCallback`; + return `IDLCallback`; case FunctionArgumentType.boolean: - return `TSBoolean`; + return `IDLBoolean`; case FunctionArgumentType.string: - return `TSDOMString`; + return `IDLDOMString`; case FunctionArgumentType.object: - return `TSObject`; + return `IDLObject`; default: case FunctionArgumentType.any: - return `TSAny`; + return `IDLAny`; } } -function generateFunctionValueInit(object: FunctionObject) { +function generateFunctionValueInit(declare: FunctionDeclaration) { function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { let type = generateTypeConverter(argument.type); return `auto&& args_${argument.name} = Converter<${type}>::FromValue(ctx, argv[${argsIndex}], exception_state);`; } function generateInitBody(argument: FunctionArguments, argsIndex: number) { - function generateInitParams(type: FunctionArgumentType | FunctionArgumentType[]) { - if (type == FunctionArgumentType.any) { - return 'ctx'; - } + function generateInitParams(type: ParameterType | ParameterType[]) { return ''; } - return `Converter>::ImplType args_${argument.name}{${generateInitParams(argument.type)}}; + return `Converter>::ImplType args_${argument.name}{${generateInitParams(argument.type)}}; if (argc > ${argsIndex}) { - args_${argument.name} = Converter>::FromValue(ctx, argv[${argsIndex}], exception_state); + args_${argument.name} = Converter>::FromValue(ctx, argv[${argsIndex}], exception_state); }` } - return object.declare.args.map((a, i) => { + return declare.args.map((a, i) => { let body = a.required ? generateRequiredInitBody(a, i) : generateInitBody(a, i); return addIndent(body, 2); }); } -function generateCoreModuleCall(blob: Blob, object: FunctionObject) { - let params = object.declare.args.map(a => `args_${a.name}`); +function generateFunctionCall(blob: Blob, declare: FunctionDeclaration, staticMethod: boolean) { + let params = declare.args.map(a => `args_${a.name}`); let coreClassName = getClassName(blob); let returnValue = ''; - if (object.declare.returnType != ReturnType.void) { - returnValue = 'ScriptValue returnValue = ' + if (declare.returnType != ReturnType.void) { + returnValue = `auto&& returnValue = ` + } + + let callParams = []; + + if (staticMethod) { + callParams.push('context'); + } + + if (params.length > 0) { + callParams = callParams.concat(params); + } + + callParams.push('exception'); + + let callCode = ''; + if (staticMethod) { + callCode = `${returnValue}${coreClassName}::${declare.name === 'constructor' ? 'Create' : declare.name}(${callParams.join(',')});`; + } else { + callCode = `auto&& ${blob.filename} = toScriptWrappable<${getClassName(blob)}>(this_val); + ${returnValue} qjs_blob->${declare.name}(${callParams.join(',')});`; } return addIndent(` auto context = static_cast(JS_GetContextOpaque(ctx)); ExceptionState exception; -${returnValue}${coreClassName}::${object.declare.name}(context, ${params.join(', ')}, exception); +${callCode} if (exception.HasException()) { return exception.ToQuickJS(); } -${returnValue ? 'return returnValue.ToQuickJS();' : 'return JS_NULL; '}`, 2); +${returnValue ? 'return returnValue->ToQuickJS();' : 'return JS_NULL; '}`, 2); } function generateFunctionSource(blob: Blob, object: FunctionObject) { - let paramCheck = generateMethodArgumentsCheck(object.declare, object); - let varInit = generateFunctionValueInit(object); - let moduleCall = generateCoreModuleCall(blob, object); + let paramCheck = generateMethodArgumentsCheck(object.declare); + let varInit = generateFunctionValueInit(object.declare); + let moduleCall = generateFunctionCall(blob, object.declare, true); return `static JSValue ${object.declare.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { ${paramCheck} ExceptionState exception_state; @@ -113,15 +135,148 @@ ${moduleCall} }`; } +function generateClassConstructorCallback(blob: Blob, declare: FunctionDeclaration) { + let paramCheck = generateMethodArgumentsCheck(declare); + let varInit = generateFunctionValueInit(declare); + let moduleCall = generateFunctionCall(blob, declare, true); + + return `JSValue QJSBlob::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { + ${paramCheck} + ExceptionState exception_state; + +${varInit.join('\n')} +${moduleCall} +} +`; +} + +function generatePropertyGetterCallback(blob: Blob, prop: PropsDeclaration) { + return `static JSValue ${prop.name}AttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* ${blob.filename} = toScriptWrappable<${getClassName(blob)}>(this_val); + assert(${blob.filename} != nullptr); + return Converter<${generateTypeConverter(prop.type)}>::ToValue(ctx, ${blob.filename}->${prop.name}()); +}`; +} + +function generatePropertySetterCallback(blob: Blob, prop: PropsDeclaration) { + return `static JSValue ${prop.name}AttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* ${blob.filename} = toScriptWrappable<${getClassName(blob)}>(this_val); + ExceptionState exception_state; + auto&& v = Converter<${generateTypeConverter(prop.type)}>::FromValue(ctx, argv[0], exception_state); + if (exception_state.HasException()) { + return exception_state.ToQuickJS(); + } + qjs_blob->set${prop.name[0].toUpperCase() + prop.name.slice(1)}(v); +}`; +} + +function generateMethodCallback(blob: Blob, methods: FunctionDeclaration[]): string[] { + return methods.map(method => { + let paramCheck = generateMethodArgumentsCheck(method); + let varInit = generateFunctionValueInit(method); + let moduleCall = generateFunctionCall(blob, method, false); + + return `static JSValue ${method.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + ${paramCheck} + ExceptionState exception_state; + +${varInit.join('\n')} +${moduleCall} +}`; + }); +} + +function generateClassSource(blob: Blob, object: ClassObject) { + let constructorCallback = generateClassConstructorCallback(blob, object.construct); + let getterCallbacks: string[] = []; + let setterCallbacks: string[] = []; + let methodCallback = generateMethodCallback(blob, object.methods); + + object.props.forEach(prop => { + getterCallbacks.push(generatePropertyGetterCallback(blob, prop)); + if (!prop.readonly) { + setterCallbacks.push(generatePropertySetterCallback(blob, prop)) + } + }); + + return [ + constructorCallback, + getterCallbacks.join('\n'), + setterCallbacks.join('\n'), + methodCallback.join('\n') + ].join('\n'); +} + +function generateInstallGlobalFunctions(blob: Blob, installList: string[]) { + return `void QJS${getClassName(blob)}::InstallGlobalFunctions(ExecutingContext* context) { + std::initializer_list functionConfig { + ${installList.join(',\n')} + }; + + MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); +}`; +} + +function generateConstructorInstaller(blob: Blob) { + return `void QJS${getClassName(blob)}::InstallConstructor(ExecutingContext* context) { + const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); + JSValue constructor = context->contextData()->constructorForType(wrapperTypeInfo); + + std::initializer_list attributeConfig { + {"${getClassName(blob)}", nullptr, nullptr, constructor} + }; + MemberInstaller::InstallAttributes(context, context->Global(), attributeConfig); +}`; +} + +function generatePrototypeMethodsInstaller(blob: Blob, installList: string[]) { + return `void QJS${getClassName(blob)}::InstallPrototypeMethods(ExecutingContext* context) { + const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); + JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); + + std::initializer_list attributesConfig { + ${installList.join(',\n')} + }; + + MemberInstaller::InstallAttributes(context, prototype, attributesConfig); +} +`; +} + +function generatePrototypePropsInstaller(blob: Blob, installList: string[]) { + return `void QJS${getClassName(blob)}::InstallPrototypeProperties(ExecutingContext* context) { + const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); + JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); + + std::initializer_list functionConfig { + ${installList.join(',\n')} + }; + + MemberInstaller::InstallFunctions(context, prototype, functionConfig); +} +`; +} + export function generateCppSource(blob: Blob) { - let installList: string[] = []; + let functionInstallList: string[] = []; + let classMethodsInstallList: string[] = []; + let classPropsInstallList: string[] = []; + let wrapperTypeInfoInit = ''; let sources = blob.objects.map(o => { if (o instanceof FunctionObject) { - installList.push(` {"${o.declare.name}", ${o.declare.name}, ${o.declare.args.length}},`); + functionInstallList.push(` {"${o.declare.name}", ${o.declare.name}, ${o.declare.args.length}},`); return generateFunctionSource(blob, o); + } else { + o.props.forEach(prop => { + classMethodsInstallList.push(`{"${prop.name}", ${prop.name}AttributeGetCallback, ${prop.readonly ? 'nullptr' : `${prop.name}AttributeSetCallback`}}`) + }); + o.methods.forEach(method => { + classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) + }); + wrapperTypeInfoInit = `const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::m_wrapperTypeInfo;`; + return generateClassSource(blob, o); } - return ''; }); return `/* @@ -138,18 +293,20 @@ export function generateCppSource(blob: Blob) { namespace kraken { +${wrapperTypeInfoInit} + ${sources.join('\n')} void QJS${getClassName(blob)}::Install(ExecutingContext* context) { InstallGlobalFunctions(context); + InstallConstructor(context); + InstallPrototypeMethods(context); + InstallPrototypeProperties(context); } -void QJS${getClassName(blob)}::InstallGlobalFunctions(ExecutingContext* context) { - std::initializer_list functionConfig { - ${installList.join('\n')} - }; - - MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); -} +${generateInstallGlobalFunctions(blob, functionInstallList)} +${generateConstructorInstaller(blob)} +${generatePrototypeMethodsInstaller(blob, classMethodsInstallList)} +${generatePrototypePropsInstaller(blob, classPropsInstallList)} }`; } diff --git a/bridge/scripts/code_generator/src/utils.ts b/bridge/scripts/code_generator/src/utils.ts index cf9a71d3e7..5d3a239ae5 100644 --- a/bridge/scripts/code_generator/src/utils.ts +++ b/bridge/scripts/code_generator/src/utils.ts @@ -17,3 +17,7 @@ export function getClassName(blob: Blob) { return `${raw[0].toUpperCase() + raw.slice(1)}`; } + +export function getMethodName(name: string) { + return name[0].toUpperCase() + name.slice(1); +} From 3a18fdf10e3ff5a41a25a7781f413c1a00b4041c Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 18 Mar 2022 18:02:16 +0800 Subject: [PATCH 028/498] refactor: refactor code generator. --- bridge/bindings/qjs/script_promise.h | 6 + bridge/core/executing_context.cc | 4 + bridge/core/executing_context.h | 2 + bridge/core/fileapi/blob.cc | 9 +- bridge/core/fileapi/blob.h | 3 +- bridge/core/frame/console.cc | 9 +- bridge/core/frame/console.h | 1 + bridge/core/frame/module_manager.cc | 16 +- bridge/core/frame/module_manager.h | 11 +- .../code_generator/bin/code_generator.js | 2 +- .../code_generator/src/generate_header.ts | 2 +- .../code_generator/src/genereate_source.ts | 163 +++++++++--------- 12 files changed, 137 insertions(+), 91 deletions(-) diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index 37cc7a54be..a5ef2012ff 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -16,6 +16,12 @@ namespace kraken { // memory leaks since it has a reference from C++ to QuickJS. class ScriptPromise final { KRAKEN_DISALLOW_NEW(); + + public: + + private: + + }; } diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index d9daf5cf98..235a8c73b8 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -142,6 +142,10 @@ ExecutingContext::~ExecutingContext() { ctx_ = nullptr; } +ExecutingContext * ExecutingContext::From(JSContext* ctx) { + return static_cast(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(code), codeLength)); JSValue result = JS_Eval(ctx_, utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index eb19cafc4a..d0d22733b2 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -73,6 +73,8 @@ class ExecutingContext { ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); ~ExecutingContext(); + static ExecutingContext* From(JSContext* ctx); + bool EvaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine); bool EvaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine); bool EvaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine); diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index fe8869c4e7..a829484895 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -4,10 +4,17 @@ */ #include "blob.h" -#include "bindings/qjs/qjs_blob.h" namespace kraken { +Blob * Blob::Create(ExecutingContext* context) { + +} + +Blob * Blob::Create(ExecutingContext* context, std::vector> data, ExceptionState& exception_state) { + +} + Blob* Blob::Create(ExecutingContext* context, std::vector> data, std::shared_ptr property, ExceptionState& exception_state) { // return makeGarbageCollected(ctx, std::forward>(data), mime); } diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index e1e6d7faa1..47d53c971e 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -9,7 +9,6 @@ #include #include #include "bindings/qjs/macros.h" -#include "bindings/qjs/qjs_blob.h" #include "bindings/qjs/script_wrappable.h" #include "blob_part.h" #include "blob_property_bag.h" @@ -20,6 +19,8 @@ class Blob : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); public: + static Blob* Create(ExecutingContext* context); + static Blob* Create(ExecutingContext* context, std::vector> data, ExceptionState& exception_state); static Blob* Create(ExecutingContext* context, std::vector> data, std::shared_ptr property, diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 6fd9f78115..825faa8d93 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -13,9 +13,14 @@ void Console::__kraken_print__(ExecutingContext* context, std::unique_ptrcontextId(), stream, nativeStringToStdString(level.get()), nullptr); +} - std::string logLevel = level == nullptr ? "info" : nativeStringToStdString(level.get()); - printLog(context->contextId(), stream, logLevel, nullptr); +void Console::__kraken_print__(ExecutingContext* context, std::unique_ptr& log, ExceptionState& exception_state) { + std::stringstream stream; + std::string buffer = nativeStringToStdString(log.get()); + stream << buffer; + printLog(context->contextId(), stream, "info", nullptr); } } // namespace kraken diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index b7fef671e9..af5ffa062f 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -14,6 +14,7 @@ namespace kraken { class Console final { public: static void __kraken_print__(ExecutingContext* context, std::unique_ptr& log, std::unique_ptr& level, ExceptionState& exception); + static void __kraken_print__(ExecutingContext* context, std::unique_ptr& log, ExceptionState& exception_state); }; } // namespace kraken diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index da96bb8e91..4045da883f 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -55,7 +55,21 @@ void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t context static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); } -ScriptValue ModuleManager::__kraken_invoke_module__(ExecutingContext* context, +std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, + std::unique_ptr &moduleName, + std::unique_ptr &method, + ExceptionState& exception) { +} + +std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, + std::unique_ptr &moduleName, + std::unique_ptr &method, + ScriptValue& paramsValue, + ExceptionState& exception) { + +} + +std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, std::unique_ptr &moduleName, std::unique_ptr &method, ScriptValue& paramsValue, diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 4060ed0eff..029ce9fc94 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -14,7 +14,16 @@ namespace kraken { class ModuleManager { public: - static ScriptValue __kraken_invoke_module__(ExecutingContext* context, + static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, + std::unique_ptr &moduleName, + std::unique_ptr &method, + ExceptionState& exception); + static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, + std::unique_ptr &moduleName, + std::unique_ptr &method, + ScriptValue& params, + ExceptionState& exception); + static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, std::unique_ptr &moduleName, std::unique_ptr &method, ScriptValue& params, diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 123de3d06b..8dd3126025 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -33,7 +33,7 @@ let blobs = files.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 Blob(path.join(source, file), dist, filename, implement); -}).filter(blob => blob.filename === 'qjs_blob'); +}).filter(blob => blob.filename === 'qjs_module_manager'); for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index 5d00bf6a4a..74277a333c 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -50,7 +50,7 @@ class QJS${getClassName(blob)} final { static void Install(ExecutingContext* context); ${interfaceDefines[0]} - ${interfaceDefines[1]} + ${interfaceDefines[1]} private: static void InstallGlobalFunctions(ExecutingContext* context); ${interfaceDefines[2]} diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index a22fb3971b..6c2fac7337 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -4,10 +4,11 @@ import { FunctionArguments, FunctionArgumentType, FunctionDeclaration, - FunctionObject, PropsDeclaration, + FunctionObject, + PropsDeclaration, ReturnType } from "./declaration"; -import {addIndent, getClassName, getMethodName} from "./utils"; +import {addIndent, getClassName} from "./utils"; import {ParameterType} from "./analyzer"; enum PropType { @@ -58,94 +59,96 @@ function generateTypeConverter(type: ParameterType | ParameterType[]): string { } } -function generateFunctionValueInit(declare: FunctionDeclaration) { - function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { - let type = generateTypeConverter(argument.type); - return `auto&& args_${argument.name} = Converter<${type}>::FromValue(ctx, argv[${argsIndex}], exception_state);`; - } - - function generateInitBody(argument: FunctionArguments, argsIndex: number) { - function generateInitParams(type: ParameterType | ParameterType[]) { - return ''; - } +function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { + let type = generateTypeConverter(argument.type); + return `auto&& args_${argument.name} = Converter<${type}>::FromValue(ctx, argv[${argsIndex}], exception_state);`; +} - return `Converter>::ImplType args_${argument.name}{${generateInitParams(argument.type)}}; -if (argc > ${argsIndex}) { - args_${argument.name} = Converter>::FromValue(ctx, argv[${argsIndex}], exception_state); -}` - } +function generateOptionalInitBody(blob: Blob, declare: FunctionDeclaration, argument: FunctionArguments, argsIndex: number, previousArguments: string[]) { + return `auto&& args_${argument.name} = Converter>::FromValue(ctx, argv[${argsIndex}], exception_state); +if (exception_state.HasException()) { + return exception_state.ToQuickJS(); +} - return declare.args.map((a, i) => { - let body = a.required ? generateRequiredInitBody(a, i) : generateInitBody(a, i); - return addIndent(body, 2); - }); +if (argc <= ${argsIndex}) { + return_value = ${getClassName(blob)}::${declare.name}(context, ${[...previousArguments, `args_${argument.name}`].join(',')}, exception_state); + break; +}`; } -function generateFunctionCall(blob: Blob, declare: FunctionDeclaration, staticMethod: boolean) { - let params = declare.args.map(a => `args_${a.name}`); - let coreClassName = getClassName(blob); - let returnValue = ''; +function generateFunctionCallBody(blob: Blob, declaration: FunctionDeclaration) { + let minimalRequiredArgc = 0; + declaration.args.forEach(m => { + if (m.required) minimalRequiredArgc++; + }); - if (declare.returnType != ReturnType.void) { - returnValue = `auto&& returnValue = ` + 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 callParams = []; + let optionalArgumentsInit: string[] = []; + let totalArguments: string[] = requiredArguments.slice(); - if (staticMethod) { - callParams.push('context'); + for (let i = minimalRequiredArgc; i < declaration.args.length; i ++) { + optionalArgumentsInit.push(generateOptionalInitBody(blob, declaration, declaration.args[i], i + 1, totalArguments)); + totalArguments.push(`args_${declaration.args[i].name}`); } - if (params.length > 0) { - callParams = callParams.concat(params); - } + requiredArguments.push('exception_state'); + + return `${requiredArgumentsInit.join('\n')} +if (argc <= ${minimalRequiredArgc}) { + return_value = ${getClassName(blob)}::${declaration.name}(context${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''}); + break; +} - callParams.push('exception'); +${optionalArgumentsInit.join('\n')} +`; +} - let callCode = ''; - if (staticMethod) { - callCode = `${returnValue}${coreClassName}::${declare.name === 'constructor' ? 'Create' : declare.name}(${callParams.join(',')});`; +function generateGlobalFunctionSource(blob: Blob, object: FunctionObject) { + let body = generateFunctionBody(blob, object.declare); + return `static JSValue ${object.declare.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { +${body} +}`; +} + +function generateFunctionBody(blob: Blob, declare: FunctionDeclaration) { + let paramCheck = generateMethodArgumentsCheck(declare); + let callBody = generateFunctionCallBody(blob, declare); + let returnValue = ''; + if (declare.returnType != ReturnType.void) { + returnValue = 'return_value->ToQuickJS();'; } else { - callCode = `auto&& ${blob.filename} = toScriptWrappable<${getClassName(blob)}>(this_val); - ${returnValue} qjs_blob->${declare.name}(${callParams.join(',')});`; + returnValue = 'JS_NULL'; } - return addIndent(` -auto context = static_cast(JS_GetContextOpaque(ctx)); -ExceptionState exception; + return `${paramCheck} -${callCode} + ExceptionState exception_state; + ${getClassName(blob)}* return_value = nullptr; + ExecutingContext* context = ExecutingContext::From(ctx); -if (exception.HasException()) { - return exception.ToQuickJS(); -} -${returnValue ? 'return returnValue->ToQuickJS();' : 'return JS_NULL; '}`, 2); -} + do { // Dummy loop for use of 'break'. +${addIndent(callBody, 4)} + } while (false); -function generateFunctionSource(blob: Blob, object: FunctionObject) { - let paramCheck = generateMethodArgumentsCheck(object.declare); - let varInit = generateFunctionValueInit(object.declare); - let moduleCall = generateFunctionCall(blob, object.declare, true); - return `static JSValue ${object.declare.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { -${paramCheck} - ExceptionState exception_state; + if (exception_state.HasException()) { + return exception_state.ToQuickJS(); + } -${varInit.join('\n')} -${moduleCall} -}`; + return ${returnValue}; +`; } function generateClassConstructorCallback(blob: Blob, declare: FunctionDeclaration) { - let paramCheck = generateMethodArgumentsCheck(declare); - let varInit = generateFunctionValueInit(declare); - let moduleCall = generateFunctionCall(blob, declare, true); - return `JSValue QJSBlob::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { - ${paramCheck} - ExceptionState exception_state; - -${varInit.join('\n')} -${moduleCall} +${generateFunctionBody(blob, declare)} } `; } @@ -172,17 +175,7 @@ function generatePropertySetterCallback(blob: Blob, prop: PropsDeclaration) { function generateMethodCallback(blob: Blob, methods: FunctionDeclaration[]): string[] { return methods.map(method => { - let paramCheck = generateMethodArgumentsCheck(method); - let varInit = generateFunctionValueInit(method); - let moduleCall = generateFunctionCall(blob, method, false); - - return `static JSValue ${method.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - ${paramCheck} - ExceptionState exception_state; - -${varInit.join('\n')} -${moduleCall} -}`; + return generateFunctionBody(blob, method); }); } @@ -266,7 +259,7 @@ export function generateCppSource(blob: Blob) { let sources = blob.objects.map(o => { if (o instanceof FunctionObject) { functionInstallList.push(` {"${o.declare.name}", ${o.declare.name}, ${o.declare.args.length}},`); - return generateFunctionSource(blob, o); + return generateGlobalFunctionSource(blob, o); } else { o.props.forEach(prop => { classMethodsInstallList.push(`{"${prop.name}", ${prop.name}AttributeGetCallback, ${prop.readonly ? 'nullptr' : `${prop.name}AttributeSetCallback`}}`) @@ -279,6 +272,8 @@ export function generateCppSource(blob: Blob) { } }); + let haveInterfaceDefine = !!blob.objects.find(object => object instanceof ClassObject); + return `/* * Copyright (C) 2021 Alibaba Inc. All rights reserved. * Author: Kraken Team. @@ -288,6 +283,7 @@ export function generateCppSource(blob: Blob) { #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 "core/executing_context.h" #include "core/${blob.implement}.h" @@ -299,14 +295,15 @@ ${sources.join('\n')} void QJS${getClassName(blob)}::Install(ExecutingContext* context) { InstallGlobalFunctions(context); - InstallConstructor(context); + ${haveInterfaceDefine ? `InstallConstructor(context); InstallPrototypeMethods(context); - InstallPrototypeProperties(context); + InstallPrototypeProperties(context)` : ''}; } ${generateInstallGlobalFunctions(blob, functionInstallList)} -${generateConstructorInstaller(blob)} + +${haveInterfaceDefine ? `${generateConstructorInstaller(blob)} ${generatePrototypeMethodsInstaller(blob, classMethodsInstallList)} -${generatePrototypePropsInstaller(blob, classPropsInstallList)} +${generatePrototypePropsInstaller(blob, classPropsInstallList)}` : ''} }`; } From 176baf5466a53708ed0b33533edcac2efbaf9e12 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 18 Mar 2022 20:25:20 +0800 Subject: [PATCH 029/498] fix: fix code generated code return value. --- bridge/bindings/qjs/converter_impl.h | 59 +++++++++++- bridge/bindings/qjs/idl_type.h | 2 + bridge/bindings/qjs/script_promise.cc | 8 ++ bridge/bindings/qjs/script_promise.h | 2 + bridge/core/fileapi/blob.cc | 24 +++-- bridge/core/fileapi/blob.d.ts | 4 +- bridge/core/fileapi/blob.h | 10 +- .../code_generator/bin/code_generator.js | 2 +- bridge/scripts/code_generator/src/analyzer.ts | 18 ++-- .../scripts/code_generator/src/declaration.ts | 9 +- .../code_generator/src/genereate_source.ts | 94 +++++++++++++++---- 11 files changed, 184 insertions(+), 48 deletions(-) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 89268f47de..353088e995 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -32,6 +32,10 @@ struct Converter, std::enable_if_t::FromValue(ctx, value, exception); } + + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, value); + } }; template @@ -44,6 +48,10 @@ struct Converter, std::enable_if_t::FromValue(ctx, value, exception); } + + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, value); + } }; // Optional value for arithmetic value @@ -57,6 +65,10 @@ struct Converter, std::enable_if_t::FromValue(ctx, value, exception); } + + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, value); + } }; // Any @@ -67,14 +79,19 @@ struct Converter : public ConverterBase { return ScriptValue(ctx, value); } - static JSValue ToValue(JSContext* ctx, const ScriptValue& value) { return value.ToQuickJS(); } + static JSValue ToValue(JSContext* ctx, ScriptValue value) { return value.ToQuickJS(); } }; + template<> struct Converter> : public ConverterBase> { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return ScriptValue(ctx, value); } + + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, value); + } }; // Boolean @@ -101,6 +118,31 @@ struct Converter : public ConverterBase { 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) { @@ -120,10 +162,25 @@ struct Converter : public ConverterBase { return jsValueToNativeString(ctx, value); } + 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 nullptr; + 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, value); + } +}; + template <> struct Converter : public ConverterBase { static AtomString FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index eef61d846e..2829873dff 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -33,6 +33,8 @@ struct IDLOptional final : public IDLTypeBase { 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 {}; diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index 5c60699dc1..fee88f1fde 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -4,3 +4,11 @@ */ #include "script_promise.h" + +namespace kraken { + +JSValue ScriptPromise::ToQuickJS() { + return JS_NULL; +} + +} diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index a5ef2012ff..8aa3b878f5 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -6,6 +6,7 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ +#include #include "foundation/macros.h" namespace kraken { @@ -19,6 +20,7 @@ class ScriptPromise final { public: + JSValue ToQuickJS(); private: diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index a829484895..7cb59d0ffc 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -7,13 +7,9 @@ namespace kraken { -Blob * Blob::Create(ExecutingContext* context) { +Blob* Blob::Create(ExecutingContext* context) {} -} - -Blob * Blob::Create(ExecutingContext* context, std::vector> data, ExceptionState& exception_state) { - -} +Blob* Blob::Create(ExecutingContext* context, std::vector> data, ExceptionState& exception_state) {} Blob* Blob::Create(ExecutingContext* context, std::vector> data, std::shared_ptr property, ExceptionState& exception_state) { // return makeGarbageCollected(ctx, std::forward>(data), mime); @@ -86,15 +82,27 @@ const char* Blob::GetHumanReadableName() const { void Blob::Trace(GCVisitor* visitor) const {} void Blob::Dispose() const {} -Blob* Blob::slice(ExceptionState& exception_state) { +Blob* Blob::slice() { return nullptr; } -Blob* Blob::slice(int64_t start, ExceptionState* exception_state) { +Blob* Blob::slice(int64_t start, ExceptionState& exception_state) { return nullptr; } +Blob* Blob::slice(int64_t start, int64_t end, ExceptionState& exception_state) {} +Blob * Blob::slice(int64_t start, int64_t end, std::unique_ptr& content_type, ExceptionState& exception_state) { + +} std::string Blob::type() { return mime_type_; } +ScriptPromise Blob::arrayBuffer() { + return ScriptPromise(); +} + +ScriptPromise Blob::text() { + +} + } // namespace kraken diff --git a/bridge/core/fileapi/blob.d.ts b/bridge/core/fileapi/blob.d.ts index 71e084df19..a15cd6a3ef 100644 --- a/bridge/core/fileapi/blob.d.ts +++ b/bridge/core/fileapi/blob.d.ts @@ -1,8 +1,10 @@ +type int64 = void; + interface Blob { readonly size: number; readonly type: string; arrayBuffer(): Promise; - slice(start?: number, end?: number, contentType?: string): Blob; + slice(start?: int64, end?: int64, contentType?: string): Blob; text(): Promise; new(blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; } diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 47d53c971e..26df67e072 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -10,6 +10,7 @@ #include #include "bindings/qjs/macros.h" #include "bindings/qjs/script_wrappable.h" +#include "bindings/qjs/script_promise.h" #include "blob_part.h" #include "blob_property_bag.h" @@ -37,8 +38,13 @@ class Blob : public ScriptWrappable { int32_t size(); std::string type(); - Blob* slice(ExceptionState& exception_state); - Blob* slice(int64_t start, ExceptionState* exception_state); + ScriptPromise arrayBuffer(); + ScriptPromise text(); + + Blob* slice(); + 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, std::unique_ptr& content_type, ExceptionState& exception_state); const char* GetHumanReadableName() const override; void Trace(GCVisitor* visitor) const override; diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 8dd3126025..123de3d06b 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -33,7 +33,7 @@ let blobs = files.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 Blob(path.join(source, file), dist, filename, implement); -}).filter(blob => blob.filename === 'qjs_module_manager'); +}).filter(blob => blob.filename === 'qjs_blob'); for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; diff --git a/bridge/scripts/code_generator/src/analyzer.ts b/bridge/scripts/code_generator/src/analyzer.ts index 254d693e1c..c3210ef088 100644 --- a/bridge/scripts/code_generator/src/analyzer.ts +++ b/bridge/scripts/code_generator/src/analyzer.ts @@ -7,7 +7,6 @@ import { FunctionDeclaration, FunctionObject, PropsDeclaration, - ReturnType } from './declaration'; import {generatorSource} from './generator'; @@ -32,14 +31,6 @@ function getHeritageType(heritage: HeritageClause) { return null; } -function getFunctionReturnType(keyword: ts.TypeNode): ReturnType { - switch (keyword.kind) { - case ts.SyntaxKind.VoidKeyword: - return ReturnType.void; - } - return ReturnType.null; -} - function getPropName(propName: ts.PropertyName) { if (propName.kind == ts.SyntaxKind.Identifier) { return propName.escapedText.toString(); @@ -75,6 +66,8 @@ function getParameterType(type: ts.TypeNode): ParameterType | ParameterType[] { } 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.TypeReference) { let typeReference: ts.TypeReference = type as unknown as ts.TypeReference; // @ts-ignore @@ -83,6 +76,8 @@ function getParameterType(type: ts.TypeNode): ParameterType | ParameterType[] { return FunctionArgumentType.function; } else if (identifier === 'int32') { return FunctionArgumentType.int32; + } else if (identifier === 'int64') { + return FunctionArgumentType.int64; } else if (identifier === 'double') { return FunctionArgumentType.double; } @@ -156,6 +151,9 @@ function walkProgram(statement: ts.Statement) { f.args.push(p); }); obj.methods.push(f); + if (m.type) { + f.returnType = getParameterType(m.type); + } break; } case ts.SyntaxKind.ConstructSignature: { @@ -184,7 +182,7 @@ function walkProgram(statement: ts.Statement) { 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 = getFunctionReturnType((type as ts.FunctionTypeNode).type); + functionObject.declare.returnType = getParameterType((type as ts.FunctionTypeNode).type); functionObject.declare.name = methodName.toString(); } diff --git a/bridge/scripts/code_generator/src/declaration.ts b/bridge/scripts/code_generator/src/declaration.ts index f0c1a481b4..86a7eb6e9d 100644 --- a/bridge/scripts/code_generator/src/declaration.ts +++ b/bridge/scripts/code_generator/src/declaration.ts @@ -5,9 +5,11 @@ export enum FunctionArgumentType { string, object, int32, + int64, double, boolean, function, + void, any, } @@ -23,14 +25,9 @@ export class PropsDeclaration { readonly: boolean; } -export enum ReturnType { - void, - null -} - export class FunctionDeclaration extends PropsDeclaration { args: FunctionArguments[]; - returnType: ReturnType; + returnType: ParameterType | ParameterType[]; } export class ClassObject { diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index 6c2fac7337..0cd020fdcd 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -6,7 +6,6 @@ import { FunctionDeclaration, FunctionObject, PropsDeclaration, - ReturnType } from "./declaration"; import {addIndent, getClassName} from "./utils"; import {ParameterType} from "./analyzer"; @@ -43,6 +42,8 @@ function generateTypeConverter(type: ParameterType | ParameterType[]): string { switch(type) { case FunctionArgumentType.int32: return `IDLInt32`; + case FunctionArgumentType.int64: + return 'IDLInt64'; case FunctionArgumentType.double: return `IDLDouble`; case FunctionArgumentType.function: @@ -64,19 +65,33 @@ function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number return `auto&& args_${argument.name} = Converter<${type}>::FromValue(ctx, argv[${argsIndex}], exception_state);`; } -function generateOptionalInitBody(blob: Blob, declare: FunctionDeclaration, argument: FunctionArguments, argsIndex: number, previousArguments: string[]) { +function generateCallMethodName(name: string) { + if (name === 'constructor') return 'Create'; + return name; +} + +function generateOptionalInitBody(blob: Blob, declare: FunctionDeclaration, argument: FunctionArguments, argsIndex: number, previousArguments: string[], options: GenFunctionBodyOptions) { + let call = ''; + if (options.isInstanceMethod) { + call = `auto* self = toScriptWrappable<${getClassName(blob)}>(this_val); +return_value = self->${generateCallMethodName(declare.name)}(${[...previousArguments, `args_${argument.name}`, 'exception_state'].join(',')});`; + } else { + call = `return_value = ${getClassName(blob)}::${generateCallMethodName(declare.name)}(context, ${[...previousArguments, `args_${argument.name}`].join(',')}, exception_state);`; + } + + return `auto&& args_${argument.name} = Converter>::FromValue(ctx, argv[${argsIndex}], exception_state); if (exception_state.HasException()) { return exception_state.ToQuickJS(); } if (argc <= ${argsIndex}) { - return_value = ${getClassName(blob)}::${declare.name}(context, ${[...previousArguments, `args_${argument.name}`].join(',')}, exception_state); + ${call} break; }`; } -function generateFunctionCallBody(blob: Blob, declaration: FunctionDeclaration) { +function generateFunctionCallBody(blob: Blob, declaration: FunctionDeclaration, options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { let minimalRequiredArgc = 0; declaration.args.forEach(m => { if (m.required) minimalRequiredArgc++; @@ -95,15 +110,23 @@ function generateFunctionCallBody(blob: Blob, declaration: FunctionDeclaration) let totalArguments: string[] = requiredArguments.slice(); for (let i = minimalRequiredArgc; i < declaration.args.length; i ++) { - optionalArgumentsInit.push(generateOptionalInitBody(blob, declaration, declaration.args[i], i + 1, totalArguments)); + optionalArgumentsInit.push(generateOptionalInitBody(blob, declaration, declaration.args[i], i + 1, totalArguments, options)); totalArguments.push(`args_${declaration.args[i].name}`); } requiredArguments.push('exception_state'); + let call = ''; + if (options.isInstanceMethod) { + call = `auto* self = toScriptWrappable<${getClassName(blob)}>(this_val); +return_value = self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''});`; + } else { + call = `return_value = ${getClassName(blob)}::${generateCallMethodName(declaration.name)}(context${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''});`; + } + return `${requiredArgumentsInit.join('\n')} if (argc <= ${minimalRequiredArgc}) { - return_value = ${getClassName(blob)}::${declaration.name}(context${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''}); + ${call} break; } @@ -118,20 +141,51 @@ ${body} }`; } -function generateFunctionBody(blob: Blob, declare: FunctionDeclaration) { - let paramCheck = generateMethodArgumentsCheck(declare); - let callBody = generateFunctionCallBody(blob, declare); - let returnValue = ''; - if (declare.returnType != ReturnType.void) { - returnValue = 'return_value->ToQuickJS();'; - } else { - returnValue = 'JS_NULL'; +function generateReturnValueInit(blob: Blob, type: ParameterType | ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { + if (type == FunctionArgumentType.void) return ''; + + if (options.isConstructor) { + return `${getClassName(blob)}* return_value = nullptr;` + } + if (typeof type === 'string') { + if (type === 'Promise') { + return 'ScriptPromise return_value;'; + } else { + return `${type}* return_value = nullptr;`; + } + } + return `Converter<${generateTypeConverter(type)}>::ImplType return_value;`; +} + +function generateReturnValueResult(blob: Blob, type: ParameterType | ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}): string { + if (type == FunctionArgumentType.void) return 'JS_NULL'; + if (options.isConstructor) { + return `return_value->ToQuickJS()`; + } + + if (typeof type === 'string') { + if (type === 'Promise') { + return 'return_value.ToQuickJS()'; + } else { + return `return_value->ToQuickJS()`; + } } + return `Converter<${generateTypeConverter(type)}>::ToValue(ctx, return_value)`; +} + +type GenFunctionBodyOptions = {isConstructor?: boolean, isInstanceMethod?: boolean}; + +function generateFunctionBody(blob: Blob, 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, options); + return `${paramCheck} ExceptionState exception_state; - ${getClassName(blob)}* return_value = nullptr; + ${returnValueInit} ExecutingContext* context = ExecutingContext::From(ctx); do { // Dummy loop for use of 'break'. @@ -141,14 +195,13 @@ ${addIndent(callBody, 4)} if (exception_state.HasException()) { return exception_state.ToQuickJS(); } - - return ${returnValue}; + return ${returnValueResult}; `; } function generateClassConstructorCallback(blob: Blob, declare: FunctionDeclaration) { return `JSValue QJSBlob::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { -${generateFunctionBody(blob, declare)} +${generateFunctionBody(blob, declare, {isConstructor: true})} } `; } @@ -175,7 +228,9 @@ function generatePropertySetterCallback(blob: Blob, prop: PropsDeclaration) { function generateMethodCallback(blob: Blob, methods: FunctionDeclaration[]): string[] { return methods.map(method => { - return generateFunctionBody(blob, method); + return `static JSValue ${method.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + ${ generateFunctionBody(blob, method, {isInstanceMethod: true}) } +}`; }); } @@ -284,6 +339,7 @@ export function generateCppSource(blob: Blob) { #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 "core/executing_context.h" #include "core/${blob.implement}.h" From 5f81d21e5bc8c0d93c479e9f85b43b3cf42f60a1 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Mon, 21 Mar 2022 21:27:29 +0800 Subject: [PATCH 030/498] feat: add blob implementation. --- bridge/CMakeLists.txt | 4 + bridge/bindings/qjs/converter_impl.h | 2 +- bridge/bindings/qjs/qjs_engine_patch.cc | 21 +++ bridge/bindings/qjs/qjs_engine_patch.h | 3 + bridge/bindings/qjs/qjs_interface_bridge.cc | 6 + bridge/bindings/qjs/qjs_interface_bridge.h | 28 ++++ bridge/bindings/qjs/script_promise.cc | 10 ++ bridge/bindings/qjs/script_promise.h | 10 +- .../bindings/qjs/script_promise_resolver.cc | 11 ++ bridge/bindings/qjs/script_promise_resolver.h | 22 ++++ bridge/bindings/qjs/script_value.h | 1 + bridge/core/fileapi/blob.cc | 123 +++++++++--------- bridge/core/fileapi/blob.h | 25 ++-- bridge/core/fileapi/blob_part.cc | 71 ++++++++-- bridge/core/fileapi/blob_part.h | 14 +- bridge/core/fileapi/blob_property_bag.cc | 15 +++ bridge/core/fileapi/blob_property_bag.h | 2 +- bridge/core/frame/module_manager.cc | 4 +- bridge/core/frame/module_manager.d.ts | 2 +- .../code_generator/src/generate_header.ts | 5 +- .../code_generator/src/genereate_source.ts | 21 ++- 21 files changed, 296 insertions(+), 104 deletions(-) create mode 100644 bridge/bindings/qjs/qjs_interface_bridge.cc create mode 100644 bridge/bindings/qjs/qjs_interface_bridge.h create mode 100644 bridge/bindings/qjs/script_promise_resolver.cc create mode 100644 bridge/bindings/qjs/script_promise_resolver.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index d2798d75ae..b38f4b7556 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -203,6 +203,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/script_value.h bindings/qjs/script_promise.cc bindings/qjs/script_promise.h + bindings/qjs/qjs_interface_bridge.cc + bindings/qjs/qjs_interface_bridge.h + bindings/qjs/script_promise_resolver.cc + bindings/qjs/script_promise_resolver.h bindings/qjs/atom_string.cc bindings/qjs/atom_string.h bindings/qjs/exception_state.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 353088e995..d5833f4c9a 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -177,7 +177,7 @@ struct Converter> : public ConverterBase 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, value); + return Converter::ToValue(ctx, std::move(value)); } }; diff --git a/bridge/bindings/qjs/qjs_engine_patch.cc b/bridge/bindings/qjs/qjs_engine_patch.cc index 3658d76ba2..95ba544de7 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.cc +++ b/bridge/bindings/qjs/qjs_engine_patch.cc @@ -320,6 +320,27 @@ bool JS_IsProxy(JSValue value) { return p->class_id == JS_CLASS_PROXY; } +bool JS_IsPromise(JSValue value) { + if (!JS_IsObject(value)) + return false; + JSObject* p = JS_VALUE_GET_OBJ(value); + return p->class_id == JS_CLASS_PROMISE; +} + +bool JS_IsArrayBuffer(JSValue value) { + if (!JS_IsObject(value)) + return false; + JSObject* p = JS_VALUE_GET_OBJ(value); + return p->class_id == JS_CLASS_ARRAY_BUFFER; +} + +bool JS_IsArrayBufferView(JSValue value) { + if (!JS_IsObject(value)) + return false; + JSObject* p = JS_VALUE_GET_OBJ(value); + return p->class_id >= JS_CLASS_UINT8C_ARRAY && p->class_id <= JS_CLASS_DATAVIEW; +} + bool JS_HasClassId(JSRuntime* runtime, JSClassID classId) { if (runtime->class_count <= classId) return false; diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h index 9669d70e8b..99f75b1de9 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -103,6 +103,9 @@ uint16_t* JS_ToUnicode(JSContext* ctx, JSValueConst value, uint32_t* length); JSValue JS_NewUnicodeString(JSContext* ctx, const uint16_t* code, uint32_t length); JSClassID JSValueGetClassId(JSValue); bool JS_IsProxy(JSValue value); +bool JS_IsPromise(JSValue value); +bool JS_IsArrayBuffer(JSValue value); +bool JS_IsArrayBufferView(JSValue value); bool JS_HasClassId(JSRuntime* runtime, JSClassID classId); JSValue JS_GetProxyTarget(JSValue value); diff --git a/bridge/bindings/qjs/qjs_interface_bridge.cc b/bridge/bindings/qjs/qjs_interface_bridge.cc new file mode 100644 index 0000000000..caa7296332 --- /dev/null +++ b/bridge/bindings/qjs/qjs_interface_bridge.cc @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "qjs_interface_bridge.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..e8b6628419 --- /dev/null +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020-present Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ + +#include "script_wrappable.h" +#include "core/executing_context.h" + +namespace kraken { + +template +class QJSInterfaceBridge { + public: + static T* ToWrappable(ExecutingContext* context, JSValue value) { + return HasInstance(context, value) ? toScriptWrappable(value) : nullptr; + } + + static bool HasInstance(ExecutingContext* context, JSValue value) { + return JS_IsInstanceOf(context->ctx(), value, context->contextData()->prototypeForType(QJST::GetWrapperTypeInfo())); + } +}; + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index fee88f1fde..01c344f547 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -7,6 +7,16 @@ namespace kraken { +ScriptPromise::ScriptPromise(ExecutingContext* context, JSValue promise): context_(context) { + if (JS_IsUndefined(promise) || JS_IsNull(promise)) return; + + if (!JS_IsPromise(promise)) { + return; + } + + promise_ = ScriptValue(context->ctx(), promise); +} + JSValue ScriptPromise::ToQuickJS() { return JS_NULL; } diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index 8aa3b878f5..0c3605f0a5 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -7,7 +7,9 @@ #define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ #include +#include "core/executing_context.h" #include "foundation/macros.h" +#include "script_value.h" namespace kraken { @@ -17,13 +19,15 @@ namespace kraken { // memory leaks since it has a reference from C++ to QuickJS. class ScriptPromise final { KRAKEN_DISALLOW_NEW(); - public: + ScriptPromise() = default; + ScriptPromise(ExecutingContext* context, JSValue promise); + JSValue ToQuickJS(); private: - - + ExecutingContext* context_; + ScriptValue promise_; }; } diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc new file mode 100644 index 0000000000..86adfc8983 --- /dev/null +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_promise_resolver.h" + +namespace kraken { + + +} diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h new file mode 100644 index 0000000000..65183f3b51 --- /dev/null +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ + +#include "script_promise.h" + +namespace kraken { + +class ScriptPromiseResolver { + public: + + private: + +}; + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 53f307dc19..2285bd4c01 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -36,6 +36,7 @@ class ScriptValue final { // Wrap an Quickjs JSValue to ScriptValue. explicit ScriptValue(JSContext* ctx, JSValue value) : m_ctx(ctx), m_value(JS_DupValue(ctx, value)){}; explicit ScriptValue(JSContext* ctx) : m_ctx(ctx){}; + ScriptValue() = default; ScriptValue& operator=(const ScriptValue& other) { if (&other != this) { diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 7cb59d0ffc..bfe00444d9 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -4,69 +4,21 @@ */ #include "blob.h" +#include "bindings/qjs/script_promise_resolver.h" namespace kraken { -Blob* Blob::Create(ExecutingContext* context) {} +Blob* Blob::Create(ExecutingContext* context) { + return makeGarbageCollected(context->ctx()); +} -Blob* Blob::Create(ExecutingContext* context, std::vector> data, ExceptionState& exception_state) {} +Blob* Blob::Create(ExecutingContext* context, std::vector>& data, ExceptionState& exception_state) { + return makeGarbageCollected(context->ctx(), data); +} -Blob* Blob::Create(ExecutingContext* context, std::vector> data, std::shared_ptr property, ExceptionState& exception_state) { - // return makeGarbageCollected(ctx, std::forward>(data), mime); +Blob* Blob::Create(ExecutingContext* context, std::vector>& data, std::shared_ptr property, ExceptionState& exception_state) { + return makeGarbageCollected(context->ctx(), data, property); } -// -// void BlobBuilder::append(ExecutingContext& context, Blob* blob) { -// std::vector blobData = blob->_data; -// _data.reserve(_data.size() + blobData.size()); -// _data.insert(_data.end(), blobData.begin(), blobData.end()); -//} -// -// void BlobBuilder::append(ExecutingContext& context, const std::string& value) { -// std::vector strArr(value.begin(), value.end()); -// _data.reserve(_data.size() + strArr.size()); -// _data.insert(_data.end(), strArr.begin(), strArr.end()); -//} -// -// void BlobBuilder::append(ExecutingContext& context, ScriptValue value) { -// if (value.isString()) { -// -// } else if (value.isArray()) { -// std::vector array = createArrayFromQuickJSArraySlow(&context, value); -// for (auto &i : array) { -// append(context, i); -// } -// } else if (value.isObject()) { -// context.contextData()->constructorForType(Blob::getStaticWrapperTypeInfo()); -// if (value.isInstanceOf(Blob::getStaticWrapperTypeInfo())) { -// auto blob = static_cast(toScriptWrappable(value.toQuickJS())); -// if (blob == nullptr) -// return; -// if (std::string(blob->getHumanReadableName()) == "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; -// if (!value.isArrayBuffer(&buffer, &length)) { -// size_t byte_offset; -// size_t byte_length; -// size_t byte_per_element; -// ExceptionState exceptionState; -// value.getTypedArrayBuffer(&buffer, &length, &byte_offset, &byte_length, &byte_per_element, &exceptionState); -// } -// -// for (size_t i = 0; i < length; i++) { -// _data.emplace_back(buffer[i]); -// } -// } -// } -//} -// -// std::vector BlobBuilder::finalize() { -// return std::move(_data); -//} int32_t Blob::size() { return _data.size(); @@ -82,15 +34,24 @@ const char* Blob::GetHumanReadableName() const { void Blob::Trace(GCVisitor* visitor) const {} void Blob::Dispose() const {} -Blob* Blob::slice() { - return nullptr; +Blob* Blob::slice(ExceptionState& exception_state) { + return slice(0, _data.size(), exception_state); } Blob* Blob::slice(int64_t start, ExceptionState& exception_state) { - return nullptr; + return slice(start, _data.size(), exception_state); } -Blob* Blob::slice(int64_t start, int64_t end, ExceptionState& exception_state) {} -Blob * Blob::slice(int64_t start, int64_t end, std::unique_ptr& content_type, ExceptionState& exception_state) { - +Blob* Blob::slice(int64_t start, int64_t end, ExceptionState& exception_state) { + std::unique_ptr contentType = nullptr; + return slice(start, end, contentType, exception_state); +} +Blob* Blob::slice(int64_t start, int64_t end, std::unique_ptr& content_type, ExceptionState& exception_state) { + auto* newBlob = makeGarbageCollected(ctx()); + std::vector 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 != nullptr ? nativeStringToStdString(content_type.get()) : mime_type_; + return newBlob; } std::string Blob::type() { @@ -98,11 +59,43 @@ std::string Blob::type() { } ScriptPromise Blob::arrayBuffer() { - return ScriptPromise(); } -ScriptPromise Blob::text() { +ScriptPromise Blob::text() {} + +void Blob::PopulateBlobData(std::vector>& 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 strArr(string.begin(), string.end()); + _data.reserve(_data.size() + strArr.size()); + _data.insert(_data.end(), strArr.begin(), strArr.end()); +} +void Blob::AppendBytes(uint8_t* buffer, uint32_t length) { + _data.reserve(_data.size() + length); + for (size_t i = 0; i < length; i++) { + _data.emplace_back(buffer[i]); + } } } // namespace kraken diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 26df67e072..456074c74e 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -9,8 +9,8 @@ #include #include #include "bindings/qjs/macros.h" -#include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/script_promise.h" +#include "bindings/qjs/script_wrappable.h" #include "blob_part.h" #include "blob_property_bag.h" @@ -21,16 +21,18 @@ class Blob : public ScriptWrappable { public: static Blob* Create(ExecutingContext* context); - static Blob* Create(ExecutingContext* context, std::vector> data, ExceptionState& exception_state); - static Blob* Create(ExecutingContext* context, - std::vector> data, - std::shared_ptr property, - ExceptionState& exception_state); + static Blob* Create(ExecutingContext* context, std::vector>& data, ExceptionState& exception_state); + static Blob* Create(ExecutingContext* context, std::vector>& data, std::shared_ptr property, ExceptionState& exception_state); Blob() = delete; explicit Blob(JSContext* ctx) : ScriptWrappable(ctx){}; - explicit Blob(JSContext* ctx, std::vector&& data) : _size(data.size()), _data(std::move(data)), ScriptWrappable(ctx){}; - explicit Blob(JSContext* ctx, std::vector&& data, std::string& mime) : mime_type_(mime), _size(data.size()), _data(std::move(data)), ScriptWrappable(ctx){}; + explicit Blob(JSContext* ctx, std::vector>& data) : ScriptWrappable(ctx) { PopulateBlobData(data); }; + explicit Blob(JSContext* ctx, std::vector>& data, std::shared_ptr& 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(); @@ -41,7 +43,7 @@ class Blob : public ScriptWrappable { ScriptPromise arrayBuffer(); ScriptPromise text(); - Blob* slice(); + 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, std::unique_ptr& content_type, ExceptionState& exception_state); @@ -50,11 +52,12 @@ class Blob : public ScriptWrappable { void Trace(GCVisitor* visitor) const override; void Dispose() const override; + protected: + void PopulateBlobData(std::vector>& data); + private: - size_t _size; std::string mime_type_; std::vector _data; - friend QJSBlob; }; } // namespace kraken diff --git a/bridge/core/fileapi/blob_part.cc b/bridge/core/fileapi/blob_part.cc index 0348486847..5b5d444646 100644 --- a/bridge/core/fileapi/blob_part.cc +++ b/bridge/core/fileapi/blob_part.cc @@ -4,10 +4,13 @@ */ #include "blob_part.h" +#include "qjs_blob.h" namespace kraken { std::shared_ptr 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(ctx, buffer); @@ -15,17 +18,69 @@ std::shared_ptr BlobPart::Create(JSContext* ctx, JSValue value, Except return result; } + // Create from another blob + if (QJSBlob::HasInstance(context, value)) { + Blob* qjs_value = toScriptWrappable(value); + return std::make_shared(ctx, qjs_value); + } + + if (JS_IsArrayBuffer(value)) { + size_t length; + uint8_t* buffer = JS_GetArrayBuffer(ctx, &length, value); + return std::make_shared(ctx, buffer, length); + } + + if (JS_IsArrayBufferView(value)) { + size_t byte_offset; + size_t byte_length; + size_t byte_per_element; + size_t length; + uint8_t* buffer; + JSValue arrayBufferObject = JS_GetTypedArrayBuffer(ctx, value, &byte_offset, &byte_length, &byte_per_element); + if (JS_IsException(arrayBufferObject)) { + exception_state.ThrowException(ctx, arrayBufferObject); + return nullptr; + } + buffer = JS_GetArrayBuffer(ctx, &length, arrayBufferObject); + return std::make_shared(ctx, buffer, length, byte_offset, byte_length, byte_per_element); + } + return nullptr; } -JSValue BlobPart::ToQuickJS(JSContext* ctx) const{ -// switch(content_type_) { -// case ContentType::kString: { -// return JS_NewString(ctx, member_string_.c_str()); -// } -// case ContentType::kBlob: { -// } -// } +JSValue BlobPart::ToQuickJS(JSContext* ctx) const { + switch(content_type_) { + case ContentType::kString: { + return JS_NewString(ctx, member_string_.c_str()); + } + case ContentType::kBlob: { + return blob_->ToQuickJS(); + } + case ContentType::kArrayBuffer: { + return JS_NewArrayBufferCopy(ctx, bytes_, byte_length_); + } + case ContentType::kArrayBufferView: { + // TODO: Create ArrayBufferView from QuickJS API is not support now. + return JS_NULL; + } + } +} + +BlobPart::ContentType BlobPart::GetContentType() const { + return content_type_; +} + +const std::string& BlobPart::GetString() const { + return member_string_; +} + +uint8_t* BlobPart::GetBytes(uint32_t* length) const { + *length = byte_length_; + return bytes_; +} + +Blob* BlobPart::GetBlob() const { + return blob_; } } diff --git a/bridge/core/fileapi/blob_part.h b/bridge/core/fileapi/blob_part.h index 45d4fc7d3d..90a13a9855 100644 --- a/bridge/core/fileapi/blob_part.h +++ b/bridge/core/fileapi/blob_part.h @@ -30,16 +30,22 @@ class BlobPart { 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) {}; - explicit BlobPart(JSContext* ctx, uint8_t* buffer, size_t byte_offset, size_t byte_length, size_t byte_per_element, uint32_t length): content_type_(ContentType::kArrayBufferView) {}; + 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) {}; + explicit BlobPart(JSContext* ctx, Blob* blob): content_type_(ContentType::kBlob), blob_(blob) {}; private: ContentType content_type_; std::string member_string_; - uint32_t* bytes{nullptr}; + Blob* blob_{nullptr}; + uint8_t* bytes_{nullptr}; + uint32_t byte_length_{0}; }; } diff --git a/bridge/core/fileapi/blob_property_bag.cc b/bridge/core/fileapi/blob_property_bag.cc index ba98c5698b..747d98aac9 100644 --- a/bridge/core/fileapi/blob_property_bag.cc +++ b/bridge/core/fileapi/blob_property_bag.cc @@ -8,7 +8,22 @@ namespace kraken { std::shared_ptr BlobPropertyBag::Create(JSContext* ctx, JSValue value, ExceptionState& exceptionState) { + auto bag = std::make_shared(); + 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); +} + } diff --git a/bridge/core/fileapi/blob_property_bag.h b/bridge/core/fileapi/blob_property_bag.h index 0bba922690..674d8b1533 100644 --- a/bridge/core/fileapi/blob_property_bag.h +++ b/bridge/core/fileapi/blob_property_bag.h @@ -21,7 +21,7 @@ class BlobPropertyBag final { const std::string& type() const { return m_type; } private: - void fillMemberFromQuickjsObject(ExecutingContext* context, JSValue value, ExceptionState* exceptionState); + void FillMemberFromQuickjsObject(JSContext* ctx, JSValue value, ExceptionState& exceptionState); std::string m_type; }; diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 4045da883f..ed56086603 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -80,13 +80,13 @@ std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingC if (!paramsValue.IsEmpty()) { params = paramsValue.ToJSONStringify(&exception).toNativeString(); if (exception.HasException()) { - return ScriptValue::Empty(context->ctx()); + return nullptr; } } if (context->dartMethodPtr()->invokeModule == nullptr) { exception.ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); - return ScriptValue::Empty(context->ctx()); + return nullptr; } auto moduleCallback = ModuleCallback::Create(callback); diff --git a/bridge/core/frame/module_manager.d.ts b/bridge/core/frame/module_manager.d.ts index 6d6c920a07..d8bf9eaebb 100644 --- a/bridge/core/frame/module_manager.d.ts +++ b/bridge/core/frame/module_manager.d.ts @@ -1,2 +1,2 @@ declare const __kraken_invoke_module__: (moduleName: string, methodName: string, paramsValue?: any, callback?: Function) => string; -declare const __kraken_add_module_listener__: (callback?: Function) => void; +declare const __kraken_add_module_listener__: (callback: Function) => void; diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index 74277a333c..bf2f3ad91b 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -30,6 +30,7 @@ function generateInterfaceAdditionalHeader(object: any): [string, string, string export function generateCppHeader(blob: Blob) { let classObject = blob.objects.find(object => object instanceof ClassObject); let interfaceDefines = generateInterfaceAdditionalHeader(classObject); + let haveInterfaceBase = !!classObject; return `/* * Copyright (C) 2021 Alibaba Inc. All rights reserved. * Author: Kraken Team. @@ -40,12 +41,14 @@ export function generateCppHeader(blob: Blob) { #include #include "bindings/qjs/wrapper_type_info.h" +#include "bindings/qjs/qjs_interface_bridge.h" +#include "core/${blob.implement}.h" namespace kraken { class ExecutingContext; -class QJS${getClassName(blob)} final { +class QJS${getClassName(blob)} ${haveInterfaceBase ? `: public QJSInterfaceBridge` : 'final'} { public: static void Install(ExecutingContext* context); diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index 0cd020fdcd..938d4435a4 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -72,11 +72,15 @@ function generateCallMethodName(name: string) { function generateOptionalInitBody(blob: Blob, declare: FunctionDeclaration, argument: FunctionArguments, argsIndex: number, previousArguments: string[], options: GenFunctionBodyOptions) { let call = ''; + let returnValueAssignment = ''; + if (declare.returnType != FunctionArgumentType.void) { + returnValueAssignment = 'return_value ='; + } if (options.isInstanceMethod) { call = `auto* self = toScriptWrappable<${getClassName(blob)}>(this_val); -return_value = self->${generateCallMethodName(declare.name)}(${[...previousArguments, `args_${argument.name}`, 'exception_state'].join(',')});`; +${returnValueAssignment} self->${generateCallMethodName(declare.name)}(${[...previousArguments, `args_${argument.name}`, 'exception_state'].join(',')});`; } else { - call = `return_value = ${getClassName(blob)}::${generateCallMethodName(declare.name)}(context, ${[...previousArguments, `args_${argument.name}`].join(',')}, exception_state);`; + call = `${returnValueAssignment} ${getClassName(blob)}::${generateCallMethodName(declare.name)}(context, ${[...previousArguments, `args_${argument.name}`].join(',')}, exception_state);`; } @@ -117,11 +121,15 @@ function generateFunctionCallBody(blob: Blob, declaration: FunctionDeclaration, requiredArguments.push('exception_state'); let call = ''; + let returnValueAssignment = ''; + if (declaration.returnType != FunctionArgumentType.void) { + returnValueAssignment = 'return_value ='; + } if (options.isInstanceMethod) { call = `auto* self = toScriptWrappable<${getClassName(blob)}>(this_val); -return_value = self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''});`; +${returnValueAssignment} self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''});`; } else { - call = `return_value = ${getClassName(blob)}::${generateCallMethodName(declaration.name)}(context${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''});`; + call = `${returnValueAssignment} ${getClassName(blob)}::${generateCallMethodName(declaration.name)}(context${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''});`; } return `${requiredArgumentsInit.join('\n')} @@ -171,7 +179,7 @@ function generateReturnValueResult(blob: Blob, type: ParameterType | ParameterTy } } - return `Converter<${generateTypeConverter(type)}>::ToValue(ctx, return_value)`; + return `Converter<${generateTypeConverter(type)}>::ToValue(ctx, std::move(return_value))`; } type GenFunctionBodyOptions = {isConstructor?: boolean, isInstanceMethod?: boolean}; @@ -313,7 +321,7 @@ export function generateCppSource(blob: Blob) { let sources = blob.objects.map(o => { if (o instanceof FunctionObject) { - functionInstallList.push(` {"${o.declare.name}", ${o.declare.name}, ${o.declare.args.length}},`); + functionInstallList.push(` {"${o.declare.name}", ${o.declare.name}, ${o.declare.args.length}}`); return generateGlobalFunctionSource(blob, o); } else { o.props.forEach(prop => { @@ -341,7 +349,6 @@ export function generateCppSource(blob: Blob) { #include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/script_promise.h" #include "core/executing_context.h" -#include "core/${blob.implement}.h" namespace kraken { From b3150837510457da4c09eff0875a8a9f58846a99 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Tue, 22 Mar 2022 20:16:12 +0800 Subject: [PATCH 031/498] feat: add script promise implements. --- bridge/CMakeLists.txt | 3 + bridge/bindings/qjs/pending_promises.cc | 15 +++++ bridge/bindings/qjs/pending_promises.h | 26 ++++++++ bridge/bindings/qjs/rejected_promises.cc | 26 ++++---- bridge/bindings/qjs/rejected_promises.h | 10 +-- bridge/bindings/qjs/script_promise.cc | 8 ++- bridge/bindings/qjs/script_promise.h | 9 ++- .../bindings/qjs/script_promise_resolver.cc | 33 ++++++++++ bridge/bindings/qjs/script_promise_resolver.h | 46 ++++++++++++++ bridge/bindings/qjs/script_value.cc | 22 ++++--- bridge/bindings/qjs/script_value.h | 15 +++-- bridge/bindings/qjs/to_quickjs.h | 54 ++++++++++++++++ bridge/core/dom/events/close_event.d.ts | 2 - bridge/core/dom/events/gesture_event.d.ts | 2 - bridge/core/dom/events/input_event.d.ts | 2 - .../dom/events/intersection_change_event.d.ts | 2 - bridge/core/executing_context.cc | 36 ++++++----- bridge/core/executing_context.h | 14 ++--- bridge/core/fileapi/array_buffer_data.h | 18 ++++++ bridge/core/fileapi/blob.cc | 62 ++++++++++++++++++- bridge/core/fileapi/blob.h | 8 ++- bridge/core/html/html_anchor_element.d.ts | 3 - bridge/core/html/html_canvas_element.d.ts | 5 +- bridge/core/html/html_input_element.d.ts | 3 - bridge/core/html/html_object_element.d.ts | 3 - bridge/core/html/html_script_element.d.ts | 3 - bridge/foundation/native_value.cc | 56 ++++++++--------- bridge/polyfill/src/test/jasmine.js | 22 +++---- .../code_generator/bin/code_generator.js | 2 +- .../scripts/code_generator/src/declaration.ts | 2 +- .../code_generator/src/genereate_source.ts | 7 ++- 31 files changed, 386 insertions(+), 133 deletions(-) create mode 100644 bridge/bindings/qjs/pending_promises.cc create mode 100644 bridge/bindings/qjs/pending_promises.h create mode 100644 bridge/bindings/qjs/to_quickjs.h create mode 100644 bridge/core/fileapi/array_buffer_data.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index b38f4b7556..9d8fbc038d 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -203,6 +203,7 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/script_value.h bindings/qjs/script_promise.cc bindings/qjs/script_promise.h + bindings/qjs/to_quickjs.h bindings/qjs/qjs_interface_bridge.cc bindings/qjs/qjs_interface_bridge.h bindings/qjs/script_promise_resolver.cc @@ -215,6 +216,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/gc_visitor.h bindings/qjs/rejected_promises.h bindings/qjs/rejected_promises.cc + bindings/qjs/pending_promises.cc + bindings/qjs/pending_promises.h bindings/qjs/qjs_window.cc bindings/qjs/qjs_window.h bindings/qjs/qjs_page.cc diff --git a/bridge/bindings/qjs/pending_promises.cc b/bridge/bindings/qjs/pending_promises.cc new file mode 100644 index 0000000000..c9afba8e36 --- /dev/null +++ b/bridge/bindings/qjs/pending_promises.cc @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "pending_promises.h" +#include "script_promise.h" + +namespace kraken { + +void PendingPromises::TrackPendingPromises(ScriptPromise promise) { + promises_.emplace_back(promise); +} + +} diff --git a/bridge/bindings/qjs/pending_promises.h b/bridge/bindings/qjs/pending_promises.h new file mode 100644 index 0000000000..ae071ff20c --- /dev/null +++ b/bridge/bindings/qjs/pending_promises.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ + +#include +#include +#include "script_promise.h" + +namespace kraken { + +class PendingPromises { + public: + PendingPromises() = default; + void TrackPendingPromises(ScriptPromise promise); + + private: + std::vector promises_; +}; + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 84f4a8856e..99116ae2af 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -16,41 +16,41 @@ RejectedPromises::Message::~Message() { JS_FreeValueRT(m_runtime, m_reason); } -void RejectedPromises::trackUnhandledPromiseRejection(ExecutingContext* 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(ExecutingContext* 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(ExecutingContext* context) { +void RejectedPromises::Process(ExecutingContext* context) { // Copy m_unhandledRejections to avoid endless recursion call. std::unordered_map> unhandledRejections; - for (auto& entry : m_unhandledRejections) { - unhandledRejections[entry.first] = std::unique_ptr(m_unhandledRejections[entry.first].release()); + for (auto& entry : unhandled_rejections_) { + unhandledRejections[entry.first] = std::unique_ptr(unhandled_rejections_[entry.first].release()); } - m_unhandledRejections.clear(); + unhandled_rejections_.clear(); // Copy m_reportHandledRejection to avoid endless recursion call. std::vector> reportHandledRejection; reportHandledRejection.reserve(reportHandledRejection.size()); - for (auto& entry : m_reportHandledRejection) { + for (auto& entry : report_handled_rejection_) { reportHandledRejection.push_back(std::unique_ptr(entry.release())); } - m_reportHandledRejection.clear(); + report_handled_rejection_.clear(); // Dispatch unhandled rejectionEvents. for (auto& entry : unhandledRejections) { diff --git a/bridge/bindings/qjs/rejected_promises.h b/bridge/bindings/qjs/rejected_promises.h index 261f29f682..1be501e58b 100644 --- a/bridge/bindings/qjs/rejected_promises.h +++ b/bridge/bindings/qjs/rejected_promises.h @@ -28,15 +28,15 @@ class RejectedPromises { }; // Keeping track unhandled promise rejection in current context, and throw unhandledRejection error - void trackUnhandledPromiseRejection(ExecutingContext* 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(ExecutingContext* context, JSValue promise, JSValue reason); + void TrackHandledPromiseRejection(ExecutingContext* context, JSValue promise, JSValue reason); // Trigger events after promise executed. - void process(ExecutingContext* context); + void Process(ExecutingContext* context); private: - std::unordered_map> m_unhandledRejections; - std::vector> m_reportHandledRejection; + std::unordered_map> unhandled_rejections_; + std::vector> report_handled_rejection_; }; } // namespace kraken diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index 01c344f547..ad37757fe2 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -4,21 +4,25 @@ */ #include "script_promise.h" +#include "qjs_engine_patch.h" namespace kraken { -ScriptPromise::ScriptPromise(ExecutingContext* context, JSValue promise): context_(context) { +ScriptPromise::ScriptPromise(JSContext* ctx, JSValue promise): ctx_(ctx) { if (JS_IsUndefined(promise) || JS_IsNull(promise)) return; if (!JS_IsPromise(promise)) { return; } - promise_ = ScriptValue(context->ctx(), promise); + promise_ = ScriptValue(ctx, promise); } JSValue ScriptPromise::ToQuickJS() { return JS_NULL; } +void ScriptPromise::Trace(GCVisitor* visitor) { +} + } diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index 0c3605f0a5..9b9ddef769 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -7,8 +7,8 @@ #define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ #include -#include "core/executing_context.h" #include "foundation/macros.h" +#include "gc_visitor.h" #include "script_value.h" namespace kraken { @@ -22,11 +22,14 @@ class ScriptPromise final { public: ScriptPromise() = default; - ScriptPromise(ExecutingContext* context, JSValue promise); + ScriptPromise(JSContext* ctx, JSValue promise); JSValue ToQuickJS(); + + void Trace(GCVisitor* visitor); + private: - ExecutingContext* context_; + JSContext* ctx_; ScriptValue promise_; }; diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index 86adfc8983..2daf6717cf 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -4,8 +4,41 @@ */ #include "script_promise_resolver.h" +#include "pending_promises.h" +#include "core/executing_context.h" namespace kraken { +ScriptPromiseResolver* ScriptPromiseResolver::Create(ExecutingContext* context) { + return new ScriptPromiseResolver(context); +} + +ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context): context_(context) { + JSValue resolving_funcs[2]; + promise_ = JS_NewPromiseCapability(context->ctx(), resolving_funcs); + resolve_func_ = resolving_funcs[0]; + reject_func_ = resolving_funcs[1]; + + context->GetPendingPromises()->TrackPendingPromises(ScriptPromise(context_->ctx(), promise_)); +} + +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); + 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); + JS_FreeValue(context_->ctx() , return_value); + } + } +} } diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 65183f3b51..3801b8d872 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -7,14 +7,60 @@ #define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ #include "script_promise.h" +#include "converter_impl.h" +#include "to_quickjs.h" namespace kraken { class ScriptPromiseResolver { public: + static ScriptPromiseResolver* Create(ExecutingContext* context); + ScriptPromiseResolver() = delete; + ScriptPromiseResolver(ExecutingContext* context); + + // Return a promise object and wait to be resolve or reject. + // Note that an empty ScriptPromise will be returned after resolve or + // reject is called. + ScriptPromise Promise(); + + // Anything that can be passed to toQuickJS can be passed to this function. + template + void Resolve(T value) { + ResolveOrReject(value, kResolving); + } + + // Anything that can be passed to toQuickJS can be passed to this function. + template + void Reject(T value) { + ResolveOrReject(value, kRejecting); + } private: + enum ResolutionState { + kPending, + kResolving, + kRejecting, + kDetached, + }; + + ExecutingContext* GetExecutionContext() const { return context_; } + + template + void ResolveOrReject(T value, ResolutionState new_state) { + if (state_ != kPending || !context_->IsValid() || !context_ ) + return; + assert(new_state == kResolving || new_state == kRejecting); + state_ = new_state; + ResolveOrRejectImmediately(toQuickJS(context_->ctx(), value)); + } + + void ResolveOrRejectImmediately(JSValue value); + ResolutionState state_; + ExecutingContext* context_{nullptr}; + JSValue promise_{JS_NULL}; + JSValue resolve_func_{JS_NULL}; + JSValue reject_func_{JS_NULL}; }; } diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index a42409de76..e92169a795 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -31,36 +31,40 @@ ScriptValue ScriptValue::Empty(JSContext* ctx) { } JSValue ScriptValue::ToQuickJS() const { - return m_value; + return value_; } ScriptValue ScriptValue::ToJSONStringify(ExceptionState* exception) { - JSValue stringifyedValue = JS_JSONStringify(m_ctx, m_value, JS_NULL, JS_NULL); - ScriptValue result = ScriptValue(m_ctx); + JSValue stringifyedValue = JS_JSONStringify(ctx_, value_, JS_NULL, JS_NULL); + ScriptValue result = ScriptValue(ctx_); // JS_JSONStringify may return JS_EXCEPTION if object is not valid. Return JS_EXCEPTION and let quickjs to handle it. if (JS_IsException(stringifyedValue)) { - exception->ThrowException(m_ctx, stringifyedValue); + exception->ThrowException(ctx_, stringifyedValue); } else { - result = ScriptValue(m_ctx, stringifyedValue); + result = ScriptValue(ctx_, stringifyedValue); } return result; } std::unique_ptr ScriptValue::toNativeString() { - return jsValueToNativeString(m_ctx, m_value); + return jsValueToNativeString(ctx_, value_); } std::string ScriptValue::toCString() { - return jsValueToStdString(m_ctx, m_value); + return jsValueToStdString(ctx_, value_); } bool ScriptValue::IsException() { - return JS_IsException(m_value); + return JS_IsException(value_); } bool ScriptValue::IsEmpty() { - return JS_IsNull(m_value) || JS_IsUndefined(m_value); + return JS_IsNull(value_) || JS_IsUndefined(value_); +} + +void ScriptValue::Trace(GCVisitor* visitor) { + visitor->Trace(value_); } } // namespace kraken diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 2285bd4c01..7c3e906431 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -12,6 +12,7 @@ #include "exception_state.h" #include "foundation/macros.h" #include "foundation/native_string.h" +#include "gc_visitor.h" namespace kraken { @@ -34,17 +35,17 @@ class ScriptValue final { // Create an empty ScriptValue; static ScriptValue Empty(JSContext* ctx); // Wrap an Quickjs JSValue to ScriptValue. - explicit ScriptValue(JSContext* ctx, JSValue value) : m_ctx(ctx), m_value(JS_DupValue(ctx, value)){}; - explicit ScriptValue(JSContext* ctx) : m_ctx(ctx){}; + explicit ScriptValue(JSContext* ctx, JSValue value) : ctx_(ctx), value_(JS_DupValue(ctx, value)){}; + explicit ScriptValue(JSContext* ctx) : ctx_(ctx){}; ScriptValue() = default; ScriptValue& operator=(const ScriptValue& other) { if (&other != this) { - m_value = JS_DupValue(m_ctx, other.m_value); + value_ = JS_DupValue(ctx_, other.value_); } return *this; }; - ~ScriptValue() { JS_FreeValue(m_ctx, m_value); }; + ~ScriptValue() { JS_FreeValue(ctx_, value_); }; JSValue ToQuickJS() const; // Create a new ScriptValue from call JSON.stringify to current value. @@ -55,9 +56,11 @@ class ScriptValue final { bool IsException(); bool IsEmpty(); + void Trace(GCVisitor* visitor); + private: - JSContext* m_ctx{nullptr}; - JSValue m_value{JS_NULL}; + JSContext* ctx_{nullptr}; + JSValue value_{JS_NULL}; }; } // namespace kraken diff --git a/bridge/bindings/qjs/to_quickjs.h b/bridge/bindings/qjs/to_quickjs.h new file mode 100644 index 0000000000..c9d1f2d699 --- /dev/null +++ b/bridge/bindings/qjs/to_quickjs.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ + +#include +#include +#include "native_string_utils.h" +#include "qjs_engine_patch.h" +#include "script_wrappable.h" +#include "core/fileapi/array_buffer_data.h" + +namespace kraken { + +// Arithmetic values +inline JSValue toQuickJS(JSContext* ctx, double v) { + return JS_NewFloat64(ctx, v); +} +inline JSValue toQuickJS(JSContext* ctx, int32_t v) { + return JS_NewInt32(ctx, v); +} +inline JSValue toQuickJS(JSContext* ctx, uint32_t v) { + return JS_NewUint32(ctx, v); +} + + +// 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); +} + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ diff --git a/bridge/core/dom/events/close_event.d.ts b/bridge/core/dom/events/close_event.d.ts index 0e5ae36fb1..6bf9423039 100644 --- a/bridge/core/dom/events/close_event.d.ts +++ b/bridge/core/dom/events/close_event.d.ts @@ -1,6 +1,4 @@ -interface Event {} type int64 = number; - interface CloseEvent extends Event { readonly code: int64; readonly reason: string; diff --git a/bridge/core/dom/events/gesture_event.d.ts b/bridge/core/dom/events/gesture_event.d.ts index be63cc1e58..063db06d4e 100644 --- a/bridge/core/dom/events/gesture_event.d.ts +++ b/bridge/core/dom/events/gesture_event.d.ts @@ -1,5 +1,3 @@ -interface Event {} - interface GestureEvent extends Event { readonly state: string; readonly direction: string; diff --git a/bridge/core/dom/events/input_event.d.ts b/bridge/core/dom/events/input_event.d.ts index e0693e9bd6..304ab79609 100644 --- a/bridge/core/dom/events/input_event.d.ts +++ b/bridge/core/dom/events/input_event.d.ts @@ -1,5 +1,3 @@ -interface Event {} - interface InputEvent extends Event { readonly inputType: string; readonly data: string; diff --git a/bridge/core/dom/events/intersection_change_event.d.ts b/bridge/core/dom/events/intersection_change_event.d.ts index 0e42d5e1e2..f612ab02d7 100644 --- a/bridge/core/dom/events/intersection_change_event.d.ts +++ b/bridge/core/dom/events/intersection_change_event.d.ts @@ -1,5 +1,3 @@ -interface Event {} - interface IntersectionChangeEvent extends Event { readonly intersectionRatio: number; } diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 235a8c73b8..caee47b1c9 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -22,7 +22,7 @@ std::unique_ptr createJSContext(int32_t contextId, const JSExc static JSRuntime* runtime_{nullptr}; -ExecutionContextGCTracker::ExecutionContextGCTracker(JSContext* ctx): ScriptWrappable(ctx) {} +ExecutionContextGCTracker::ExecutionContextGCTracker(JSContext* ctx) : ScriptWrappable(ctx) {} const WrapperTypeInfo& ExecutionContextGCTracker::wrapper_type_info_{"GCTracker"}; void ExecutionContextGCTracker::Trace(GCVisitor* visitor) const { @@ -30,7 +30,9 @@ void ExecutionContextGCTracker::Trace(GCVisitor* visitor) const { context->Trace(visitor); } void ExecutionContextGCTracker::Dispose() const {} -const char * ExecutionContextGCTracker::GetHumanReadableName() const { return "GCTracker"; } +const char* ExecutionContextGCTracker::GetHumanReadableName() const { + return "GCTracker"; +} ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) : context_id_(contextId), handler_(handler), owner_(owner), ctx_invalid_(false), unique_id_(context_unique_id++) { @@ -100,15 +102,15 @@ ExecutingContext::~ExecutingContext() { ctx_invalid_ = true; // 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(ctx_, promiseContext->resolveFunc); - JS_FreeValue(ctx_, promiseContext->rejectFunc); - delete promiseContext; - } - } + // { + // struct list_head *el, *el1; + // list_for_each_safe(el, el1, &promise_job_list) { + // auto* promiseContext = list_entry(el, PromiseContext, link); + // JS_FreeValue(ctx_, promiseContext->resolveFunc); + // JS_FreeValue(ctx_, promiseContext->rejectFunc); + // delete promiseContext; + // } + // } // Free unreleased native_functions. { @@ -142,7 +144,7 @@ ExecutingContext::~ExecutingContext() { ctx_ = nullptr; } -ExecutingContext * ExecutingContext::From(JSContext* ctx) { +ExecutingContext* ExecutingContext::From(JSContext* ctx) { return static_cast(JS_GetContextOpaque(ctx)); } @@ -270,7 +272,7 @@ void ExecutingContext::DrainPendingPromiseJobs() { } // Throw error when promise are not handled. - rejected_promises_.process(this); + rejected_promises_.Process(this); } void ExecutingContext::DefineGlobalProperty(const char* prop, JSValue value) { @@ -376,9 +378,9 @@ void ExecutingContext::promiseRejectTracker(JSContext* ctx, JSValue promise, JSV // 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); + context->rejected_promises_.TrackHandledPromiseRejection(context, promise, reason); } else { - context->rejected_promises_.trackUnhandledPromiseRejection(context, promise, reason); + context->rejected_promises_.TrackUnhandledPromiseRejection(context, promise, reason); } } @@ -394,6 +396,10 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { return &module_callbacks_; } +//PendingPromises* ExecutingContext::PendingPromises() { +// return &pending_promises_; +//} + void ExecutingContext::Trace(GCVisitor* visitor) { timers_.trace(visitor); module_listener_container_.trace(visitor); diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index d0d22733b2..3a3e6ce5a7 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -19,6 +19,7 @@ #include "bindings/qjs/binding_initializer.h" #include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/rejected_promises.h" +#include "bindings/qjs/pending_promises.h" #include "bindings/qjs/script_value.h" #include "foundation/macros.h" #include "foundation/ui_command_buffer.h" @@ -41,15 +42,6 @@ class Document; using JSExceptionHandler = std::function; -struct PromiseContext { - void* data; - ExecutingContext* context; - JSValue resolveFunc; - JSValue rejectFunc; - JSValue promise; - list_head link; -}; - bool isContextValid(int32_t contextId); class ExecutionContextGCTracker : public ScriptWrappable { @@ -105,6 +97,9 @@ class ExecutingContext { // Gets the ModuleCallbacks which from the 4th parameter of `kraken.invokeModule` function. ModuleCallbackCoordinator* ModuleCallbacks(); + // Get all pending promises which are not resolved or rejected. + PendingPromises* GetPendingPromises() { return &pending_promises_; }; + FORCE_INLINE Document* document() { return document_; }; FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; FORCE_INLINE std::unique_ptr& dartMethodPtr() { return dart_method_ptr_; } @@ -145,6 +140,7 @@ class ExecutingContext { UICommandBuffer ui_command_buffer_{this}; std::unique_ptr dart_method_ptr_ = std::make_unique(); RejectedPromises rejected_promises_; + PendingPromises pending_promises_; }; class ObjectProperty { diff --git a/bridge/core/fileapi/array_buffer_data.h b/bridge/core/fileapi/array_buffer_data.h new file mode 100644 index 0000000000..9e21edb3b4 --- /dev/null +++ b/bridge/core/fileapi/array_buffer_data.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ +#define KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ + +namespace kraken { + +struct ArrayBufferData { + uint8_t* buffer; + int32_t length; +}; + +} + +#endif // KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index bfe00444d9..a15068d321 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -3,11 +3,51 @@ * Author: Kraken Team. */ +#include #include "blob.h" #include "bindings/qjs/script_promise_resolver.h" +#include "core/executing_context.h" namespace kraken { +class BlobReaderClient { + public: + enum ReadType { kReadAsText, kReadAsArrayBuffer }; + + BlobReaderClient(ExecutingContext* context, Blob* blob, ScriptPromiseResolver* resolver, ReadType read_type) : context_(context), blob_(blob), resolver_(resolver), read_type_(read_type) { + Start(); + }; + + void Start(); + void DidFinishLoading(); + + private: + ExecutingContext* context_; + Blob* blob_; + ScriptPromiseResolver* resolver_; + ReadType read_type_; +}; + +void BlobReaderClient::Start() { + // Use setTimeout to simulate async data loading. + // TODO: Blob are part of File API in W3C standard, but not supported by Kraken from now on. + // Needs to remove this after File API had landed. + auto callback = [](void* ptr, int32_t contextId, const char* errmsg) -> void { + auto* client = static_cast(ptr); + client->DidFinishLoading(); + }; + context_->dartMethodPtr()->setTimeout(this, context_->contextId(), callback, 0); +} + +void BlobReaderClient::DidFinishLoading() { + if (read_type_ == ReadType::kReadAsText) { + resolver_->Resolve(blob_->StringResult()); + } else if (read_type_ == ReadType::kReadAsArrayBuffer) { + resolver_->Resolve(blob_->ArrayBufferResult()); + } + delete this; +} + Blob* Blob::Create(ExecutingContext* context) { return makeGarbageCollected(context->ctx()); } @@ -54,14 +94,32 @@ Blob* Blob::slice(int64_t start, int64_t end, std::unique_ptr& con 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() { +ScriptPromise Blob::arrayBuffer(ExceptionState& exception_state) { + auto* resolver = ScriptPromiseResolver::Create(context()); + new BlobReaderClient(context(), this, resolver, BlobReaderClient::ReadType::kReadAsArrayBuffer); + return resolver->Promise(); } -ScriptPromise Blob::text() {} +ScriptPromise Blob::text(ExceptionState& exception_state) { + auto* resolver = ScriptPromiseResolver::Create(context()); + new BlobReaderClient(context(), this, resolver, BlobReaderClient::ReadType::kReadAsText); + return resolver->Promise(); +} void Blob::PopulateBlobData(std::vector>& data) { for (auto& item : data) { diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 456074c74e..4752115c52 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -13,6 +13,7 @@ #include "bindings/qjs/script_wrappable.h" #include "blob_part.h" #include "blob_property_bag.h" +#include "array_buffer_data.h" namespace kraken { @@ -40,14 +41,17 @@ class Blob : public ScriptWrappable { int32_t size(); std::string type(); - ScriptPromise arrayBuffer(); - ScriptPromise text(); + 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, std::unique_ptr& content_type, ExceptionState& exception_state); + std::string StringResult(); + ArrayBufferData ArrayBufferResult(); + const char* GetHumanReadableName() const override; void Trace(GCVisitor* visitor) const override; void Dispose() const override; diff --git a/bridge/core/html/html_anchor_element.d.ts b/bridge/core/html/html_anchor_element.d.ts index 95a3a5b239..a8ea6fbf15 100644 --- a/bridge/core/html/html_anchor_element.d.ts +++ b/bridge/core/html/html_anchor_element.d.ts @@ -1,6 +1,3 @@ -interface HostObject {} -interface Element {} - interface AnchorElement extends Element { href: string; target: string; diff --git a/bridge/core/html/html_canvas_element.d.ts b/bridge/core/html/html_canvas_element.d.ts index dc4ac9a8f6..4a2d01f32c 100644 --- a/bridge/core/html/html_canvas_element.d.ts +++ b/bridge/core/html/html_canvas_element.d.ts @@ -1,7 +1,4 @@ -interface HostObject {} -interface Element {} - -interface CanvasRenderingContext2D extends HostObject { +interface CanvasRenderingContext2D { fillStyle: string; direction: string; font: string; diff --git a/bridge/core/html/html_input_element.d.ts b/bridge/core/html/html_input_element.d.ts index f95871549d..6ba271e9d8 100644 --- a/bridge/core/html/html_input_element.d.ts +++ b/bridge/core/html/html_input_element.d.ts @@ -1,6 +1,3 @@ -interface HostObject {} -interface Element {} - interface InputElement extends Element { width: number; height: number; diff --git a/bridge/core/html/html_object_element.d.ts b/bridge/core/html/html_object_element.d.ts index 6804b1430f..686636772a 100644 --- a/bridge/core/html/html_object_element.d.ts +++ b/bridge/core/html/html_object_element.d.ts @@ -1,6 +1,3 @@ -interface HostObject {} -interface Element {} - interface ObjectElement extends Element { type: string; data: string; diff --git a/bridge/core/html/html_script_element.d.ts b/bridge/core/html/html_script_element.d.ts index fe1d320bee..1ebfba5b31 100644 --- a/bridge/core/html/html_script_element.d.ts +++ b/bridge/core/html/html_script_element.d.ts @@ -1,6 +1,3 @@ -interface HostObject {} -interface Element {} - interface ScriptElement extends Element { src: string; } diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 3144aff934..9f08e8039d 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -160,34 +160,34 @@ static JSValue anonymousFunction(JSContext* ctx, JSValueConst this_val, int argc } 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->contextId() != contextId) - return; - - auto* context = promiseContext->context; - - if (nativeValue != nullptr) { - JSValue value = nativeValueToJSValue(promiseContext->context, *nativeValue); - JSValue returnValue = JS_Call(context->ctx(), promiseContext->resolveFunc, context->Global(), 1, &value); - context->DrainPendingPromiseJobs(); - context->HandleException(&returnValue); - JS_FreeValue(context->ctx(), value); - JS_FreeValue(context->ctx(), returnValue); - } else if (errmsg != nullptr) { - JSValue error = JS_NewError(context->ctx()); - JS_DefinePropertyValueStr(context->ctx(), error, "message", JS_NewString(context->ctx(), errmsg), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); - JSValue returnValue = JS_Call(context->ctx(), promiseContext->rejectFunc, context->Global(), 1, &error); - context->DrainPendingPromiseJobs(); - context->HandleException(&returnValue); - JS_FreeValue(context->ctx(), error); - JS_FreeValue(context->ctx(), returnValue); - } - - JS_FreeValue(context->ctx(), promiseContext->resolveFunc); - JS_FreeValue(context->ctx(), promiseContext->rejectFunc); - list_del(&promiseContext->link); +// auto* promiseContext = static_cast(callbackContext); +// if (!promiseContext->context->IsValid()) +// return; +// if (promiseContext->context->contextId() != contextId) +// return; +// +// auto* context = promiseContext->context; +// +// if (nativeValue != nullptr) { +// JSValue value = nativeValueToJSValue(promiseContext->context, *nativeValue); +// JSValue returnValue = JS_Call(context->ctx(), promiseContext->resolveFunc, context->Global(), 1, &value); +// context->DrainPendingPromiseJobs(); +// context->HandleException(&returnValue); +// JS_FreeValue(context->ctx(), value); +// JS_FreeValue(context->ctx(), returnValue); +// } else if (errmsg != nullptr) { +// JSValue error = JS_NewError(context->ctx()); +// JS_DefinePropertyValueStr(context->ctx(), error, "message", JS_NewString(context->ctx(), errmsg), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); +// JSValue returnValue = JS_Call(context->ctx(), promiseContext->rejectFunc, context->Global(), 1, &error); +// context->DrainPendingPromiseJobs(); +// context->HandleException(&returnValue); +// JS_FreeValue(context->ctx(), error); +// JS_FreeValue(context->ctx(), returnValue); +// } +// +// JS_FreeValue(context->ctx(), promiseContext->resolveFunc); +// JS_FreeValue(context->ctx(), promiseContext->rejectFunc); +// list_del(&promiseContext->link); } static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { diff --git a/bridge/polyfill/src/test/jasmine.js b/bridge/polyfill/src/test/jasmine.js index 84f444c0a9..ce1537db36 100644 --- a/bridge/polyfill/src/test/jasmine.js +++ b/bridge/polyfill/src/test/jasmine.js @@ -2764,9 +2764,9 @@ getJasmineRequireObj().clearStack = function (j$) { getJasmineRequireObj().Clock = function () { /* global process */ var NODE_JS = - typeof process !== 'undefined' && - process.versions && - typeof process.versions.node === 'string'; + typeof Process !== 'undefined' && + Process.versions && + typeof Process.versions.node === 'string'; /** * _Note:_ Do not construct this directly, Jasmine will make one during booting. You can get the current clock with {@link jasmine.clock}. @@ -3727,22 +3727,22 @@ getJasmineRequireObj().GlobalErrors = function (j$) { } } - this.originalHandlers[errorType] = global.process.listeners(errorType); + this.originalHandlers[errorType] = global.Process.listeners(errorType); this.jasmineHandlers[errorType] = taggedOnError; - global.process.removeAllListeners(errorType); - global.process.on(errorType, taggedOnError); + global.Process.removeAllListeners(errorType); + global.Process.on(errorType, taggedOnError); this.uninstall = function uninstall() { var errorTypes = Object.keys(this.originalHandlers); for (var iType = 0; iType < errorTypes.length; iType++) { var errorType = errorTypes[iType]; - global.process.removeListener( + global.Process.removeListener( errorType, this.jasmineHandlers[errorType] ); for (var i = 0; i < this.originalHandlers[errorType].length; i++) { - global.process.on(errorType, this.originalHandlers[errorType][i]); + global.Process.on(errorType, this.originalHandlers[errorType][i]); } delete this.originalHandlers[errorType]; delete this.jasmineHandlers[errorType]; @@ -3752,9 +3752,9 @@ getJasmineRequireObj().GlobalErrors = function (j$) { this.install = function install() { if ( - global.process && - global.process.listeners && - j$.isFunction_(global.process.on) + global.Process && + global.Process.listeners && + j$.isFunction_(global.Process.on) ) { this.installOne_('uncaughtException', 'Uncaught exception'); this.installOne_('unhandledRejection', 'Unhandled promise rejection'); diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 123de3d06b..8ba4333da0 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -33,7 +33,7 @@ let blobs = files.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 Blob(path.join(source, file), dist, filename, implement); -}).filter(blob => blob.filename === 'qjs_blob'); +}); for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; diff --git a/bridge/scripts/code_generator/src/declaration.ts b/bridge/scripts/code_generator/src/declaration.ts index 86a7eb6e9d..9501a1e560 100644 --- a/bridge/scripts/code_generator/src/declaration.ts +++ b/bridge/scripts/code_generator/src/declaration.ts @@ -35,7 +35,7 @@ export class ClassObject { parent: string; props: PropsDeclaration[] = []; methods: FunctionDeclaration[] = []; - construct: FunctionDeclaration; + construct?: FunctionDeclaration; } export class FunctionObject { diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index 938d4435a4..66595ce1db 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -127,7 +127,7 @@ function generateFunctionCallBody(blob: Blob, declaration: FunctionDeclaration, } if (options.isInstanceMethod) { call = `auto* self = toScriptWrappable<${getClassName(blob)}>(this_val); -${returnValueAssignment} self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''});`; +${returnValueAssignment} self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : 'exception_state'});`; } else { call = `${returnValueAssignment} ${getClassName(blob)}::${generateCallMethodName(declaration.name)}(context${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''});`; } @@ -243,7 +243,10 @@ function generateMethodCallback(blob: Blob, methods: FunctionDeclaration[]): str } function generateClassSource(blob: Blob, object: ClassObject) { - let constructorCallback = generateClassConstructorCallback(blob, object.construct); + let constructorCallback = ''; + if (object.construct) { + constructorCallback = generateClassConstructorCallback(blob, object.construct); + } let getterCallbacks: string[] = []; let setterCallbacks: string[] = []; let methodCallback = generateMethodCallback(blob, object.methods); From a91f0c4118fdee4aeaba52d9fce24cd56f13b165 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 24 Mar 2022 20:25:14 +0800 Subject: [PATCH 032/498] feat: new memory manage life circle for timer and module callbacks. --- bridge/CMakeLists.txt | 2 + bridge/bindings/qjs/converter_impl.h | 2 +- bridge/bindings/qjs/pending_promises.cc | 2 +- bridge/bindings/qjs/pending_promises.h | 2 +- bridge/bindings/qjs/qjs_function.cc | 8 - bridge/bindings/qjs/qjs_function.h | 9 +- bridge/bindings/qjs/rejected_promises.cc | 2 +- bridge/bindings/qjs/script_value.cc | 2 +- bridge/bindings/qjs/script_value.h | 30 +++- bridge/bindings/qjs/script_wrappable.cc | 7 +- bridge/bindings/qjs/wrapper_type_info.h | 7 +- bridge/core/executing_context.cc | 149 ++++++------------ bridge/core/executing_context.h | 27 ++-- bridge/core/executing_context_data.cc | 6 +- bridge/core/frame/console.cc | 2 +- bridge/core/frame/dom_timer.cc | 8 - bridge/core/frame/dom_timer.h | 3 - bridge/core/frame/dom_timer_coordinator.cc | 15 -- bridge/core/frame/dom_timer_coordinator.h | 2 - bridge/core/frame/module_callback.cc | 8 - bridge/core/frame/module_callback.h | 14 -- .../core/frame/module_callback_coordinator.cc | 8 +- .../core/frame/module_callback_coordinator.h | 4 +- bridge/core/frame/module_listener.cc | 14 +- bridge/core/frame/module_listener.h | 4 +- .../core/frame/module_listener_container.cc | 10 +- bridge/core/frame/module_listener_container.h | 5 +- bridge/core/frame/module_manager.cc | 66 ++++---- bridge/core/frame/module_manager.h | 2 +- bridge/core/frame/module_manager_test.cc | 34 +++- .../frame/window_or_worker_global_scope.cc | 2 +- bridge/core/script_state.cc | 41 +++++ bridge/core/script_state.h | 31 ++++ bridge/foundation/ui_command_buffer.cc | 6 +- bridge/polyfill/src/index.ts | 4 +- bridge/polyfill/src/method-channel.ts | 15 +- .../code_generator/src/generate_header.ts | 6 +- .../code_generator/src/genereate_source.ts | 4 +- bridge/test/kraken_test_context.cc | 5 +- bridge/test/kraken_test_env.cc | 20 ++- bridge/test/test.cmake | 2 + 41 files changed, 289 insertions(+), 301 deletions(-) create mode 100644 bridge/core/script_state.cc create mode 100644 bridge/core/script_state.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 9d8fbc038d..eb0815b782 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -226,6 +226,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") # Core sources core/executing_context.cc core/executing_context.h + core/script_state.cc + core/script_state.h core/page.h core/page.cc core/executing_context_data.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index d5833f4c9a..12ec84bea7 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -90,7 +90,7 @@ struct Converter> : public ConverterBase } static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { - return Converter::ToValue(ctx, value); + return Converter::ToValue(ctx, std::move(value)); } }; diff --git a/bridge/bindings/qjs/pending_promises.cc b/bridge/bindings/qjs/pending_promises.cc index c9afba8e36..8687149cd3 100644 --- a/bridge/bindings/qjs/pending_promises.cc +++ b/bridge/bindings/qjs/pending_promises.cc @@ -8,7 +8,7 @@ namespace kraken { -void PendingPromises::TrackPendingPromises(ScriptPromise promise) { +void PendingPromises::TrackPendingPromises(ScriptPromise&& promise) { promises_.emplace_back(promise); } diff --git a/bridge/bindings/qjs/pending_promises.h b/bridge/bindings/qjs/pending_promises.h index ae071ff20c..bf6735ea42 100644 --- a/bridge/bindings/qjs/pending_promises.h +++ b/bridge/bindings/qjs/pending_promises.h @@ -15,7 +15,7 @@ namespace kraken { class PendingPromises { public: PendingPromises() = default; - void TrackPendingPromises(ScriptPromise promise); + void TrackPendingPromises(ScriptPromise&& promise); private: std::vector promises_; diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index a1cfcc47b4..c75968bc19 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -29,12 +29,4 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, int32_t argc, ScriptValue* argum return ScriptValue(ctx, returnValue); } - -void QJSFunction::Trace(GCVisitor* visitor) const { - visitor->Trace(function_); -} - -void QJSFunction::Dispose() const { - JS_FreeValueRT(JS_GetRuntime(ctx_), function_); -} } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index db1da00c72..1c32a26f8f 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -12,10 +12,14 @@ namespace kraken { // https://webidl.spec.whatwg.org/#dfn-callback-interface +// QJSFunction memory are auto managed by std::shared_ptr. class QJSFunction { public: static std::shared_ptr Create(JSContext* ctx, JSValue function) { return std::make_shared(ctx, function); } - explicit QJSFunction(JSContext* ctx, JSValue function) : function_(JS_DupValue(ctx, function)) {}; + explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), function_(JS_DupValue(ctx, function)) {}; + ~QJSFunction() { + JS_FreeValue(ctx_, function_); + } bool IsFunction(JSContext* ctx); @@ -23,9 +27,6 @@ class QJSFunction { // https://webidl.spec.whatwg.org/#invoke-a-callback-function ScriptValue Invoke(JSContext* ctx, int32_t argc, ScriptValue* arguments); - void Trace(GCVisitor* visitor) const; - void Dispose() const; - private: JSContext* ctx_{nullptr}; JSValue function_{JS_NULL}; diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 99116ae2af..d4f29e8689 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -9,7 +9,7 @@ namespace kraken { RejectedPromises::Message::Message(ExecutingContext* context, JSValue promise, JSValue reason) - : m_runtime(context->runtime()), m_promise(JS_DupValue(context->ctx(), promise)), m_reason(JS_DupValue(context->ctx(), 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); diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index e92169a795..afa0cd9882 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -43,7 +43,7 @@ ScriptValue ScriptValue::ToJSONStringify(ExceptionState* exception) { } else { result = ScriptValue(ctx_, stringifyedValue); } - + JS_FreeValue(ctx_, stringifyedValue); return result; } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 7c3e906431..a16824d32f 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -39,12 +39,36 @@ class ScriptValue final { explicit ScriptValue(JSContext* ctx) : ctx_(ctx){}; ScriptValue() = default; - ScriptValue& operator=(const ScriptValue& other) { - if (&other != this) { - value_ = JS_DupValue(ctx_, other.value_); + // Copy and assignment + ScriptValue(ScriptValue const &value) { + if (&value != this) { + value_ = JS_DupValue(ctx_, value.value_); } + ctx_ = value.ctx_; + }; + ScriptValue& operator=(const ScriptValue& value) { + if (&value != this) { + value_ = JS_DupValue(ctx_, value.value_); + } + ctx_ = value.ctx_; return *this; + } + + // Move operations + ScriptValue(ScriptValue&& value) noexcept { + if (&value != this) { + value_ = JS_DupValue(ctx_, value.value_); + } + ctx_ = value.ctx_; }; + ScriptValue& operator=(ScriptValue&& value) noexcept { + if (&value != this) { + value_ = JS_DupValue(ctx_, value.value_); + } + ctx_ = value.ctx_; + return *this; + } + ~ScriptValue() { JS_FreeValue(ctx_, value_); }; JSValue ToQuickJS() const; diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 9f9e0d8231..08b9128a33 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -22,14 +22,11 @@ JSValue ScriptWrappable::ToQuickJS() { } void ScriptWrappable::InitializeQuickJSObject() { - auto* wrapperTypeInfo = const_cast(GetWrapperTypeInfo()); + auto* wrapperTypeInfo = GetWrapperTypeInfo(); JSRuntime* runtime = runtime_; - /// 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 ToQuickJS to make sure JSClassDef when this class are created at the first class. - if (wrapperTypeInfo->classId == 0 || !JS_HasClassId(runtime, wrapperTypeInfo->classId)) { - /// Allocate a new unique classID from QuickJS. - JS_NewClassID(&wrapperTypeInfo->classId); + if (!JS_HasClassId(runtime, wrapperTypeInfo->classId)) { /// Basic template to describe the behavior about this class. JSClassDef def{}; diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 370b67b64f..e5e05a9e68 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -12,6 +12,11 @@ namespace kraken { +enum { + JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, + JS_CLASS_BLOB +}; + // 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 @@ -28,11 +33,11 @@ class WrapperTypeInfo final { return false; } + JSClassID classId{0}; const char* className{nullptr}; const WrapperTypeInfo* parent_class{nullptr}; JSClassCall* callFunc{nullptr}; JSClassExoticMethods *exoticMethods{nullptr}; - JSClassID classId{0}; }; } // namespace kraken diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index caee47b1c9..e74efb3c30 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -10,8 +10,6 @@ namespace kraken { static std::atomic context_unique_id{0}; -std::atomic runningContexts{0}; - #define MAX_JS_CONTEXT 1024 bool valid_contexts[MAX_JS_CONTEXT]; std::atomic running_context_list{0}; @@ -20,20 +18,6 @@ std::unique_ptr createJSContext(int32_t contextId, const JSExc return std::make_unique(contextId, handler, owner); } -static JSRuntime* runtime_{nullptr}; - -ExecutionContextGCTracker::ExecutionContextGCTracker(JSContext* ctx) : ScriptWrappable(ctx) {} -const WrapperTypeInfo& ExecutionContextGCTracker::wrapper_type_info_{"GCTracker"}; - -void ExecutionContextGCTracker::Trace(GCVisitor* visitor) const { - auto* context = static_cast(JS_GetContextOpaque(ctx())); - context->Trace(visitor); -} -void ExecutionContextGCTracker::Dispose() const {} -const char* ExecutionContextGCTracker::GetHumanReadableName() const { - return "GCTracker"; -} - ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) : context_id_(contextId), handler_(handler), owner_(owner), ctx_invalid_(false), unique_id_(context_unique_id++) { #if ENABLE_PROFILE @@ -52,31 +36,19 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& 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 (runtime_ == nullptr) { - runtime_ = JS_NewRuntime(); - } - // Avoid stack overflow when running in multiple threads. - JS_UpdateStackTop(runtime_); - ctx_ = JS_NewContext(runtime_); - time_origin_ = std::chrono::system_clock::now(); - global_object_ = JS_GetGlobalObject(ctx_); - JSValue windowGetter = JS_NewCFunction( - ctx_, [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_GetGlobalObject(ctx); }, "get", 0); - JSAtom windowKey = JS_NewAtom(ctx_, "window"); - JS_DefinePropertyGetSet(ctx_, global_object_, windowKey, windowGetter, JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); - JS_FreeAtom(ctx_, windowKey); - JS_SetContextOpaque(ctx_, this); - JS_SetHostPromiseRejectionTracker(runtime_, promiseRejectTracker, nullptr); - gc_tracker_ = makeGarbageCollected(ctx()); - JS_DefinePropertyValueStr(ctx_, global_object_, "_gc_tracker_", gc_tracker_->ToQuickJS(), JS_PROP_NORMAL); - DefineGlobalProperty("__GC_Tracker__", contextData()->constructorForType(ExecutionContextGCTracker::GetStaticWrapperTypeInfo())); - - runningContexts++; + JSContext* ctx = script_state_.ctx(); + global_object_ = JS_GetGlobalObject(script_state_.ctx()); + JSValue windowGetter = JS_NewCFunction( + ctx, [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_GetGlobalObject(ctx); }, "get", 0); + JSAtom windowKey = JS_NewAtom(ctx, "window"); + JS_DefinePropertyGetSet(ctx, global_object_, windowKey, windowGetter, JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); + JS_FreeAtom(ctx, windowKey); + JS_SetContextOpaque(ctx, this); + JS_SetHostPromiseRejectionTracker(script_state_.runtime(), promiseRejectTracker, nullptr); // Register all built-in native bindings. InstallBindings(this); @@ -101,17 +73,6 @@ ExecutingContext::~ExecutingContext() { valid_contexts[context_id_] = false; ctx_invalid_ = true; - // 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(ctx_, promiseContext->resolveFunc); - // JS_FreeValue(ctx_, promiseContext->rejectFunc); - // delete promiseContext; - // } - // } - // Free unreleased native_functions. { struct list_head *el, *el1; @@ -122,26 +83,14 @@ ExecutingContext::~ExecutingContext() { } // Check if current context have unhandled exceptions. - JSValue exception = JS_GetException(ctx_); + 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(ctx_, global_object_); - JS_FreeContext(ctx_); - - // Run GC to clean up remaining objects about m_ctx; - JS_RunGC(runtime_); - -#if DUMP_LEAKS - if (--runningContexts == 0) { - JS_FreeRuntime(runtime_); - runtime_ = nullptr; - } -#endif - ctx_ = nullptr; + JS_FreeValue(script_state_.ctx(), global_object_); } ExecutingContext* ExecutingContext::From(JSContext* ctx) { @@ -150,39 +99,39 @@ ExecutingContext* ExecutingContext::From(JSContext* ctx) { bool ExecutingContext::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(ctx_, utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); + JSValue result = JS_Eval(script_state_.ctx(), utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); DrainPendingPromiseJobs(); bool success = HandleException(&result); - JS_FreeValue(ctx_, 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(code), length)); - JSValue result = JS_Eval(ctx_, utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); + JSValue result = JS_Eval(script_state_.ctx(), utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); DrainPendingPromiseJobs(); bool success = HandleException(&result); - JS_FreeValue(ctx_, 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(ctx_, code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL); + JSValue result = JS_Eval(script_state_.ctx(), code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL); DrainPendingPromiseJobs(); bool success = HandleException(&result); - JS_FreeValue(ctx_, result); + JS_FreeValue(script_state_.ctx(), result); return success; } bool ExecutingContext::EvaluateByteCode(uint8_t* bytes, size_t byteLength) { JSValue obj, val; - obj = JS_ReadObject(ctx_, bytes, byteLength, JS_READ_OBJ_BYTECODE); + obj = JS_ReadObject(script_state_.ctx(), bytes, byteLength, JS_READ_OBJ_BYTECODE); if (!HandleException(&obj)) return false; - val = JS_EvalFunction(ctx_, obj); + val = JS_EvalFunction(script_state_.ctx(), obj); if (!HandleException(&val)) return false; - JS_FreeValue(ctx_, val); + JS_FreeValue(script_state_.ctx(), val); return true; } @@ -197,10 +146,10 @@ void* ExecutingContext::owner() { bool ExecutingContext::HandleException(JSValue* exc) { if (JS_IsException(*exc)) { - JSValue error = JS_GetException(ctx_); + JSValue error = JS_GetException(script_state_.ctx()); ReportError(error); DispatchGlobalErrorEvent(this, error); - JS_FreeValue(ctx_, error); + JS_FreeValue(script_state_.ctx(), error); return false; } @@ -218,25 +167,22 @@ JSValue ExecutingContext::Global() { JSContext* ExecutingContext::ctx() { assert(!ctx_invalid_ && "context has been released"); - return ctx_; -} - -JSRuntime* ExecutingContext::runtime() { - return runtime_; + return script_state_.ctx(); } void ExecutingContext::ReportError(JSValueConst error) { - if (!JS_IsError(ctx_, 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); + 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"); + JSValue stackValue = JS_GetPropertyStr(ctx, error, "stack"); if (!JS_IsUndefined(stackValue)) { - stack = JS_ToCString(ctx_, stackValue); + stack = JS_ToCString(ctx, stackValue); } uint32_t messageLength = strlen(type) + strlen(title); @@ -252,20 +198,20 @@ void ExecutingContext::ReportError(JSValueConst error) { 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); + 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(runtime(), &pctx); + int finished = JS_ExecutePendingJob(script_state_.runtime(), &pctx); while (finished != 0) { - finished = JS_ExecutePendingJob(runtime(), &pctx); + finished = JS_ExecutePendingJob(script_state_.runtime(), &pctx); if (finished == -1) { break; } @@ -276,9 +222,9 @@ void ExecutingContext::DrainPendingPromiseJobs() { } void ExecutingContext::DefineGlobalProperty(const char* prop, JSValue value) { - JSAtom atom = JS_NewAtom(ctx_, prop); - JS_SetProperty(ctx_, global_object_, atom, value); - JS_FreeAtom(ctx_, atom); + 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() { @@ -286,12 +232,12 @@ ExecutionContextData* ExecutingContext::contextData() { } uint8_t* ExecutingContext::DumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength) { - JSValue object = JS_Eval(ctx_, code, codeLength, sourceURL, JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_COMPILE_ONLY); + 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(ctx_, bytecodeLength, object, JS_WRITE_OBJ_BYTECODE); - JS_FreeValue(ctx_, object); + uint8_t* bytes = JS_WriteObject(script_state_.ctx(), bytecodeLength, object, JS_WRITE_OBJ_BYTECODE); + JS_FreeValue(script_state_.ctx(), object); return bytes; } @@ -400,11 +346,6 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { // return &pending_promises_; //} -void ExecutingContext::Trace(GCVisitor* visitor) { - timers_.trace(visitor); - module_listener_container_.trace(visitor); -} - void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01) { if (!JS_IsString(key)) return; diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 3a3e6ce5a7..fe2337e238 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -24,6 +24,7 @@ #include "foundation/macros.h" #include "foundation/ui_command_buffer.h" +#include "script_state.h" #include "dart_methods.h" #include "executing_context_data.h" #include "frame/dom_timer_coordinator.h" @@ -44,18 +45,6 @@ using JSExceptionHandler = std::function& dartMethodPtr() { return dart_method_ptr_; } - void Trace(GCVisitor* visitor); - std::chrono::time_point time_origin_; int32_t unique_id_; 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 void DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error); @@ -125,17 +113,20 @@ class ExecutingContext { private: static void promiseRejectTracker(JSContext* ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void* opaque); + // From C++ standard, https://isocpp.org/wiki/faq/dtors#order-dtors-for-members + // Members first initialized and destructed at the last. + // Always keep ScriptState at the top of all stack allocated members to make sure it destructed in the last. + ScriptState script_state_; + int32_t context_id_; JSExceptionHandler handler_; void* owner_; JSValue global_object_{JS_NULL}; bool ctx_invalid_{false}; - JSContext* ctx_{nullptr}; Document* document_{nullptr}; DOMTimerCoordinator timers_; ModuleListenerContainer module_listener_container_; ModuleCallbackCoordinator module_callbacks_; - ExecutionContextGCTracker* gc_tracker_{nullptr}; ExecutionContextData context_data_{this}; UICommandBuffer ui_command_buffer_{this}; std::unique_ptr dart_method_ptr_ = std::make_unique(); diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc index 17f9f0086d..751d79990a 100644 --- a/bridge/core/executing_context_data.cc +++ b/bridge/core/executing_context_data.cc @@ -36,7 +36,7 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty JSClassDef def{}; def.class_name = type->className; def.call = type->callFunc; - JS_NewClass(m_context->runtime(), class_id, &def); + 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); @@ -71,11 +71,11 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty void ExecutionContextData::Dispose() { for(auto& entry: prototype_map_) { - JS_FreeValueRT(m_context->runtime(), entry.second); + JS_FreeValueRT(ScriptState::runtime(), entry.second); } for(auto& entry: constructor_map_) { - JS_FreeValueRT(m_context->runtime(), entry.second); + JS_FreeValueRT(ScriptState::runtime(), entry.second); } } diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 825faa8d93..181d6974b8 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -13,7 +13,7 @@ void Console::__kraken_print__(ExecutingContext* context, std::unique_ptrcontextId(), stream, nativeStringToStdString(level.get()), nullptr); + printLog(context->contextId(), stream, level != nullptr ? nativeStringToStdString(level.get()) : "info", nullptr); } void Console::__kraken_print__(ExecutingContext* context, std::unique_ptr& log, ExceptionState& exception_state) { diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index a49f16310f..ba57516668 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -31,14 +31,6 @@ void DOMTimer::Fire() { } } -void DOMTimer::Trace(GCVisitor* visitor) const { - callback_->Trace(visitor); -} - -void DOMTimer::Dispose() const { - callback_->Dispose(); -} - void DOMTimer::setTimerId(int32_t timerId) { timerId_ = timerId; } diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index 3ec5eecda1..0d856f1b10 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -25,9 +25,6 @@ class DOMTimer { ExecutingContext* context() { return context_; } - void Trace(GCVisitor* visitor) const; - void Dispose() const; - private: ExecutingContext* context_{nullptr}; int32_t timerId_{-1}; diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 5129996c13..75d9aee533 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -64,19 +64,4 @@ std::shared_ptr DOMTimerCoordinator::getTimerById(int32_t timerId) { return m_activeTimers[timerId]; } -void DOMTimerCoordinator::trace(GCVisitor* visitor) { - for (auto& timer : m_activeTimers) { - timer.second->Trace(visitor); - } - - // Recycle all abandoned timers. - if (!m_abandonedTimers.empty()) { - for (auto& timer : m_abandonedTimers) { - timer->Trace(visitor); - } - // All abandoned timers should be freed at the sweep stage. - m_abandonedTimers.clear(); - } -} - } // namespace kraken diff --git a/bridge/core/frame/dom_timer_coordinator.h b/bridge/core/frame/dom_timer_coordinator.h index f008adc511..51226b495a 100644 --- a/bridge/core/frame/dom_timer_coordinator.h +++ b/bridge/core/frame/dom_timer_coordinator.h @@ -31,8 +31,6 @@ class DOMTimerCoordinator { void* removeTimeoutById(int32_t timerId); std::shared_ptr getTimerById(int32_t timerId); - void trace(GCVisitor* visitor); - private: std::unordered_map> m_activeTimers; std::vector> m_abandonedTimers; diff --git a/bridge/core/frame/module_callback.cc b/bridge/core/frame/module_callback.cc index fa604218f3..552f3ceb05 100644 --- a/bridge/core/frame/module_callback.cc +++ b/bridge/core/frame/module_callback.cc @@ -17,12 +17,4 @@ std::shared_ptr ModuleCallback::value() { return function_; } -void ModuleCallback::Trace(GCVisitor* visitor) const { - function_->Trace(visitor); -} - -void ModuleCallback::Dispose() const { - function_->Dispose(); -} - } // namespace kraken diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h index 585d31156a..d64ef8a0ee 100644 --- a/bridge/core/frame/module_callback.h +++ b/bridge/core/frame/module_callback.h @@ -12,15 +12,6 @@ namespace kraken { -class ModuleCallback; - -// In C++ code, We can not use offsetof to access members of structures or classes that are not Plain Old Data Structures. -// So we use struct which support offsetof. -struct ModuleCallbackLinker { - ModuleCallback* ptr; - list_head link; -}; - // ModuleCallback is an asynchronous callback function, usually from the 4th parameter of `kraken.invokeModule` function. // When the asynchronous operation on the Dart side ends, the callback is will called and to return to the JS executing environment. class ModuleCallback { @@ -30,11 +21,6 @@ class ModuleCallback { std::shared_ptr value(); - void Trace(GCVisitor* visitor) const; - void Dispose() const; - - ModuleCallbackLinker linker{this}; - private: std::shared_ptr function_{nullptr}; }; diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc index 0049f76210..e381336ca2 100644 --- a/bridge/core/frame/module_callback_coordinator.cc +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -7,7 +7,7 @@ namespace kraken { -void ModuleCallbackCoordinator::AddModuleCallbacks(std::shared_ptr callback) { +void ModuleCallbackCoordinator::AddModuleCallbacks(std::shared_ptr&& callback) { listeners_.push_front(callback); } @@ -18,10 +18,4 @@ void ModuleCallbackCoordinator::RemoveModuleCallbacks(std::shared_ptrTrace(visitor); - } -} - } // namespace kraken diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_callback_coordinator.h index 6e59ce59bb..9394e16668 100644 --- a/bridge/core/frame/module_callback_coordinator.h +++ b/bridge/core/frame/module_callback_coordinator.h @@ -20,11 +20,9 @@ class ModuleCallbackCoordinator final { public: ModuleCallbackCoordinator(); - void AddModuleCallbacks(std::shared_ptr callback); + void AddModuleCallbacks(std::shared_ptr&& callback); void RemoveModuleCallbacks(std::shared_ptr callback); - void Trace(GCVisitor* visitor); - private: std::forward_list> listeners_; friend ModuleListener; diff --git a/bridge/core/frame/module_listener.cc b/bridge/core/frame/module_listener.cc index 18ed76b318..2ef03d4fe0 100644 --- a/bridge/core/frame/module_listener.cc +++ b/bridge/core/frame/module_listener.cc @@ -5,20 +5,14 @@ #include "module_listener.h" +#include + namespace kraken { -std::shared_ptr ModuleListener::Create(std::shared_ptr function) { +std::shared_ptr ModuleListener::Create(const std::shared_ptr& function) { return std::make_shared(function); } -ModuleListener::ModuleListener(std::shared_ptr function) : function_(function) {} - -void ModuleListener::Trace(GCVisitor* visitor) const { - function_->Trace(visitor); -} - -void ModuleListener::Dispose() const { - function_->Dispose(); -} +ModuleListener::ModuleListener(std::shared_ptr function) : function_(std::move(function)) {} } // namespace kraken diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h index 0918c5e136..8c9dd8f44b 100644 --- a/bridge/core/frame/module_listener.h +++ b/bridge/core/frame/module_listener.h @@ -18,12 +18,10 @@ class ModuleListenerContainer; // 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 Create(std::shared_ptr function); + static std::shared_ptr Create(const std::shared_ptr& function); explicit ModuleListener(std::shared_ptr function); private: - void Trace(GCVisitor* visitor) const; - void Dispose() const; std::shared_ptr function_{nullptr}; diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc index a9e7fe895c..c7714c0809 100644 --- a/bridge/core/frame/module_listener_container.cc +++ b/bridge/core/frame/module_listener_container.cc @@ -7,14 +7,8 @@ namespace kraken { -void ModuleListenerContainer::addModuleListener(std::shared_ptr listener) { - m_listeners.insert_after(m_listeners.end(), listener); -} - -void ModuleListenerContainer::trace(GCVisitor* visitor) { - for (auto& listener : m_listeners) { - listener->function_->Trace(visitor); - } +void ModuleListenerContainer::AddModuleListener(const std::shared_ptr& listener) { + listeners_.push_front(listener); } } // namespace kraken diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h index 8165c1d310..615dd8bda0 100644 --- a/bridge/core/frame/module_listener_container.h +++ b/bridge/core/frame/module_listener_container.h @@ -13,11 +13,10 @@ namespace kraken { class ModuleListenerContainer final { public: - void addModuleListener(std::shared_ptr listener); - void trace(GCVisitor* visitor); + void AddModuleListener(const std::shared_ptr& listener); private: - std::forward_list> m_listeners; + std::forward_list> listeners_; friend ModuleListener; }; diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index ed56086603..db0043ce2c 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -31,7 +31,9 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha if (errmsg != nullptr) { ScriptValue errorObject = ScriptValue::createErrorObject(ctx, errmsg); - ScriptValue arguments[] = {errorObject}; + ScriptValue arguments[] = { + errorObject + }; ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); if (returnValue.IsException()) { context->HandleException(&returnValue); @@ -40,7 +42,9 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha std::u16string argumentString = std::u16string(reinterpret_cast(json->string), json->length); std::string utf8Arguments = toUTF8(argumentString); ScriptValue jsonObject = ScriptValue::createJSONObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); - ScriptValue arguments[] = {jsonObject}; + ScriptValue arguments[] = { + jsonObject + }; ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); if (returnValue.IsException()) { context->HandleException(&returnValue); @@ -49,6 +53,8 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha context->DrainPendingPromiseJobs(); context->ModuleCallbacks()->RemoveModuleCallbacks(moduleContext->callback); + + delete moduleContext; } void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json) { @@ -59,6 +65,8 @@ std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingC std::unique_ptr &moduleName, std::unique_ptr &method, ExceptionState& exception) { + ScriptValue empty = ScriptValue::Empty(context->ctx()); + return __kraken_invoke_module__(context, moduleName, method, empty, nullptr, exception); } std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, @@ -66,7 +74,7 @@ std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingC std::unique_ptr &method, ScriptValue& paramsValue, ExceptionState& exception) { - + return __kraken_invoke_module__(context, moduleName, method, paramsValue, nullptr, exception); } std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, @@ -90,38 +98,32 @@ std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingC } auto moduleCallback = ModuleCallback::Create(callback); - context->ModuleCallbacks()->AddModuleCallbacks(moduleCallback); -// -// ModuleContext* moduleContext = new ModuleContext{context, moduleCallback}; -// -// NativeString* result; -// if (callback != nullptr) { -// result = context->dartMethodPtr()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleTransientCallback); -// } else { -// result = context->dartMethodPtr()->invokeModule(moduleContext, context->getContextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleUnexpectedCallback); -// } -// -// moduleName->free(); -// method->free(); -// if (params != nullptr) { -// params->free(); -// } -// -// if (result == nullptr) { -// return ScriptValue::Empty(context->ctx()); -// } -// -// ScriptValue resultString = ScriptValue::fromNativeString(context->ctx(), result); -// -// // Manual free returned result string; -// result->free(); - -// return resultString; + context->ModuleCallbacks()->AddModuleCallbacks(std::move(moduleCallback)); + ModuleContext* moduleContext = new ModuleContext{context, moduleCallback}; + + NativeString* result; + if (callback != nullptr) { + result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleTransientCallback); + } else { + result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleUnexpectedCallback); + } + + moduleName->free(); + method->free(); + if (params != nullptr) { + params->free(); + } + + if (result == nullptr) { + return nullptr; + } + + return std::unique_ptr(result); } -void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, std::shared_ptr handler, ExceptionState& exception) { +void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, const std::shared_ptr& handler, ExceptionState& exception) { auto listener = ModuleListener::Create(handler); - context->ModuleListeners()->addModuleListener(listener); + context->ModuleListeners()->AddModuleListener(listener); } } // namespace kraken diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 029ce9fc94..2efdb52938 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -29,7 +29,7 @@ class ModuleManager { ScriptValue& params, std::shared_ptr callback, ExceptionState& exception); - static void __kraken_add_module_listener__(ExecutingContext* context, std::shared_ptr handler, ExceptionState& exception); + static void __kraken_add_module_listener__(ExecutingContext* context, const std::shared_ptr& handler, ExceptionState& exception); }; } // namespace kraken diff --git a/bridge/core/frame/module_manager_test.cc b/bridge/core/frame/module_manager_test.cc index 5218360da0..853af35261 100644 --- a/bridge/core/frame/module_manager_test.cc +++ b/bridge/core/frame/module_manager_test.cc @@ -4,13 +4,37 @@ */ #include -#include "executing_context.h" -#include "host_object.h" #include "kraken_test_env.h" -#include "page.h" namespace kraken { +TEST(ModuleManager, ShouldReturnCorrectValue) { + bool static errorCalled = false; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + errorCalled = true; + }); + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + auto context = bridge->getContext(); + + std::string code = std::string(R"( +let object = { + key: { + v: { + a: { + other: null + } + } + } +}; +let result = kraken.methodChannel.invokeMethod('abc', 'fn', object); +console.log(result); +)"); + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} + TEST(ModuleManager, shouldThrowErrorWhenBadJSON) { bool static errorCalled = false; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { @@ -35,7 +59,7 @@ let object = { object.other = object; kraken.methodChannel.invokeMethod('abc', 'fn', object); )"); - context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); + context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, true); } @@ -66,7 +90,7 @@ 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); } diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index fcdb1d2c49..ab125785dd 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -52,7 +52,7 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState* exception) { #if FLUTTER_BACKEND if (context->dartMethodPtr()->setTimeout == nullptr) { - exception->throwException(context->ctx(), ErrorType::InternalError, "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); + exception->ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); return -1; } #endif diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc new file mode 100644 index 0000000000..a0320bdc10 --- /dev/null +++ b/bridge/core/script_state.cc @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "script_state.h" + +namespace kraken { + +JSRuntime* runtime_ = nullptr; +std::atomic runningContexts{0}; + +ScriptState::ScriptState() { + runningContexts++; + if (runtime_ == nullptr) { + runtime_ = JS_NewRuntime(); + } + // Avoid stack overflow when running in multiple threads. + JS_UpdateStackTop(runtime_); + ctx_ = JS_NewContext(runtime_); +} + +JSRuntime * ScriptState::runtime() { + return runtime_; +} + +ScriptState::~ScriptState() { + JS_FreeContext(ctx_); + + // Run GC to clean up remaining objects about m_ctx; + JS_RunGC(runtime_); + +#if DUMP_LEAKS + if (--runningContexts == 0) { + JS_FreeRuntime(runtime_); + runtime_ = nullptr; + } +#endif + ctx_ = nullptr; +} +} diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h new file mode 100644 index 0000000000..09db20a7c8 --- /dev/null +++ b/bridge/core/script_state.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ +#define KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ + +#include +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +// ScriptState is an abstraction class that holds all information about script +// execution (e.g., JSContext etc). If you need any info about the script execution, you're expected to +// pass around ScriptState in the code base. ScriptState is in a 1:1 +// relationship with JSContext. +class ScriptState { + public: + ScriptState(); + ~ScriptState(); + + inline JSContext* ctx() { return ctx_; } + static JSRuntime* runtime(); + private: + JSContext* ctx_{nullptr}; +}; + +} + +#endif // KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index d72eaba618..da3ac9290d 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -24,7 +24,7 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND - m_context->dartMethodPtr()->requestBatchUpdate(m_context->getContextId()); + m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); #endif update_batched = true; } @@ -36,7 +36,7 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND - m_context->dartMethodPtr()->requestBatchUpdate(m_context->getContextId()); + m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); update_batched = true; #endif } @@ -48,7 +48,7 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01 void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01, NativeString& args_02, void* nativePtr) { #if FLUTTER_BACKEND if (!update_batched) { - m_context->dartMethodPtr()->requestBatchUpdate(m_context->getContextId()); + m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); update_batched = true; } #endif diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index 0f917a535b..3969d0d368 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -10,7 +10,7 @@ import { console } from './console'; // import { asyncStorage } from './async-storage'; // import { URLSearchParams } from './url-search-params'; // import { URL } from './url'; -// import { kraken } from './kraken'; +import { kraken } from './kraken'; // import { ErrorEvent, PromiseRejectionEvent } from './events'; // defineGlobalProperty('ErrorEvent', ErrorEvent); @@ -28,7 +28,7 @@ defineGlobalProperty('console', console); // defineGlobalProperty('asyncStorage', asyncStorage); // defineGlobalProperty('URLSearchParams', URLSearchParams); // defineGlobalProperty('URL', URL); -// defineGlobalProperty('kraken', kraken); +defineGlobalProperty('kraken', kraken); // defineGlobalProperty('ErrorEvent', ErrorEvent); function defineGlobalProperty(key: string, value: any, isEnumerable: boolean = true) { diff --git a/bridge/polyfill/src/method-channel.ts b/bridge/polyfill/src/method-channel.ts index e93a9afafa..6b5b6a7285 100644 --- a/bridge/polyfill/src/method-channel.ts +++ b/bridge/polyfill/src/method-channel.ts @@ -22,13 +22,16 @@ export const methodChannel = { clearMethodCallHandler() { methodCallHandlers.length = 0; }, - invokeMethod(method: string, ...args: any[]): Promise { - return new Promise((resolve, reject) => { - krakenInvokeModule('MethodChannel', 'invokeMethod', [method, args], (e, data) => { - if (e) return reject(e); - resolve(data); + invokeMethod(method: string, ...args: any[]): string { + // return new Promise((resolve, reject) => { + // krakenInvokeModule('MethodChannel', 'invokeMethod', [method, args], (e, data) => { + // if (e) return reject(e); + // resolve(data); + // }); + // }); + return krakenInvokeModule('MethodChannel', 'invokeMethod', [method, args], function aaa(e, data) { + console.log('1234'); }); - }); }, }; diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index bf2f3ad91b..7e79f48215 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -3,7 +3,7 @@ import {uniqBy} from "lodash"; import {Blob} from "./blob"; import {addIndent, getClassName} from "./utils"; -function generateInterfaceAdditionalHeader(object: any): [string, string, string] { +function generateInterfaceAdditionalHeader(blob: Blob, object: any): [string, string, string] { if (!(object instanceof ClassObject)) { return ['', '', '']; } @@ -13,7 +13,7 @@ function generateInterfaceAdditionalHeader(object: any): [string, string, string }`; let wrapperTypeDefine = `static JSValue ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); - constexpr static const WrapperTypeInfo m_wrapperTypeInfo = {"Blob", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ConstructorCallback}; + constexpr static const WrapperTypeInfo m_wrapperTypeInfo = {JS_CLASS_${getClassName(blob).toUpperCase()}, "Blob", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ConstructorCallback}; `; let installFunctions = `static void InstallPrototypeMethods(ExecutingContext* context); @@ -29,7 +29,7 @@ function generateInterfaceAdditionalHeader(object: any): [string, string, string export function generateCppHeader(blob: Blob) { let classObject = blob.objects.find(object => object instanceof ClassObject); - let interfaceDefines = generateInterfaceAdditionalHeader(classObject); + let interfaceDefines = generateInterfaceAdditionalHeader(blob, classObject); let haveInterfaceBase = !!classObject; return `/* * Copyright (C) 2021 Alibaba Inc. All rights reserved. diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index 66595ce1db..61d5c2c311 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -89,7 +89,7 @@ if (exception_state.HasException()) { return exception_state.ToQuickJS(); } -if (argc <= ${argsIndex}) { +if (argc <= ${argsIndex + 1}) { ${call} break; }`; @@ -114,7 +114,7 @@ function generateFunctionCallBody(blob: Blob, declaration: FunctionDeclaration, let totalArguments: string[] = requiredArguments.slice(); for (let i = minimalRequiredArgc; i < declaration.args.length; i ++) { - optionalArgumentsInit.push(generateOptionalInitBody(blob, declaration, declaration.args[i], i + 1, totalArguments, options)); + optionalArgumentsInit.push(generateOptionalInitBody(blob, declaration, declaration.args[i], i, totalArguments, options)); totalArguments.push(`args_${declaration.args[i].name}`); } diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index 1a38f8ff62..1c788307c0 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -114,11 +114,12 @@ static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int arg } static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* context = ExecutingContext::From(ctx); #if FLUTTER_BACKEND - if (getDartMethod()->environment == nullptr) { + if (context->dartMethodPtr()->environment == nullptr) { return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_environment__': dart method (environment) is not registered."); } - const char* env = getDartMethod()->environment(); + const char* env = context->dartMethodPtr()->environment(); return JS_ParseJSON(ctx, env, strlen(env), ""); #else return JS_NewObject(ctx); diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index a3f480019b..0a5ac7dd54 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -67,7 +67,11 @@ NativeString* TEST_invokeModule(void* callbackContext, int32_t contextId, Native callback(callbackContext, contextId, nativeStringToStdString(method).c_str(), nullptr); } - return nullptr; + if (module == "MethodChannel") { + callback(callbackContext, contextId, nullptr, stringToNativeString("{\"result\": 1234}").release()); + } + + return stringToNativeString(module).release(); }; void TEST_requestBatchUpdate(int32_t contextId){}; @@ -77,7 +81,7 @@ void TEST_reloadApp(int32_t contextId) {} int32_t timerId = 0; int32_t TEST_setTimeout(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { - JSRuntime* rt = timer->context()->runtime(); + JSRuntime* rt = ScriptState::runtime(); auto* context = timer->context(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); @@ -94,7 +98,7 @@ int32_t TEST_setTimeout(kraken::DOMTimer* timer, int32_t contextId, AsyncCallbac } int32_t TEST_setInterval(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { - JSRuntime* rt = timer->context()->runtime(); + JSRuntime* rt = ScriptState::runtime(); auto* context = timer->context(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); JSOSTimer* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); @@ -113,7 +117,7 @@ int32_t TEST_setInterval(kraken::DOMTimer* timer, int32_t contextId, AsyncCallba int32_t callbackId = 0; uint32_t TEST_requestAnimationFrame(kraken::FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { - JSRuntime* rt = frameCallback->context()->runtime(); + JSRuntime* rt = ScriptState::runtime(); auto* context = frameCallback->context(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); JSFrameCallback* th = static_cast(js_mallocz(context->ctx(), sizeof(*th))); @@ -132,14 +136,14 @@ uint32_t TEST_requestAnimationFrame(kraken::FrameCallback* frameCallback, int32_ void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { auto* page = static_cast(getPage(contextId)); auto* context = page->getContext(); - JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(ScriptState::runtime())); ts->os_frameCallbacks.erase(id); } void TEST_clearTimeout(int32_t contextId, int32_t timerId) { auto* page = static_cast(getPage(contextId)); auto* context = page->getContext(); - JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(context->runtime())); + JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(ScriptState::runtime())); ts->os_timers.erase(timerId); } @@ -185,7 +189,7 @@ std::unique_ptr TEST_init(OnJSError onJsError) { auto* page = static_cast(getPage(contextId)); auto* context = page->getContext(); JSThreadState* th = new JSThreadState(); - JS_SetRuntimeOpaque(context->runtime(), th); + JS_SetRuntimeOpaque(ScriptState::runtime(), th); TEST_mockDartMethods(contextId, onJsError); @@ -203,7 +207,7 @@ std::unique_ptr TEST_allocateNewPage() { } static bool jsPool(kraken::ExecutingContext* context) { - JSRuntime* rt = context->runtime(); + JSRuntime* rt = ScriptState::runtime(); JSThreadState* ts = static_cast(JS_GetRuntimeOpaque(rt)); int64_t cur_time, delay; struct list_head* el; diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 0858329d5d..ccb1870030 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -18,6 +18,7 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./test/kraken_test_env.h ./core/executing_context_test.cc ./core/frame/console_test.cc + ./core/frame/module_manager_test.cc # ./bindings/qjs/bom/timer_test.cc # ./bindings/qjs/qjs_patch_test.cc # ./bindings/qjs/garbage_collected_test.cc @@ -94,6 +95,7 @@ add_library(kraken_test SHARED ${KRAKEN_TEST_SOURCE}) target_link_libraries(kraken_test PRIVATE ${BRIDGE_LINK_LIBS} kraken) target_include_directories(kraken_test PRIVATE ${BRIDGE_INCLUDE} + ./test ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ./include) if (DEFINED ENV{LIBRARY_OUTPUT_DIR}) From 86ee5cbe2a0b47003deef9c0f5832166b67c4650 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Thu, 24 Mar 2022 12:26:04 +0000 Subject: [PATCH 033/498] Committing clang-format changes --- bridge/bindings/qjs/atom_string.h | 16 ++--- bridge/bindings/qjs/converter_impl.h | 40 ++++------- bridge/bindings/qjs/idl_type.h | 8 +-- bridge/bindings/qjs/member_installer.cc | 5 +- bridge/bindings/qjs/pending_promises.cc | 2 +- bridge/bindings/qjs/pending_promises.h | 2 +- bridge/bindings/qjs/qjs_function.h | 6 +- bridge/bindings/qjs/qjs_interface_bridge.h | 14 ++-- bridge/bindings/qjs/script_promise.cc | 10 +-- bridge/bindings/qjs/script_promise.h | 4 +- .../bindings/qjs/script_promise_resolver.cc | 8 +-- bridge/bindings/qjs/script_promise_resolver.h | 6 +- bridge/bindings/qjs/script_value.cc | 4 +- bridge/bindings/qjs/script_value.h | 2 +- bridge/bindings/qjs/script_wrappable.cc | 4 +- bridge/bindings/qjs/script_wrappable.h | 20 +++--- bridge/bindings/qjs/to_quickjs.h | 5 +- bridge/bindings/qjs/wrapper_type_info.h | 7 +- .../dom/frame_request_callback_collection.h | 2 +- bridge/core/executing_context.cc | 2 +- bridge/core/executing_context.h | 6 +- bridge/core/executing_context_data.cc | 4 +- bridge/core/fileapi/array_buffer_data.h | 2 +- bridge/core/fileapi/blob.cc | 7 +- bridge/core/fileapi/blob.h | 2 +- bridge/core/fileapi/blob_part.cc | 4 +- bridge/core/fileapi/blob_part.h | 22 +++--- bridge/core/fileapi/blob_property_bag.cc | 2 +- bridge/core/fileapi/blob_property_bag.h | 5 +- bridge/core/frame/dom_timer.h | 2 +- .../core/frame/module_callback_coordinator.cc | 3 +- bridge/core/frame/module_listener.h | 1 - bridge/core/frame/module_manager.cc | 33 ++++----- bridge/core/frame/module_manager.h | 23 +++---- bridge/core/frame/module_manager_test.cc | 4 +- bridge/core/script_state.cc | 4 +- bridge/core/script_state.h | 3 +- bridge/foundation/macros.h | 10 +-- bridge/foundation/native_value.cc | 68 +++++++++---------- 39 files changed, 162 insertions(+), 210 deletions(-) diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index 190ac78aa5..723ba2ea28 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -15,17 +15,14 @@ namespace kraken { class AtomString final { // ScriptAtom should only allocate at stack. KRAKEN_DISALLOW_NEW(); + public: - explicit AtomString(JSContext* ctx, const char* string): ctx_(ctx), atom_(JS_NewAtom(ctx, string)) {} - explicit AtomString(JSContext* ctx, JSAtom atom): ctx_(ctx), atom_(JS_DupAtom(ctx, atom)) {}; + explicit AtomString(JSContext* ctx, const char* string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string)) {} + explicit AtomString(JSContext* ctx, JSAtom atom) : ctx_(ctx), atom_(JS_DupAtom(ctx, atom)){}; - ~AtomString() { - JS_FreeAtom(ctx_, atom_); - } + ~AtomString() { JS_FreeAtom(ctx_, atom_); } - JSValue ToQuickJS() const { - return JS_AtomToValue(ctx_, atom_); - } + JSValue ToQuickJS() const { return JS_AtomToValue(ctx_, atom_); } AtomString& operator=(const AtomString& other) { if (&other != this) { @@ -40,7 +37,6 @@ class AtomString final { JSAtom atom_{JS_ATOM_NULL}; }; - -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 12ec84bea7..3f69af79ed 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -33,9 +33,7 @@ struct Converter, std::enable_if_t::FromValue(ctx, value, exception); } - static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { - return Converter::ToValue(ctx, value); - } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, value); } }; template @@ -49,9 +47,7 @@ struct Converter, std::enable_if_t::FromValue(ctx, value, exception); } - static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { - return Converter::ToValue(ctx, value); - } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, value); } }; // Optional value for arithmetic value @@ -66,9 +62,7 @@ struct Converter, std::enable_if_t::FromValue(ctx, value, exception); } - static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { - return Converter::ToValue(ctx, value); - } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, value); } }; // Any @@ -82,16 +76,14 @@ struct Converter : public ConverterBase { static JSValue ToValue(JSContext* ctx, ScriptValue value) { return value.ToQuickJS(); } }; -template<> +template <> struct Converter> : public ConverterBase> { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return ScriptValue(ctx, value); } - static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { - return Converter::ToValue(ctx, std::move(value)); - } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, std::move(value)); } }; // Boolean @@ -119,7 +111,7 @@ struct Converter : public ConverterBase { }; // Int32 -template<> +template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); @@ -130,9 +122,8 @@ struct Converter : public ConverterBase { static JSValue ToValue(JSContext* ctx, uint32_t v) { return JS_NewInt32(ctx, v); } }; - // Int64 -template<> +template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); @@ -164,21 +155,20 @@ struct Converter : public ConverterBase { 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());} + static JSValue ToValue(JSContext* ctx, const std::string& str) { return JS_NewString(ctx, str.c_str()); } }; -template<> +template <> struct Converter> : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - if (JS_IsUndefined(value)) return nullptr; + if (JS_IsUndefined(value)) + return nullptr; 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)); - } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, std::move(value)); } }; template <> @@ -220,7 +210,7 @@ struct Converter> : public ConverterBase> { } }; -template +template struct Converter>> : public ConverterBase> { using ImplType = typename IDLSequence::ImplType>::ImplType; static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { @@ -255,9 +245,9 @@ struct Converter : public ConverterBase { static JSValue ToValue(JSContext* ctx, BlobPart* data) { return data->ToQuickJS(ctx); } }; -template<> +template <> struct Converter : public ConverterBase { - using ImplType = BlobPropertyBag::ImplType; + 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); diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 2829873dff..1f0945e929 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -15,7 +15,7 @@ struct IDLTypeBase { using ImplType = void; }; -template +template struct IDLTypeBaseHelper { using ImplType = T; }; @@ -24,7 +24,7 @@ class ScriptValue; // Any struct IDLAny final : public IDLTypeBaseHelper {}; -template +template struct IDLOptional final : public IDLTypeBase { using ImplType = typename Converter::ImplType; }; @@ -59,11 +59,11 @@ struct IDLCallback : public IDLTypeBaseHelper> { }; // Sequence -template +template struct IDLSequence final : public IDLTypeBase { using ImplType = typename std::vector; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc index b02054b850..3fc495ce89 100644 --- a/bridge/bindings/qjs/member_installer.cc +++ b/bridge/bindings/qjs/member_installer.cc @@ -3,10 +3,10 @@ * Author: Kraken Team. */ -#include #include "member_installer.h" -#include "qjs_engine_patch.h" +#include #include "core/executing_context.h" +#include "qjs_engine_patch.h" namespace kraken { @@ -37,7 +37,6 @@ static JSValue handleCallThisOnProxy(JSContext* ctx, JSValueConst this_val, int return result; } - void MemberInstaller::InstallAttributes(ExecutingContext* context, JSValue root, std::initializer_list config) { JSContext* ctx = context->ctx(); for (auto& c : config) { diff --git a/bridge/bindings/qjs/pending_promises.cc b/bridge/bindings/qjs/pending_promises.cc index 8687149cd3..eb708d88bc 100644 --- a/bridge/bindings/qjs/pending_promises.cc +++ b/bridge/bindings/qjs/pending_promises.cc @@ -12,4 +12,4 @@ void PendingPromises::TrackPendingPromises(ScriptPromise&& promise) { promises_.emplace_back(promise); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/pending_promises.h b/bridge/bindings/qjs/pending_promises.h index bf6735ea42..cfe83b8e1b 100644 --- a/bridge/bindings/qjs/pending_promises.h +++ b/bridge/bindings/qjs/pending_promises.h @@ -21,6 +21,6 @@ class PendingPromises { std::vector promises_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 1c32a26f8f..58b3217282 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -16,10 +16,8 @@ namespace kraken { class QJSFunction { public: static std::shared_ptr Create(JSContext* ctx, JSValue function) { return std::make_shared(ctx, function); } - explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), function_(JS_DupValue(ctx, function)) {}; - ~QJSFunction() { - JS_FreeValue(ctx_, function_); - } + explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), function_(JS_DupValue(ctx, function)){}; + ~QJSFunction() { JS_FreeValue(ctx_, function_); } bool IsFunction(JSContext* ctx); diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index e8b6628419..9a352869cf 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -6,23 +6,19 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ -#include "script_wrappable.h" #include "core/executing_context.h" +#include "script_wrappable.h" namespace kraken { -template +template class QJSInterfaceBridge { public: - static T* ToWrappable(ExecutingContext* context, JSValue value) { - return HasInstance(context, value) ? toScriptWrappable(value) : nullptr; - } + 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()->prototypeForType(QJST::GetWrapperTypeInfo())); - } + static bool HasInstance(ExecutingContext* context, JSValue value) { return JS_IsInstanceOf(context->ctx(), value, context->contextData()->prototypeForType(QJST::GetWrapperTypeInfo())); } }; -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index ad37757fe2..376d8fa9e4 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -8,8 +8,9 @@ namespace kraken { -ScriptPromise::ScriptPromise(JSContext* ctx, JSValue promise): ctx_(ctx) { - if (JS_IsUndefined(promise) || JS_IsNull(promise)) return; +ScriptPromise::ScriptPromise(JSContext* ctx, JSValue promise) : ctx_(ctx) { + if (JS_IsUndefined(promise) || JS_IsNull(promise)) + return; if (!JS_IsPromise(promise)) { return; @@ -22,7 +23,6 @@ JSValue ScriptPromise::ToQuickJS() { return JS_NULL; } -void ScriptPromise::Trace(GCVisitor* visitor) { -} +void ScriptPromise::Trace(GCVisitor* visitor) {} -} +} // namespace kraken diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index 9b9ddef769..a2512b4f3c 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -19,8 +19,8 @@ namespace kraken { // memory leaks since it has a reference from C++ to QuickJS. class ScriptPromise final { KRAKEN_DISALLOW_NEW(); - public: + public: ScriptPromise() = default; ScriptPromise(JSContext* ctx, JSValue promise); @@ -33,6 +33,6 @@ class ScriptPromise final { ScriptValue promise_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index 2daf6717cf..8023772071 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -4,8 +4,8 @@ */ #include "script_promise_resolver.h" -#include "pending_promises.h" #include "core/executing_context.h" +#include "pending_promises.h" namespace kraken { @@ -13,7 +13,7 @@ ScriptPromiseResolver* ScriptPromiseResolver::Create(ExecutingContext* context) return new ScriptPromiseResolver(context); } -ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context): context_(context) { +ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context) : context_(context) { JSValue resolving_funcs[2]; promise_ = JS_NewPromiseCapability(context->ctx(), resolving_funcs); resolve_func_ = resolving_funcs[0]; @@ -36,9 +36,9 @@ void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) { assert(state_ == kRejecting); JSValue arguments[] = {value}; JSValue return_value = JS_Call(context_->ctx(), reject_func_, JS_NULL, 1, arguments); - JS_FreeValue(context_->ctx() , return_value); + JS_FreeValue(context_->ctx(), return_value); } } } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 3801b8d872..16e0612231 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ -#include "script_promise.h" #include "converter_impl.h" +#include "script_promise.h" #include "to_quickjs.h" namespace kraken { @@ -47,7 +47,7 @@ class ScriptPromiseResolver { template void ResolveOrReject(T value, ResolutionState new_state) { - if (state_ != kPending || !context_->IsValid() || !context_ ) + if (state_ != kPending || !context_->IsValid() || !context_) return; assert(new_state == kResolving || new_state == kRejecting); state_ = new_state; @@ -63,6 +63,6 @@ class ScriptPromiseResolver { JSValue reject_func_{JS_NULL}; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index afa0cd9882..ebe7ed01fb 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -4,10 +4,10 @@ */ #include "script_value.h" -#include "native_string_utils.h" -#include "qjs_engine_patch.h" #include #include "core/executing_context.h" +#include "native_string_utils.h" +#include "qjs_engine_patch.h" namespace kraken { diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index a16824d32f..118a729fd6 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -40,7 +40,7 @@ class ScriptValue final { ScriptValue() = default; // Copy and assignment - ScriptValue(ScriptValue const &value) { + ScriptValue(ScriptValue const& value) { if (&value != this) { value_ = JS_DupValue(ctx_, value.value_); } diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 08b9128a33..ef977fcbb7 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -8,7 +8,7 @@ namespace kraken { -ScriptWrappable::ScriptWrappable(JSContext* ctx): ctx_(ctx), runtime_(JS_GetRuntime(ctx)) {} +ScriptWrappable::ScriptWrappable(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)) {} JSValue ScriptWrappable::ToQuickJS() { if (wrapped_) { @@ -70,4 +70,4 @@ void ScriptWrappable::InitializeQuickJSObject() { wrapped_ = true; } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index bc553bdd91..13268dd408 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -19,16 +19,12 @@ namespace kraken { // 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: \ +#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 @@ -57,11 +53,11 @@ class ScriptWrappable : public GarbageCollected { }; // Converts a QuickJS object back to a ScriptWrappable. -template +template inline ScriptWrappable* toScriptWrappable(JSValue object) { return static_cast(JS_GetOpaque(object, JSValueGetClassId(object))); } -} +} // namespace kraken #endif // KRAKENBRIDGE_SCRIPT_WRAPPABLE_H diff --git a/bridge/bindings/qjs/to_quickjs.h b/bridge/bindings/qjs/to_quickjs.h index c9d1f2d699..2de59de433 100644 --- a/bridge/bindings/qjs/to_quickjs.h +++ b/bridge/bindings/qjs/to_quickjs.h @@ -8,10 +8,10 @@ #include #include +#include "core/fileapi/array_buffer_data.h" #include "native_string_utils.h" #include "qjs_engine_patch.h" #include "script_wrappable.h" -#include "core/fileapi/array_buffer_data.h" namespace kraken { @@ -26,7 +26,6 @@ inline JSValue toQuickJS(JSContext* ctx, uint32_t v) { return JS_NewUint32(ctx, v); } - // String inline JSValue toQuickJS(JSContext* ctx, const std::string& str) { return JS_NewString(ctx, str.c_str()); @@ -49,6 +48,6 @@ inline JSValue toQuickJS(JSContext* ctx, ArrayBufferData data) { return JS_NewArrayBufferCopy(ctx, data.buffer, data.length); } -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index e5e05a9e68..5b57e61881 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -12,10 +12,7 @@ namespace kraken { -enum { - JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, - JS_CLASS_BLOB -}; +enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB }; // 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 @@ -37,7 +34,7 @@ class WrapperTypeInfo final { const char* className{nullptr}; const WrapperTypeInfo* parent_class{nullptr}; JSClassCall* callFunc{nullptr}; - JSClassExoticMethods *exoticMethods{nullptr}; + JSClassExoticMethods* exoticMethods{nullptr}; }; } // namespace kraken diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index 3d5c5df23d..591f34f66f 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ #define KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ -#include "core/executing_context.h" #include "bindings/qjs/script_wrappable.h" +#include "core/executing_context.h" namespace kraken { diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index e74efb3c30..0598be9f1b 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -342,7 +342,7 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { return &module_callbacks_; } -//PendingPromises* ExecutingContext::PendingPromises() { +// PendingPromises* ExecutingContext::PendingPromises() { // return &pending_promises_; //} diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index fe2337e238..ba9c0cff3c 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -17,19 +17,19 @@ #include #include #include "bindings/qjs/binding_initializer.h" -#include "bindings/qjs/script_wrappable.h" -#include "bindings/qjs/rejected_promises.h" #include "bindings/qjs/pending_promises.h" +#include "bindings/qjs/rejected_promises.h" #include "bindings/qjs/script_value.h" +#include "bindings/qjs/script_wrappable.h" #include "foundation/macros.h" #include "foundation/ui_command_buffer.h" -#include "script_state.h" #include "dart_methods.h" #include "executing_context_data.h" #include "frame/dom_timer_coordinator.h" #include "frame/module_callback_coordinator.h" #include "frame/module_listener_container.h" +#include "script_state.h" namespace kraken { diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc index 751d79990a..5439b4e126 100644 --- a/bridge/core/executing_context_data.cc +++ b/bridge/core/executing_context_data.cc @@ -70,11 +70,11 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty } void ExecutionContextData::Dispose() { - for(auto& entry: prototype_map_) { + for (auto& entry : prototype_map_) { JS_FreeValueRT(ScriptState::runtime(), entry.second); } - for(auto& entry: constructor_map_) { + for (auto& entry : constructor_map_) { JS_FreeValueRT(ScriptState::runtime(), entry.second); } } diff --git a/bridge/core/fileapi/array_buffer_data.h b/bridge/core/fileapi/array_buffer_data.h index 9e21edb3b4..e90a8722c0 100644 --- a/bridge/core/fileapi/array_buffer_data.h +++ b/bridge/core/fileapi/array_buffer_data.h @@ -13,6 +13,6 @@ struct ArrayBufferData { int32_t length; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index a15068d321..41e64570d4 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -3,8 +3,8 @@ * Author: Kraken Team. */ -#include #include "blob.h" +#include #include "bindings/qjs/script_promise_resolver.h" #include "core/executing_context.h" @@ -99,10 +99,7 @@ std::string Blob::StringResult() { } ArrayBufferData Blob::ArrayBufferResult() { - return ArrayBufferData{ - bytes(), - size() - }; + return ArrayBufferData{bytes(), size()}; } std::string Blob::type() { diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 4752115c52..8406c8d234 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -8,12 +8,12 @@ #include #include +#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" -#include "array_buffer_data.h" namespace kraken { diff --git a/bridge/core/fileapi/blob_part.cc b/bridge/core/fileapi/blob_part.cc index 5b5d444646..a042475df1 100644 --- a/bridge/core/fileapi/blob_part.cc +++ b/bridge/core/fileapi/blob_part.cc @@ -49,7 +49,7 @@ std::shared_ptr BlobPart::Create(JSContext* ctx, JSValue value, Except } JSValue BlobPart::ToQuickJS(JSContext* ctx) const { - switch(content_type_) { + switch (content_type_) { case ContentType::kString: { return JS_NewString(ctx, member_string_.c_str()); } @@ -83,4 +83,4 @@ Blob* BlobPart::GetBlob() const { return blob_; } -} +} // namespace kraken diff --git a/bridge/core/fileapi/blob_part.h b/bridge/core/fileapi/blob_part.h index 90a13a9855..67767a5c89 100644 --- a/bridge/core/fileapi/blob_part.h +++ b/bridge/core/fileapi/blob_part.h @@ -6,10 +6,10 @@ #ifndef KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ #define KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ +#include #include #include #include -#include #include "bindings/qjs/exception_state.h" namespace kraken { @@ -20,14 +20,9 @@ class BlobPart { public: using ImplType = std::shared_ptr; - enum class ContentType { - kArrayBuffer, kArrayBufferView, kBlob, kString - }; + enum class ContentType { kArrayBuffer, kArrayBufferView, kBlob, kString }; - static std::shared_ptr Create( - JSContext* ctx, - JSValue value, - ExceptionState& exception_state); + static std::shared_ptr Create(JSContext* ctx, JSValue value, ExceptionState& exception_state); JSValue ToQuickJS(JSContext* ctx) const; ContentType GetContentType() const; @@ -35,10 +30,11 @@ class BlobPart { 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) {}; + 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_; @@ -48,6 +44,6 @@ class BlobPart { uint32_t byte_length_{0}; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ diff --git a/bridge/core/fileapi/blob_property_bag.cc b/bridge/core/fileapi/blob_property_bag.cc index 747d98aac9..6b8f85523c 100644 --- a/bridge/core/fileapi/blob_property_bag.cc +++ b/bridge/core/fileapi/blob_property_bag.cc @@ -26,4 +26,4 @@ void BlobPropertyBag::FillMemberFromQuickjsObject(JSContext* ctx, JSValue value, JS_FreeValue(ctx, typeValue); } -} +} // namespace kraken diff --git a/bridge/core/fileapi/blob_property_bag.h b/bridge/core/fileapi/blob_property_bag.h index 674d8b1533..452417d009 100644 --- a/bridge/core/fileapi/blob_property_bag.h +++ b/bridge/core/fileapi/blob_property_bag.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ #define KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ -#include #include +#include #include "core/executing_context.h" namespace kraken { @@ -25,7 +25,6 @@ class BlobPropertyBag final { std::string m_type; }; -} - +} // namespace kraken #endif // KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index 0d856f1b10..9f64f3f098 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_DOM_TIMER_H #define KRAKENBRIDGE_DOM_TIMER_H -#include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/qjs_function.h" +#include "bindings/qjs/script_wrappable.h" #include "dom_timer_coordinator.h" namespace kraken { diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc index e381336ca2..de04833a2c 100644 --- a/bridge/core/frame/module_callback_coordinator.cc +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -15,7 +15,6 @@ void ModuleCallbackCoordinator::RemoveModuleCallbacks(std::shared_ptr function); private: - std::shared_ptr function_{nullptr}; friend ModuleListenerContainer; diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index db0043ce2c..c50c1eed59 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -31,9 +31,7 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha if (errmsg != nullptr) { ScriptValue errorObject = ScriptValue::createErrorObject(ctx, errmsg); - ScriptValue arguments[] = { - errorObject - }; + ScriptValue arguments[] = {errorObject}; ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); if (returnValue.IsException()) { context->HandleException(&returnValue); @@ -42,9 +40,7 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha std::u16string argumentString = std::u16string(reinterpret_cast(json->string), json->length); std::string utf8Arguments = toUTF8(argumentString); ScriptValue jsonObject = ScriptValue::createJSONObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); - ScriptValue arguments[] = { - jsonObject - }; + ScriptValue arguments[] = {jsonObject}; ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); if (returnValue.IsException()) { context->HandleException(&returnValue); @@ -62,28 +58,27 @@ void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t context } std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr &moduleName, - std::unique_ptr &method, - ExceptionState& exception) { + std::unique_ptr& moduleName, + std::unique_ptr& method, + ExceptionState& exception) { ScriptValue empty = ScriptValue::Empty(context->ctx()); return __kraken_invoke_module__(context, moduleName, method, empty, nullptr, exception); } std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr &moduleName, - std::unique_ptr &method, - ScriptValue& paramsValue, - ExceptionState& exception) { + std::unique_ptr& moduleName, + std::unique_ptr& method, + ScriptValue& paramsValue, + ExceptionState& exception) { return __kraken_invoke_module__(context, moduleName, method, paramsValue, nullptr, exception); } std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr &moduleName, - std::unique_ptr &method, - ScriptValue& paramsValue, - std::shared_ptr callback, - ExceptionState& exception) { - + std::unique_ptr& moduleName, + std::unique_ptr& method, + ScriptValue& paramsValue, + std::shared_ptr callback, + ExceptionState& exception) { std::unique_ptr params; if (!paramsValue.IsEmpty()) { params = paramsValue.ToJSONStringify(&exception).toNativeString(); diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 2efdb52938..5cac35467a 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -14,21 +14,18 @@ namespace kraken { class ModuleManager { public: + static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, std::unique_ptr& moduleName, std::unique_ptr& method, ExceptionState& exception); static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr &moduleName, - std::unique_ptr &method, - ExceptionState& exception); + std::unique_ptr& moduleName, + std::unique_ptr& method, + ScriptValue& params, + ExceptionState& exception); static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr &moduleName, - std::unique_ptr &method, - ScriptValue& params, - ExceptionState& exception); - static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr &moduleName, - std::unique_ptr &method, - ScriptValue& params, - std::shared_ptr callback, - ExceptionState& exception); + std::unique_ptr& moduleName, + std::unique_ptr& method, + ScriptValue& params, + std::shared_ptr callback, + ExceptionState& exception); static void __kraken_add_module_listener__(ExecutingContext* context, const std::shared_ptr& handler, ExceptionState& exception); }; diff --git a/bridge/core/frame/module_manager_test.cc b/bridge/core/frame/module_manager_test.cc index 853af35261..b8a085ce38 100644 --- a/bridge/core/frame/module_manager_test.cc +++ b/bridge/core/frame/module_manager_test.cc @@ -10,9 +10,7 @@ namespace kraken { TEST(ModuleManager, ShouldReturnCorrectValue) { bool static errorCalled = false; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - errorCalled = true; - }); + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; auto context = bridge->getContext(); diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index a0320bdc10..b4da1091b0 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -20,7 +20,7 @@ ScriptState::ScriptState() { ctx_ = JS_NewContext(runtime_); } -JSRuntime * ScriptState::runtime() { +JSRuntime* ScriptState::runtime() { return runtime_; } @@ -38,4 +38,4 @@ ScriptState::~ScriptState() { #endif ctx_ = nullptr; } -} +} // namespace kraken diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h index 09db20a7c8..e3b7f407a2 100644 --- a/bridge/core/script_state.h +++ b/bridge/core/script_state.h @@ -22,10 +22,11 @@ class ScriptState { inline JSContext* ctx() { return ctx_; } static JSRuntime* runtime(); + private: JSContext* ctx_{nullptr}; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h index e123a54031..2fd9abd9be 100644 --- a/bridge/foundation/macros.h +++ b/bridge/foundation/macros.h @@ -28,11 +28,11 @@ TypeName(TypeName&&) = delete; \ TypeName& operator=(TypeName&&) = delete -#define KRAKEN_STATIC_ONLY(Type) \ - Type() = delete; \ - Type(const Type&) = delete; \ - Type& operator=(const Type&) = delete; \ - void* operator new(size_t) = delete; \ +#define KRAKEN_STATIC_ONLY(Type) \ + Type() = delete; \ + Type(const Type&) = delete; \ + Type& operator=(const Type&) = delete; \ + void* operator new(size_t) = delete; \ void* operator new(size_t, void*) = delete // KRAKEN_DISALLOW_NEW(): Cannot be allocated with new operators but can be a diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 9f08e8039d..292c51de80 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -160,34 +160,34 @@ static JSValue anonymousFunction(JSContext* ctx, JSValueConst this_val, int argc } 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->contextId() != contextId) -// return; -// -// auto* context = promiseContext->context; -// -// if (nativeValue != nullptr) { -// JSValue value = nativeValueToJSValue(promiseContext->context, *nativeValue); -// JSValue returnValue = JS_Call(context->ctx(), promiseContext->resolveFunc, context->Global(), 1, &value); -// context->DrainPendingPromiseJobs(); -// context->HandleException(&returnValue); -// JS_FreeValue(context->ctx(), value); -// JS_FreeValue(context->ctx(), returnValue); -// } else if (errmsg != nullptr) { -// JSValue error = JS_NewError(context->ctx()); -// JS_DefinePropertyValueStr(context->ctx(), error, "message", JS_NewString(context->ctx(), errmsg), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); -// JSValue returnValue = JS_Call(context->ctx(), promiseContext->rejectFunc, context->Global(), 1, &error); -// context->DrainPendingPromiseJobs(); -// context->HandleException(&returnValue); -// JS_FreeValue(context->ctx(), error); -// JS_FreeValue(context->ctx(), returnValue); -// } -// -// JS_FreeValue(context->ctx(), promiseContext->resolveFunc); -// JS_FreeValue(context->ctx(), promiseContext->rejectFunc); -// list_del(&promiseContext->link); + // auto* promiseContext = static_cast(callbackContext); + // if (!promiseContext->context->IsValid()) + // return; + // if (promiseContext->context->contextId() != contextId) + // return; + // + // auto* context = promiseContext->context; + // + // if (nativeValue != nullptr) { + // JSValue value = nativeValueToJSValue(promiseContext->context, *nativeValue); + // JSValue returnValue = JS_Call(context->ctx(), promiseContext->resolveFunc, context->Global(), 1, &value); + // context->DrainPendingPromiseJobs(); + // context->HandleException(&returnValue); + // JS_FreeValue(context->ctx(), value); + // JS_FreeValue(context->ctx(), returnValue); + // } else if (errmsg != nullptr) { + // JSValue error = JS_NewError(context->ctx()); + // JS_DefinePropertyValueStr(context->ctx(), error, "message", JS_NewString(context->ctx(), errmsg), JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); + // JSValue returnValue = JS_Call(context->ctx(), promiseContext->rejectFunc, context->Global(), 1, &error); + // context->DrainPendingPromiseJobs(); + // context->HandleException(&returnValue); + // JS_FreeValue(context->ctx(), error); + // JS_FreeValue(context->ctx(), returnValue); + // } + // + // JS_FreeValue(context->ctx(), promiseContext->resolveFunc); + // JS_FreeValue(context->ctx(), promiseContext->rejectFunc); + // list_del(&promiseContext->link); } static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { @@ -222,12 +222,12 @@ static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { switch (value.tag) { case NativeTag::TAG_STRING: { -// auto* string = static_cast(value.u.ptr); -// if (string == nullptr) -// return JS_NULL; -// JSValue returnedValue = JS_NewUnicodeString(context->runtime(), context->ctx(), string->string, string->length); -// string->free(); -// return returnedValue; + // auto* string = static_cast(value.u.ptr); + // if (string == nullptr) + // return JS_NULL; + // JSValue returnedValue = JS_NewUnicodeString(context->runtime(), context->ctx(), string->string, string->length); + // string->free(); + // return returnedValue; } case NativeTag::TAG_INT: { return JS_NewUint32(context->ctx(), value.u.int64); From 3a98f5c3bf622a78976343fe35e562fc74267250 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 25 Mar 2022 08:32:26 +0800 Subject: [PATCH 034/498] chore: basic event_target --- bridge/CMakeLists.txt | 2 + bridge/bindings/qjs/wrapper_type_info.h | 6 +- bridge/core/dom/events/event_target.cc | 495 +----------------- bridge/core/dom/events/event_target.d.ts | 6 + bridge/core/dom/events/event_target.h | 231 ++++---- .../code_generator/src/generate_header.ts | 2 +- kraken/lib/src/dom/event_target.dart | 1 + 7 files changed, 141 insertions(+), 602 deletions(-) create mode 100644 bridge/core/dom/events/event_target.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index e8115417b0..c2944eddf6 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -259,6 +259,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/frame/module_callback_coordinator.h core/dom/frame_request_callback_collection.cc core/dom/frame_request_callback_collection.h + core/dom/events/event_target.h + core/dom/events/event_target.cc # core/dom/character_data.cc # core/dom/character_data.h # core/dom/comment.cc diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 5b57e61881..376ec5bd8c 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -12,7 +12,11 @@ namespace kraken { -enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB }; +enum { + JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, + JS_CLASS_BLOB, + JS_CLASS_EVENTTARGET +}; // 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 diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 53f7e1612b..a77ff4aafd 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -5,15 +5,6 @@ #include "event_target.h" -#include -#include "bindings/qjs/bom/window.h" -#include "bindings/qjs/dom/text_node.h" -#include "bindings/qjs/qjs_patch.h" -#include "document.h" -#include "element.h" -#include "event.h" -#include "kraken_bridge.h" - #define PROPAGATION_STOPPED 1 #define PROPAGATION_CONTINUE 0 @@ -21,490 +12,22 @@ #include "kraken_test_env.h" #endif -namespace kraken::binding::qjs { - -static std::atomic globalEventTargetId{0}; -std::once_flag kEventTargetInitFlag; - -void bindEventTarget(ExecutionContext* context) { - auto* constructor = EventTarget::instance(context); - // Set globalThis and Window's prototype to EventTarget's prototype to support EventTarget methods in global. - JS_SetPrototype(context->ctx(), context->global(), constructor->jsObject); - context->defineGlobalProperty("EventTarget", constructor->jsObject); -} - -JSClassID EventTarget::kEventTargetClassId{0}; - -EventTarget::EventTarget(ExecutionContext* context, const char* name) : HostClass(context, name) {} -EventTarget::EventTarget(ExecutionContext* context) : HostClass(context, "EventTarget") { - std::call_once(kEventTargetInitFlag, []() { JS_NewClassID(&kEventTargetClassId); }); -} - -JSValue EventTarget::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - auto eventTarget = new EventTargetInstance(this, kEventTargetClassId, "EventTarget"); - return eventTarget->jsObject; -} - -JSClassID EventTarget::classId() { - assert_m(false, "classId is not implemented"); - return 0; -} - -JSClassID EventTarget::classId(JSValue& value) { - JSClassID classId = JSValueGetClassId(value); - return classId; -} - -JSValue EventTarget::addEventListener(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 2) { - return JS_ThrowTypeError(ctx, "Failed to addEventListener: type and listener are required."); - } - - auto* eventTargetInstance = static_cast(JS_GetOpaque(this_val, EventTarget::classId(this_val))); - if (eventTargetInstance == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); - } - - JSValue eventTypeValue = argv[0]; - JSValue callback = argv[1]; +namespace kraken { - 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; +EventTarget* EventTarget::Create(ExecutingContext* context) { + return makeGarbageCollected(context); } -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]; +EventTarget::EventTarget(ExecutingContext* context): ScriptWrappable(context->ctx()) {} - if (!JS_IsString(eventTypeValue) || !JS_IsObject(callback) || !JS_IsObject(callback)) { - return JS_ThrowTypeError(ctx, "Failed to removeEventListener: eventName should be an string."); - } - - 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.empty() && 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); - }; - - if (m_eventListenerMap.contains(eventType)) { - const EventListenerVector* vector = m_eventListenerMap.find(eventType); - for (auto& eventHandler : *vector) { - _dispatchEvent(eventHandler); - } - } - - // Dispatch event listener white by 'on' prefix property. - if (m_eventHandlerMap.contains(eventType)) { - // Let special error event handling be true if event is an ErrorEvent. - bool specialErrorEventHanding = eventTypeStr == "error"; - - 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)); - } - } - - 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++; -} +void EventTarget::Trace(GCVisitor* visitor) const {} -EventTargetInstance::EventTargetInstance(EventTarget* eventTarget, JSClassID classId, std::string name, int64_t eventTargetId) - : Instance(eventTarget, std::move(name), nullptr, classId, finalize), m_eventTargetId(eventTargetId) {} +void EventTarget::Dispose() const {} -JSClassID EventTargetInstance::classId() { - assert_m(false, "classId is not implemented"); - return 0; +const char* EventTarget::GetHumanReadableName() const { + return "EventTarget"; } -EventTargetInstance::~EventTargetInstance() { -#if UNIT_TEST - // Callback to unit test specs before eventTarget finalized. - if (TEST_getEnv(m_context->uniqueId)->onEventTargetDisposed != nullptr) { - TEST_getEnv(m_context->uniqueId)->onEventTargetDisposed(this); - } -#endif - - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::disposeEventTarget, nullptr, false); - getDartMethod()->flushUICommand(); - delete nativeEventTarget; -} - -int EventTargetInstance::hasProperty(JSContext* ctx, JSValue obj, JSAtom atom) { - auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); - auto* prototype = static_cast(eventTarget->prototype()); - - if (JS_HasProperty(ctx, prototype->m_prototypeObject, atom)) - return true; - - JSValue atomString = JS_AtomToString(ctx, atom); - JSString* p = JS_VALUE_GET_STRING(atomString); - // There are still one reference_count in atom. It's safe to free here. - JS_FreeValue(ctx, atomString); - - if (!p->is_wide_char && p->u.str8[0] == 'o' && p->u.str8[1] == 'n') { - return !JS_IsNull(eventTarget->getAttributesEventHandler(p)); - } - - return eventTarget->m_properties.contains(atom); -} - -JSValue EventTargetInstance::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { - auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); - JSValue prototype = JS_GetPrototype(ctx, eventTarget->jsObject); - if (JS_HasProperty(ctx, prototype, atom)) { - JSValue ret = JS_GetPropertyInternal(ctx, prototype, atom, eventTarget->jsObject, 0); - JS_FreeValue(ctx, prototype); - return ret; - } - JS_FreeValue(ctx, prototype); - - JSValue atomString = JS_AtomToString(ctx, atom); - JSString* p = JS_VALUE_GET_STRING(atomString); - // There are still one reference_count in atom. It's safe to free here. - JS_FreeValue(ctx, atomString); - - if (!p->is_wide_char && p->u.str8[0] == 'o' && p->u.str8[1] == 'n') { - return eventTarget->getAttributesEventHandler(p); - } - - if (eventTarget->m_properties.contains(atom)) { - return JS_DupValue(ctx, eventTarget->m_properties.getProperty(atom)); - } - - // For plugin elements, try to auto generate properties and functions from dart response. - if (isJavaScriptExtensionElementInstance(eventTarget->context(), eventTarget->jsObject)) { - const char* cmethod = JS_AtomToCString(eventTarget->m_ctx, atom); - // Property starts with underscore are taken as private property in javascript object. - if (cmethod[0] == '_') { - JS_FreeCString(eventTarget->m_ctx, cmethod); - return JS_UNDEFINED; - } - JSValue result = eventTarget->getBindingProperty(cmethod); - JS_FreeCString(ctx, cmethod); - return result; - } - - return JS_UNDEFINED; -} - -int EventTargetInstance::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { - auto* eventTarget = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); - JSValue prototype = JS_GetPrototype(ctx, eventTarget->jsObject); - - // Check there are setter functions on prototype. - if (JS_HasProperty(ctx, prototype, atom)) { - // Read setter function from prototype Object. - JSPropertyDescriptor descriptor; - JS_GetOwnProperty(ctx, &descriptor, prototype, atom); - JSValue setterFunc = descriptor.setter; - assert_m(JS_IsFunction(ctx, setterFunc), "Setter on prototype should be an function."); - JSValue ret = JS_Call(ctx, setterFunc, eventTarget->jsObject, 1, &value); - if (JS_IsException(ret)) - return -1; - - JS_FreeValue(ctx, ret); - JS_FreeValue(ctx, descriptor.setter); - JS_FreeValue(ctx, descriptor.getter); - JS_FreeValue(ctx, prototype); - return 1; - } - - JS_FreeValue(ctx, prototype); - - JSValue atomString = JS_AtomToString(ctx, atom); - JSString* p = JS_VALUE_GET_STRING(atomString); - - if (!p->is_wide_char && p->len > 2 && p->u.str8[0] == 'o' && p->u.str8[1] == 'n') { - eventTarget->setAttributesEventHandler(p, value); - } else { - eventTarget->m_properties.setProperty(JS_DupAtom(ctx, atom), JS_DupValue(ctx, value)); - if (isJavaScriptExtensionElementInstance(eventTarget->context(), eventTarget->jsObject) && !p->is_wide_char && p->u.str8[0] != '_') { - std::unique_ptr args_01 = atomToNativeString(ctx, atom); - std::unique_ptr args_02 = jsValueToNativeString(ctx, value); - eventTarget->m_context->uiCommandBuffer()->addCommand(eventTarget->m_eventTargetId, UICommand::setAttribute, *args_01, *args_02, nullptr); - } - } - - JS_FreeValue(ctx, atomString); - - return 0; -} - -int EventTargetInstance::deleteProperty(JSContext* ctx, JSValue obj, JSAtom prop) { - return 0; -} - -JSValue EventTargetInstance::invokeBindingMethod(const char* method, int32_t argc, NativeValue* argv) { - if (nativeEventTarget->invokeBindingMethod == nullptr) { - return JS_ThrowTypeError(m_ctx, "Failed to call dart method: invokeBindingMethod not initialized."); - } - - std::u16string methodString; - fromUTF8(method, methodString); - - NativeString m{reinterpret_cast(methodString.c_str()), static_cast(methodString.size())}; - - NativeValue nativeValue{}; - nativeEventTarget->invokeBindingMethod(nativeEventTarget, &nativeValue, &m, argc, argv); - JSValue returnValue = nativeValueToJSValue(m_context, nativeValue); - return returnValue; -} - -void EventTargetInstance::setAttributesEventHandler(JSString* p, JSValue value) { - char eventType[p->len + 1 - 2]; - memcpy(eventType, &p->u.str8[2], p->len + 1 - 2); - JSAtom atom = JS_NewAtom(m_ctx, eventType); - - // When evaluate scripts like 'element.onclick = null', we needs to remove the event handlers callbacks - if (JS_IsNull(value)) { - m_eventHandlerMap.erase(atom); - JS_FreeAtom(m_ctx, atom); - return; - } - - m_eventHandlerMap.setProperty(atom, JS_DupValue(m_ctx, value)); - - if (JS_IsFunction(m_ctx, value) && m_eventListenerMap.empty()) { - int32_t contextId = m_context->getContextId(); - std::unique_ptr args_01 = atomToNativeString(m_ctx, atom); - int32_t type = JS_IsFunction(m_ctx, value) ? UICommand::addEvent : UICommand::removeEvent; - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, type, *args_01, nullptr); - } -} - -JSValue EventTargetInstance::getAttributesEventHandler(JSString* p) { - char eventType[p->len + 1 - 2]; - memcpy(eventType, &p->u.str8[2], p->len + 1 - 2); - JSAtom atom = JS_NewAtom(m_ctx, eventType); - if (!m_eventHandlerMap.contains(atom)) { - JS_FreeAtom(m_ctx, atom); - return JS_NULL; - } - - JSValue handler = JS_DupValue(m_ctx, m_eventHandlerMap.getProperty(atom)); - JS_FreeAtom(m_ctx, atom); - return handler; -} - -void EventTargetInstance::finalize(JSRuntime* rt, JSValue val) { - auto* eventTarget = static_cast(JS_GetOpaque(val, EventTarget::classId(val))); - delete eventTarget; -} - -JSValue EventTargetInstance::getBindingProperty(const char* prop) { - getDartMethod()->flushUICommand(); - NativeValue args[] = {Native_NewCString(prop)}; - return invokeBindingMethod(GetPropertyMagic, 1, args); -} - -void EventTargetInstance::setBindingProperty(const char* prop, NativeValue value) { - // If not flush UICommands, the element may not be created. - getDartMethod()->flushUICommand(); - NativeValue args[] = {Native_NewCString(prop), value}; - invokeBindingMethod(SetPropertyMagic, 2, args); -} - -// JSValues are stored in this class are no visible to QuickJS GC. -// We needs to gc which JSValues are still holding. -void EventTargetInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - // Trace m_eventListeners. - m_eventListenerMap.trace(rt, JS_UNDEFINED, mark_func); - - // Trace m_eventHandlers. - m_eventHandlerMap.trace(rt, JS_UNDEFINED, mark_func); - - // Trace properties. - m_properties.trace(rt, JS_UNDEFINED, mark_func); -} - -void EventTargetInstance::copyNodeProperties(EventTargetInstance* newNode, EventTargetInstance* referenceNode) { - referenceNode->m_properties.copyWith(&newNode->m_properties); -} - -int32_t NativeEventTarget::dispatchEventImpl(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* nativeEventType, void* rawEvent, int32_t isCustomEvent) { - assert_m(nativeEventTarget->instance != nullptr, "NativeEventTarget should have owner"); - EventTargetInstance* eventTargetInstance = nativeEventTarget->instance; - - auto* runtime = ExecutionContext::runtime(); - - // Should avoid dispatch event is ctx is invalid. - if (!isContextValid(contextId)) { - return 1; - } - - // We should avoid trigger event if eventTarget are no long live on heap. - if (!JS_IsLiveObject(runtime, eventTargetInstance->jsObject)) { - return 1; - } - - ExecutionContext* context = eventTargetInstance->context(); - std::u16string u16EventType = std::u16string(reinterpret_cast(nativeEventType->string), nativeEventType->length); - std::string eventType = toUTF8(u16EventType); - auto* raw = static_cast(rawEvent); - // NativeEvent members are memory aligned corresponding to NativeEvent. - // So we can reinterpret_cast raw bytes pointer to NativeEvent type directly. - auto* nativeEvent = reinterpret_cast(raw->bytes); - EventInstance* eventInstance = Event::buildEventInstance(eventType, context, nativeEvent, isCustomEvent == 1); - - eventTargetInstance->dispatchEvent(eventInstance); - - bool propagationStopped = eventInstance->propagationStopped(); - - JS_FreeValue(context->ctx(), eventInstance->jsObject); - - // FIXME: The return value is first propagationStopped instead of cancelable, and then implement a separate method to synchronize propagationStopped. - // Dispatches a synthetic event event to target and returns true if either event’s cancelable attribute value is false or its preventDefault() method was not invoked; otherwise false. - // https://dom.spec.whatwg.org/#ref-for-dom-eventtarget-dispatchevent%E2%91%A2 - return propagationStopped ? PROPAGATION_STOPPED : PROPAGATION_CONTINUE; } -} // namespace kraken::binding::qjs +// namespace kraken::binding::qjs diff --git a/bridge/core/dom/events/event_target.d.ts b/bridge/core/dom/events/event_target.d.ts new file mode 100644 index 0000000000..b2678ea65c --- /dev/null +++ b/bridge/core/dom/events/event_target.d.ts @@ -0,0 +1,6 @@ +interface EventTarget { + addEventListener(type: string, callback: EventListenerOrEventListenerObject | null): void; + dispatchEvent(event: Event): boolean; + removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null): void; + new(): EventTarget; +} diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 1b0a7164a8..dae2297fed 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -6,13 +6,7 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H -#include "bindings/qjs/dom/event.h" -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/heap_hashmap.h" -#include "bindings/qjs/host_class.h" -#include "bindings/qjs/host_object.h" -#include "bindings/qjs/native_value.h" -#include "bindings/qjs/qjs_patch.h" +#include "bindings/qjs/script_wrappable.h" #include "event_listener_map.h" #if UNIT_TEST @@ -22,119 +16,128 @@ void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, #define GetPropertyMagic "%g" #define SetPropertyMagic "%s" -namespace kraken::binding::qjs { - -class EventTargetInstance; -class NativeEventTarget; -class CSSStyleDeclaration; -class StyleDeclarationInstance; - -void bindEventTarget(ExecutionContext* context); - -class EventTarget : public HostClass { - public: - static JSClassID kEventTargetClassId; - JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; - EventTarget() = delete; - explicit EventTarget(ExecutionContext* context, const char* name); - explicit EventTarget(ExecutionContext* context); - - static JSClassID classId(); - static JSClassID classId(JSValue& value); - - OBJECT_INSTANCE(EventTarget); - - private: - static JSValue addEventListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue removeEventListener(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - static JSValue dispatchEvent(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); - - DEFINE_PROTOTYPE_FUNCTION(addEventListener, 3); - DEFINE_PROTOTYPE_FUNCTION(removeEventListener, 2); - DEFINE_PROTOTYPE_FUNCTION(dispatchEvent, 1); - friend EventTargetInstance; -}; - -using NativeDispatchEvent = int32_t (*)(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); -using InvokeBindingMethod = void (*)(void* nativePtr, NativeValue* returnValue, NativeString* method, int32_t argc, NativeValue* argv); - -struct NativeEventTarget { - NativeEventTarget() = delete; - explicit NativeEventTarget(EventTargetInstance* _instance) : instance(_instance), dispatchEvent(reinterpret_cast(NativeEventTarget::dispatchEventImpl)){}; - - // Add more memory valid check with contextId. - static int32_t dispatchEventImpl(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); - EventTargetInstance* instance{nullptr}; - NativeDispatchEvent dispatchEvent{nullptr}; -#if UNIT_TEST - InvokeBindingMethod invokeBindingMethod{reinterpret_cast(TEST_invokeBindingMethod)}; -#else - InvokeBindingMethod invokeBindingMethod{nullptr}; -#endif -}; - -class EventTargetProperties : public HeapHashMap { - public: - EventTargetProperties(JSContext* ctx) : HeapHashMap(ctx){}; -}; - -class EventHandlerMap : public HeapHashMap { - public: - EventHandlerMap(JSContext* ctx) : HeapHashMap(ctx){}; -}; - -class EventTargetInstance : public Instance { +namespace kraken { + +// 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. +// All nodes are EventTargets, some other event targets include: XMLHttpRequest, +// AudioNode and AudioContext. + +// To make your class an EventTarget, follow these steps: +// - Make your IDL interface inherit from EventTarget. +// - Inherit from EventTargetWithInlineData (only in rare cases should you +// use EventTarget directly). +// - In your class declaration, EventTargetWithInlineData must come first in +// the base class list. If your class is non-final, classes inheriting from +// your class need to come first, too. +// - If you added an onfoo attribute, use DEFINE_ATTRIBUTE_EVENT_LISTENER(foo) +// in your class declaration. Add "attribute EventHandler onfoo;" to the IDL +// file. +// - Override EventTarget::interfaceName() and getExecutionContext(). The former +// will typically return EventTargetNames::YourClassName. The latter will +// return ExecutionContextLifecycleObserver::executionContext (if you are an +// ExecutionContextLifecycleObserver) +// or the document you're in. +// - Your trace() method will need to call EventTargetWithInlineData::trace +// depending on the base class of your class. +class EventTarget : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); 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(); + static EventTarget* Create(ExecutingContext* context); - 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); + EventTarget() = delete; + explicit EventTarget(ExecutingContext* context); - // Used for legacy "onEvent" attribute APIs. - void setAttributesEventHandler(JSString* p, JSValue value); - JSValue getAttributesEventHandler(JSString* p); + void Trace(GCVisitor* visitor) const override; + void Dispose() const override; + const char* GetHumanReadableName() const override; private: - bool internalDispatchEvent(EventInstance* eventInstance); - static void finalize(JSRuntime* rt, JSValue val); - friend EventTarget; - friend StyleDeclarationInstance; }; +// +//using NativeDispatchEvent = int32_t (*)(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); +//using InvokeBindingMethod = void (*)(void* nativePtr, NativeValue* returnValue, NativeString* method, int32_t argc, NativeValue* argv); +// +//struct NativeEventTarget { +// NativeEventTarget() = delete; +// explicit NativeEventTarget(EventTargetInstance* _instance) : instance(_instance), dispatchEvent(reinterpret_cast(NativeEventTarget::dispatchEventImpl)){}; +// +// // Add more memory valid check with contextId. +// static int32_t dispatchEventImpl(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); +// EventTargetInstance* instance{nullptr}; +// NativeDispatchEvent dispatchEvent{nullptr}; +//#if UNIT_TEST +// InvokeBindingMethod invokeBindingMethod{reinterpret_cast(TEST_invokeBindingMethod)}; +//#else +// InvokeBindingMethod invokeBindingMethod{nullptr}; +//#endif +//}; +// +//class EventTargetProperties : public HeapHashMap { +// public: +// EventTargetProperties(JSContext* ctx) : HeapHashMap(ctx){}; +//}; +// +//class EventHandlerMap : public HeapHashMap { +// public: +// EventHandlerMap(JSContext* ctx) : HeapHashMap(ctx){}; +//}; +// +//class EventTargetInstance : public Instance { +// public: +// EventTargetInstance() = delete; +// explicit EventTargetInstance(EventTarget* eventTarget, JSClassID classId, JSClassExoticMethods& exoticMethods, std::string name); +// explicit EventTargetInstance(EventTarget* eventTarget, JSClassID classId, std::string name); +// explicit EventTargetInstance(EventTarget* eventTarget, JSClassID classId, std::string name, int64_t eventTargetId); +// ~EventTargetInstance(); +// +// virtual bool dispatchEvent(EventInstance* event); +// static inline JSClassID classId(); +// inline int32_t eventTargetId() const { return m_eventTargetId; } +// +// // @TODO: Should move to BindingObject. +// JSValue invokeBindingMethod(const char* method, int32_t argc, NativeValue* argv); +// JSValue getBindingProperty(const char* prop); +// void setBindingProperty(const char* prop, NativeValue value); +// +// NativeEventTarget* nativeEventTarget{new NativeEventTarget(this)}; +// +// protected: +// int32_t m_eventTargetId; +// // EventListener handlers registered with addEventListener API. +// // https://dom.spec.whatwg.org/#concept-event-listener +// EventListenerMap m_eventListenerMap{m_ctx}; +// +// // EventListener handlers registered with DOM attributes API. +// // https://html.spec.whatwg.org/C/#event-handler-attributes +// EventHandlerMap m_eventHandlerMap{m_ctx}; +// +// // When javascript code set a property on EventTarget instance, EventTarget::setAttribute callback will be called when +// // property are not defined by Object.defineProperty or setAttribute. +// // We store there values in here. +// EventTargetProperties m_properties{m_ctx}; +// +// void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; +// static void copyNodeProperties(EventTargetInstance* newNode, EventTargetInstance* referenceNode); +// +// static int hasProperty(JSContext* ctx, JSValueConst obj, JSAtom atom); +// static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); +// static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); +// static int deleteProperty(JSContext* ctx, JSValueConst obj, JSAtom prop); +// +// // Used for legacy "onEvent" attribute APIs. +// void setAttributesEventHandler(JSString* p, JSValue value); +// JSValue getAttributesEventHandler(JSString* p); +// +// private: +// bool internalDispatchEvent(EventInstance* eventInstance); +// static void finalize(JSRuntime* rt, JSValue val); +// friend EventTarget; +// friend StyleDeclarationInstance; +//}; } // namespace kraken::binding::qjs diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index 7e79f48215..0b9975c088 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -1,5 +1,5 @@ import {ClassObject, FunctionObject, PropsDeclaration} from "./declaration"; -import {uniqBy} from "lodash"; +import {uniqBy, snakeCase} from "lodash"; import {Blob} from "./blob"; import {addIndent, getClassName} from "./utils"; diff --git a/kraken/lib/src/dom/event_target.dart b/kraken/lib/src/dom/event_target.dart index e5194f750a..1d1888de6f 100644 --- a/kraken/lib/src/dom/event_target.dart +++ b/kraken/lib/src/dom/event_target.dart @@ -24,6 +24,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; From 8961d2c87531d1bd8f51e13d3a2685e35a7ffaaf Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Fri, 25 Mar 2022 00:33:19 +0000 Subject: [PATCH 035/498] Committing clang-format changes --- bridge/bindings/qjs/wrapper_type_info.h | 6 +----- bridge/core/dom/events/event_target.cc | 4 ++-- bridge/core/dom/events/event_target.h | 16 +++++++++------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 376ec5bd8c..0ec7819b9a 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -12,11 +12,7 @@ namespace kraken { -enum { - JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, - JS_CLASS_BLOB, - JS_CLASS_EVENTTARGET -}; +enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB, JS_CLASS_EVENTTARGET }; // 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 diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index a77ff4aafd..ed763b5f69 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -18,7 +18,7 @@ EventTarget* EventTarget::Create(ExecutingContext* context) { return makeGarbageCollected(context); } -EventTarget::EventTarget(ExecutingContext* context): ScriptWrappable(context->ctx()) {} +EventTarget::EventTarget(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} void EventTarget::Trace(GCVisitor* visitor) const {} @@ -28,6 +28,6 @@ const char* EventTarget::GetHumanReadableName() const { return "EventTarget"; } -} +} // namespace kraken // namespace kraken::binding::qjs diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index dae2297fed..656eb310f6 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -45,6 +45,7 @@ namespace kraken { // depending on the base class of your class. class EventTarget : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); + public: static EventTarget* Create(ExecutingContext* context); @@ -55,13 +56,14 @@ class EventTarget : public ScriptWrappable { void Dispose() const override; const char* GetHumanReadableName() const override; + private: }; // -//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); +// 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 { +// struct NativeEventTarget { // NativeEventTarget() = delete; // explicit NativeEventTarget(EventTargetInstance* _instance) : instance(_instance), dispatchEvent(reinterpret_cast(NativeEventTarget::dispatchEventImpl)){}; // @@ -76,17 +78,17 @@ class EventTarget : public ScriptWrappable { //#endif //}; // -//class EventTargetProperties : public HeapHashMap { +// class EventTargetProperties : public HeapHashMap { // public: // EventTargetProperties(JSContext* ctx) : HeapHashMap(ctx){}; //}; // -//class EventHandlerMap : public HeapHashMap { +// class EventHandlerMap : public HeapHashMap { // public: // EventHandlerMap(JSContext* ctx) : HeapHashMap(ctx){}; //}; // -//class EventTargetInstance : public Instance { +// class EventTargetInstance : public Instance { // public: // EventTargetInstance() = delete; // explicit EventTargetInstance(EventTarget* eventTarget, JSClassID classId, JSClassExoticMethods& exoticMethods, std::string name); @@ -139,6 +141,6 @@ class EventTarget : public ScriptWrappable { // friend StyleDeclarationInstance; //}; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_EVENT_TARGET_H From 7e5f4c79fcf1862cac382cfff2e31a401e7be637 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 25 Mar 2022 21:07:41 +0800 Subject: [PATCH 036/498] feat: add event and eventTarget --- bridge/CMakeLists.txt | 6 + bridge/bindings/qjs/atom_string.h | 3 + bridge/bindings/qjs/converter_impl.h | 94 ++++++++ bridge/bindings/qjs/idl_type.h | 10 + bridge/bindings/qjs/wrapper_type_info.h | 2 +- bridge/core/dom/events/event.cc | 214 +++--------------- bridge/core/dom/events/event.d.ts | 43 ++++ bridge/core/dom/events/event.h | 186 ++++++--------- bridge/core/dom/events/event_target.cc | 4 + bridge/core/dom/events/event_target.d.ts | 4 +- bridge/core/dom/events/event_target.h | 84 +++++-- bridge/scripts/code_generator/src/analyzer.ts | 31 ++- .../scripts/code_generator/src/declaration.ts | 13 +- .../code_generator/src/genereate_source.ts | 91 ++++---- integration_tests/webpack.config.js | 9 +- 15 files changed, 416 insertions(+), 378 deletions(-) create mode 100644 bridge/core/dom/events/event.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index c2944eddf6..dba605929f 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -259,6 +259,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/frame/module_callback_coordinator.h core/dom/frame_request_callback_collection.cc core/dom/frame_request_callback_collection.h + core/dom/events/event.h + core/dom/events/event.cc core/dom/events/event_target.h core/dom/events/event_target.cc # core/dom/character_data.cc @@ -285,6 +287,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_module_manager.h out/qjs_blob.cc out/qjs_blob.h + out/qjs_event.cc + out/qjs_event.h + out/qjs_event_target.cc + out/qjs_event_target.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index 723ba2ea28..4ee3d09eb2 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -6,8 +6,11 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ #define KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ +#include #include #include "foundation/macros.h" +#include "foundation/native_string.h" +#include "native_string_utils.h" namespace kraken { diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 3f69af79ed..772033bf4c 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -11,6 +11,7 @@ #include "converter.h" #include "core/fileapi/blob_part.h" #include "core/fileapi/blob_property_bag.h" +#include "core/dom/events/event_target.h" #include "idl_type.h" #include "native_string_utils.h" @@ -36,6 +37,21 @@ struct Converter, std::enable_if_t::ImplType value) { return Converter::ToValue(ctx, value); } }; +// Nullable value for pointer value +template +struct Converter, std::enable_if::ImplType>::value>> : public ConverterBase> { + using ImplType = typename Converter::ImplType; + + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsNull(value)) { + return nullptr; + } + return Converter::FromValue(ctx, value, exception_state); + } + + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value){ return Converter::ToValue(ctx, value); } +}; + template struct Converter, std::enable_if_t::ImplType>::value>> : public ConverterBase> { using ImplType = typename Converter::ImplType; @@ -86,6 +102,14 @@ struct Converter> : public ConverterBase 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) { + assert(!JS_IsException(value)); + return ScriptValue(ctx, value); + } +}; + // Boolean template <> struct Converter : public ConverterBase { @@ -153,6 +177,7 @@ struct Converter : public ConverterBase { return jsValueToNativeString(ctx, value); } + 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()); } @@ -171,6 +196,15 @@ struct Converter> : public ConverterBase 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 nullptr; + return Converter::FromValue(ctx, value, exception_state); + } +}; + template <> struct Converter : public ConverterBase { static AtomString FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { @@ -222,6 +256,18 @@ struct Converter>> : public ConverterBase +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) { @@ -254,6 +300,54 @@ struct Converter : public ConverterBase { } }; +// EventListener +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 { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + if (JS_IsNull(value)) { + return nullptr; + } + + 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)); + return toScriptWrappable(value); + } + + 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 Converter::FromValue(ctx, value, exception_state); + } + + static JSValue ToValue(JSContext* ctx, ImplType value) { + return Converter::ToValue(ctx, value); + } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 1f0945e929..7f006b707e 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -29,6 +29,12 @@ 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 {}; @@ -58,6 +64,10 @@ struct IDLCallback : public IDLTypeBaseHelper> { using ImplType = typename Converter>::ImplType; }; +struct EventListener : public IDLTypeBaseHelper> { + using ImplType = typename Converter>::ImplType; +}; + // Sequence template struct IDLSequence final : public IDLTypeBase { diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 0ec7819b9a..b43729aa4b 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -12,7 +12,7 @@ namespace kraken { -enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB, JS_CLASS_EVENTTARGET }; +enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB, JS_CLASS_EVENT, JS_CLASS_EVENTTARGET }; // 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 diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index b3aca4712b..2dc140ebe1 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -4,206 +4,62 @@ */ #include "event.h" -#include "bindings/qjs/qjs_engine_patch.h" -#include "custom_event.h" -#include "event_target.h" +#include "core/executing_context.h" namespace kraken { -void bindEvent(std::unique_ptr& context) { - JSValue constructor = Event::constructor(context.get()); - JSValue prototype = Event::prototype(context.get()); +Event::Event(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} - // Install readonly properties. - INSTALL_READONLY_PROPERTY(Event, prototype, type); - INSTALL_READONLY_PROPERTY(Event, prototype, bubbles); - INSTALL_READONLY_PROPERTY(Event, prototype, cancelable); - INSTALL_READONLY_PROPERTY(Event, prototype, timestamp); - INSTALL_READONLY_PROPERTY(Event, prototype, bubbles); - INSTALL_READONLY_PROPERTY(Event, prototype, defaultPrevented); - INSTALL_READONLY_PROPERTY(Event, prototype, target); - INSTALL_READONLY_PROPERTY(Event, prototype, srcElement); - INSTALL_READONLY_PROPERTY(Event, prototype, currentTarget); - INSTALL_READONLY_PROPERTY(Event, prototype, returnValue); - INSTALL_READONLY_PROPERTY(Event, prototype, cancelBubble); - - // Install functions - INSTALL_FUNCTION(Event, prototype, stopPropagation, 0); - INSTALL_FUNCTION(Event, prototype, stopImmediatePropagation, 0); - INSTALL_FUNCTION(Event, prototype, preventDefault, 1); - INSTALL_FUNCTION(Event, prototype, initEvent, 3); - - context->defineGlobalProperty("Event", constructor); -} - -JSClassID Event::classId{0}; - -Event* Event::create(JSContext* ctx) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - JSValue prototype = context->contextData()->prototypeForType(&eventTypeInfo); - - auto* event = makeGarbageCollected()->initialize(ctx, &classId); - - // Let eventTarget instance inherit EventTarget prototype methods. - JS_SetPrototype(ctx, event->toQuickJS(), prototype); - - return event; -} - -Event* Event::create(JSContext* ctx, NativeEvent* nativeEvent) { - auto* event = create(ctx); - event->nativeEvent = nativeEvent; - return event; -} - -Event::Event(NativeEvent* nativeEvent) : nativeEvent(nativeEvent) {} -Event::Event(JSValue eventType, JSValue eventInit) {} - -JSValue Event::constructor(ExecutionContext* context) { - return context->contextData()->constructorForType(&eventTypeInfo); -} - -JSValue Event::prototype(ExecutionContext* context) { - return context->contextData()->prototypeForType(&eventTypeInfo); -} - -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::classId)); - return JS_NewUnicodeString(eventInstance->context()->runtime(), eventInstance->context()->ctx(), eventInstance->nativeEvent->type->string, eventInstance->nativeEvent->type->length); -} - -IMPL_PROPERTY_GETTER(Event, bubbles)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - return JS_NewBool(ctx, event->nativeEvent->bubbles); -} - -IMPL_PROPERTY_GETTER(Event, cancelable)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - return JS_NewBool(ctx, event->nativeEvent->cancelable); -} - -IMPL_PROPERTY_GETTER(Event, timestamp)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - return JS_NewInt64(ctx, event->nativeEvent->timeStamp); +Event::Event(ExecutingContext* context, NativeEvent* native_event) + : ScriptWrappable(context->ctx()), +#if ANDROID_32_BIT + type_(reinterpret_cast(nativeEvent->type)), + target_(reinterpret_cast(native_event->target)), + current_target_(reinterpret_cast(native_event->currentTarget)), +#else + type_(native_event->type), + target_(static_cast(native_event->target)), + current_target_(static_cast(native_event->currentTarget)), +#endif + bubbles_(native_event->bubbles), + cancelable_(native_event->cancelable), + time_stamp_(static_cast(native_event->timeStamp)), + default_prevented_(native_event->defaultPrevented) { } -IMPL_PROPERTY_GETTER(Event, defaultPrevented)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - return JS_NewBool(ctx, event->cancelled()); -} +void Event::Trace(GCVisitor* visitor) const {} +void Event::Dispose() const {} -IMPL_PROPERTY_GETTER(Event, target)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - if (event->nativeEvent->target != nullptr) { - auto eventTarget = reinterpret_cast(event->nativeEvent->target); - return JS_DupValue(ctx, eventTarget->toQuickJS()); - } - return JS_NULL; +const char* Event::GetHumanReadableName() const { + return "Event"; } -IMPL_PROPERTY_GETTER(Event, srcElement)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - if (event->nativeEvent->target != nullptr) { - auto eventTarget = reinterpret_cast(event->nativeEvent->target); - return JS_DupValue(ctx, eventTarget->toQuickJS()); - } - return JS_NULL; +void Event::SetType(NativeString* type) { + type_ = type; } -IMPL_PROPERTY_GETTER(Event, currentTarget)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - if (event->nativeEvent->currentTarget != nullptr) { - auto eventTarget = reinterpret_cast(event->nativeEvent->currentTarget); - return JS_DupValue(ctx, eventTarget->toQuickJS()); - } - return JS_NULL; +EventTarget* Event::target() const { + return target_; } -IMPL_PROPERTY_GETTER(Event, returnValue)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - return JS_NewBool(ctx, !event->cancelled()); +void Event::SetTarget(EventTarget* target) { + target_ = target; } -IMPL_PROPERTY_GETTER(Event, cancelBubble)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - return JS_NewBool(ctx, event->cancelled()); +EventTarget* Event::currentTarget() const { + return current_target_; } -// Event* Event::buildEvent(JSValue eventType, JSContext* ctx, void* nativeEvent, bool isCustomEvent) { -// Event* event; -// if (isCustomEvent) { -// event = CustomEvent::create(ctx, reinterpret_cast(nativeEvent), eventType); -// } else if (m_eventCreatorMap.count(eventType) > 0) { -// event = m_eventCreatorMap[eventType](ctx, nativeEvent); -// } else { -// event = Event::create(ctx, static_cast(nativeEvent)); -// } -// return event; -//} - -void Event::defineEvent(const std::string& eventType, EventCreator creator) { - m_eventCreatorMap[eventType] = creator; +EventTarget* Event::srcElement() const { + return target(); } -IMPL_FUNCTION(Event, stopPropagation)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - event->m_propagationStopped = true; - return JS_NULL; +void Event::SetCurrentTarget(EventTarget* target) { + current_target_ = target; } -IMPL_FUNCTION(Event, stopImmediatePropagation)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - event->m_propagationStopped = true; - event->m_propagationImmediatelyStopped = true; - return JS_NULL; -} - -IMPL_FUNCTION(Event, preventDefault)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast(JS_GetOpaque(this_val, Event::classId)); - if (event->nativeEvent->cancelable) { - event->m_cancelled = true; - } - return JS_NULL; -} - -IMPL_FUNCTION(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::classId)); - event->nativeEvent->type = 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; -} - -void Event::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const {} - -void Event::dispose() const { - delete nativeEvent; +void Event::preventDefault(ExceptionState& exception_state) { + default_prevented_ = true; } } // namespace kraken diff --git a/bridge/core/dom/events/event.d.ts b/bridge/core/dom/events/event.d.ts new file mode 100644 index 0000000000..354d6cd5c1 --- /dev/null +++ b/bridge/core/dom/events/event.d.ts @@ -0,0 +1,43 @@ +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; + /** + * 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; +} diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 901e61f8ce..65da013180 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -6,97 +6,15 @@ #ifndef KRAKENBRIDGE_EVENT_H #define KRAKENBRIDGE_EVENT_H -#include "bindings/qjs/host_class.h" - -namespace kraken::binding::qjs { - -#define EVENT_CLICK "click" -#define EVENT_INPUT "input" -#define EVENT_APPEAR "appear" -#define EVENT_DISAPPEAR "disappear" -#define EVENT_COLOR_SCHEME_CHANGE "colorschemechange" -#define EVENT_ERROR "error" -#define EVENT_MEDIA_ERROR "mediaerror" -#define EVENT_TOUCH_START "touchstart" -#define EVENT_TOUCH_MOVE "touchmove" -#define EVENT_TOUCH_END "touchend" -#define EVENT_TOUCH_CANCEL "touchcancel" -#define EVENT_MESSAGE "message" -#define EVENT_CLOSE "close" -#define EVENT_OPEN "open" -#define EVENT_INTERSECTION_CHANGE "intersectionchange" -#define EVENT_CANCEL "cancel" -#define EVENT_POPSTATE "popstate" -#define EVENT_FINISH "finish" -#define EVENT_TRANSITION_RUN "transitionrun" -#define EVENT_TRANSITION_CANCEL "transitioncancel" -#define EVENT_TRANSITION_START "transitionstart" -#define EVENT_TRANSITION_END "transitionend" -#define EVENT_FOCUS "focus" -#define EVENT_LOAD "load" -#define EVENT_UNLOAD "unload" -#define EVENT_CHANGE "change" -#define EVENT_CAN_PLAY "canplay" -#define EVENT_CAN_PLAY_THROUGH "canplaythrough" -#define EVENT_ENDED "ended" -#define EVENT_PAUSE "pause" -#define EVENT_PLAY "play" -#define EVENT_SEEKED "seeked" -#define EVENT_SEEKING "seeking" -#define EVENT_VOLUME_CHANGE "volumechange" -#define EVENT_SCROLL "scroll" -#define EVENT_SWIPE "swipe" -#define EVENT_PAN "pan" -#define EVENT_LONG_PRESS "longpress" -#define EVENT_SCALE "scale" - -void bindEvent(ExecutionContext* context); - -class EventInstance; -class EventTargetInstance; -class NativeEventTarget; - -using EventCreator = EventInstance* (*)(ExecutionContext* context, void* nativeEvent); - -class Event : public HostClass { - public: - static JSClassID kEventClassID; +#include +#include "bindings/qjs/script_wrappable.h" +#include "foundation/native_string.h" +#include "core/executing_context.h" - 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; -}; +namespace kraken { + +class EventTarget; +class ExceptionState; // Dart generated nativeEvent member are force align to 64-bit system. So all members in NativeEvent should have 64 bit width. #if ANDROID_32_BIT @@ -131,43 +49,69 @@ struct RawEvent { int64_t length; }; -class EventInstance : public Instance { - public: - EventInstance() = delete; - ~EventInstance() override { delete nativeEvent; } +class Event : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); - static EventInstance* fromNativeEvent(Event* event, NativeEvent* nativeEvent); - NativeEvent* nativeEvent{nullptr}; + public: + static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; + static Event* From(ExecutingContext* context, NativeEvent* native_event) { + } - 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 + Event() = delete; + explicit Event(ExecutingContext* context); + explicit Event(ExecutingContext* context, NativeEvent* native_event); + + void Trace(GCVisitor* visitor) const override; + void Dispose() const override; + const char* GetHumanReadableName() const override; + bool propagationStopped() const { return propagation_stopped_; } + bool bubbles() { return bubbles_; }; + double timeStamp() { return time_stamp_; } + bool propagationImmediatelyStopped(ExceptionState& exception_state) { return propagation_immediately_stopped_; } + bool cancelable() const { return cancelable_; } + FORCE_INLINE NativeString* type() { return type_; }; + void SetType(NativeString* type); + EventTarget* target() const; + void SetTarget(EventTarget* target); + EventTarget* currentTarget() const; + void SetCurrentTarget(EventTarget* target); + + bool cancelBubble() const { + return propagationStopped(); + } + void setCancelBubble(bool cancel) { + if (cancel) { + propagation_stopped_ = true; + } }; - void setType(NativeString* type) const; - EventTargetInstance* target() const; - void setTarget(EventTargetInstance* target) const; - EventTargetInstance* currentTarget() const; - void setCurrentTarget(EventTargetInstance* target) const; + + // IE legacy + EventTarget* srcElement() const; + + void stopPropagation() { propagation_stopped_ = true; } + void SetStopPropagation(bool stop_propagation) { + propagation_stopped_ = stop_propagation; + } + void stopImmediatePropagation(ExceptionState& exception_state) { propagation_immediately_stopped_ = true; } + void SetStopImmediatePropagation(bool stop_immediate_propagation) { + propagation_immediately_stopped_ = stop_immediate_propagation; + } + + bool defaultPrevented() const { return default_prevented_; } + void preventDefault(ExceptionState& exception_state); 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; + bool bubbles_{false}; + bool cancelable_{false}; + double time_stamp_{0.0}; + bool default_prevented_{false}; + EventTarget* target_{nullptr}; + EventTarget* current_target_{nullptr}; + bool propagation_stopped_{false}; + bool propagation_immediately_stopped_{false}; + NativeString* type_{nullptr}; }; -} // namespace kraken::binding::qjs +} // namespace kraken #endif // KRAKENBRIDGE_EVENT_H diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index ed763b5f69..0a02bf11bf 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -20,6 +20,10 @@ EventTarget* EventTarget::Create(ExecutingContext* context) { EventTarget::EventTarget(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} +bool addEventListener(std::unique_ptr &event_type, const std::shared_ptr& callback, ExceptionState& exception_state) { + +} + void EventTarget::Trace(GCVisitor* visitor) const {} void EventTarget::Dispose() const {} diff --git a/bridge/core/dom/events/event_target.d.ts b/bridge/core/dom/events/event_target.d.ts index b2678ea65c..021eae747f 100644 --- a/bridge/core/dom/events/event_target.d.ts +++ b/bridge/core/dom/events/event_target.d.ts @@ -1,6 +1,6 @@ interface EventTarget { - addEventListener(type: string, callback: EventListenerOrEventListenerObject | null): void; + addEventListener(type: string, callback: EventListener | null): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListenerOrEventListenerObject | null): void; + removeEventListener(type: string, callback: EventListener | null): void; new(): EventTarget; } diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 656eb310f6..3ef94f6934 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -6,6 +6,8 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H +#include "foundation/native_string.h" +#include "bindings/qjs/qjs_function.h" #include "bindings/qjs/script_wrappable.h" #include "event_listener_map.h" @@ -23,35 +25,18 @@ namespace kraken { // 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. -// All nodes are EventTargets, some other event targets include: XMLHttpRequest, -// AudioNode and AudioContext. - -// To make your class an EventTarget, follow these steps: -// - Make your IDL interface inherit from EventTarget. -// - Inherit from EventTargetWithInlineData (only in rare cases should you -// use EventTarget directly). -// - In your class declaration, EventTargetWithInlineData must come first in -// the base class list. If your class is non-final, classes inheriting from -// your class need to come first, too. -// - If you added an onfoo attribute, use DEFINE_ATTRIBUTE_EVENT_LISTENER(foo) -// in your class declaration. Add "attribute EventHandler onfoo;" to the IDL -// file. -// - Override EventTarget::interfaceName() and getExecutionContext(). The former -// will typically return EventTargetNames::YourClassName. The latter will -// return ExecutionContextLifecycleObserver::executionContext (if you are an -// ExecutionContextLifecycleObserver) -// or the document you're in. -// - Your trace() method will need to call EventTargetWithInlineData::trace -// depending on the base class of your class. class EventTarget : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); - public: + using ImplType = EventTarget*; + static EventTarget* Create(ExecutingContext* context); EventTarget() = delete; explicit EventTarget(ExecutingContext* context); + bool addEventListener(std::unique_ptr &event_type, const std::shared_ptr& callback, ExceptionState& exception_state); + void Trace(GCVisitor* visitor) const override; void Dispose() const override; @@ -59,6 +44,63 @@ class EventTarget : public ScriptWrappable { private: }; + +// Macros to define an attribute event listener. +// |lower_name| - Lower-cased event type name. e.g. |focus| +// |symbol_name| - C++ symbol name in event_type_names namespace. e.g. |kFocus| +#define DEFINE_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + EventListener* on##lower_name() { \ + return GetAttributeEventListener(event_type_names::symbol_name); \ + } \ + void setOn##lower_name(EventListener* listener) { \ + SetAttributeEventListener(event_type_names::symbol_name, listener); \ + } + +#define DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + static EventListener* on##lower_name(EventTarget& eventTarget) { \ + return eventTarget.GetAttributeEventListener( \ + event_type_names::symbol_name); \ + } \ + static void setOn##lower_name(EventTarget& eventTarget, \ + EventListener* listener) { \ + eventTarget.SetAttributeEventListener(event_type_names::symbol_name, \ + listener); \ + } + + +#define DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + EventListener* on##lower_name() { \ + return GetDocument().GetWindowAttributeEventListener( \ + event_type_names::symbol_name); \ + } \ + void setOn##lower_name(EventListener* listener) { \ + GetDocument().SetWindowAttributeEventListener( \ + event_type_names::symbol_name, listener); \ + } + +#define DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + static EventListener* on##lower_name(EventTarget& eventTarget) { \ + if (Node* node = eventTarget.ToNode()) { \ + return node->GetDocument().GetWindowAttributeEventListener( \ + event_type_names::symbol_name); \ + } \ + DCHECK(eventTarget.ToLocalDOMWindow()); \ + return eventTarget.GetAttributeEventListener( \ + event_type_names::symbol_name); \ + } \ + static void setOn##lower_name(EventTarget& eventTarget, \ + EventListener* listener) { \ + if (Node* node = eventTarget.ToNode()) { \ + node->GetDocument().SetWindowAttributeEventListener( \ + event_type_names::symbol_name, listener); \ + } else { \ + DCHECK(eventTarget.ToLocalDOMWindow()); \ + eventTarget.SetAttributeEventListener(event_type_names::symbol_name, \ + listener); \ + } \ + } + + // // using NativeDispatchEvent = int32_t (*)(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); // using InvokeBindingMethod = void (*)(void* nativePtr, NativeValue* returnValue, NativeString* method, int32_t argc, NativeValue* argv); diff --git a/bridge/scripts/code_generator/src/analyzer.ts b/bridge/scripts/code_generator/src/analyzer.ts index c3210ef088..051baa93b8 100644 --- a/bridge/scripts/code_generator/src/analyzer.ts +++ b/bridge/scripts/code_generator/src/analyzer.ts @@ -51,12 +51,9 @@ function getParameterName(name: ts.BindingName) : string { export type ParameterType = FunctionArgumentType | string; -function getParameterType(type: ts.TypeNode): ParameterType | ParameterType[] { - if (type.kind == ts.SyntaxKind.ArrayType) { - let arrayType = type as unknown as ts.ArrayTypeNode; - return [getParameterType(arrayType.elementType) as FunctionArgumentType]; - } else if (type.kind === ts.SyntaxKind.StringKeyword) { - return FunctionArgumentType.string; +function getParameterBaseType(type: ts.TypeNode): 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) { @@ -68,6 +65,10 @@ function getParameterType(type: ts.TypeNode): ParameterType | ParameterType[] { // @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 @@ -83,11 +84,27 @@ function getParameterType(type: ts.TypeNode): ParameterType | ParameterType[] { } return identifier; + } else if (type.kind === ts.SyntaxKind.LiteralType) { + // @ts-ignore + return getParameterBaseType((type as ts.LiteralTypeNode).literal); } return FunctionArgumentType.any; } +function getParameterType(type: ts.TypeNode): ParameterType[] { + if (type.kind == ts.SyntaxKind.ArrayType) { + let arrayType = type as unknown as ts.ArrayTypeNode; + return [FunctionArgumentType.array, getParameterBaseType(arrayType.elementType)]; + } 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)); + } + return [getParameterBaseType(type)]; +} + function paramsNodeToArguments(parameter: ts.ParameterDeclaration): FunctionArguments { let args = new FunctionArguments(); args.name = getParameterName(parameter.name); @@ -126,7 +143,7 @@ function walkProgram(statement: ts.Statement) { let propKind = m.type; if (propKind) { prop.type = getParameterType(propKind); - if (prop.type === FunctionArgumentType.function) { + if (prop.type[0] === FunctionArgumentType.function) { let f = (m.type as ts.FunctionTypeNode); let functionProps = prop as FunctionDeclaration; functionProps.args = []; diff --git a/bridge/scripts/code_generator/src/declaration.ts b/bridge/scripts/code_generator/src/declaration.ts index 9501a1e560..9cfe9a2773 100644 --- a/bridge/scripts/code_generator/src/declaration.ts +++ b/bridge/scripts/code_generator/src/declaration.ts @@ -2,7 +2,7 @@ import {ParameterType} from "./analyzer"; export enum FunctionArgumentType { // Basic types - string, + dom_string, object, int32, int64, @@ -11,23 +11,26 @@ export enum FunctionArgumentType { function, void, any, + null, + undefined, + array, } export class FunctionArguments { name: string; - type: ParameterType | ParameterType[]; + type: ParameterType[] = []; required: boolean; } export class PropsDeclaration { - type: ParameterType | ParameterType[]; + type: ParameterType[] = []; name: string; readonly: boolean; } export class FunctionDeclaration extends PropsDeclaration { - args: FunctionArguments[]; - returnType: ParameterType | ParameterType[]; + args: FunctionArguments[] = []; + returnType: ParameterType[] = []; } export class ClassObject { diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index 61d5c2c311..7d28498104 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -30,34 +30,49 @@ function generateMethodArgumentsCheck(m: FunctionDeclaration) { `; } -function generateTypeConverter(type: ParameterType | ParameterType[]): string { - if (Array.isArray(type)) { - return `IDLSequence<${generateTypeConverter(type[0])}>`; +function generateTypeConverter(type: ParameterType[]): string { + let haveNull = type.some(t => t === FunctionArgumentType.null); + let returnValue = ''; + + if (type[0] === FunctionArgumentType.array) { + returnValue = `IDLSequence<${generateTypeConverter(type.slice(1))}>`; + } else if (typeof type[0] === 'string') { + returnValue = type[0]; + } else { + switch(type[0]) { + case FunctionArgumentType.int32: + returnValue = `IDLInt32`; + break; + case FunctionArgumentType.int64: + returnValue = 'IDLInt64'; + break; + case FunctionArgumentType.double: + returnValue = `IDLDouble`; + break; + case FunctionArgumentType.function: + returnValue = `IDLCallback`; + break; + case FunctionArgumentType.boolean: + returnValue = `IDLBoolean`; + break; + case FunctionArgumentType.dom_string: + returnValue = `IDLDOMString`; + break; + case FunctionArgumentType.object: + returnValue = `IDLObject`; + break; + default: + case FunctionArgumentType.any: + returnValue = `IDLAny`; + break; + } } - if (typeof type === 'string') { - return type; + if (haveNull) { + returnValue = `IDLNullable<${returnValue}>`; } - switch(type) { - case FunctionArgumentType.int32: - return `IDLInt32`; - case FunctionArgumentType.int64: - return 'IDLInt64'; - case FunctionArgumentType.double: - return `IDLDouble`; - case FunctionArgumentType.function: - return `IDLCallback`; - case FunctionArgumentType.boolean: - return `IDLBoolean`; - case FunctionArgumentType.string: - return `IDLDOMString`; - case FunctionArgumentType.object: - return `IDLObject`; - default: - case FunctionArgumentType.any: - return `IDLAny`; - } + return returnValue; } function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { @@ -73,7 +88,7 @@ function generateCallMethodName(name: string) { function generateOptionalInitBody(blob: Blob, declare: FunctionDeclaration, argument: FunctionArguments, argsIndex: number, previousArguments: string[], options: GenFunctionBodyOptions) { let call = ''; let returnValueAssignment = ''; - if (declare.returnType != FunctionArgumentType.void) { + if (declare.returnType[0] != FunctionArgumentType.void) { returnValueAssignment = 'return_value ='; } if (options.isInstanceMethod) { @@ -122,12 +137,12 @@ function generateFunctionCallBody(blob: Blob, declaration: FunctionDeclaration, let call = ''; let returnValueAssignment = ''; - if (declaration.returnType != FunctionArgumentType.void) { + if (declaration.returnType[0] != FunctionArgumentType.void) { returnValueAssignment = 'return_value ='; } if (options.isInstanceMethod) { call = `auto* self = toScriptWrappable<${getClassName(blob)}>(this_val); -${returnValueAssignment} self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : 'exception_state'});`; +${returnValueAssignment} self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `${requiredArguments.join(',')}` : 'exception_state'});`; } else { call = `${returnValueAssignment} ${getClassName(blob)}::${generateCallMethodName(declaration.name)}(context${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''});`; } @@ -149,30 +164,30 @@ ${body} }`; } -function generateReturnValueInit(blob: Blob, type: ParameterType | ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { - if (type == FunctionArgumentType.void) return ''; +function generateReturnValueInit(blob: Blob, 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 === 'string') { - if (type === 'Promise') { + if (typeof type[0] === 'string') { + if (type[0] === 'Promise') { return 'ScriptPromise return_value;'; } else { - return `${type}* return_value = nullptr;`; + return `${type[0]}* return_value = nullptr;`; } } return `Converter<${generateTypeConverter(type)}>::ImplType return_value;`; } -function generateReturnValueResult(blob: Blob, type: ParameterType | ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}): string { - if (type == FunctionArgumentType.void) return 'JS_NULL'; +function generateReturnValueResult(blob: Blob, type: ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}): string { + if (type[0] == FunctionArgumentType.void) return 'JS_NULL'; if (options.isConstructor) { return `return_value->ToQuickJS()`; } - if (typeof type === 'string') { - if (type === 'Promise') { + if (typeof type[0] === 'string') { + if (type[0] === 'Promise') { return 'return_value.ToQuickJS()'; } else { return `return_value->ToQuickJS()`; @@ -208,7 +223,7 @@ ${addIndent(callBody, 4)} } function generateClassConstructorCallback(blob: Blob, declare: FunctionDeclaration) { - return `JSValue QJSBlob::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { + return `JSValue QJS${getClassName(blob)}::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { ${generateFunctionBody(blob, declare, {isConstructor: true})} } `; @@ -230,7 +245,7 @@ function generatePropertySetterCallback(blob: Blob, prop: PropsDeclaration) { if (exception_state.HasException()) { return exception_state.ToQuickJS(); } - qjs_blob->set${prop.name[0].toUpperCase() + prop.name.slice(1)}(v); + ${blob.filename}->set${prop.name[0].toUpperCase() + prop.name.slice(1)}(v); }`; } diff --git a/integration_tests/webpack.config.js b/integration_tests/webpack.config.js index 61813e5dd8..cbeb1ae476 100644 --- a/integration_tests/webpack.config.js +++ b/integration_tests/webpack.config.js @@ -9,10 +9,11 @@ const resetRuntimePath = path.join(context, 'runtime/reset'); const buildPath = path.join(context, '.specs'); const testPath = path.join(context, 'specs'); const snapshotPath = path.join(context, 'snapshots'); -const coreSpecFiles = glob.sync('specs/**/*.{js,jsx,ts,tsx,html}', { - cwd: context, - ignore: ['node_modules/**'], -}).map((file) => './' + file).filter(name => name.indexOf('plugins') < 0); +// const coreSpecFiles = glob.sync('specs/**/*.{js,jsx,ts,tsx,html}', { +// cwd: context, +// ignore: ['node_modules/**'], +// }).map((file) => './' + file).filter(name => name.indexOf('plugins') < 0); +const coreSpecFiles = []; const pluginSpecFiles = glob.sync('specs/plugins/**/*.{js,jsx,ts,tsx}', { cwd: context, From a244cac7936062d1aca4e0200ce6b89bd2460f81 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Fri, 25 Mar 2022 13:08:30 +0000 Subject: [PATCH 037/498] Committing clang-format changes --- bridge/bindings/qjs/atom_string.h | 2 +- bridge/bindings/qjs/converter_impl.h | 26 ++++---- bridge/bindings/qjs/idl_type.h | 2 +- bridge/core/dom/events/event.h | 17 ++---- bridge/core/dom/events/event_target.cc | 4 +- bridge/core/dom/events/event_target.h | 83 ++++++++++---------------- 6 files changed, 49 insertions(+), 85 deletions(-) diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index 4ee3d09eb2..bb0b916c28 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ #define KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ -#include #include +#include #include "foundation/macros.h" #include "foundation/native_string.h" #include "native_string_utils.h" diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 772033bf4c..de0e67cea2 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -9,9 +9,9 @@ #include #include "atom_string.h" #include "converter.h" +#include "core/dom/events/event_target.h" #include "core/fileapi/blob_part.h" #include "core/fileapi/blob_property_bag.h" -#include "core/dom/events/event_target.h" #include "idl_type.h" #include "native_string_utils.h" @@ -38,7 +38,7 @@ struct Converter, std::enable_if_t +template struct Converter, std::enable_if::ImplType>::value>> : public ConverterBase> { using ImplType = typename Converter::ImplType; @@ -49,7 +49,7 @@ struct Converter, std::enable_if::FromValue(ctx, value, exception_state); } - static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value){ return Converter::ToValue(ctx, value); } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, value); } }; template @@ -102,7 +102,7 @@ struct Converter> : public ConverterBase static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, std::move(value)); } }; -template<> +template <> struct Converter> : public ConverterBase> { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); @@ -196,7 +196,7 @@ struct Converter> : public ConverterBase static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, std::move(value)); } }; -template<> +template <> struct Converter> : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { if (JS_IsNull(value)) @@ -301,7 +301,7 @@ struct Converter : public ConverterBase { }; // EventListener -template<> +template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); @@ -312,7 +312,7 @@ struct Converter : public ConverterBase { return QJSFunction::Create(ctx, value); } }; -template<> +template <> struct Converter> : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); @@ -324,28 +324,24 @@ struct Converter> : public ConverterBase +template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return toScriptWrappable(value); } - static JSValue ToValue(JSContext* ctx, ImplType value) { - return value->ToQuickJS(); - } + static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } }; -template<> +template <> struct Converter> : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return Converter::FromValue(ctx, value, exception_state); } - static JSValue ToValue(JSContext* ctx, ImplType value) { - return Converter::ToValue(ctx, value); - } + static JSValue ToValue(JSContext* ctx, ImplType value) { return Converter::ToValue(ctx, value); } }; } // namespace kraken diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 7f006b707e..f88e036808 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -30,7 +30,7 @@ struct IDLOptional final : public IDLTypeBase { }; // Nullable -template +template struct IDLNullable final : public IDLTypeBase { using ImplType = typename Converter::ImplType; }; diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 65da013180..ddf915489e 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -8,8 +8,8 @@ #include #include "bindings/qjs/script_wrappable.h" -#include "foundation/native_string.h" #include "core/executing_context.h" +#include "foundation/native_string.h" namespace kraken { @@ -54,8 +54,7 @@ class Event : public ScriptWrappable { public: static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; - static Event* From(ExecutingContext* context, NativeEvent* native_event) { - } + static Event* From(ExecutingContext* context, NativeEvent* native_event) {} Event() = delete; explicit Event(ExecutingContext* context); @@ -76,9 +75,7 @@ class Event : public ScriptWrappable { EventTarget* currentTarget() const; void SetCurrentTarget(EventTarget* target); - bool cancelBubble() const { - return propagationStopped(); - } + bool cancelBubble() const { return propagationStopped(); } void setCancelBubble(bool cancel) { if (cancel) { propagation_stopped_ = true; @@ -89,13 +86,9 @@ class Event : public ScriptWrappable { EventTarget* srcElement() const; void stopPropagation() { propagation_stopped_ = true; } - void SetStopPropagation(bool stop_propagation) { - propagation_stopped_ = stop_propagation; - } + void SetStopPropagation(bool stop_propagation) { propagation_stopped_ = stop_propagation; } void stopImmediatePropagation(ExceptionState& exception_state) { propagation_immediately_stopped_ = true; } - void SetStopImmediatePropagation(bool stop_immediate_propagation) { - propagation_immediately_stopped_ = stop_immediate_propagation; - } + void SetStopImmediatePropagation(bool stop_immediate_propagation) { propagation_immediately_stopped_ = stop_immediate_propagation; } bool defaultPrevented() const { return default_prevented_; } void preventDefault(ExceptionState& exception_state); diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 0a02bf11bf..9e01f8248c 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -20,9 +20,7 @@ EventTarget* EventTarget::Create(ExecutingContext* context) { EventTarget::EventTarget(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} -bool addEventListener(std::unique_ptr &event_type, const std::shared_ptr& callback, ExceptionState& exception_state) { - -} +bool addEventListener(std::unique_ptr& event_type, const std::shared_ptr& callback, ExceptionState& exception_state) {} void EventTarget::Trace(GCVisitor* visitor) const {} diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 3ef94f6934..428df884e3 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -6,10 +6,10 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H -#include "foundation/native_string.h" #include "bindings/qjs/qjs_function.h" #include "bindings/qjs/script_wrappable.h" #include "event_listener_map.h" +#include "foundation/native_string.h" #if UNIT_TEST void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); @@ -27,6 +27,7 @@ namespace kraken { // the target to which an event is dispatched when something has occurred. class EventTarget : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = EventTarget*; @@ -35,7 +36,7 @@ class EventTarget : public ScriptWrappable { EventTarget() = delete; explicit EventTarget(ExecutingContext* context); - bool addEventListener(std::unique_ptr &event_type, const std::shared_ptr& callback, ExceptionState& exception_state); + bool addEventListener(std::unique_ptr& event_type, const std::shared_ptr& callback, ExceptionState& exception_state); void Trace(GCVisitor* visitor) const override; void Dispose() const override; @@ -48,59 +49,35 @@ class EventTarget : public ScriptWrappable { // 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_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + EventListener* on##lower_name() { return GetAttributeEventListener(event_type_names::symbol_name); } \ + void setOn##lower_name(EventListener* listener) { SetAttributeEventListener(event_type_names::symbol_name, listener); } + +#define DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + static EventListener* on##lower_name(EventTarget& eventTarget) { return eventTarget.GetAttributeEventListener(event_type_names::symbol_name); } \ + static void setOn##lower_name(EventTarget& eventTarget, EventListener* listener) { eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener); } + +#define DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + EventListener* on##lower_name() { return GetDocument().GetWindowAttributeEventListener(event_type_names::symbol_name); } \ + void setOn##lower_name(EventListener* listener) { GetDocument().SetWindowAttributeEventListener(event_type_names::symbol_name, listener); } + +#define DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + static EventListener* on##lower_name(EventTarget& eventTarget) { \ + if (Node* node = eventTarget.ToNode()) { \ + return node->GetDocument().GetWindowAttributeEventListener(event_type_names::symbol_name); \ + } \ + DCHECK(eventTarget.ToLocalDOMWindow()); \ + return eventTarget.GetAttributeEventListener(event_type_names::symbol_name); \ + } \ + static void setOn##lower_name(EventTarget& eventTarget, EventListener* listener) { \ + if (Node* node = eventTarget.ToNode()) { \ + node->GetDocument().SetWindowAttributeEventListener(event_type_names::symbol_name, listener); \ + } else { \ + DCHECK(eventTarget.ToLocalDOMWindow()); \ + eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener); \ + } \ } -#define DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ - static EventListener* on##lower_name(EventTarget& eventTarget) { \ - return eventTarget.GetAttributeEventListener( \ - event_type_names::symbol_name); \ - } \ - static void setOn##lower_name(EventTarget& eventTarget, \ - EventListener* listener) { \ - eventTarget.SetAttributeEventListener(event_type_names::symbol_name, \ - listener); \ - } - - -#define DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ - EventListener* on##lower_name() { \ - return GetDocument().GetWindowAttributeEventListener( \ - event_type_names::symbol_name); \ - } \ - void setOn##lower_name(EventListener* listener) { \ - GetDocument().SetWindowAttributeEventListener( \ - event_type_names::symbol_name, listener); \ - } - -#define DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ - static EventListener* on##lower_name(EventTarget& eventTarget) { \ - if (Node* node = eventTarget.ToNode()) { \ - return node->GetDocument().GetWindowAttributeEventListener( \ - event_type_names::symbol_name); \ - } \ - DCHECK(eventTarget.ToLocalDOMWindow()); \ - return eventTarget.GetAttributeEventListener( \ - event_type_names::symbol_name); \ - } \ - static void setOn##lower_name(EventTarget& eventTarget, \ - EventListener* listener) { \ - if (Node* node = eventTarget.ToNode()) { \ - node->GetDocument().SetWindowAttributeEventListener( \ - event_type_names::symbol_name, listener); \ - } else { \ - DCHECK(eventTarget.ToLocalDOMWindow()); \ - eventTarget.SetAttributeEventListener(event_type_names::symbol_name, \ - listener); \ - } \ - } - - // // using NativeDispatchEvent = int32_t (*)(int32_t contextId, NativeEventTarget* nativeEventTarget, NativeString* eventType, void* nativeEvent, int32_t isCustomEvent); // using InvokeBindingMethod = void (*)(void* nativePtr, NativeValue* returnValue, NativeString* method, int32_t argc, NativeValue* argv); From f184e9fdd2b4f131850d21a0469562fc57883d33 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 28 Mar 2022 00:01:48 +0800 Subject: [PATCH 038/498] feat: add event and event listener impl --- bridge/CMakeLists.txt | 13 +++ .../qjs/add_event_listener_options.cc | 43 +++++++++ .../bindings/qjs/add_event_listener_options.h | 53 +++++++++++ bridge/bindings/qjs/atom_string.cc | 6 ++ bridge/bindings/qjs/atom_string.h | 38 +++++++- bridge/bindings/qjs/converter_impl.h | 31 +++---- bridge/bindings/qjs/dictionary_base.cc | 18 ++++ bridge/bindings/qjs/dictionary_base.h | 42 +++++++++ bridge/bindings/qjs/event_listener_options.cc | 36 +++++++ bridge/bindings/qjs/event_listener_options.h | 37 ++++++++ bridge/bindings/qjs/gc_visitor.cc | 7 +- bridge/bindings/qjs/gc_visitor.h | 5 +- bridge/bindings/qjs/idl_type.h | 5 +- bridge/core/dom/events/event.cc | 30 ++++-- bridge/core/dom/events/event.d.ts | 2 +- bridge/core/dom/events/event.h | 93 ++++++++++++++++--- bridge/core/dom/events/event_listener.h | 55 +++++++++++ bridge/core/dom/events/event_listener_map.cc | 12 ++- bridge/core/dom/events/event_listener_map.h | 23 +++-- bridge/core/dom/events/event_target.cc | 11 +++ bridge/core/dom/events/event_target.h | 51 ++++++++++ bridge/core/dom/events/event_target_impl.cc | 6 ++ bridge/core/dom/events/event_target_impl.h | 26 ++++++ .../dom/events/registered_eventListener.cc | 56 +++++++++++ .../dom/events/registered_eventListener.h | 66 +++++++++++++ bridge/core/html_names.cc | 6 ++ bridge/core/html_names.h | 13 +++ .../code_generator/src/generate_header.ts | 4 +- .../code_generator/src/genereate_source.ts | 2 +- 29 files changed, 732 insertions(+), 58 deletions(-) create mode 100644 bridge/bindings/qjs/add_event_listener_options.cc create mode 100644 bridge/bindings/qjs/add_event_listener_options.h create mode 100644 bridge/bindings/qjs/dictionary_base.cc create mode 100644 bridge/bindings/qjs/dictionary_base.h create mode 100644 bridge/bindings/qjs/event_listener_options.cc create mode 100644 bridge/bindings/qjs/event_listener_options.h create mode 100644 bridge/core/dom/events/event_listener.h create mode 100644 bridge/core/dom/events/event_target_impl.cc create mode 100644 bridge/core/dom/events/event_target_impl.h create mode 100644 bridge/core/dom/events/registered_eventListener.cc create mode 100644 bridge/core/dom/events/registered_eventListener.h create mode 100644 bridge/core/html_names.cc create mode 100644 bridge/core/html_names.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index dba605929f..9f4475155f 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -184,6 +184,12 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/idl_type.h bindings/qjs/converter.h bindings/qjs/converter_impl.h + bindings/qjs/dictionary_base.cc + bindings/qjs/dictionary_base.h + bindings/qjs/event_listener_options.cc + bindings/qjs/event_listener_options.h + bindings/qjs/add_event_listener_options.cc + bindings/qjs/add_event_listener_options.h bindings/qjs/binding_initializer.cc bindings/qjs/binding_initializer.h bindings/qjs/member_installer.cc @@ -259,10 +265,17 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/frame/module_callback_coordinator.h core/dom/frame_request_callback_collection.cc core/dom/frame_request_callback_collection.h + core/dom/events/event_listener.h + core/dom/events/registered_eventListener.cc + core/dom/events/registered_eventListener.h + core/dom/events/event_listener_map.cc + core/dom/events/event_listener_map.h core/dom/events/event.h core/dom/events/event.cc core/dom/events/event_target.h core/dom/events/event_target.cc + core/dom/events/event_target_impl.cc + core/dom/events/event_target_impl.h # core/dom/character_data.cc # core/dom/character_data.h # core/dom/comment.cc diff --git a/bridge/bindings/qjs/add_event_listener_options.cc b/bridge/bindings/qjs/add_event_listener_options.cc new file mode 100644 index 0000000000..7e40b2ca63 --- /dev/null +++ b/bridge/bindings/qjs/add_event_listener_options.cc @@ -0,0 +1,43 @@ +/* +* Copyright (C) 2019 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#include "add_event_listener_options.h" + +namespace kraken { + +AddEventListenerOptions::AddEventListenerOptions() {} + +AddEventListenerOptions::AddEventListenerOptions(JSContext* ctx, JSValue dictionary_value, ExecutingContext& executing_context) { + +} + +bool AddEventListenerOptions::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { + if (!JS_IsObject(qjs_dictionary)) { + return false; + } + + JS_SetPropertyStr(ctx, qjs_dictionary, "passive", JS_NewBool(ctx, member_passive_)); + JS_SetPropertyStr(ctx, qjs_dictionary, "once", JS_NewBool(ctx, member_once_)); + + EventListenerOptions::FillQJSObjectWithMembers(ctx, qjs_dictionary); + + return true; +} + +void AddEventListenerOptions::FillMembersFromQJSObject(JSContext* ctx, JSValue qjs_dictionary) { + if (!JS_IsObject(qjs_dictionary)) { + return; + } + + JSValue passive = JS_GetPropertyStr(ctx, qjs_dictionary, "passive"); + member_passive_ = JS_ToBool(ctx, passive); + JS_FreeValue(ctx, passive); + + JSValue once = JS_GetPropertyStr(ctx, qjs_dictionary, "once"); + member_once_ = JS_ToBool(ctx, once); + JS_FreeValue(ctx, once); +} + +} diff --git a/bridge/bindings/qjs/add_event_listener_options.h b/bridge/bindings/qjs/add_event_listener_options.h new file mode 100644 index 0000000000..548fd7efb2 --- /dev/null +++ b/bridge/bindings/qjs/add_event_listener_options.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_ADD_EVENT_LISTENER_OPTIONS_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_ADD_EVENT_LISTENER_OPTIONS_H_ + +#include "dictionary_base.h" +#include "event_listener_options.h" + +namespace kraken { + +class AddEventListenerOptions : public EventListenerOptions { + public: + static std::unique_ptr Create(JSContext* ctx, JSValue dictionary_value, ExecutingContext& executing_context) {} + + explicit AddEventListenerOptions(); + explicit AddEventListenerOptions(JSContext* ctx, JSValue dictionary_value, ExecutingContext& executing_context); + + bool hasOnce() const { return true; } + bool once() const { return member_once_; } + void setOnce(bool value) { member_once_ = value; } + + bool hasPassive() const { return has_passive_; } + bool passive() const { + return member_passive_; + } + bool getPassiveOr(bool fallback_value) const { + if (!hasPassive()) { + return fallback_value; + } + return member_passive_; + } + void setPassive(bool value) { + member_passive_ = value; + has_passive_ = true; + } + + protected: + bool FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const override; + + private: + void FillMembersFromQJSObject(JSContext* ctx, JSValue qjs_dictionary); + + bool has_passive_ = false; + bool member_once_{false}; + bool member_passive_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_ADD_EVENT_LISTENER_OPTIONS_H_ diff --git a/bridge/bindings/qjs/atom_string.cc b/bridge/bindings/qjs/atom_string.cc index 73ef074b15..d1fcd08937 100644 --- a/bridge/bindings/qjs/atom_string.cc +++ b/bridge/bindings/qjs/atom_string.cc @@ -4,3 +4,9 @@ */ #include "atom_string.h" + +namespace kraken { + +AtomString::AtomString(const AtomString&) {} + +} diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index bb0b916c28..d71526a5f6 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -14,19 +14,35 @@ namespace kraken { -// ScriptAtom is a stack allocate only QuickJS JSAtom wrapper. +// 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. +// +// AtomicString instances are not thread-safe. An AtomicString instance created +// in a thread must be used only in the creator thread. class AtomString final { // ScriptAtom should only allocate at stack. KRAKEN_DISALLOW_NEW(); public: - explicit AtomString(JSContext* ctx, const char* string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string)) {} + static AtomString Empty(JSContext* ctx) { return AtomString(ctx, JS_ATOM_NULL); }; + + explicit AtomString(JSContext* ctx, const std::string& string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())) {} explicit AtomString(JSContext* ctx, JSAtom atom) : ctx_(ctx), atom_(JS_DupAtom(ctx, atom)){}; + explicit AtomString(JSContext* ctx, JSValue value): ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)) {}; ~AtomString() { JS_FreeAtom(ctx_, atom_); } JSValue ToQuickJS() const { return JS_AtomToValue(ctx_, atom_); } + // Copy assignment + AtomString(AtomString const& value) { + if (&value != this) { + atom_ = JS_DupAtom(ctx_, value.atom_); + } + ctx_ = value.ctx_; + }; AtomString& operator=(const AtomString& other) { if (&other != this) { atom_ = JS_DupAtom(ctx_, other.atom_); @@ -34,6 +50,24 @@ class AtomString final { return *this; }; + // Move assignment + AtomString(AtomString&& value) noexcept { + if (&value != this) { + atom_ = JS_DupAtom(ctx_, value.atom_); + } + ctx_ = value.ctx_; + }; + AtomString& operator=(AtomString&& value) noexcept { + if (&value != this) { + atom_ = JS_DupAtom(ctx_, value.atom_); + } + ctx_ = value.ctx_; + return *this; + } + + bool operator==(const AtomString& other) const { + return other.atom_ == this->atom_; + } private: AtomString() = delete; JSContext* ctx_{nullptr}; diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index de0e67cea2..1f7f3b18f1 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -9,6 +9,7 @@ #include #include "atom_string.h" #include "converter.h" +#include "core/dom/events/event.h" #include "core/dom/events/event_target.h" #include "core/fileapi/blob_part.h" #include "core/fileapi/blob_property_bag.h" @@ -174,9 +175,10 @@ template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); - return jsValueToNativeString(ctx, value); + return AtomString(ctx, value); } + static JSValue ToValue(JSContext* ctx, const AtomString& value) { return value.ToQuickJS(); } 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); } @@ -187,7 +189,7 @@ template <> struct Converter> : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { if (JS_IsUndefined(value)) - return nullptr; + return AtomString::Empty(ctx); return Converter::FromValue(ctx, value, exception_state); } @@ -200,24 +202,11 @@ template <> struct Converter> : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { if (JS_IsNull(value)) - return nullptr; + return AtomString::Empty(ctx); return Converter::FromValue(ctx, value, exception_state); } }; -template <> -struct Converter : public ConverterBase { - static AtomString FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - assert(!JS_IsException(value)); - JSAtom atom = JS_ValueToAtom(ctx, value); - AtomString result = AtomString(ctx, atom); - JS_FreeAtom(ctx, atom); - return result; - } - - static JSValue ToValue(JSContext* ctx, const AtomString& atom_string) { return atom_string.ToQuickJS(); } -}; - template struct Converter> : public ConverterBase> { using ImplType = typename IDLSequence::ImplType>::ImplType; @@ -344,6 +333,16 @@ struct Converter> : public ConverterBase { static JSValue ToValue(JSContext* ctx, ImplType value) { return Converter::ToValue(ctx, value); } }; +template <> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return toScriptWrappable(value); + } + + static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/dictionary_base.cc b/bridge/bindings/qjs/dictionary_base.cc new file mode 100644 index 0000000000..18fc96c307 --- /dev/null +++ b/bridge/bindings/qjs/dictionary_base.cc @@ -0,0 +1,18 @@ +/* +* Copyright (C) 2022 Alibaba Inc. All rights reserved. +* Author: Kraken Team. + */ + +#include "dictionary_base.h" + +namespace kraken { + +JSValue DictionaryBase::toQuickJS(JSContext* ctx) const { + JSValue object = JS_NewObject(ctx); + if (!FillQJSObjectWithMembers(ctx, object)) { + return JS_NULL; + } + return object; +} + +} diff --git a/bridge/bindings/qjs/dictionary_base.h b/bridge/bindings/qjs/dictionary_base.h new file mode 100644 index 0000000000..236b062062 --- /dev/null +++ b/bridge/bindings/qjs/dictionary_base.h @@ -0,0 +1,42 @@ +/* +* Copyright (C) 2022 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ + +#include "garbage_collected.h" + +namespace kraken { + +// DictionaryBase is the common base class of all the IDL dictionary classes. +// Most importantly this class provides a way of type dispatching (e.g. overload +// resolutions, SFINAE technique, etc.) so that it's possible to distinguish +// IDL dictionaries from anything else. Also it provides a common +// implementation of IDL dictionaries. +class DictionaryBase { + public: + virtual ~DictionaryBase() = default; + + JSValue toQuickJS(JSContext* ctx) const; + + protected: + DictionaryBase() = default; + + DictionaryBase(const DictionaryBase&) = delete; + DictionaryBase(const DictionaryBase&&) = delete; + DictionaryBase& operator=(const DictionaryBase&) = delete; + DictionaryBase& operator=(const DictionaryBase&&) = delete; + + // Fills the given QuickJS object with the dictionary members. Returns true on + // success, otherwise returns false with throwing an exception. + virtual bool FillQJSObjectWithMembers( + JSContext* ctx, + JSValue qjs_dictionary) const = 0; +}; + + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ diff --git a/bridge/bindings/qjs/event_listener_options.cc b/bridge/bindings/qjs/event_listener_options.cc new file mode 100644 index 0000000000..ccd994c5ff --- /dev/null +++ b/bridge/bindings/qjs/event_listener_options.cc @@ -0,0 +1,36 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#include "event_listener_options.h" + +namespace kraken { + +EventListenerOptions::EventListenerOptions() {} + +EventListenerOptions::EventListenerOptions(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + FillMembersFromQJSObject(ctx, value, exception_state); +} + +bool EventListenerOptions::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { + if (!JS_IsObject(qjs_dictionary)) { + return false; + } + + JS_SetPropertyStr(ctx, qjs_dictionary, "capture", JS_NewBool(ctx, capture_)); + + return true; +} + +void EventListenerOptions::FillMembersFromQJSObject(JSContext* ctx, JSValue qjs_dictionary, ExceptionState& exception_state) { + if (!JS_IsObject(qjs_dictionary)) { + return; + } + + JSValue capture = JS_GetPropertyStr(ctx, qjs_dictionary, "capture"); + capture_ = JS_ToBool(ctx, capture); + JS_FreeValue(ctx, capture); +} + +} diff --git a/bridge/bindings/qjs/event_listener_options.h b/bridge/bindings/qjs/event_listener_options.h new file mode 100644 index 0000000000..da432c6502 --- /dev/null +++ b/bridge/bindings/qjs/event_listener_options.h @@ -0,0 +1,37 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_OPTIONS_H_ +#define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_OPTIONS_H_ + +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/exception_state.h" + +namespace kraken { + +class EventListenerOptions : public DictionaryBase { + public: + static std::shared_ptr Create(JSContext* ctx, + JSValue value, + ExceptionState& exception_state) { + return std::make_shared(); + }; + explicit EventListenerOptions(); + explicit EventListenerOptions(JSContext* ctx, JSValue value, ExceptionState& exception_state); + + bool hasCapture() const { return true; } + bool capture() const { return capture_; } + void setCapture(bool value) { capture_ = value; } + + protected: + bool FillQJSObjectWithMembers(JSContext *ctx, JSValue qjs_dictionary) const override; + private: + bool capture_{false}; + void FillMembersFromQJSObject(JSContext* ctx, JSValue qjs_dictionary, ExceptionState& exception_state); +}; + +} + +#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_OPTIONS_H_ diff --git a/bridge/bindings/qjs/gc_visitor.cc b/bridge/bindings/qjs/gc_visitor.cc index 50820cfc2d..b93e8b3920 100644 --- a/bridge/bindings/qjs/gc_visitor.cc +++ b/bridge/bindings/qjs/gc_visitor.cc @@ -4,11 +4,14 @@ */ #include "gc_visitor.h" +#include "garbage_collected.h" namespace kraken { -void GCVisitor::Trace(JSValue value) { - JS_MarkValue(runtime_, value, markFunc_); +void GCVisitor::Trace(ScriptWrappable* target) { + if (target != nullptr) { + JS_MarkValue(runtime_, target->ToQuickJS(), markFunc_); + } } } // namespace kraken diff --git a/bridge/bindings/qjs/gc_visitor.h b/bridge/bindings/qjs/gc_visitor.h index b782e4ca03..3fb56b4e93 100644 --- a/bridge/bindings/qjs/gc_visitor.h +++ b/bridge/bindings/qjs/gc_visitor.h @@ -7,15 +7,18 @@ #define KRAKENBRIDGE_GC_VISITOR_H #include +#include "script_wrappable.h" namespace kraken { +class GarbageCollected; + // Use GCVisitor to keep track gc managed members in C++ class. class GCVisitor final { public: explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc) : runtime_(rt), markFunc_(markFunc){}; - void Trace(JSValue value); + void Trace(ScriptWrappable* target); private: JSRuntime* runtime_{nullptr}; diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index f88e036808..7bbcdcd660 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -47,10 +47,7 @@ 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> {}; - -class AtomString; -struct IDLAtomString final : public IDLTypeBaseHelper {}; +struct IDLDOMString final : public IDLTypeBaseHelper {}; // https://developer.mozilla.org/en-US/docs/Web/API/USVString struct IDLUSVString final : public IDLTypeBaseHelper {}; diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 2dc140ebe1..4a0e23e79f 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -8,7 +8,7 @@ namespace kraken { -Event::Event(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} +Event::Event(ExecutingContext* context) : Event(context, nullptr) {} Event::Event(ExecutingContext* context, NativeEvent* native_event) : ScriptWrappable(context->ctx()), @@ -24,11 +24,8 @@ Event::Event(ExecutingContext* context, NativeEvent* native_event) bubbles_(native_event->bubbles), cancelable_(native_event->cancelable), time_stamp_(static_cast(native_event->timeStamp)), - default_prevented_(native_event->defaultPrevented) { -} - -void Event::Trace(GCVisitor* visitor) const {} -void Event::Dispose() const {} + default_prevented_(native_event->defaultPrevented) +{} const char* Event::GetHumanReadableName() const { return "Event"; @@ -62,4 +59,25 @@ void Event::preventDefault(ExceptionState& exception_state) { default_prevented_ = true; } +void Event::initEvent(std::unique_ptr& 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->clone(); + bubbles_ = bubbles; + cancelable_ = cancelable; +} + +void Event::Trace(GCVisitor* visitor) const { + visitor->Trace(target_) + +} +void Event::Dispose() const {} + } // namespace kraken diff --git a/bridge/core/dom/events/event.d.ts b/bridge/core/dom/events/event.d.ts index 354d6cd5c1..8926a05f86 100644 --- a/bridge/core/dom/events/event.d.ts +++ b/bridge/core/dom/events/event.d.ts @@ -27,7 +27,7 @@ interface Event { */ readonly type: string; /** @deprecated */ - initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void; + 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. */ diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index ddf915489e..3fd2ffa73d 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -53,6 +53,25 @@ class Event : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); public: + using ImplType = Event*; + + enum class Bubbles { + kNo, + kYes, + }; + + enum class Cancelable { + kNo, + kYes, + }; + + enum PhaseType { + kNone = 0, + kCapturingPhase = 1, + kAtTarget = 2, + kBubblingPhase = 3 + }; + static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; static Event* From(ExecutingContext* context, NativeEvent* native_event) {} @@ -60,13 +79,11 @@ class Event : public ScriptWrappable { explicit Event(ExecutingContext* context); explicit Event(ExecutingContext* context, NativeEvent* native_event); - void Trace(GCVisitor* visitor) const override; - void Dispose() const override; const char* GetHumanReadableName() const override; bool propagationStopped() const { return propagation_stopped_; } bool bubbles() { return bubbles_; }; double timeStamp() { return time_stamp_; } - bool propagationImmediatelyStopped(ExceptionState& exception_state) { return propagation_immediately_stopped_; } + bool propagationImmediatelyStopped(ExceptionState& exception_state) { return immediate_propagation_stopped_; } bool cancelable() const { return cancelable_; } FORCE_INLINE NativeString* type() { return type_; }; void SetType(NativeString* type); @@ -75,6 +92,9 @@ class Event : public ScriptWrappable { EventTarget* currentTarget() const; void SetCurrentTarget(EventTarget* target); + uint8_t eventPhase() const { return event_phase_; } + void SetEventPhase(uint8_t event_phase) { event_phase_ = event_phase; } + bool cancelBubble() const { return propagationStopped(); } void setCancelBubble(bool cancel) { if (cancel) { @@ -82,27 +102,76 @@ class Event : public ScriptWrappable { } }; + bool IsBeingDispatched() { return eventPhase(); } + // IE legacy EventTarget* srcElement() const; - void stopPropagation() { propagation_stopped_ = true; } + void stopPropagation(ExceptionState& exception_state) { propagation_stopped_ = true; } void SetStopPropagation(bool stop_propagation) { propagation_stopped_ = stop_propagation; } - void stopImmediatePropagation(ExceptionState& exception_state) { propagation_immediately_stopped_ = true; } - void SetStopImmediatePropagation(bool stop_immediate_propagation) { propagation_immediately_stopped_ = stop_immediate_propagation; } + void stopImmediatePropagation(ExceptionState& exception_state) { immediate_propagation_stopped_ = true; } + void SetStopImmediatePropagation(bool stop_immediate_propagation) { immediate_propagation_stopped_ = stop_immediate_propagation; } + void initEvent(std::unique_ptr &event_type, bool bubbles, bool cancelable, ExceptionState& exception_state); bool defaultPrevented() const { return default_prevented_; } void preventDefault(ExceptionState& exception_state); + 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; + void Dispose() const override; + protected: - bool bubbles_{false}; - bool cancelable_{false}; + NativeString* type_{nullptr}; + + unsigned bubbles_ : 1; + unsigned cancelable_ : 1; + unsigned composed_ : 1; + + 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; + double time_stamp_{0.0}; - bool default_prevented_{false}; + + uint8_t event_phase_ = PhaseType::kNone; + + // Whether preventDefault was called on uncancelable event. + unsigned prevent_default_called_on_uncancelable_event_ : 1; + + // Whether any of listeners have thrown an exception or not. + // Corresponds to |legacyOutputDidListenersThrowFlag| in DOM standard. + // https://dom.spec.whatwg.org/#dispatching-events + // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke + unsigned legacy_did_listeners_throw_flag_ : 1; + + unsigned fire_only_capture_listeners_at_target_ : 1; + unsigned fire_only_non_capture_listeners_at_target_ : 1; + EventTarget* target_{nullptr}; EventTarget* current_target_{nullptr}; - bool propagation_stopped_{false}; - bool propagation_immediately_stopped_{false}; - NativeString* type_{nullptr}; }; } // namespace kraken diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h new file mode 100644 index 0000000000..ea3dd30021 --- /dev/null +++ b/bridge/core/dom/events/event_listener.h @@ -0,0 +1,55 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ +#define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ + +#include "core/executing_context.h" +#include "event.h" + +namespace kraken { + +// 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*) = 0; + + // 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; + + private: + EventListener() = default; +}; + +} + +#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index 0264b257ee..30f14f758d 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -29,19 +29,23 @@ static bool removeListenerFromVector(EventListenerVector* listenerVector, JSValu return true; } -bool EventListenerMap::contains(JSAtom eventType) const { +bool EventListenerMap::Contains(const AtomString& event_type) const { for (const auto& entry : m_entries) { - if (entry.first == eventType) + if (entry.first == event_type) return true; } return false; } -void EventListenerMap::clear() { +bool EventListenerMap::ContainsCapturing(const AtomString& event_type) const { + +} + +void EventListenerMap::Clear() { m_entries.clear(); } -bool EventListenerMap::add(JSAtom eventType, JSValue callback) { +bool EventListenerMap::Add(const AtomString& event_type, JSValue callback) { for (const auto& entry : m_entries) { if (entry.first == eventType) { return addListenerToVector(const_cast(&entry.second), callback); diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index a33ac79102..356367eb1c 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -9,19 +9,28 @@ #include #include +#include "foundation/macros.h" +#include "bindings/qjs/atom_string.h" +#include "event_listener.h" +#include "registered_eventListener.h" + namespace kraken { using EventListenerVector = std::vector; class EventListenerMap final { + KRAKEN_DISALLOW_NEW(); public: - EventListenerMap(JSContext* ctx) : m_runtime(JS_GetRuntime(ctx)){}; + EventListenerMap(); ~EventListenerMap(); - - [[nodiscard]] bool empty() const { return m_entries.empty(); } - [[nodiscard]] bool contains(JSAtom eventType) const; - void clear(); - bool add(JSAtom eventType, JSValue callback); + EventListenerMap(const EventListenerMap&) = delete; + EventListenerMap& operator=(const EventListenerMap&) = delete; + + bool IsEmpty() const { return m_entries.empty(); } + bool Contains(const AtomString& event_type) const; + bool ContainsCapturing(const AtomString& event_type) const; + void Clear(); + bool Add(const AtomString& eventType, JSValue callback); bool remove(JSAtom eventType, JSValue callback); const EventListenerVector* find(JSAtom eventType); @@ -33,7 +42,7 @@ class EventListenerMap final { // - 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; + std::vector> m_entries; JSRuntime* m_runtime; }; diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 9e01f8248c..1440b6d026 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -14,6 +14,13 @@ namespace kraken { +// EventTargetData +EventTargetData::EventTargetData() {} + +EventTargetData::~EventTargetData() {} + +void EventTargetData::Trace(GCVisitor* visitor) const {} + EventTarget* EventTarget::Create(ExecutingContext* context) { return makeGarbageCollected(context); } @@ -30,6 +37,10 @@ const char* EventTarget::GetHumanReadableName() const { return "EventTarget"; } +bool EventTarget::addEventListener(std::unique_ptr& event_type, const std::shared_ptr& callback, ExceptionState& exception_state) { + return false; +} + } // namespace kraken // namespace kraken::binding::qjs diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 428df884e3..f85a75fed8 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -20,6 +20,20 @@ void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, namespace kraken { +class EventTargetData final { + KRAKEN_DISALLOW_NEW(); + public: + EventTargetData(); + EventTargetData(const EventTargetData&) = delete; + EventTargetData& operator=(const EventTargetData&) = delete; + ~EventTargetData(); + + void Trace(GCVisitor* visitor) const; + + private: + EventListenerMap event_listener_map_; +}; + // 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 @@ -41,11 +55,48 @@ class EventTarget : public ScriptWrappable { void Trace(GCVisitor* visitor) const override; void Dispose() const override; +// virtual bool AddEventListenerInternal(const AtomicString& event_type, +// EventListener*, +// const AddEventListenerOptionsResolved*); +// bool RemoveEventListenerInternal(const AtomicString& event_type, +// const EventListener*, +// const EventListenerOptions*); +// +// // Called when an event listener has been successfully added. +// virtual void AddedEventListener(const AtomicString& event_type, +// RegisteredEventListener&); +// +// // Called when an event listener is removed. The original registration +// // parameters of this event listener are available to be queried. +// virtual void RemovedEventListener(const AtomicString& event_type, +// const RegisteredEventListener&); +// +// virtual DispatchEventResult DispatchEventInternal(Event&); + + // Subclasses should likely not override these themselves; instead, they + // should subclass EventTargetWithInlineData. + virtual EventTargetData* GetEventTargetData() = 0; + virtual EventTargetData& EnsureEventTargetData() = 0; + const char* GetHumanReadableName() const override; private: }; +// Provide EventTarget with inlined EventTargetData for improved performance. +class EventTargetWithInlineData : public EventTarget { + public: + 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| 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..9ce4364f68 --- /dev/null +++ b/bridge/core/dom/events/event_target_impl.cc @@ -0,0 +1,6 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#include "event_target_impl.h" diff --git a/bridge/core/dom/events/event_target_impl.h b/bridge/core/dom/events/event_target_impl.h new file mode 100644 index 0000000000..e752ea531f --- /dev/null +++ b/bridge/core/dom/events/event_target_impl.h @@ -0,0 +1,26 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ +#define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ + +#include "event_target.h" + +namespace kraken { + +// Constructible version of EventTarget. Calls to EventTarget +// constructor in JavaScript will return an instance of this class. +// We don't use EventTarget directly because EventTarget is an abstract +// class and and making it non-abstract is unfavorable because it will +// increase the size of EventTarget and all of its subclasses with code +// that are mostly unnecessary for them, resulting in a performance +// decrease. +class EventTargetImpl : public EventTarget { + +}; + +} + +#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc new file mode 100644 index 0000000000..115028e700 --- /dev/null +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "registered_eventListener.h" + +namespace kraken { + +RegisteredEventListener::RegisteredEventListener() + : use_capture_(false), passive_(false), once_(false), blocked_event_warning_emitted_(false){} + +RegisteredEventListener::RegisteredEventListener(const std::shared_ptr& listener, std::shared_ptr options) + : callback_(listener), + use_capture_(options->capture()), + passive_(options->passive()), + once_(options->once()), + blocked_event_warning_emitted_(false) { + }; + +RegisteredEventListener::RegisteredEventListener(const RegisteredEventListener& that) = default; + +RegisteredEventListener& RegisteredEventListener::operator=(const RegisteredEventListener& that) = default; + +void RegisteredEventListener::SetCallback(EventListener* listener) {} + +bool RegisteredEventListener::Matches(const std::shared_ptr& listener, const std::shared_ptr& options) const { + // Equality is soley based on the listener and useCapture flags. + assert(callback_); + assert(listener); + return callback_->Matches(*listener) && static_cast(use_capture_) == options->capture(); +} + +bool RegisteredEventListener::ShouldFire(const Event& event) const { + if (event.FireOnlyCaptureListenersAtTarget()) { + assert(event.eventPhase() == Event::kAtTarget); + return Capture(); + } + if (event.FireOnlyNonCaptureListenersAtTarget()) { + assert(event.eventPhase() == Event::kAtTarget); + return !Capture(); + } + if (event.eventPhase() == Event::kCapturingPhase) + return Capture(); + if (event.eventPhase() == Event::kBubblingPhase) + return !Capture(); + return true; +} + +bool operator==(const RegisteredEventListener& lhs, const RegisteredEventListener& rhs) { + assert(lhs.Callback()); + assert(rhs.Callback()); + return lhs.Callback()->Matches(*rhs.Callback()) && lhs.Capture() == rhs.Capture(); +} + +} // namespace kraken diff --git a/bridge/core/dom/events/registered_eventListener.h b/bridge/core/dom/events/registered_eventListener.h new file mode 100644 index 0000000000..b38f37ed57 --- /dev/null +++ b/bridge/core/dom/events/registered_eventListener.h @@ -0,0 +1,66 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ +#define KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ + +#include "foundation/macros.h" +#include "event_listener.h" +#include "bindings/qjs/add_event_listener_options.h" +#include "bindings/qjs/event_listener_options.h" + +namespace kraken { + +// RegisteredEventListener represents 'event listener' defined in the DOM +// standard. https://dom.spec.whatwg.org/#concept-event-listener +class RegisteredEventListener final { + KRAKEN_DISALLOW_NEW() + public: + RegisteredEventListener(); + RegisteredEventListener(const std::shared_ptr& listener, std::shared_ptr options); + RegisteredEventListener(const RegisteredEventListener& that); + RegisteredEventListener& operator=(const RegisteredEventListener& that); + + const std::shared_ptr Callback() const { return callback_; } + std::shared_ptr Callback() { return callback_; } + + 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& listener, + const std::shared_ptr & options) const; + + bool ShouldFire(const Event&) const; + + private: + std::shared_ptr callback_; + unsigned use_capture_ : 1; + unsigned passive_ : 1; + unsigned once_ : 1; + unsigned blocked_event_warning_emitted_ : 1; + + private: + +}; + +bool operator==(const RegisteredEventListener&, const RegisteredEventListener&); + + +} + +#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ diff --git a/bridge/core/html_names.cc b/bridge/core/html_names.cc new file mode 100644 index 0000000000..d8d0bc08ce --- /dev/null +++ b/bridge/core/html_names.cc @@ -0,0 +1,6 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#include "html_names.h" diff --git a/bridge/core/html_names.h b/bridge/core/html_names.h new file mode 100644 index 0000000000..5013aa22d9 --- /dev/null +++ b/bridge/core/html_names.h @@ -0,0 +1,13 @@ +/* +* Copyright (C) 2021 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_CORE_HTML_NAMES_H_ +#define KRAKENBRIDGE_CORE_HTML_NAMES_H_ + +namespace kraken { + +} + +#endif // KRAKENBRIDGE_CORE_HTML_NAMES_H_ diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/generate_header.ts index 0b9975c088..4e4f41ac63 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/generate_header.ts @@ -9,11 +9,11 @@ function generateInterfaceAdditionalHeader(blob: Blob, object: any): [string, st } let wrapperTypeInfo = `static WrapperTypeInfo* GetWrapperTypeInfo() { - return const_cast(&m_wrapperTypeInfo); + return const_cast(&wrapper_type_info_); }`; let wrapperTypeDefine = `static JSValue ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); - constexpr static const WrapperTypeInfo m_wrapperTypeInfo = {JS_CLASS_${getClassName(blob).toUpperCase()}, "Blob", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ConstructorCallback}; + constexpr static const WrapperTypeInfo wrapper_type_info_ = {JS_CLASS_${getClassName(blob).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ConstructorCallback}; `; let installFunctions = `static void InstallPrototypeMethods(ExecutingContext* context); diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/genereate_source.ts index 7d28498104..9cde6bff00 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/genereate_source.ts @@ -348,7 +348,7 @@ export function generateCppSource(blob: Blob) { o.methods.forEach(method => { classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) }); - wrapperTypeInfoInit = `const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::m_wrapperTypeInfo;`; + wrapperTypeInfoInit = `const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::wrapper_type_info_;`; return generateClassSource(blob, o); } }); From ba408ace273e9f5bba121e0db416731df81b5100 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Sun, 27 Mar 2022 16:02:32 +0000 Subject: [PATCH 039/498] Committing clang-format changes --- .../qjs/add_event_listener_options.cc | 12 +++---- .../bindings/qjs/add_event_listener_options.h | 4 +-- bridge/bindings/qjs/atom_string.cc | 2 +- bridge/bindings/qjs/atom_string.h | 7 ++-- bridge/bindings/qjs/dictionary_base.cc | 6 ++-- bridge/bindings/qjs/dictionary_base.h | 13 +++---- bridge/bindings/qjs/event_listener_options.cc | 8 ++--- bridge/bindings/qjs/event_listener_options.h | 17 ++++----- bridge/core/dom/events/event.cc | 5 ++- bridge/core/dom/events/event.h | 29 +++++---------- bridge/core/dom/events/event_listener.h | 8 ++--- bridge/core/dom/events/event_listener_map.cc | 4 +-- bridge/core/dom/events/event_listener_map.h | 3 +- bridge/core/dom/events/event_target.h | 36 +++++++++---------- bridge/core/dom/events/event_target_impl.cc | 6 ++-- bridge/core/dom/events/event_target_impl.h | 12 +++---- .../dom/events/registered_eventListener.cc | 10 ++---- .../dom/events/registered_eventListener.h | 27 ++++++-------- bridge/core/html_names.cc | 6 ++-- bridge/core/html_names.h | 10 +++--- 20 files changed, 91 insertions(+), 134 deletions(-) diff --git a/bridge/bindings/qjs/add_event_listener_options.cc b/bridge/bindings/qjs/add_event_listener_options.cc index 7e40b2ca63..151c8a502d 100644 --- a/bridge/bindings/qjs/add_event_listener_options.cc +++ b/bridge/bindings/qjs/add_event_listener_options.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "add_event_listener_options.h" @@ -9,9 +9,7 @@ namespace kraken { AddEventListenerOptions::AddEventListenerOptions() {} -AddEventListenerOptions::AddEventListenerOptions(JSContext* ctx, JSValue dictionary_value, ExecutingContext& executing_context) { - -} +AddEventListenerOptions::AddEventListenerOptions(JSContext* ctx, JSValue dictionary_value, ExecutingContext& executing_context) {} bool AddEventListenerOptions::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { if (!JS_IsObject(qjs_dictionary)) { @@ -40,4 +38,4 @@ void AddEventListenerOptions::FillMembersFromQJSObject(JSContext* ctx, JSValue q JS_FreeValue(ctx, once); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/add_event_listener_options.h b/bridge/bindings/qjs/add_event_listener_options.h index 548fd7efb2..6dc1c3f4a9 100644 --- a/bridge/bindings/qjs/add_event_listener_options.h +++ b/bridge/bindings/qjs/add_event_listener_options.h @@ -23,9 +23,7 @@ class AddEventListenerOptions : public EventListenerOptions { void setOnce(bool value) { member_once_ = value; } bool hasPassive() const { return has_passive_; } - bool passive() const { - return member_passive_; - } + bool passive() const { return member_passive_; } bool getPassiveOr(bool fallback_value) const { if (!hasPassive()) { return fallback_value; diff --git a/bridge/bindings/qjs/atom_string.cc b/bridge/bindings/qjs/atom_string.cc index d1fcd08937..ccc98c1030 100644 --- a/bridge/bindings/qjs/atom_string.cc +++ b/bridge/bindings/qjs/atom_string.cc @@ -9,4 +9,4 @@ namespace kraken { AtomString::AtomString(const AtomString&) {} -} +} // namespace kraken diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index d71526a5f6..007bd57d9c 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -30,7 +30,7 @@ class AtomString final { explicit AtomString(JSContext* ctx, const std::string& string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())) {} explicit AtomString(JSContext* ctx, JSAtom atom) : ctx_(ctx), atom_(JS_DupAtom(ctx, atom)){}; - explicit AtomString(JSContext* ctx, JSValue value): ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)) {}; + explicit AtomString(JSContext* ctx, JSValue value) : ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; ~AtomString() { JS_FreeAtom(ctx_, atom_); } @@ -65,9 +65,8 @@ class AtomString final { return *this; } - bool operator==(const AtomString& other) const { - return other.atom_ == this->atom_; - } + bool operator==(const AtomString& other) const { return other.atom_ == this->atom_; } + private: AtomString() = delete; JSContext* ctx_{nullptr}; diff --git a/bridge/bindings/qjs/dictionary_base.cc b/bridge/bindings/qjs/dictionary_base.cc index 18fc96c307..bc2ff2c4fe 100644 --- a/bridge/bindings/qjs/dictionary_base.cc +++ b/bridge/bindings/qjs/dictionary_base.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022 Alibaba Inc. All rights reserved. -* Author: Kraken Team. + * Copyright (C) 2022 Alibaba Inc. All rights reserved. + * Author: Kraken Team. */ #include "dictionary_base.h" @@ -15,4 +15,4 @@ JSValue DictionaryBase::toQuickJS(JSContext* ctx) const { return object; } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/dictionary_base.h b/bridge/bindings/qjs/dictionary_base.h index 236b062062..ce60137831 100644 --- a/bridge/bindings/qjs/dictionary_base.h +++ b/bridge/bindings/qjs/dictionary_base.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2022 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2022 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ @@ -31,12 +31,9 @@ class DictionaryBase { // 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 FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const = 0; }; - -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ diff --git a/bridge/bindings/qjs/event_listener_options.cc b/bridge/bindings/qjs/event_listener_options.cc index ccd994c5ff..902f2ce53a 100644 --- a/bridge/bindings/qjs/event_listener_options.cc +++ b/bridge/bindings/qjs/event_listener_options.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "event_listener_options.h" @@ -33,4 +33,4 @@ void EventListenerOptions::FillMembersFromQJSObject(JSContext* ctx, JSValue qjs_ JS_FreeValue(ctx, capture); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/event_listener_options.h b/bridge/bindings/qjs/event_listener_options.h index da432c6502..107cd019d1 100644 --- a/bridge/bindings/qjs/event_listener_options.h +++ b/bridge/bindings/qjs/event_listener_options.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_OPTIONS_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_OPTIONS_H_ @@ -13,11 +13,7 @@ namespace kraken { class EventListenerOptions : public DictionaryBase { public: - static std::shared_ptr Create(JSContext* ctx, - JSValue value, - ExceptionState& exception_state) { - return std::make_shared(); - }; + static std::shared_ptr Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { return std::make_shared(); }; explicit EventListenerOptions(); explicit EventListenerOptions(JSContext* ctx, JSValue value, ExceptionState& exception_state); @@ -26,12 +22,13 @@ class EventListenerOptions : public DictionaryBase { void setCapture(bool value) { capture_ = value; } protected: - bool FillQJSObjectWithMembers(JSContext *ctx, JSValue qjs_dictionary) const override; + bool FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const override; + private: bool capture_{false}; void FillMembersFromQJSObject(JSContext* ctx, JSValue qjs_dictionary, ExceptionState& exception_state); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_OPTIONS_H_ diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 4a0e23e79f..4984d164d0 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -24,8 +24,8 @@ Event::Event(ExecutingContext* context, NativeEvent* native_event) bubbles_(native_event->bubbles), cancelable_(native_event->cancelable), time_stamp_(static_cast(native_event->timeStamp)), - default_prevented_(native_event->defaultPrevented) -{} + default_prevented_(native_event->defaultPrevented) { +} const char* Event::GetHumanReadableName() const { return "Event"; @@ -76,7 +76,6 @@ void Event::initEvent(std::unique_ptr& event_type, bool bubbles, b void Event::Trace(GCVisitor* visitor) const { visitor->Trace(target_) - } void Event::Dispose() const {} diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 3fd2ffa73d..48075f4f0b 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -65,12 +65,7 @@ class Event : public ScriptWrappable { kYes, }; - enum PhaseType { - kNone = 0, - kCapturingPhase = 1, - kAtTarget = 2, - kBubblingPhase = 3 - }; + enum PhaseType { kNone = 0, kCapturingPhase = 1, kAtTarget = 2, kBubblingPhase = 3 }; static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; static Event* From(ExecutingContext* context, NativeEvent* native_event) {} @@ -111,31 +106,23 @@ class Event : public ScriptWrappable { 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(std::unique_ptr &event_type, bool bubbles, bool cancelable, ExceptionState& exception_state); + void initEvent(std::unique_ptr& event_type, bool bubbles, bool cancelable, ExceptionState& exception_state); bool defaultPrevented() const { return default_prevented_; } void preventDefault(ExceptionState& exception_state); - void SetFireOnlyCaptureListenersAtTarget( - bool fire_only_capture_listeners_at_target) { + 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; + fire_only_capture_listeners_at_target_ = fire_only_capture_listeners_at_target; } - void SetFireOnlyNonCaptureListenersAtTarget( - bool fire_only_non_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; + 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_; - } + 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; void Dispose() const override; diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h index ea3dd30021..435af100b2 100644 --- a/bridge/core/dom/events/event_listener.h +++ b/bridge/core/dom/events/event_listener.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ @@ -50,6 +50,6 @@ class EventListener { EventListener() = default; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index 30f14f758d..fbab75e0de 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -37,9 +37,7 @@ bool EventListenerMap::Contains(const AtomString& event_type) const { return false; } -bool EventListenerMap::ContainsCapturing(const AtomString& event_type) const { - -} +bool EventListenerMap::ContainsCapturing(const AtomString& event_type) const {} void EventListenerMap::Clear() { m_entries.clear(); diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index 356367eb1c..ec00452571 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -9,9 +9,9 @@ #include #include -#include "foundation/macros.h" #include "bindings/qjs/atom_string.h" #include "event_listener.h" +#include "foundation/macros.h" #include "registered_eventListener.h" namespace kraken { @@ -20,6 +20,7 @@ using EventListenerVector = std::vector; class EventListenerMap final { KRAKEN_DISALLOW_NEW(); + public: EventListenerMap(); ~EventListenerMap(); diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index f85a75fed8..9e69fa418e 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -22,6 +22,7 @@ namespace kraken { class EventTargetData final { KRAKEN_DISALLOW_NEW(); + public: EventTargetData(); EventTargetData(const EventTargetData&) = delete; @@ -55,23 +56,23 @@ class EventTarget : public ScriptWrappable { void Trace(GCVisitor* visitor) const override; void Dispose() const override; -// virtual bool AddEventListenerInternal(const AtomicString& event_type, -// EventListener*, -// const AddEventListenerOptionsResolved*); -// bool RemoveEventListenerInternal(const AtomicString& event_type, -// const EventListener*, -// const EventListenerOptions*); -// -// // Called when an event listener has been successfully added. -// virtual void AddedEventListener(const AtomicString& event_type, -// RegisteredEventListener&); -// -// // Called when an event listener is removed. The original registration -// // parameters of this event listener are available to be queried. -// virtual void RemovedEventListener(const AtomicString& event_type, -// const RegisteredEventListener&); -// -// virtual DispatchEventResult DispatchEventInternal(Event&); + // virtual bool AddEventListenerInternal(const AtomicString& event_type, + // EventListener*, + // const AddEventListenerOptionsResolved*); + // bool RemoveEventListenerInternal(const AtomicString& event_type, + // const EventListener*, + // const EventListenerOptions*); + // + // // Called when an event listener has been successfully added. + // virtual void AddedEventListener(const AtomicString& event_type, + // RegisteredEventListener&); + // + // // Called when an event listener is removed. The original registration + // // parameters of this event listener are available to be queried. + // virtual void RemovedEventListener(const AtomicString& event_type, + // const RegisteredEventListener&); + // + // virtual DispatchEventResult DispatchEventInternal(Event&); // Subclasses should likely not override these themselves; instead, they // should subclass EventTargetWithInlineData. @@ -96,7 +97,6 @@ class EventTargetWithInlineData : public EventTarget { 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| diff --git a/bridge/core/dom/events/event_target_impl.cc b/bridge/core/dom/events/event_target_impl.cc index 9ce4364f68..05a255735c 100644 --- a/bridge/core/dom/events/event_target_impl.cc +++ b/bridge/core/dom/events/event_target_impl.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "event_target_impl.h" diff --git a/bridge/core/dom/events/event_target_impl.h b/bridge/core/dom/events/event_target_impl.h index e752ea531f..e01a2795b4 100644 --- a/bridge/core/dom/events/event_target_impl.h +++ b/bridge/core/dom/events/event_target_impl.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ @@ -17,10 +17,8 @@ namespace kraken { // 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 { +class EventTargetImpl : public EventTarget {}; -}; - -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc index 115028e700..3d211526d8 100644 --- a/bridge/core/dom/events/registered_eventListener.cc +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -7,16 +7,10 @@ namespace kraken { -RegisteredEventListener::RegisteredEventListener() - : use_capture_(false), passive_(false), once_(false), blocked_event_warning_emitted_(false){} +RegisteredEventListener::RegisteredEventListener() : use_capture_(false), passive_(false), once_(false), blocked_event_warning_emitted_(false) {} RegisteredEventListener::RegisteredEventListener(const std::shared_ptr& listener, std::shared_ptr options) - : callback_(listener), - use_capture_(options->capture()), - passive_(options->passive()), - once_(options->once()), - blocked_event_warning_emitted_(false) { - }; + : callback_(listener), use_capture_(options->capture()), passive_(options->passive()), once_(options->once()), blocked_event_warning_emitted_(false){}; RegisteredEventListener::RegisteredEventListener(const RegisteredEventListener& that) = default; diff --git a/bridge/core/dom/events/registered_eventListener.h b/bridge/core/dom/events/registered_eventListener.h index b38f37ed57..52bdd5c97d 100644 --- a/bridge/core/dom/events/registered_eventListener.h +++ b/bridge/core/dom/events/registered_eventListener.h @@ -1,15 +1,15 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ -#include "foundation/macros.h" -#include "event_listener.h" #include "bindings/qjs/add_event_listener_options.h" #include "bindings/qjs/event_listener_options.h" +#include "event_listener.h" +#include "foundation/macros.h" namespace kraken { @@ -23,7 +23,7 @@ class RegisteredEventListener final { RegisteredEventListener(const RegisteredEventListener& that); RegisteredEventListener& operator=(const RegisteredEventListener& that); - const std::shared_ptr Callback() const { return callback_; } + const std::shared_ptr Callback() const { return callback_; } std::shared_ptr Callback() { return callback_; } void SetCallback(EventListener* listener); @@ -34,16 +34,11 @@ class RegisteredEventListener final { bool Capture() const { return use_capture_; } - bool BlockedEventWarningEmitted() const { - return blocked_event_warning_emitted_; - } + bool BlockedEventWarningEmitted() const { return blocked_event_warning_emitted_; } - void SetBlockedEventWarningEmitted() { - blocked_event_warning_emitted_ = true; - } + void SetBlockedEventWarningEmitted() { blocked_event_warning_emitted_ = true; } - bool Matches(const std::shared_ptr& listener, - const std::shared_ptr & options) const; + bool Matches(const std::shared_ptr& listener, const std::shared_ptr& options) const; bool ShouldFire(const Event&) const; @@ -55,12 +50,10 @@ class RegisteredEventListener final { unsigned blocked_event_warning_emitted_ : 1; private: - }; bool operator==(const RegisteredEventListener&, const RegisteredEventListener&); - -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ diff --git a/bridge/core/html_names.cc b/bridge/core/html_names.cc index d8d0bc08ce..a7d678d821 100644 --- a/bridge/core/html_names.cc +++ b/bridge/core/html_names.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "html_names.h" diff --git a/bridge/core/html_names.h b/bridge/core/html_names.h index 5013aa22d9..243244de8e 100644 --- a/bridge/core/html_names.h +++ b/bridge/core/html_names.h @@ -1,13 +1,11 @@ /* -* Copyright (C) 2021 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_CORE_HTML_NAMES_H_ #define KRAKENBRIDGE_CORE_HTML_NAMES_H_ -namespace kraken { - -} +namespace kraken {} #endif // KRAKENBRIDGE_CORE_HTML_NAMES_H_ From 7c77d7a7233b382f048a2fbae7b038b331bff6ed Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Mon, 28 Mar 2022 21:45:50 +0800 Subject: [PATCH 040/498] feat: add js event handler and listener. --- bridge/CMakeLists.txt | 7 ++ .../qjs/add_event_listener_options.cc | 2 +- .../bindings/qjs/add_event_listener_options.h | 8 +- bridge/bindings/qjs/atom_string.cc | 2 - bridge/bindings/qjs/converter_impl.h | 45 ++++----- bridge/bindings/qjs/gc_visitor.cc | 6 +- bridge/bindings/qjs/gc_visitor.h | 7 +- bridge/bindings/qjs/idl_type.h | 4 - .../bindings/qjs/js_based_event_listener.cc | 24 +++++ bridge/bindings/qjs/js_based_event_listener.h | 58 ++++++++++++ bridge/bindings/qjs/js_event_handler.cc | 6 ++ bridge/bindings/qjs/js_event_handler.h | 43 +++++++++ bridge/bindings/qjs/js_event_listener.cc | 6 ++ bridge/bindings/qjs/js_event_listener.h | 11 +++ bridge/core/dom/events/event.cc | 4 +- bridge/core/dom/events/event.h | 4 +- bridge/core/dom/events/event_listener_map.cc | 92 +++++++++---------- bridge/core/dom/events/event_listener_map.h | 22 ++--- bridge/foundation/casting.h | 82 +++++++++++++++++ 19 files changed, 339 insertions(+), 94 deletions(-) create mode 100644 bridge/bindings/qjs/js_based_event_listener.cc create mode 100644 bridge/bindings/qjs/js_based_event_listener.h create mode 100644 bridge/bindings/qjs/js_event_handler.cc create mode 100644 bridge/bindings/qjs/js_event_handler.h create mode 100644 bridge/bindings/qjs/js_event_listener.cc create mode 100644 bridge/bindings/qjs/js_event_listener.h create mode 100644 bridge/foundation/casting.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 9f4475155f..1428b6e0cd 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -96,6 +96,7 @@ list(APPEND BRIDGE_SOURCE foundation/task_queue.h foundation/native_value.cc foundation/native_value.h + foundation/casting.h foundation/ui_command_buffer.cc foundation/ui_command_buffer.h polyfill/dist/polyfill.cc @@ -190,6 +191,12 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/event_listener_options.h bindings/qjs/add_event_listener_options.cc bindings/qjs/add_event_listener_options.h + bindings/qjs/js_based_event_listener.cc + bindings/qjs/js_based_event_listener.h + bindings/qjs/js_event_handler.cc + bindings/qjs/js_event_handler.h + bindings/qjs/js_event_listener.cc + bindings/qjs/js_event_listener.h bindings/qjs/binding_initializer.cc bindings/qjs/binding_initializer.h bindings/qjs/member_installer.cc diff --git a/bridge/bindings/qjs/add_event_listener_options.cc b/bridge/bindings/qjs/add_event_listener_options.cc index 151c8a502d..38d83daeee 100644 --- a/bridge/bindings/qjs/add_event_listener_options.cc +++ b/bridge/bindings/qjs/add_event_listener_options.cc @@ -9,7 +9,7 @@ namespace kraken { AddEventListenerOptions::AddEventListenerOptions() {} -AddEventListenerOptions::AddEventListenerOptions(JSContext* ctx, JSValue dictionary_value, ExecutingContext& executing_context) {} +AddEventListenerOptions::AddEventListenerOptions(JSContext* ctx, JSValue dictionary_value, ExceptionState& exception_state) {} bool AddEventListenerOptions::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { if (!JS_IsObject(qjs_dictionary)) { diff --git a/bridge/bindings/qjs/add_event_listener_options.h b/bridge/bindings/qjs/add_event_listener_options.h index 6dc1c3f4a9..21351bf9e6 100644 --- a/bridge/bindings/qjs/add_event_listener_options.h +++ b/bridge/bindings/qjs/add_event_listener_options.h @@ -6,17 +6,19 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_ADD_EVENT_LISTENER_OPTIONS_H_ #define KRAKENBRIDGE_BINDINGS_QJS_ADD_EVENT_LISTENER_OPTIONS_H_ -#include "dictionary_base.h" +#include "dictionary_base.h" #include "event_listener_options.h" namespace kraken { class AddEventListenerOptions : public EventListenerOptions { public: - static std::unique_ptr Create(JSContext* ctx, JSValue dictionary_value, ExecutingContext& executing_context) {} + static std::unique_ptr Create(JSContext* ctx, JSValue dictionary_value, ExceptionState& exception_state) { + return std::make_unique(ctx, dictionary_value, exception_state); + } explicit AddEventListenerOptions(); - explicit AddEventListenerOptions(JSContext* ctx, JSValue dictionary_value, ExecutingContext& executing_context); + explicit AddEventListenerOptions(JSContext* ctx, JSValue dictionary_value, ExceptionState& exception_state); bool hasOnce() const { return true; } bool once() const { return member_once_; } diff --git a/bridge/bindings/qjs/atom_string.cc b/bridge/bindings/qjs/atom_string.cc index ccc98c1030..86edca941b 100644 --- a/bridge/bindings/qjs/atom_string.cc +++ b/bridge/bindings/qjs/atom_string.cc @@ -7,6 +7,4 @@ namespace kraken { -AtomString::AtomString(const AtomString&) {} - } // namespace kraken diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 1f7f3b18f1..9a15ea7346 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -290,28 +290,29 @@ struct Converter : public ConverterBase { }; // EventListener -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 { - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - assert(!JS_IsException(value)); - if (JS_IsNull(value)) { - return nullptr; - } - - 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 EventListener::Create(ctx, value); +// } +//}; +// +//template <> +//struct Converter> : public ConverterBase { +// static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { +// assert(!JS_IsException(value)); +// if (JS_IsNull(value)) { +// return nullptr; +// } +// +// return Converter::FromValue(ctx, value, exception_state); +// } +//}; template <> struct Converter : public ConverterBase { diff --git a/bridge/bindings/qjs/gc_visitor.cc b/bridge/bindings/qjs/gc_visitor.cc index b93e8b3920..2489b0b2de 100644 --- a/bridge/bindings/qjs/gc_visitor.cc +++ b/bridge/bindings/qjs/gc_visitor.cc @@ -4,7 +4,7 @@ */ #include "gc_visitor.h" -#include "garbage_collected.h" +#include "script_wrappable.h" namespace kraken { @@ -14,4 +14,8 @@ void GCVisitor::Trace(ScriptWrappable* target) { } } +void GCVisitor::Trace(JSValue value) { + JS_MarkValue(runtime_, value, markFunc_); +} + } // namespace kraken diff --git a/bridge/bindings/qjs/gc_visitor.h b/bridge/bindings/qjs/gc_visitor.h index 3fb56b4e93..6e70f72e7e 100644 --- a/bridge/bindings/qjs/gc_visitor.h +++ b/bridge/bindings/qjs/gc_visitor.h @@ -7,18 +7,21 @@ #define KRAKENBRIDGE_GC_VISITOR_H #include -#include "script_wrappable.h" +#include "foundation/macros.h" namespace kraken { -class GarbageCollected; +class ScriptWrappable; // Use GCVisitor to keep track gc managed members in C++ class. class GCVisitor final { + KRAKEN_DISALLOW_NEW(); + KRAKEN_DISALLOW_IMPLICIT_CONSTRUCTORS(GCVisitor); public: explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc) : runtime_(rt), markFunc_(markFunc){}; void Trace(ScriptWrappable* target); + void Trace(JSValue value); private: JSRuntime* runtime_{nullptr}; diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 7bbcdcd660..00e95abf82 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -61,10 +61,6 @@ struct IDLCallback : public IDLTypeBaseHelper> { using ImplType = typename Converter>::ImplType; }; -struct EventListener : public IDLTypeBaseHelper> { - using ImplType = typename Converter>::ImplType; -}; - // Sequence template struct IDLSequence final : public IDLTypeBase { 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..37a9f8a339 --- /dev/null +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "js_based_event_listener.h" + +namespace kraken { + +// Implements step 2. of "inner invoke". +// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke +void JSBasedEventListener::Invoke(ExecutingContext* context, Event* event) { + assert(context); + assert(event); + + if (!context->IsValid()) return; + + ExceptionState exception_state; + // Step 10: Call a listener with event's currentTarget as receiver and event + // and handle errors if thrown. + InvokeInternal(*event->currentTarget(), *event, exception_state); +} + +} 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..91f3152c46 --- /dev/null +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ + +#include +#include "core/dom/events/event_listener.h" +#include "core/executing_context.h" + +namespace kraken { + +// |JSBasedEventListener| is the base class for JS-based event listeners, +// i.e. EventListener and EventHandler in the standards. +// This provides the essential APIs of JS-based event listeners and also +// implements the common features. +class JSBasedEventListener : public EventListener { + public: + // Implements step 2. of "inner invoke". + // See: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke + void Invoke(ExecutingContext* context, Event* event) final; + + // Implements "get the current value of the event handler". + // https://html.spec.whatwg.org/C/#getting-the-current-value-of-the-event-handler + // Returns null with firing error event instead of throwing an exception. + virtual JSValue GetListenerObject(EventTarget&) = 0; + + // Returns Functions that handles invoked event or undefined without + // throwing any exception. + virtual JSValue GetEffectiveFunction(EventTarget&) = 0; + + virtual bool IsJSEventListener() const { return false; } + virtual bool IsJSEventHandler() const { return false; } + + protected: + JSBasedEventListener() = default; + + virtual JSContext* GetJSContext() const = 0; + // Returns the ScriptState of the relevant realm of the callback object. + virtual ScriptState* GetScriptState() const = 0; + 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; +}; + + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc new file mode 100644 index 0000000000..df7d4c8383 --- /dev/null +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "js_event_handler.h" diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h new file mode 100644 index 0000000000..bcf75db120 --- /dev/null +++ b/bridge/bindings/qjs/js_event_handler.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ + +#include "js_based_event_listener.h" +#include "foundation/casting.h" + +namespace kraken { + +// |JSEventHandler| implements EventHandler in the HTML standard. +// https://html.spec.whatwg.org/C/#event-handler-attributes +class JSEventHandler : public JSBasedEventListener { + public: + enum class HandlerType { + kEventHandler, + // For kOnErrorEventHandler + // https://html.spec.whatwg.org/C/#onerroreventhandler + kOnErrorEventHandler, + // For OnBeforeUnloadEventHandler + // https://html.spec.whatwg.org/C/#onbeforeunloadeventhandler + kOnBeforeUnloadEventHandler, + }; + + static std::unique_ptr CreateOrNull(JSContext* ctx, HandlerType handler_type); + static JSValue ToQuickJS(JSContext* ctx, EventTarget* event_target, EventListener* listener) { + if (auto* event_handler = DynamicTo(listener)) { + return event_handler->GetListenerObject(*event_target); + } + return JS_NULL; + } + + + private: + +}; + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc new file mode 100644 index 0000000000..8b21b8acf9 --- /dev/null +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "js_event_listener.h" diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h new file mode 100644 index 0000000000..3f8317ac1f --- /dev/null +++ b/bridge/bindings/qjs/js_event_listener.h @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ + +class js_event_listener {}; + +#endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 4984d164d0..fc587ff75e 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -5,6 +5,7 @@ #include "event.h" #include "core/executing_context.h" +#include "event_target.h" namespace kraken { @@ -75,7 +76,8 @@ void Event::initEvent(std::unique_ptr& event_type, bool bubbles, b } void Event::Trace(GCVisitor* visitor) const { - visitor->Trace(target_) + visitor->Trace(target_); + visitor->Trace(current_target_); } void Event::Dispose() const {} diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 48075f4f0b..b382da992f 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -68,7 +68,9 @@ class Event : public ScriptWrappable { enum PhaseType { kNone = 0, kCapturingPhase = 1, kAtTarget = 2, kBubblingPhase = 3 }; static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; - static Event* From(ExecutingContext* context, NativeEvent* native_event) {} + static Event* From(ExecutingContext* context, NativeEvent* native_event) { + return makeGarbageCollected(context, native_event); + } Event() = delete; explicit Event(ExecutingContext* context); diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index fbab75e0de..a4010533fd 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -7,30 +7,44 @@ namespace kraken { -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()) { +static bool AddListenerToVector(EventListenerVector* vector, + const std::shared_ptr& listener, + const std::shared_ptr& options, + RegisteredEventListener* registered_event_listener) { + *registered_event_listener = RegisteredEventListener(listener, options); + + if (std::find(vector->begin(), vector->end(), *registered_event_listener) != vector->end()) { return false; // Duplicate listener. } - vector->push_back(callback); + vector->push_back(*registered_event_listener); return true; } -static bool removeListenerFromVector(EventListenerVector* listenerVector, JSValue callback) { +static bool RemoveListenerFromVector(EventListenerVector* listener_vector, + const std::shared_ptr& listener, + const std::shared_ptr& options, + size_t* index_of_removed_listener, + RegisteredEventListener* registered_event_listener) { // Do a manual search for the matching listener. It is not // possible to create a listener on the stack because of the // const on |listener|. - auto it = std::find_if(listenerVector->begin(), listenerVector->end(), [&callback](const JSValue& listener) -> bool { return JS_VALUE_GET_PTR(listener) == JS_VALUE_GET_PTR(callback); }); + 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 == listenerVector->end()) { + if (it == listener_vector->end()) { + *index_of_removed_listener = -1; return false; } - listenerVector->erase(it); + + *registered_event_listener = *it; + *index_of_removed_listener = it - listener_vector->begin(); + listener_vector->erase(it); + return true; } bool EventListenerMap::Contains(const AtomString& event_type) const { - for (const auto& entry : m_entries) { + for (const auto& entry : entries_) { if (entry.first == event_type) return true; } @@ -40,29 +54,32 @@ bool EventListenerMap::Contains(const AtomString& event_type) const { bool EventListenerMap::ContainsCapturing(const AtomString& event_type) const {} void EventListenerMap::Clear() { - m_entries.clear(); + entries_.clear(); } -bool EventListenerMap::Add(const AtomString& event_type, JSValue callback) { - for (const auto& entry : m_entries) { - if (entry.first == eventType) { - return addListenerToVector(const_cast(&entry.second), callback); - } +bool EventListenerMap::Add(const AtomString& event_type, + const std::shared_ptr& listener, + const std::shared_ptr& options, + RegisteredEventListener* registered_event_listener) { + for (const auto& entry : entries_) { + if (entry.first == event_type) + return AddListenerToVector(entry.second.get(), listener, options, registered_event_listener); } - std::vector list; - list.reserve(8); - m_entries.emplace_back(std::make_pair(eventType, list)); - - return addListenerToVector(&m_entries.back().second, callback); + entries_.emplace_back(event_type, std::make_unique()); + return AddListenerToVector(entries_.back().second.get(), listener, options, registered_event_listener); } -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); +bool EventListenerMap::Remove(const AtomString& event_type, + const std::shared_ptr& listener, + const std::shared_ptr& options, + size_t* index_of_removed_listener, + RegisteredEventListener* registered_event_listener) { + for (unsigned i = 0; i < entries_.size(); ++i) { + if (entries_[i].first == event_type) { + bool was_removed = RemoveListenerFromVector(entries_[i].second.get(), listener, options, index_of_removed_listener, registered_event_listener); + if (entries_[i].second->empty()) { + entries_.erase(entries_.begin() + i); } return was_removed; } @@ -71,30 +88,13 @@ bool EventListenerMap::remove(JSAtom eventType, JSValue callback) { return false; } -const EventListenerVector* EventListenerMap::find(JSAtom eventType) { - for (const auto& entry : m_entries) { - if (entry.first == eventType) - return &entry.second; +const EventListenerVector* EventListenerMap::Find(const AtomString& event_type) { + for (const auto& entry : entries_) { + if (entry.first == event_type) + return entry.second.get(); } return nullptr; } -void EventListenerMap::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - for (const auto& entry : m_entries) { - for (const auto& vector : entry.second) { - JS_MarkValue(rt, vector, mark_func); - } - } -} - -EventListenerMap::~EventListenerMap() { - for (const auto& entry : m_entries) { - for (const auto& vector : entry.second) { - JS_FreeAtomRT(m_runtime, entry.first); - JS_FreeValueRT(m_runtime, vector); - } - } -} - } // namespace kraken diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index ec00452571..4746c3a6bd 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -7,6 +7,7 @@ #define KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ #include + #include #include "bindings/qjs/atom_string.h" @@ -16,26 +17,27 @@ namespace kraken { -using EventListenerVector = std::vector; +using EventListenerVector = std::vector; class EventListenerMap final { KRAKEN_DISALLOW_NEW(); public: EventListenerMap(); - ~EventListenerMap(); EventListenerMap(const EventListenerMap&) = delete; EventListenerMap& operator=(const EventListenerMap&) = delete; - bool IsEmpty() const { return m_entries.empty(); } + bool IsEmpty() const { return entries_.empty(); } bool Contains(const AtomString& event_type) const; bool ContainsCapturing(const AtomString& event_type) const; void Clear(); - bool Add(const AtomString& eventType, JSValue callback); - bool remove(JSAtom eventType, JSValue callback); - const EventListenerVector* find(JSAtom eventType); - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const; + bool Add(const AtomString& event_type, const std::shared_ptr& listener, const std::shared_ptr& options, RegisteredEventListener* registered_event_listener); + bool Remove(const AtomString& event_type, + const std::shared_ptr& listener, + const std::shared_ptr& options, + size_t* index_of_removed_listener, + RegisteredEventListener* registered_event_listener); + const EventListenerVector* Find(const AtomString& event_type); private: // EventListener handlers registered with addEventListener API. @@ -43,9 +45,7 @@ class EventListenerMap final { // - 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; + std::vector>> entries_; }; } // namespace kraken diff --git a/bridge/foundation/casting.h b/bridge/foundation/casting.h new file mode 100644 index 0000000000..885fafdc7f --- /dev/null +++ b/bridge/foundation/casting.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_FOUNDATION_CASTING_H_ +#define KRAKENBRIDGE_FOUNDATION_CASTING_H_ + +#include + +namespace kraken { + +// Returns true iff the conversion from Base to Derived is allowed. For the +// pointer overloads, returns false if the input pointer is nullptr. +template +bool IsA(const Base& from) { + return std::is_base_of::value; +} + +template +bool IsA(const Base* from) { + return from && IsA(*from); +} + +template +bool IsA(Base& from) { + return IsA(static_cast(from)); +} + +template +bool IsA(Base* from) { + return from && IsA(*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 +const Derived& To(const Base& from) { + return static_cast(from); +} + +template +const Derived* To(const Base* from) { + return from ? &To(*from) : nullptr; +} + +template +Derived& To(Base& from) { + return static_cast(from); +} +template +Derived* To(Base* from) { + return from ? &To(*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 +const Derived* DynamicTo(const Base* from) { + return IsA(from) ? To(from) : nullptr; +} + +template +const Derived* DynamicTo(const Base& from) { + return IsA(from) ? &To(from) : nullptr; +} + +template +Derived* DynamicTo(Base* from) { + return IsA(from) ? To(from) : nullptr; +} + +template +Derived* DynamicTo(Base& from) { + return IsA(from) ? &To(from) : nullptr; +} + +} + +#endif // KRAKENBRIDGE_FOUNDATION_CASTING_H_ From 299cf2fee4ad80ed7158c9261bcdff5a773d610a Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Tue, 29 Mar 2022 19:38:30 +0800 Subject: [PATCH 041/498] feat: support static string and add generate support. --- bridge/CMakeLists.txt | 4 + .../code_generator/bin/code_generator.js | 82 +- .../scripts/code_generator/src/generator.ts | 12 - .../src/{blob.ts => idl/IDLBlob.ts} | 2 +- .../code_generator/src/{ => idl}/analyzer.ts | 4 +- .../src/{ => idl}/declaration.ts | 0 .../generateHeader.ts} | 6 +- .../generateSource.ts} | 34 +- .../code_generator/src/idl/generator.ts | 12 + .../code_generator/src/{ => idl}/utils.ts | 4 +- .../code_generator/src/json/JSONBlob.ts | 18 + .../code_generator/src/json/generator.ts | 27 + .../code_generator/src/json/template.ts | 17 + .../static/json_templates/make_names.cc.tpl | 23 + .../static/json_templates/make_names.h.tpl | 24 + .../static/json_templates/qjs_atom.h.tpl | 32 + bridge/third_party/quickjs/event_type_names.h | 734 ++++++++++++++++++ bridge/third_party/quickjs/quickjs-atom.h | 10 +- .../quickjs/quickjs-external-atom.h | 7 + bridge/third_party/quickjs/quickjs.c | 20 - bridge/third_party/quickjs/quickjs.h | 30 +- 21 files changed, 1019 insertions(+), 83 deletions(-) delete mode 100644 bridge/scripts/code_generator/src/generator.ts rename bridge/scripts/code_generator/src/{blob.ts => idl/IDLBlob.ts} (95%) rename bridge/scripts/code_generator/src/{ => idl}/analyzer.ts (98%) rename bridge/scripts/code_generator/src/{ => idl}/declaration.ts (100%) rename bridge/scripts/code_generator/src/{generate_header.ts => idl/generateHeader.ts} (91%) rename bridge/scripts/code_generator/src/{genereate_source.ts => idl/generateSource.ts} (87%) create mode 100644 bridge/scripts/code_generator/src/idl/generator.ts rename bridge/scripts/code_generator/src/{ => idl}/utils.ts (85%) create mode 100644 bridge/scripts/code_generator/src/json/JSONBlob.ts create mode 100644 bridge/scripts/code_generator/src/json/generator.ts create mode 100644 bridge/scripts/code_generator/src/json/template.ts create mode 100644 bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl create mode 100644 bridge/scripts/code_generator/static/json_templates/make_names.h.tpl create mode 100644 bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl create mode 100644 bridge/third_party/quickjs/event_type_names.h create mode 100644 bridge/third_party/quickjs/quickjs-external-atom.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 1428b6e0cd..d82b3ebb34 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -283,6 +283,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/events/event_target.cc core/dom/events/event_target_impl.cc core/dom/events/event_target_impl.h + core/dom/events/error_event.cc + core/dom/events/error_event.h # core/dom/character_data.cc # core/dom/character_data.h # core/dom/comment.cc @@ -311,6 +313,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_event.h out/qjs_event_target.cc out/qjs_event_target.h + out/event_type_names.h + out/event_type_names.cc ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 8ba4333da0..a13f282870 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -5,8 +5,11 @@ const packageJSON = require('../package.json'); const path = require('path'); const glob = require('glob'); const fs = require('fs'); -const { Blob } = require('../dist/blob'); -const { analyzer } = require('../dist/analyzer'); +const { IDLBlob } = require('../dist/idl/IDLBlob'); +const { JSONBlob } = require('../dist/json/JSONBlob'); +const { Template } = require('../dist/json/template'); +const { analyzer } = require('../dist/idl/analyzer'); +const { generateJSONTemplate } = require('../dist/json/generator'); program .version(packageJSON.version) @@ -25,27 +28,70 @@ 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 files = glob.sync("**/*.d.ts", { + cwd: source, + }); -let blobs = files.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 Blob(path.join(source, file), dist, filename, implement); -}); + let blobs = files.map(file => { + let filename = 'qjs_' + file.split('/').slice(-1)[0].replace('.d.ts', ''); + let implement = file.replace(path.join(__dirname, '../../')).replace('.d.ts', ''); + return new IDLBlob(path.join(source, file), dist, filename, implement); + }); -for (let i = 0; i < blobs.length; i ++) { - let b = blobs[i]; - let result = analyzer(b); + for (let i = 0; i < blobs.length; i ++) { + let b = blobs[i]; + let result = analyzer(b); - if (!fs.existsSync(b.dist)) { - fs.mkdirSync(b.dist, {recursive: true}); + 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('**/*.json', { + cwd: source + }); + let templateFiles = glob.sync('**/*.tpl', { + cwd: path.join(__dirname, '../static') + }); - let genFilePath = path.join(b.dist, b.filename); + let blobs = jsonFiles.map(file => { + let filename = file.split('/').slice(-1)[0].replace('.json', ''); + return new JSONBlob(path.join(source, file), dist, filename); + }); - fs.writeFileSync(genFilePath + '.h', result.header); - fs.writeFileSync(genFilePath + '.cc', result.source); + let templates = templateFiles.map(template => { + let filename = template.split('/').slice(-1)[0].replace('.tpl', ''); + return new Template(path.join(path.join(__dirname, '../static'), template), filename); + }); + + for (let i = 0; i < blobs.length; i ++) { + let blob = blobs[i]; + blob.json.metadata.templates.forEach((targetTemplate) => { + let targetTemplateHeaderData = templates.find(t => t.filename === targetTemplate + '.h'); + let targetTemplateBodyData = templates.find(t => t.filename === targetTemplate + '.h'); + let result = generateJSONTemplate(blobs[i], targetTemplateHeaderData, targetTemplateBodyData); + let dist = blob.dist; + + if (targetTemplate === 'qjs_atom') { + dist = path.join(__dirname, '../../../third_party/quickjs') + } + + let genFilePath = path.join(dist, blob.filename); + fs.writeFileSync(genFilePath + '.h', result.header); + result.source && fs.writeFileSync(genFilePath + '.cc', result.source); + }); + } } +// genCodeFromTypeDefine(); +genCodeFromJSONData(); 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/blob.ts b/bridge/scripts/code_generator/src/idl/IDLBlob.ts similarity index 95% rename from bridge/scripts/code_generator/src/blob.ts rename to bridge/scripts/code_generator/src/idl/IDLBlob.ts index d42c3ea695..da74425840 100644 --- a/bridge/scripts/code_generator/src/blob.ts +++ b/bridge/scripts/code_generator/src/idl/IDLBlob.ts @@ -1,7 +1,7 @@ import fs from 'fs'; import {ClassObject, FunctionObject} from "./declaration"; -export class Blob { +export class IDLBlob { raw: string; dist: string; source: string; diff --git a/bridge/scripts/code_generator/src/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts similarity index 98% rename from bridge/scripts/code_generator/src/analyzer.ts rename to bridge/scripts/code_generator/src/idl/analyzer.ts index 051baa93b8..899636bcc7 100644 --- a/bridge/scripts/code_generator/src/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -1,5 +1,5 @@ import ts, {HeritageClause, ScriptTarget, VariableStatement} from 'typescript'; -import {Blob} from './blob'; +import {IDLBlob} from './IDLBlob'; import { ClassObject, FunctionArguments, @@ -10,7 +10,7 @@ import { } from './declaration'; import {generatorSource} from './generator'; -export function analyzer(blob: Blob) { +export function analyzer(blob: IDLBlob) { let code = blob.raw; const sourceFile = ts.createSourceFile(blob.source, blob.raw, ScriptTarget.ES2020); blob.objects = sourceFile.statements.map(statement => walkProgram(statement)).filter(o => { diff --git a/bridge/scripts/code_generator/src/declaration.ts b/bridge/scripts/code_generator/src/idl/declaration.ts similarity index 100% rename from bridge/scripts/code_generator/src/declaration.ts rename to bridge/scripts/code_generator/src/idl/declaration.ts diff --git a/bridge/scripts/code_generator/src/generate_header.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts similarity index 91% rename from bridge/scripts/code_generator/src/generate_header.ts rename to bridge/scripts/code_generator/src/idl/generateHeader.ts index 4e4f41ac63..a44f7cd613 100644 --- a/bridge/scripts/code_generator/src/generate_header.ts +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -1,9 +1,9 @@ import {ClassObject, FunctionObject, PropsDeclaration} from "./declaration"; import {uniqBy, snakeCase} from "lodash"; -import {Blob} from "./blob"; +import {IDLBlob} from "./IDLBlob"; import {addIndent, getClassName} from "./utils"; -function generateInterfaceAdditionalHeader(blob: Blob, object: any): [string, string, string] { +function generateInterfaceAdditionalHeader(blob: IDLBlob, object: any): [string, string, string] { if (!(object instanceof ClassObject)) { return ['', '', '']; } @@ -27,7 +27,7 @@ function generateInterfaceAdditionalHeader(blob: Blob, object: any): [string, st ]; } -export function generateCppHeader(blob: Blob) { +export function generateCppHeader(blob: IDLBlob) { let classObject = blob.objects.find(object => object instanceof ClassObject); let interfaceDefines = generateInterfaceAdditionalHeader(blob, classObject); let haveInterfaceBase = !!classObject; diff --git a/bridge/scripts/code_generator/src/genereate_source.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts similarity index 87% rename from bridge/scripts/code_generator/src/genereate_source.ts rename to bridge/scripts/code_generator/src/idl/generateSource.ts index 9cde6bff00..9bf25ef29d 100644 --- a/bridge/scripts/code_generator/src/genereate_source.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -1,4 +1,4 @@ -import {Blob} from "./blob"; +import {IDLBlob} from "./IDLBlob"; import { ClassObject, FunctionArguments, @@ -85,7 +85,7 @@ function generateCallMethodName(name: string) { return name; } -function generateOptionalInitBody(blob: Blob, declare: FunctionDeclaration, argument: FunctionArguments, argsIndex: number, previousArguments: string[], options: GenFunctionBodyOptions) { +function generateOptionalInitBody(blob: IDLBlob, declare: FunctionDeclaration, argument: FunctionArguments, argsIndex: number, previousArguments: string[], options: GenFunctionBodyOptions) { let call = ''; let returnValueAssignment = ''; if (declare.returnType[0] != FunctionArgumentType.void) { @@ -110,7 +110,7 @@ if (argc <= ${argsIndex + 1}) { }`; } -function generateFunctionCallBody(blob: Blob, declaration: FunctionDeclaration, options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { +function generateFunctionCallBody(blob: IDLBlob, declaration: FunctionDeclaration, options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { let minimalRequiredArgc = 0; declaration.args.forEach(m => { if (m.required) minimalRequiredArgc++; @@ -157,14 +157,14 @@ ${optionalArgumentsInit.join('\n')} `; } -function generateGlobalFunctionSource(blob: Blob, object: FunctionObject) { +function generateGlobalFunctionSource(blob: IDLBlob, object: FunctionObject) { let body = generateFunctionBody(blob, object.declare); return `static JSValue ${object.declare.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { ${body} }`; } -function generateReturnValueInit(blob: Blob, type: ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { +function generateReturnValueInit(blob: IDLBlob, type: ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { if (type[0] == FunctionArgumentType.void) return ''; if (options.isConstructor) { @@ -180,7 +180,7 @@ function generateReturnValueInit(blob: Blob, type: ParameterType[], options: Gen return `Converter<${generateTypeConverter(type)}>::ImplType return_value;`; } -function generateReturnValueResult(blob: Blob, type: ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}): string { +function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}): string { if (type[0] == FunctionArgumentType.void) return 'JS_NULL'; if (options.isConstructor) { return `return_value->ToQuickJS()`; @@ -199,7 +199,7 @@ function generateReturnValueResult(blob: Blob, type: ParameterType[], options: G type GenFunctionBodyOptions = {isConstructor?: boolean, isInstanceMethod?: boolean}; -function generateFunctionBody(blob: Blob, declare: FunctionDeclaration, options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod : false}) { +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); @@ -222,14 +222,14 @@ ${addIndent(callBody, 4)} `; } -function generateClassConstructorCallback(blob: Blob, declare: FunctionDeclaration) { +function generateClassConstructorCallback(blob: IDLBlob, declare: FunctionDeclaration) { return `JSValue QJS${getClassName(blob)}::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { ${generateFunctionBody(blob, declare, {isConstructor: true})} } `; } -function generatePropertyGetterCallback(blob: Blob, prop: PropsDeclaration) { +function generatePropertyGetterCallback(blob: IDLBlob, prop: PropsDeclaration) { return `static JSValue ${prop.name}AttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* ${blob.filename} = toScriptWrappable<${getClassName(blob)}>(this_val); assert(${blob.filename} != nullptr); @@ -237,7 +237,7 @@ function generatePropertyGetterCallback(blob: Blob, prop: PropsDeclaration) { }`; } -function generatePropertySetterCallback(blob: Blob, prop: PropsDeclaration) { +function generatePropertySetterCallback(blob: IDLBlob, prop: PropsDeclaration) { return `static JSValue ${prop.name}AttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* ${blob.filename} = toScriptWrappable<${getClassName(blob)}>(this_val); ExceptionState exception_state; @@ -249,7 +249,7 @@ function generatePropertySetterCallback(blob: Blob, prop: PropsDeclaration) { }`; } -function generateMethodCallback(blob: Blob, methods: FunctionDeclaration[]): string[] { +function generateMethodCallback(blob: IDLBlob, methods: FunctionDeclaration[]): string[] { return methods.map(method => { return `static JSValue ${method.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { ${ generateFunctionBody(blob, method, {isInstanceMethod: true}) } @@ -257,7 +257,7 @@ function generateMethodCallback(blob: Blob, methods: FunctionDeclaration[]): str }); } -function generateClassSource(blob: Blob, object: ClassObject) { +function generateClassSource(blob: IDLBlob, object: ClassObject) { let constructorCallback = ''; if (object.construct) { constructorCallback = generateClassConstructorCallback(blob, object.construct); @@ -281,7 +281,7 @@ function generateClassSource(blob: Blob, object: ClassObject) { ].join('\n'); } -function generateInstallGlobalFunctions(blob: Blob, installList: string[]) { +function generateInstallGlobalFunctions(blob: IDLBlob, installList: string[]) { return `void QJS${getClassName(blob)}::InstallGlobalFunctions(ExecutingContext* context) { std::initializer_list functionConfig { ${installList.join(',\n')} @@ -291,7 +291,7 @@ function generateInstallGlobalFunctions(blob: Blob, installList: string[]) { }`; } -function generateConstructorInstaller(blob: Blob) { +function generateConstructorInstaller(blob: IDLBlob) { return `void QJS${getClassName(blob)}::InstallConstructor(ExecutingContext* context) { const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); JSValue constructor = context->contextData()->constructorForType(wrapperTypeInfo); @@ -303,7 +303,7 @@ function generateConstructorInstaller(blob: Blob) { }`; } -function generatePrototypeMethodsInstaller(blob: Blob, installList: string[]) { +function generatePrototypeMethodsInstaller(blob: IDLBlob, installList: string[]) { return `void QJS${getClassName(blob)}::InstallPrototypeMethods(ExecutingContext* context) { const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); @@ -317,7 +317,7 @@ function generatePrototypeMethodsInstaller(blob: Blob, installList: string[]) { `; } -function generatePrototypePropsInstaller(blob: Blob, installList: string[]) { +function generatePrototypePropsInstaller(blob: IDLBlob, installList: string[]) { return `void QJS${getClassName(blob)}::InstallPrototypeProperties(ExecutingContext* context) { const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); @@ -331,7 +331,7 @@ function generatePrototypePropsInstaller(blob: Blob, installList: string[]) { `; } -export function generateCppSource(blob: Blob) { +export function generateCppSource(blob: IDLBlob) { let functionInstallList: string[] = []; let classMethodsInstallList: string[] = []; let classPropsInstallList: string[] = []; 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..0e01c4d22b --- /dev/null +++ b/bridge/scripts/code_generator/src/idl/generator.ts @@ -0,0 +1,12 @@ +import {IDLBlob} from './IDLBlob'; +import {generateCppHeader} from "./generateHeader"; +import {generateCppSource} from "./generateSource"; + +export function generatorSource(blob: IDLBlob) { + let header = generateCppHeader(blob); + let source = generateCppSource(blob); + return { + header, + source + }; +} diff --git a/bridge/scripts/code_generator/src/utils.ts b/bridge/scripts/code_generator/src/idl/utils.ts similarity index 85% rename from bridge/scripts/code_generator/src/utils.ts rename to bridge/scripts/code_generator/src/idl/utils.ts index 5d3a239ae5..6bd5b4c0de 100644 --- a/bridge/scripts/code_generator/src/utils.ts +++ b/bridge/scripts/code_generator/src/idl/utils.ts @@ -1,4 +1,4 @@ -import {Blob} from './blob'; +import {IDLBlob} from './IDLBlob'; import {camelCase} from 'lodash'; export function addIndent(str: String, space: number) { @@ -12,7 +12,7 @@ export function addIndent(str: String, space: number) { return lines.join('\n'); } -export function getClassName(blob: Blob) { +export function getClassName(blob: IDLBlob) { let raw = camelCase(blob.filename[4].toUpperCase() + blob.filename.slice(5)); return `${raw[0].toUpperCase() + raw.slice(1)}`; diff --git a/bridge/scripts/code_generator/src/json/JSONBlob.ts b/bridge/scripts/code_generator/src/json/JSONBlob.ts new file mode 100644 index 0000000000..ddbc50f73a --- /dev/null +++ b/bridge/scripts/code_generator/src/json/JSONBlob.ts @@ -0,0 +1,18 @@ +import {ClassObject, FunctionObject} from "../idl/declaration"; +import fs from "fs"; + +export class JSONBlob { + raw: string; + dist: string; + source: string; + filename: string; + 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 = JSON.parse(this.raw); + } +} 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..4669ff922b --- /dev/null +++ b/bridge/scripts/code_generator/src/json/generator.ts @@ -0,0 +1,27 @@ +import {JSONBlob} from './JSONBlob'; +import {Template} from './template'; +import _ from 'lodash'; + +function generateHeader(blob: JSONBlob, template: Template): string { + let compiled = _.template(template.raw); + return compiled({ + _: _, + name: blob.filename, + template_path: blob.source, + data: blob.json.data + }); +} + +function generateBody(blob: JSONBlob, template: Template): string { + +} + +export function generateJSONTemplate(blob: JSONBlob, headerTemplate: Template, bodyTemplate?: Template) { + let header = generateHeader(blob, headerTemplate); + let body = bodyTemplate ? generateBody(blob, bodyTemplate) : ''; + + return { + header: header, + source: body, + }; +} diff --git a/bridge/scripts/code_generator/src/json/template.ts b/bridge/scripts/code_generator/src/json/template.ts new file mode 100644 index 0000000000..cb76f50731 --- /dev/null +++ b/bridge/scripts/code_generator/src/json/template.ts @@ -0,0 +1,17 @@ +import fs from "fs"; + +enum TemplateType { + header, + body +} + +export class Template { + 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/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl new file mode 100644 index 0000000000..db75f70e58 --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -0,0 +1,23 @@ +// 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 "third_party/blink/renderer/platform/wtf/text/atomic_string.h" +#include "third_party/blink/renderer/core/core_export.h" + +namespace kraken { + +extern const WTF::AtomicString& kAbort; + +constexpr unsigned kNamesCount = 352; + +void Init(); + +} // kraken + +#endif // #define <%= _.snakeCase(name).toUpperCase() %> diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl new file mode 100644 index 0000000000..718835096b --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl @@ -0,0 +1,24 @@ +// 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/atom_string.h" + +namespace kraken { + +<% _.forEach(data, function(name, index) { %> +extern const AtomicString& k<%= name[0].toUpperCase() + name.slice(1) %>; +<% }) %> + +constexpr unsigned kNamesCount = <%= data.length %>; + +void Init(); + +} // kraken + +#endif // #define <%= _.snakeCase(name).toUpperCase() %> diff --git a/bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl b/bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl new file mode 100644 index 0000000000..7f32b18783 --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl @@ -0,0 +1,32 @@ +/* +* QuickJS atom definitions +* +* Copyright (c) 2017-2018 Fabrice Bellard +* Copyright (c) 2017-2018 Charlie Gordon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#ifdef DEF + +<% _.forEach(data, function(name) { %> +DEF(<%= name %>, "<%= name %>") +<% }); %> + +#endif /* DEF */ diff --git a/bridge/third_party/quickjs/event_type_names.h b/bridge/third_party/quickjs/event_type_names.h new file mode 100644 index 0000000000..ceb1cf8466 --- /dev/null +++ b/bridge/third_party/quickjs/event_type_names.h @@ -0,0 +1,734 @@ +/* +* QuickJS atom definitions +* +* Copyright (c) 2017-2018 Fabrice Bellard +* Copyright (c) 2017-2018 Charlie Gordon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#ifdef DEF + + +DEF(DOMActivate, "DOMActivate") + +DEF(DOMCharacterDataModified, "DOMCharacterDataModified") + +DEF(DOMContentLoaded, "DOMContentLoaded") + +DEF(DOMFocusIn, "DOMFocusIn") + +DEF(DOMFocusOut, "DOMFocusOut") + +DEF(DOMNodeInserted, "DOMNodeInserted") + +DEF(DOMNodeInsertedIntoDocument, "DOMNodeInsertedIntoDocument") + +DEF(DOMNodeRemoved, "DOMNodeRemoved") + +DEF(DOMNodeRemovedFromDocument, "DOMNodeRemovedFromDocument") + +DEF(DOMSubtreeModified, "DOMSubtreeModified") + +DEF(abort, "abort") + +DEF(abortpayment, "abortpayment") + +DEF(accessibleclick, "accessibleclick") + +DEF(accessiblecontextmenu, "accessiblecontextmenu") + +DEF(accessibledecrement, "accessibledecrement") + +DEF(accessiblefocus, "accessiblefocus") + +DEF(accessibleincrement, "accessibleincrement") + +DEF(accessiblescrollintoview, "accessiblescrollintoview") + +DEF(activate, "activate") + +DEF(active, "active") + +DEF(addsourcebuffer, "addsourcebuffer") + +DEF(addstream, "addstream") + +DEF(addtrack, "addtrack") + +DEF(advertisementreceived, "advertisementreceived") + +DEF(afterprint, "afterprint") + +DEF(animationcancel, "animationcancel") + +DEF(animationend, "animationend") + +DEF(animationiteration, "animationiteration") + +DEF(animationstart, "animationstart") + +DEF(appinstalled, "appinstalled") + +DEF(audioend, "audioend") + +DEF(audioprocess, "audioprocess") + +DEF(audiostart, "audiostart") + +DEF(auxclick, "auxclick") + +DEF(availablechange, "availablechange") + +DEF(backgroundfetchabort, "backgroundfetchabort") + +DEF(backgroundfetchclick, "backgroundfetchclick") + +DEF(backgroundfetchfail, "backgroundfetchfail") + +DEF(backgroundfetchsuccess, "backgroundfetchsuccess") + +DEF(beforecopy, "beforecopy") + +DEF(beforecreatepolicy, "beforecreatepolicy") + +DEF(beforecut, "beforecut") + +DEF(beforeinput, "beforeinput") + +DEF(beforeinstallprompt, "beforeinstallprompt") + +DEF(beforematch, "beforematch") + +DEF(beforepaste, "beforepaste") + +DEF(beforeprint, "beforeprint") + +DEF(beforeunload, "beforeunload") + +DEF(beforexrselect, "beforexrselect") + +DEF(beginEvent, "beginEvent") + +DEF(blocked, "blocked") + +DEF(blur, "blur") + +DEF(boundary, "boundary") + +DEF(bufferedamountlow, "bufferedamountlow") + +DEF(cached, "cached") + +DEF(cancel, "cancel") + +DEF(canmakepayment, "canmakepayment") + +DEF(canplay, "canplay") + +DEF(canplaythrough, "canplaythrough") + +DEF(capturehandlechange, "capturehandlechange") + +DEF(change, "change") + +DEF(characterboundsupdate, "characterboundsupdate") + +DEF(characteristicvaluechanged, "characteristicvaluechanged") + +DEF(chargingchange, "chargingchange") + +DEF(chargingtimechange, "chargingtimechange") + +DEF(checking, "checking") + +DEF(click, "click") + +DEF(close, "close") + +DEF(closing, "closing") + +DEF(complete, "complete") + +DEF(compositionend, "compositionend") + +DEF(compositionstart, "compositionstart") + +DEF(compositionupdate, "compositionupdate") + +DEF(connect, "connect") + +DEF(connecting, "connecting") + +DEF(connectionavailable, "connectionavailable") + +DEF(connectionstatechange, "connectionstatechange") + +DEF(contextlost, "contextlost") + +DEF(contextmenu, "contextmenu") + +DEF(contextrestored, "contextrestored") + +DEF(controllerchange, "controllerchange") + +DEF(cookiechange, "cookiechange") + +DEF(copy, "copy") + +DEF(contentdelete, "contentdelete") + +DEF(crossoriginmessage, "crossoriginmessage") + +DEF(currentscreenchange, "currentscreenchange") + +DEF(cuechange, "cuechange") + +DEF(currententrychange, "currententrychange") + +DEF(cut, "cut") + +DEF(datachannel, "datachannel") + +DEF(dblclick, "dblclick") + +DEF(defaultsessionstart, "defaultsessionstart") + +DEF(devicechange, "devicechange") + +DEF(devicemotion, "devicemotion") + +DEF(deviceorientation, "deviceorientation") + +DEF(deviceorientationabsolute, "deviceorientationabsolute") + +DEF(dischargingtimechange, "dischargingtimechange") + +DEF(disconnect, "disconnect") + +DEF(display, "display") + +DEF(dispose, "dispose") + +DEF(downloading, "downloading") + +DEF(dataavailable, "dataavailable") + +DEF(drag, "drag") + +DEF(dragend, "dragend") + +DEF(dragenter, "dragenter") + +DEF(dragleave, "dragleave") + +DEF(dragover, "dragover") + +DEF(dragstart, "dragstart") + +DEF(drop, "drop") + +DEF(durationchange, "durationchange") + +DEF(elementtimingbufferfull, "elementtimingbufferfull") + +DEF(emptied, "emptied") + +DEF(encrypted, "encrypted") + +DEF(end, "end") + +DEF(ended, "ended") + +DEF(endEvent, "endEvent") + +DEF(enter, "enter") + +DEF(enterpictureinpicture, "enterpictureinpicture") + +DEF(error, "error") + +DEF(eventtimingbufferfull, "eventtimingbufferfull") + +DEF(exit, "exit") + +DEF(fetch, "fetch") + +DEF(finish, "finish") + +DEF(focus, "focus") + +DEF(focusin, "focusin") + +DEF(focusout, "focusout") + +DEF(foreignfetch, "foreignfetch") + +DEF(formdata, "formdata") + +DEF(freeze, "freeze") + +DEF(fullscreenchange, "fullscreenchange") + +DEF(fullscreenerror, "fullscreenerror") + +DEF(gamepadconnected, "gamepadconnected") + +DEF(gamepaddisconnected, "gamepaddisconnected") + +DEF(gatheringstatechange, "gatheringstatechange") + +DEF(gattserverdisconnected, "gattserverdisconnected") + +DEF(geofenceenter, "geofenceenter") + +DEF(geofenceleave, "geofenceleave") + +DEF(geometrychange, "geometrychange") + +DEF(gesturelongpress, "gesturelongpress") + +DEF(gesturescrollend, "gesturescrollend") + +DEF(gesturescrollstart, "gesturescrollstart") + +DEF(gesturescrollupdate, "gesturescrollupdate") + +DEF(gestureshowpress, "gestureshowpress") + +DEF(gesturetap, "gesturetap") + +DEF(gesturetapdown, "gesturetapdown") + +DEF(gesturetapunconfirmed, "gesturetapunconfirmed") + +DEF(gestureflingstart, "gestureflingstart") + +DEF(gotpointercapture, "gotpointercapture") + +DEF(hashchange, "hashchange") + +DEF(hide, "hide") + +DEF(icecandidate, "icecandidate") + +DEF(icecandidateerror, "icecandidateerror") + +DEF(iceconnectionstatechange, "iceconnectionstatechange") + +DEF(icegatheringstatechange, "icegatheringstatechange") + +DEF(inactive, "inactive") + +DEF(input, "input") + +DEF(inputreport, "inputreport") + +DEF(inputsourceschange, "inputsourceschange") + +DEF(install, "install") + +DEF(interfacerequest, "interfacerequest") + +DEF(invalid, "invalid") + +DEF(keydown, "keydown") + +DEF(keypress, "keypress") + +DEF(keystatuseschange, "keystatuseschange") + +DEF(keyup, "keyup") + +DEF(languagechange, "languagechange") + +DEF(leavepictureinpicture, "leavepictureinpicture") + +DEF(levelchange, "levelchange") + +DEF(load, "load") + +DEF(loadeddata, "loadeddata") + +DEF(loadedmetadata, "loadedmetadata") + +DEF(loadend, "loadend") + +DEF(loading, "loading") + +DEF(loadingdone, "loadingdone") + +DEF(loadingerror, "loadingerror") + +DEF(loadstart, "loadstart") + +DEF(lostpointercapture, "lostpointercapture") + +DEF(managedconfigurationchange, "managedconfigurationchange") + +DEF(mark, "mark") + +DEF(message, "message") + +DEF(messageerror, "messageerror") + +DEF(midimessage, "midimessage") + +DEF(mousedown, "mousedown") + +DEF(mouseenter, "mouseenter") + +DEF(mouseleave, "mouseleave") + +DEF(mousemove, "mousemove") + +DEF(mouseout, "mouseout") + +DEF(mouseover, "mouseover") + +DEF(mouseup, "mouseup") + +DEF(mousewheel, "mousewheel") + +DEF(mute, "mute") + +DEF(navigate, "navigate") + +DEF(navigateerror, "navigateerror") + +DEF(navigatesuccess, "navigatesuccess") + +DEF(negotiationneeded, "negotiationneeded") + +DEF(nomatch, "nomatch") + +DEF(notificationclick, "notificationclick") + +DEF(notificationclose, "notificationclose") + +DEF(notificationerror, "notificationerror") + +DEF(noupdate, "noupdate") + +DEF(obsolete, "obsolete") + +DEF(offline, "offline") + +DEF(online, "online") + +DEF(open, "open") + +DEF(orientationchange, "orientationchange") + +DEF(overscroll, "overscroll") + +DEF(pagehide, "pagehide") + +DEF(pageshow, "pageshow") + +DEF(paste, "paste") + +DEF(pause, "pause") + +DEF(payerdetailchange, "payerdetailchange") + +DEF(paymentmethodchange, "paymentmethodchange") + +DEF(paymentrequest, "paymentrequest") + +DEF(periodicsync, "periodicsync") + +DEF(play, "play") + +DEF(playing, "playing") + +DEF(pointercancel, "pointercancel") + +DEF(pointerdown, "pointerdown") + +DEF(pointerenter, "pointerenter") + +DEF(pointerleave, "pointerleave") + +DEF(pointerlockchange, "pointerlockchange") + +DEF(pointerlockerror, "pointerlockerror") + +DEF(pointermove, "pointermove") + +DEF(pointerout, "pointerout") + +DEF(pointerover, "pointerover") + +DEF(pointerrawupdate, "pointerrawupdate") + +DEF(pointerup, "pointerup") + +DEF(popstate, "popstate") + +DEF(portalactivate, "portalactivate") + +DEF(prerenderingchange, "prerenderingchange") + +DEF(prioritychange, "prioritychange") + +DEF(progress, "progress") + +DEF(processorerror, "processorerror") + +DEF(push, "push") + +DEF(pushsubscriptionchange, "pushsubscriptionchange") + +DEF(quicstream, "quicstream") + +DEF(quotachange, "quotachange") + +DEF(ratechange, "ratechange") + +DEF(reading, "reading") + +DEF(readingerror, "readingerror") + +DEF(readystatechange, "readystatechange") + +DEF(reflectionchange, "reflectionchange") + +DEF(rejectionhandled, "rejectionhandled") + +DEF(release, "release") + +DEF(remove, "remove") + +DEF(removesourcebuffer, "removesourcebuffer") + +DEF(removestream, "removestream") + +DEF(removetrack, "removetrack") + +DEF(repeatEvent, "repeatEvent") + +DEF(reset, "reset") + +DEF(resetpose, "resetpose") + +DEF(resize, "resize") + +DEF(resourcetimingbufferfull, "resourcetimingbufferfull") + +DEF(result, "result") + +DEF(resume, "resume") + +DEF(samplebufferfull, "samplebufferfull") + +DEF(screenschange, "screenschange") + +DEF(scroll, "scroll") + +DEF(scrollend, "scrollend") + +DEF(search, "search") + +DEF(securitypolicyviolation, "securitypolicyviolation") + +DEF(seeked, "seeked") + +DEF(seeking, "seeking") + +DEF(select, "select") + +DEF(selectedcandidatepairchange, "selectedcandidatepairchange") + +DEF(selectend, "selectend") + +DEF(selectionchange, "selectionchange") + +DEF(selectstart, "selectstart") + +DEF(shippingaddresschange, "shippingaddresschange") + +DEF(shippingoptionchange, "shippingoptionchange") + +DEF(show, "show") + +DEF(signalingstatechange, "signalingstatechange") + +DEF(slotchange, "slotchange") + +DEF(soundend, "soundend") + +DEF(soundstart, "soundstart") + +DEF(sourceclose, "sourceclose") + +DEF(sourceended, "sourceended") + +DEF(sourceopen, "sourceopen") + +DEF(speechend, "speechend") + +DEF(speechstart, "speechstart") + +DEF(squeeze, "squeeze") + +DEF(squeezeend, "squeezeend") + +DEF(squeezestart, "squeezestart") + +DEF(stalled, "stalled") + +DEF(start, "start") + +DEF(stop, "stop") + +DEF(statechange, "statechange") + +DEF(storage, "storage") + +DEF(submit, "submit") + +DEF(success, "success") + +DEF(suspend, "suspend") + +DEF(sync, "sync") + +DEF(terminate, "terminate") + +DEF(textInput, "textInput") + +DEF(textupdate, "textupdate") + +DEF(textformatupdate, "textformatupdate") + +DEF(timeout, "timeout") + +DEF(timeupdate, "timeupdate") + +DEF(timezonechange, "timezonechange") + +DEF(toggle, "toggle") + +DEF(tonechange, "tonechange") + +DEF(touchcancel, "touchcancel") + +DEF(touchend, "touchend") + +DEF(touchmove, "touchmove") + +DEF(touchstart, "touchstart") + +DEF(track, "track") + +DEF(transitioncancel, "transitioncancel") + +DEF(transitionend, "transitionend") + +DEF(transitionrun, "transitionrun") + +DEF(transitionstart, "transitionstart") + +DEF(typechange, "typechange") + +DEF(uncapturederror, "uncapturederror") + +DEF(unhandledrejection, "unhandledrejection") + +DEF(unload, "unload") + +DEF(unmute, "unmute") + +DEF(update, "update") + +DEF(updateend, "updateend") + +DEF(updatefound, "updatefound") + +DEF(updateready, "updateready") + +DEF(updatestart, "updatestart") + +DEF(upgradeneeded, "upgradeneeded") + +DEF(versionchange, "versionchange") + +DEF(visibilitychange, "visibilitychange") + +DEF(voiceschanged, "voiceschanged") + +DEF(volumechange, "volumechange") + +DEF(vrdisplayconnect, "vrdisplayconnect") + +DEF(vrdisplaydisconnect, "vrdisplaydisconnect") + +DEF(vrdisplayactivate, "vrdisplayactivate") + +DEF(vrdisplaydeactivate, "vrdisplaydeactivate") + +DEF(vrdisplayblur, "vrdisplayblur") + +DEF(vrdisplayfocus, "vrdisplayfocus") + +DEF(vrdisplaypresentchange, "vrdisplaypresentchange") + +DEF(waiting, "waiting") + +DEF(waitingforkey, "waitingforkey") + +DEF(webglcontextcreationerror, "webglcontextcreationerror") + +DEF(webglcontextlost, "webglcontextlost") + +DEF(webglcontextrestored, "webglcontextrestored") + +DEF(webkitAnimationEnd, "webkitAnimationEnd") + +DEF(webkitAnimationIteration, "webkitAnimationIteration") + +DEF(webkitAnimationStart, "webkitAnimationStart") + +DEF(webkitBeforeTextInserted, "webkitBeforeTextInserted") + +DEF(webkitEditableContentChanged, "webkitEditableContentChanged") + +DEF(webkitTransitionEnd, "webkitTransitionEnd") + +DEF(webkitfullscreenchange, "webkitfullscreenchange") + +DEF(webkitfullscreenerror, "webkitfullscreenerror") + +DEF(webkitspeechchange, "webkitspeechchange") + +DEF(webkitvisibilitychange, "webkitvisibilitychange") + +DEF(wheel, "wheel") + +DEF(write, "write") + +DEF(writeend, "writeend") + +DEF(writestart, "writestart") + +DEF(zoom, "zoom") + + +#endif /* DEF */ diff --git a/bridge/third_party/quickjs/quickjs-atom.h b/bridge/third_party/quickjs/quickjs-atom.h index 4c2279452a..d8f71dc16e 100644 --- a/bridge/third_party/quickjs/quickjs-atom.h +++ b/bridge/third_party/quickjs/quickjs-atom.h @@ -1,6 +1,6 @@ /* * QuickJS atom definitions - * + * * Copyright (c) 2017-2018 Fabrice Bellard * Copyright (c) 2017-2018 Charlie Gordon * @@ -81,7 +81,7 @@ DEF(empty_string, "") DEF(length, "length") DEF(fileName, "fileName") DEF(lineNumber, "lineNumber") -DEF(message, "message") +//DEF(message, "message") DEF(errors, "errors") DEF(stack, "stack") DEF(name, "name") @@ -118,7 +118,7 @@ DEF(_with_, "") DEF(lastIndex, "lastIndex") DEF(target, "target") DEF(index, "index") -DEF(input, "input") +//DEF(input, "input") DEF(defineProperties, "defineProperties") DEF(apply, "apply") DEF(join, "join") @@ -202,7 +202,7 @@ DEF(RegExp, "RegExp") DEF(ArrayBuffer, "ArrayBuffer") DEF(SharedArrayBuffer, "SharedArrayBuffer") /* must keep same order as class IDs for typed arrays */ -DEF(Uint8ClampedArray, "Uint8ClampedArray") +DEF(Uint8ClampedArray, "Uint8ClampedArray") DEF(Int8Array, "Int8Array") DEF(Uint8Array, "Uint8Array") DEF(Int16Array, "Int16Array") @@ -269,5 +269,5 @@ DEF(Symbol_asyncIterator, "Symbol.asyncIterator") #ifdef CONFIG_BIGNUM DEF(Symbol_operatorSet, "Symbol.operatorSet") #endif - + #endif /* DEF */ diff --git a/bridge/third_party/quickjs/quickjs-external-atom.h b/bridge/third_party/quickjs/quickjs-external-atom.h new file mode 100644 index 0000000000..4a63a4c243 --- /dev/null +++ b/bridge/third_party/quickjs/quickjs-external-atom.h @@ -0,0 +1,7 @@ +// External static string atoms defined by users. + +#ifdef DEF + + + +#endif /* DEF */ diff --git a/bridge/third_party/quickjs/quickjs.c b/bridge/third_party/quickjs/quickjs.c index cb09d5f3e4..1b5a4c9bf3 100644 --- a/bridge/third_party/quickjs/quickjs.c +++ b/bridge/third_party/quickjs/quickjs.c @@ -67,12 +67,6 @@ #define CONFIG_PRINTF_RNDN #endif -/* define to include Atomics.* operations which depend on the OS - threads */ -#if !defined(EMSCRIPTEN) -#define CONFIG_ATOMICS -#endif - #if !defined(EMSCRIPTEN) /* enable stack limitation */ #define CONFIG_STACK_CHECK @@ -949,21 +943,7 @@ struct JSObject { } u; /* byte sizes: 40/48/72 */ }; -enum { - __JS_ATOM_NULL = JS_ATOM_NULL, -#define DEF(name, str) JS_ATOM_ ## name, -#include "quickjs-atom.h" -#undef DEF - JS_ATOM_END, -}; -#define JS_ATOM_LAST_KEYWORD JS_ATOM_super -#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield -static const char js_atom_init[] = -#define DEF(name, str) str "\0" -#include "quickjs-atom.h" -#undef DEF -; typedef enum OPCodeFormat { #define FMT(f) OP_FMT_ ## f, diff --git a/bridge/third_party/quickjs/quickjs.h b/bridge/third_party/quickjs/quickjs.h index d4a5cd3114..9a743b932e 100644 --- a/bridge/third_party/quickjs/quickjs.h +++ b/bridge/third_party/quickjs/quickjs.h @@ -126,7 +126,7 @@ static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v) { return 0; } - + #elif defined(JS_NAN_BOXING) typedef uint64_t JSValue; @@ -191,9 +191,15 @@ static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v) tag = JS_VALUE_GET_TAG(v); return tag == (JS_NAN >> 32); } - + #else /* !JS_NAN_BOXING */ +/* define to include Atomics.* operations which depend on the OS + threads */ +#if !defined(EMSCRIPTEN) +#define CONFIG_ATOMICS +#endif + typedef union JSValueUnion { int32_t int32; double float64; @@ -418,6 +424,24 @@ void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt); /* atom support */ #define JS_ATOM_NULL 0 +enum { + __JS_ATOM_NULL = JS_ATOM_NULL, +#define DEF(name, str) JS_ATOM_ ## name, +#include "quickjs-atom.h" +#include "event_type_names.h" +#undef DEF + JS_ATOM_END, +}; +#define JS_ATOM_LAST_KEYWORD JS_ATOM_super +#define JS_ATOM_LAST_STRICT_KEYWORD JS_ATOM_yield + +static const char js_atom_init[] = +#define DEF(name, str) str "\0" +#include "quickjs-atom.h" +#include "event_type_names.h" +#undef DEF +; + JSAtom JS_NewAtomLen(JSContext *ctx, const char *str, size_t len); JSAtom JS_NewAtom(JSContext *ctx, const char *str); JSAtom JS_NewAtomUInt32(JSContext *ctx, uint32_t n); @@ -957,7 +981,7 @@ static inline JSValue JS_NewCFunctionMagic(JSContext *ctx, JSCFunctionMagic *fun { return JS_NewCFunction2(ctx, (JSCFunction *)func, name, length, cproto, magic); } -void JS_SetConstructor(JSContext *ctx, JSValueConst func_obj, +void JS_SetConstructor(JSContext *ctx, JSValueConst func_obj, JSValueConst proto); /* C property definition */ From b1b365fc8107f3b250d276a3ec4acb34e969e213 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Wed, 30 Mar 2022 12:19:52 +0800 Subject: [PATCH 042/498] feat: auto gen static strings from json. --- bridge/bindings/qjs/atom_string.cc | 5 + bridge/bindings/qjs/atom_string.h | 60 ++-- bridge/bindings/qjs/converter_impl.h | 8 +- bridge/bindings/qjs/idl_type.h | 2 +- .../bindings/qjs/js_based_event_listener.cc | 2 + bridge/bindings/qjs/js_based_event_listener.h | 11 +- bridge/bindings/qjs/js_event_handler.cc | 159 ++++++++++ bridge/bindings/qjs/js_event_handler.h | 32 +- bridge/bindings/qjs/qjs_function.h | 2 + bridge/core/dom/events/error_event.cc | 6 + bridge/core/dom/events/error_event.h | 22 ++ bridge/core/dom/events/event.cc | 4 +- bridge/core/dom/events/event.h | 10 +- bridge/core/dom/events/event_listener.h | 4 + bridge/core/dom/events/event_listener_map.cc | 10 +- bridge/core/dom/events/event_listener_map.h | 12 +- bridge/core/dom/events/event_type_names.json | 216 +++++++++++++ .../code_generator/bin/code_generator.js | 2 +- .../code_generator/src/json/generator.ts | 7 +- .../static/json_templates/make_names.cc.tpl | 34 ++- .../static/json_templates/make_names.h.tpl | 7 +- bridge/third_party/quickjs/event_type_names.h | 288 ------------------ 22 files changed, 550 insertions(+), 353 deletions(-) create mode 100644 bridge/core/dom/events/error_event.cc create mode 100644 bridge/core/dom/events/error_event.h create mode 100644 bridge/core/dom/events/event_type_names.json diff --git a/bridge/bindings/qjs/atom_string.cc b/bridge/bindings/qjs/atom_string.cc index 86edca941b..2f0fcd5aaa 100644 --- a/bridge/bindings/qjs/atom_string.cc +++ b/bridge/bindings/qjs/atom_string.cc @@ -7,4 +7,9 @@ namespace kraken { +JSValue StaticAtomicString::ToQuickJS(JSContext* ctx) const { + return JS_AtomToValue(ctx, atom_); +} + + } // namespace kraken diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index 007bd57d9c..48eef3478a 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -18,32 +18,55 @@ namespace kraken { // 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. -// -// AtomicString instances are not thread-safe. An AtomicString instance created -// in a thread must be used only in the creator thread. -class AtomString final { - // ScriptAtom should only allocate at stack. - KRAKEN_DISALLOW_NEW(); +class AtomicString { + public: + AtomicString() = default; + AtomicString(JSAtom atom) : atom_(atom) {} + + // Return the undefined string value from atom key. + virtual JSValue ToQuickJS(JSContext* ctx) const = 0; + + bool operator==(const AtomicString& other) const { return other.atom_ == this->atom_; } + + protected: + JSAtom atom_{JS_ATOM_NULL}; +}; +// AtomicString which holding quickjs built-in atoms string. +// These string are stored in JSRuntime instead of JSContext. +// So it can be used by any JSContext and don't needs to be freed. +class PersistentAtomicString : public AtomicString { public: - static AtomString Empty(JSContext* ctx) { return AtomString(ctx, JS_ATOM_NULL); }; + PersistentAtomicString(JSAtom atom): AtomicString(atom) {}; - explicit AtomString(JSContext* ctx, const std::string& string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())) {} - explicit AtomString(JSContext* ctx, JSAtom atom) : ctx_(ctx), atom_(JS_DupAtom(ctx, atom)){}; - explicit AtomString(JSContext* ctx, JSValue value) : ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; + JSValue ToQuickJS(JSContext* ctx) const override; +}; + +// PeriodicAtomicString holding string atom key created by JSContext. +// Could be freed when string refer_count set to 0. +class PeriodicAtomicString : public AtomicString { + // Should only allocate on stack. + KRAKEN_DISALLOW_NEW(); - ~AtomString() { JS_FreeAtom(ctx_, atom_); } + public: + static PeriodicAtomicString Empty(JSContext* ctx) { return PeriodicAtomicString(ctx); }; - JSValue ToQuickJS() const { return JS_AtomToValue(ctx_, atom_); } + explicit PeriodicAtomicString(JSContext* ctx) : ctx_(ctx), AtomicString(JS_ATOM_NULL) {} + explicit PeriodicAtomicString(JSContext* ctx, const std::string& string) : ctx_(ctx), AtomicString(JS_NewAtom(ctx, string.c_str())) {} + explicit PeriodicAtomicString(JSContext* ctx, JSAtom atom) : ctx_(ctx), AtomicString(JS_DupAtom(ctx, atom)) {}; + explicit PeriodicAtomicString(JSContext* ctx, JSValue value) : ctx_(ctx), AtomicString(JS_ValueToAtom(ctx, value)){}; + ~PeriodicAtomicString() { JS_FreeAtom(ctx_, atom_); } + + JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); } // Copy assignment - AtomString(AtomString const& value) { + PeriodicAtomicString(PeriodicAtomicString const& value) { if (&value != this) { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; }; - AtomString& operator=(const AtomString& other) { + PeriodicAtomicString& operator=(const PeriodicAtomicString& other) { if (&other != this) { atom_ = JS_DupAtom(ctx_, other.atom_); } @@ -51,13 +74,13 @@ class AtomString final { }; // Move assignment - AtomString(AtomString&& value) noexcept { + PeriodicAtomicString(PeriodicAtomicString&& value) noexcept { if (&value != this) { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; }; - AtomString& operator=(AtomString&& value) noexcept { + PeriodicAtomicString& operator=(PeriodicAtomicString&& value) noexcept { if (&value != this) { atom_ = JS_DupAtom(ctx_, value.atom_); } @@ -65,12 +88,9 @@ class AtomString final { return *this; } - bool operator==(const AtomString& other) const { return other.atom_ == this->atom_; } - private: - AtomString() = delete; + PeriodicAtomicString() = delete; JSContext* ctx_{nullptr}; - JSAtom atom_{JS_ATOM_NULL}; }; } // namespace kraken diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 9a15ea7346..73fff320d6 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -175,10 +175,10 @@ template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); - return AtomString(ctx, value); + return AtomicString(ctx, value); } - static JSValue ToValue(JSContext* ctx, const AtomString& value) { return value.ToQuickJS(); } + static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(); } 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); } @@ -189,7 +189,7 @@ template <> struct Converter> : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { if (JS_IsUndefined(value)) - return AtomString::Empty(ctx); + return AtomicString::Empty(ctx); return Converter::FromValue(ctx, value, exception_state); } @@ -202,7 +202,7 @@ template <> struct Converter> : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { if (JS_IsNull(value)) - return AtomString::Empty(ctx); + return AtomicString::Empty(ctx); return Converter::FromValue(ctx, value, exception_state); } }; diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 00e95abf82..42bc6521db 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -47,7 +47,7 @@ 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 {}; +struct IDLDOMString final : public IDLTypeBaseHelper {}; // https://developer.mozilla.org/en-US/docs/Web/API/USVString struct IDLUSVString final : public IDLTypeBaseHelper {}; diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc index 37a9f8a339..a90afbc1a1 100644 --- a/bridge/bindings/qjs/js_based_event_listener.cc +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -21,4 +21,6 @@ void JSBasedEventListener::Invoke(ExecutingContext* context, Event* event) { InvokeInternal(*event->currentTarget(), *event, exception_state); } +JSBasedEventListener::JSBasedEventListener() {} + } diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index 91f3152c46..e9c0960ed9 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -21,12 +21,6 @@ class JSBasedEventListener : public EventListener { // Implements step 2. of "inner invoke". // See: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke void Invoke(ExecutingContext* context, Event* event) final; - - // Implements "get the current value of the event handler". - // https://html.spec.whatwg.org/C/#getting-the-current-value-of-the-event-handler - // Returns null with firing error event instead of throwing an exception. - virtual JSValue GetListenerObject(EventTarget&) = 0; - // Returns Functions that handles invoked event or undefined without // throwing any exception. virtual JSValue GetEffectiveFunction(EventTarget&) = 0; @@ -35,11 +29,8 @@ class JSBasedEventListener : public EventListener { virtual bool IsJSEventHandler() const { return false; } protected: - JSBasedEventListener() = default; + JSBasedEventListener(); - virtual JSContext* GetJSContext() const = 0; - // Returns the ScriptState of the relevant realm of the callback object. - virtual ScriptState* GetScriptState() const = 0; private: // Performs "call a user object's operation", required in "inner-invoke". // "The event handler processing algorithm" corresponds to this in the case of diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index df7d4c8383..e25545abdd 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -4,3 +4,162 @@ */ #include "js_event_handler.h" +#include "core/dom/events/error_event.h" + +namespace kraken { + +std::unique_ptr JSEventHandler::CreateOrNull(JSContext* ctx, JSValue value, JSEventHandler::HandlerType handler_type) { + if (!JS_IsFunction(ctx, value)) { + return nullptr; + } + + return std::make_unique(QJSFunction::Create(ctx, value), handler_type); +} + +bool JSEventHandler::Matches(const EventListener& other) const { + return this == &other; +} + +// https://html.spec.whatwg.org/C/#the-event-handler-processing-algorithm +void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, ExceptionState& exception_state) { + // Step 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.) + HeapVector arguments; + ScriptState* script_state_of_listener = + event_handler_->CallbackRelevantScriptState(); + v8::Isolate* isolate = script_state_of_listener->GetIsolate(); + + if (special_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(script_state_of_listener); + if (error_attribute.IsEmpty() || + error_event->target()->InterfaceName() == event_target_names::kWorker) { + error_attribute = ScriptValue::CreateNull(isolate); + } + arguments = { + ScriptValue(isolate, + ToV8Traits::ToV8(script_state_of_listener, + error_event->message()) + .ToLocalChecked()), + ScriptValue(isolate, + ToV8Traits::ToV8(script_state_of_listener, + error_event->filename()) + .ToLocalChecked()), + ScriptValue(isolate, + ToV8Traits::ToV8(script_state_of_listener, + error_event->lineno()) + .ToLocalChecked()), + ScriptValue(isolate, ToV8Traits::ToV8( + script_state_of_listener, error_event->colno()) + .ToLocalChecked()), + error_attribute}; + } else { + arguments.push_back(ScriptValue(isolate, js_event)); + } + + if (!event_handler_->IsRunnableOrThrowException( + event.ShouldDispatchEvenWhenExecutionContextIsPaused() + ? V8EventHandlerNonNull::IgnorePause::kIgnore + : V8EventHandlerNonNull::IgnorePause::kDontIgnore)) { + return; + } + ScriptValue result; + if (!event_handler_ + ->InvokeWithoutRunnabilityCheck(event.currentTarget(), arguments) + .To(&result) || + isolate->IsExecutionTerminating()) + return; + v8::Local v8_return_value = result.V8Value(); + + // There is nothing to do if |v8_return_value| is null or undefined. + // See Step 5. for more information. + if (v8_return_value->IsNullOrUndefined()) + 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. + String result_for_beforeunload; + if (IsOnBeforeUnloadEventHandler()) { + event_handler_->EvaluateAsPartOfCallback(Bind( + [](v8::Local& v8_return_value, + String& result_for_beforeunload) { + // TODO(yukiy): use |NativeValueTraits|. + V8StringResource native_result( + v8_return_value); + + // |native_result.Prepare()| throws exception if it fails to convert + // |native_result| to String. + if (!native_result.Prepare()) + return; + result_for_beforeunload = native_result; + }, + std::ref(v8_return_value), std::ref(result_for_beforeunload))); + if (!result_for_beforeunload) + return; + } + + // Step 5. Process return value as follows: + // If event is a BeforeUnloadEvent object and event's type is beforeunload + // If return value is not null, then: + // 1. Set event's canceled flag. + // 2. If event's returnValue attribute's value is the empty string, then + // set event's returnValue attribute's value to return value. + // If special error event handling is true + // If return value is true, then set event's canceled flag. + // Otherwise + // If return value is false, then set event's canceled flag. + // Note: If we've gotten to this "Otherwise" clause because event's type + // is beforeunload but event is not a BeforeUnloadEvent object, + // then return value will never be false, since in such cases + // return value will have been coerced into either null or a + // DOMString. + auto* before_unload_event = DynamicTo(&event); + const bool is_beforeunload_event = + before_unload_event && event.type() == event_type_names::kBeforeunload; + if (is_beforeunload_event) { + if (result_for_beforeunload) { + event.preventDefault(); + if (before_unload_event->returnValue().IsEmpty()) + before_unload_event->setReturnValue(result_for_beforeunload); + } + } else if (!IsOnBeforeUnloadEventHandler()) { + if (special_error_event_handling && v8_return_value->IsBoolean() && + v8_return_value.As()->Value()) + event.preventDefault(); + else if (!special_error_event_handling && v8_return_value->IsBoolean() && + !v8_return_value.As()->Value()) + event.preventDefault(); + } +} + +} diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index bcf75db120..5727a7eb17 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -25,17 +25,45 @@ class JSEventHandler : public JSBasedEventListener { kOnBeforeUnloadEventHandler, }; - static std::unique_ptr CreateOrNull(JSContext* ctx, HandlerType handler_type); + static std::unique_ptr CreateOrNull(JSContext* ctx, JSValue value, HandlerType handler_type); static JSValue ToQuickJS(JSContext* ctx, EventTarget* event_target, EventListener* listener) { if (auto* event_handler = DynamicTo(listener)) { - return event_handler->GetListenerObject(*event_target); + return event_handler->GetEffectiveFunction(*event_target); } return JS_NULL; } + explicit JSEventHandler(const std::shared_ptr& event_handler, HandlerType type): type_(type), event_handler_(event_handler) {}; + + JSValue GetEffectiveFunction(EventTarget&) { + return event_handler_->ToQuickJS(); + } + + // Helper functions for DowncastTraits. + bool IsJSEventHandler() const override { return true; } + + // For checking special types of EventHandler. + bool IsOnErrorEventHandler() const { + return type_ == HandlerType::kOnErrorEventHandler; + } + + bool IsOnBeforeUnloadEventHandler() const { + return type_ == HandlerType::kOnBeforeUnloadEventHandler; + } + + // EventListener overrides: + bool Matches(const EventListener&) const override; 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_; }; } diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 58b3217282..89c0aa46a0 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -21,6 +21,8 @@ class QJSFunction { 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, int32_t argc, ScriptValue* arguments); diff --git a/bridge/core/dom/events/error_event.cc b/bridge/core/dom/events/error_event.cc new file mode 100644 index 0000000000..db1bafeca7 --- /dev/null +++ b/bridge/core/dom/events/error_event.cc @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "error_event.h" diff --git a/bridge/core/dom/events/error_event.h b/bridge/core/dom/events/error_event.h new file mode 100644 index 0000000000..5ced2fe109 --- /dev/null +++ b/bridge/core/dom/events/error_event.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ +#define KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ + +#include "event.h" + +namespace kraken { + +class ErrorEvent : public Event { + public: + + private: + +}; + +} + +#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index fc587ff75e..a08c054d66 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -11,6 +11,8 @@ namespace kraken { Event::Event(ExecutingContext* context) : Event(context, nullptr) {} +Event::Event(ExecutingContext* context, const AtomicString& event_type): type_(event_type), ScriptWrappable(context->ctx()) {} + Event::Event(ExecutingContext* context, NativeEvent* native_event) : ScriptWrappable(context->ctx()), #if ANDROID_32_BIT @@ -18,7 +20,7 @@ Event::Event(ExecutingContext* context, NativeEvent* native_event) target_(reinterpret_cast(native_event->target)), current_target_(reinterpret_cast(native_event->currentTarget)), #else - type_(native_event->type), + type_(), target_(static_cast(native_event->target)), current_target_(static_cast(native_event->currentTarget)), #endif diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index b382da992f..7f0ee1bee2 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -8,6 +8,7 @@ #include #include "bindings/qjs/script_wrappable.h" +#include "bindings/qjs/atom_string.h" #include "core/executing_context.h" #include "foundation/native_string.h" @@ -68,12 +69,17 @@ class Event : public ScriptWrappable { enum PhaseType { kNone = 0, kCapturingPhase = 1, kAtTarget = 2, kBubblingPhase = 3 }; static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; + static Event* Create(ExecutingContext* context, const AtomicString& type) { + return makeGarbageCollected(context, type); + }; + static Event* From(ExecutingContext* context, NativeEvent* native_event) { return makeGarbageCollected(context, native_event); } Event() = delete; explicit Event(ExecutingContext* context); + explicit Event(ExecutingContext* context, const AtomicString& event_type); explicit Event(ExecutingContext* context, NativeEvent* native_event); const char* GetHumanReadableName() const override; @@ -82,7 +88,7 @@ class Event : public ScriptWrappable { double timeStamp() { return time_stamp_; } bool propagationImmediatelyStopped(ExceptionState& exception_state) { return immediate_propagation_stopped_; } bool cancelable() const { return cancelable_; } - FORCE_INLINE NativeString* type() { return type_; }; + const AtomicString& type() { return type_; }; void SetType(NativeString* type); EventTarget* target() const; void SetTarget(EventTarget* target); @@ -130,7 +136,7 @@ class Event : public ScriptWrappable { void Dispose() const override; protected: - NativeString* type_{nullptr}; + AtomicString type_; unsigned bubbles_ : 1; unsigned cancelable_ : 1; diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h index 435af100b2..86bbda63cd 100644 --- a/bridge/core/dom/events/event_listener.h +++ b/bridge/core/dom/events/event_listener.h @@ -11,6 +11,8 @@ namespace kraken { +class JSBasedEventListener; + // EventListener represents 'callback' in 'event listener' in DOM standard. // https://dom.spec.whatwg.org/#concept-event-listener // @@ -48,6 +50,8 @@ class EventListener { private: EventListener() = default; + + friend JSBasedEventListener; }; } // namespace kraken diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index a4010533fd..e2b2433630 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -43,7 +43,7 @@ static bool RemoveListenerFromVector(EventListenerVector* listener_vector, return true; } -bool EventListenerMap::Contains(const AtomString& event_type) const { +bool EventListenerMap::Contains(const AtomicString& event_type) const { for (const auto& entry : entries_) { if (entry.first == event_type) return true; @@ -51,13 +51,13 @@ bool EventListenerMap::Contains(const AtomString& event_type) const { return false; } -bool EventListenerMap::ContainsCapturing(const AtomString& event_type) const {} +bool EventListenerMap::ContainsCapturing(const AtomicString& event_type) const {} void EventListenerMap::Clear() { entries_.clear(); } -bool EventListenerMap::Add(const AtomString& event_type, +bool EventListenerMap::Add(const AtomicString& event_type, const std::shared_ptr& listener, const std::shared_ptr& options, RegisteredEventListener* registered_event_listener) { @@ -70,7 +70,7 @@ bool EventListenerMap::Add(const AtomString& event_type, return AddListenerToVector(entries_.back().second.get(), listener, options, registered_event_listener); } -bool EventListenerMap::Remove(const AtomString& event_type, +bool EventListenerMap::Remove(const AtomicString& event_type, const std::shared_ptr& listener, const std::shared_ptr& options, size_t* index_of_removed_listener, @@ -88,7 +88,7 @@ bool EventListenerMap::Remove(const AtomString& event_type, return false; } -const EventListenerVector* EventListenerMap::Find(const AtomString& event_type) { +const EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) { for (const auto& entry : entries_) { if (entry.first == event_type) return entry.second.get(); diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index 4746c3a6bd..97b83a2ee8 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -28,16 +28,16 @@ class EventListenerMap final { EventListenerMap& operator=(const EventListenerMap&) = delete; bool IsEmpty() const { return entries_.empty(); } - bool Contains(const AtomString& event_type) const; - bool ContainsCapturing(const AtomString& event_type) const; + bool Contains(const AtomicString& event_type) const; + bool ContainsCapturing(const AtomicString& event_type) const; void Clear(); - bool Add(const AtomString& event_type, const std::shared_ptr& listener, const std::shared_ptr& options, RegisteredEventListener* registered_event_listener); - bool Remove(const AtomString& event_type, + bool Add(const AtomicString& event_type, const std::shared_ptr& listener, const std::shared_ptr& options, RegisteredEventListener* registered_event_listener); + bool Remove(const AtomicString& event_type, const std::shared_ptr& listener, const std::shared_ptr& options, size_t* index_of_removed_listener, RegisteredEventListener* registered_event_listener); - const EventListenerVector* Find(const AtomString& event_type); + const EventListenerVector* Find(const AtomicString& event_type); private: // EventListener handlers registered with addEventListener API. @@ -45,7 +45,7 @@ class EventListenerMap final { // - 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>> entries_; + std::vector>> entries_; }; } // namespace kraken diff --git a/bridge/core/dom/events/event_type_names.json b/bridge/core/dom/events/event_type_names.json new file mode 100644 index 0000000000..85b7ff5cb5 --- /dev/null +++ b/bridge/core/dom/events/event_type_names.json @@ -0,0 +1,216 @@ +{ + "annotation": "Simplified from https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/events/event_type_names.json5", + "metadata": { + "templates": ["make_names", "qjs_atom"] + }, + "data": [ + "DOMActivate", + "DOMCharacterDataModified", + "DOMContentLoaded", + "DOMFocusIn", + "DOMFocusOut", + "DOMNodeInserted", + "DOMNodeInsertedIntoDocument", + "DOMNodeRemoved", + "DOMNodeRemovedFromDocument", + "DOMSubtreeModified", + "abort", + "abortpayment", + "activate", + "active", + "addsourcebuffer", + "addtrack", + "animationcancel", + "animationend", + "animationiteration", + "animationstart", + "backgroundfetchabort", + "backgroundfetchclick", + "backgroundfetchfail", + "backgroundfetchsuccess", + "beforeunload", + "beginEvent", + "blocked", + "blur", + "boundary", + "cached", + "cancel", + "canplay", + "canplaythrough", + "capturehandlechange", + "change", + "checking", + "click", + "close", + "closing", + "complete", + "compositionend", + "compositionstart", + "compositionupdate", + "connect", + "contextlost", + "contextmenu", + "contextrestored", + "controllerchange", + "cookiechange", + "copy", + "contentdelete", + "crossoriginmessage", + "currentscreenchange", + "cuechange", + "currententrychange", + "cut", + "datachannel", + "dblclick", + "defaultsessionstart", + "disconnect", + "display", + "drop", + "durationchange", + "emptied", + "encrypted", + "end", + "ended", + "endEvent", + "enter", + "error", + "exit", + "fetch", + "finish", + "focus", + "focusin", + "focusout", + "freeze", + "fullscreenchange", + "fullscreenerror", + "hashchange", + "hide", + "inactive", + "input", + "inputreport", + "inputsourceschange", + "install", + "interfacerequest", + "invalid", + "keydown", + "keypress", + "keystatuseschange", + "keyup", + "languagechange", + "leavepictureinpicture", + "levelchange", + "load", + "loadeddata", + "loadedmetadata", + "loadend", + "loading", + "loadstart", + "lostpointercapture", + "mark", + "message", + "messageerror", + "mousedown", + "mouseenter", + "mouseleave", + "mousemove", + "mouseout", + "mouseover", + "mouseup", + "mousewheel", + "mute", + "navigate", + "navigateerror", + "navigatesuccess", + "noupdate", + "open", + "orientationchange", + "overscroll", + "pagehide", + "pageshow", + "paste", + "pause", + "play", + "playing", + "pointercancel", + "pointerdown", + "pointerenter", + "pointerleave", + "pointerlockchange", + "pointerlockerror", + "pointermove", + "pointerout", + "pointerover", + "pointerup", + "popstate", + "progress", + "processorerror", + "push", + "pushsubscriptionchange", + "ratechange", + "reading", + "readingerror", + "readystatechange", + "reflectionchange", + "rejectionhandled", + "release", + "remove", + "removestream", + "removetrack", + "repeatEvent", + "reset", + "resize", + "result", + "resume", + "screenschange", + "scroll", + "scrollend", + "search", + "seeked", + "seeking", + "select", + "selectionchange", + "selectstart", + "show", + "squeeze", + "squeezeend", + "squeezestart", + "stalled", + "start", + "stop", + "statechange", + "storage", + "submit", + "success", + "suspend", + "sync", + "terminate", + "textInput", + "textupdate", + "textformatupdate", + "toggle", + "tonechange", + "touchcancel", + "touchend", + "touchmove", + "touchstart", + "transitioncancel", + "transitionend", + "transitionrun", + "transitionstart", + "typechange", + "uncapturederror", + "unhandledrejection", + "unload", + "unmute", + "update", + "versionchange", + "visibilitychange", + "waiting", + "waitingforkey", + "webglcontextcreationerror", + "webglcontextlost", + "webglcontextrestored", + "wheel", + "zoom" + ] +} diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index a13f282870..ffe4529fa1 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -78,7 +78,7 @@ function genCodeFromJSONData() { let blob = blobs[i]; blob.json.metadata.templates.forEach((targetTemplate) => { let targetTemplateHeaderData = templates.find(t => t.filename === targetTemplate + '.h'); - let targetTemplateBodyData = templates.find(t => t.filename === targetTemplate + '.h'); + let targetTemplateBodyData = templates.find(t => t.filename === targetTemplate + '.cc'); let result = generateJSONTemplate(blobs[i], targetTemplateHeaderData, targetTemplateBodyData); let dist = blob.dist; diff --git a/bridge/scripts/code_generator/src/json/generator.ts b/bridge/scripts/code_generator/src/json/generator.ts index 4669ff922b..4cdad33792 100644 --- a/bridge/scripts/code_generator/src/json/generator.ts +++ b/bridge/scripts/code_generator/src/json/generator.ts @@ -13,7 +13,12 @@ function generateHeader(blob: JSONBlob, template: Template): string { } function generateBody(blob: JSONBlob, template: Template): string { - + let compiled = _.template(template.raw); + return compiled({ + template_path: blob.source, + name: blob.filename, + data: blob.json.data + }); } export function generateJSONTemplate(blob: JSONBlob, headerTemplate: Template, bodyTemplate?: Template) { diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index db75f70e58..a9a19b15c4 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -3,21 +3,35 @@ // and input files: // <%= template_path %> +#include "<%= name %>.h" -#ifndef <%= _.snakeCase(name).toUpperCase() %>_H_ -#define <%= _.snakeCase(name).toUpperCase() %>_H_ +namespace kraken { +namespace event_type_names { -#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h" -#include "third_party/blink/renderer/core/core_export.h" +void* names_storage[kNamesCount * ((sizeof(AtomicString) + sizeof(void *) - 1) / sizeof(void *))]; -namespace kraken { +<% _.forEach(data, function(name, index) { %>const AtomicString& k<%= name[0].toUpperCase() + name.slice(1) %> = reinterpret_cast(&names_storage)[<%= index %>]; +<% }) %> -extern const WTF::AtomicString& kAbort; +void Init() { + static bool is_loaded = false; + if (is_loaded) return; + is_loaded = true; -constexpr unsigned kNamesCount = 352; + struct NameEntry { + JSAtom atom; + }; -void Init(); + static const NameEntry kNames[] = { + <% _.forEach(data, function(name) { %>{ JS_ATOM_<%= name %> }, + <% }); %> + }; -} // kraken + for(size_t i = 0; i < std::size(kNames); i ++) { + void* address = reinterpret_cast(&names_storage) + i; + new (address) PersistentAtomicString(kNames[i].atom); + } +}; -#endif // #define <%= _.snakeCase(name).toUpperCase() %> +} +} // kraken diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl index 718835096b..4417d607f4 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl @@ -10,15 +10,18 @@ #include "bindings/qjs/atom_string.h" namespace kraken { +namespace event_type_names { <% _.forEach(data, function(name, index) { %> -extern const AtomicString& k<%= name[0].toUpperCase() + name.slice(1) %>; -<% }) %> + extern const AtomicString& k<%= name[0].toUpperCase() + name.slice(1) %>; + <% }) %> constexpr unsigned kNamesCount = <%= data.length %>; void Init(); +} + } // kraken #endif // #define <%= _.snakeCase(name).toUpperCase() %> diff --git a/bridge/third_party/quickjs/event_type_names.h b/bridge/third_party/quickjs/event_type_names.h index ceb1cf8466..53b41d7082 100644 --- a/bridge/third_party/quickjs/event_type_names.h +++ b/bridge/third_party/quickjs/event_type_names.h @@ -50,32 +50,14 @@ DEF(abort, "abort") DEF(abortpayment, "abortpayment") -DEF(accessibleclick, "accessibleclick") - -DEF(accessiblecontextmenu, "accessiblecontextmenu") - -DEF(accessibledecrement, "accessibledecrement") - -DEF(accessiblefocus, "accessiblefocus") - -DEF(accessibleincrement, "accessibleincrement") - -DEF(accessiblescrollintoview, "accessiblescrollintoview") - DEF(activate, "activate") DEF(active, "active") DEF(addsourcebuffer, "addsourcebuffer") -DEF(addstream, "addstream") - DEF(addtrack, "addtrack") -DEF(advertisementreceived, "advertisementreceived") - -DEF(afterprint, "afterprint") - DEF(animationcancel, "animationcancel") DEF(animationend, "animationend") @@ -84,18 +66,6 @@ DEF(animationiteration, "animationiteration") DEF(animationstart, "animationstart") -DEF(appinstalled, "appinstalled") - -DEF(audioend, "audioend") - -DEF(audioprocess, "audioprocess") - -DEF(audiostart, "audiostart") - -DEF(auxclick, "auxclick") - -DEF(availablechange, "availablechange") - DEF(backgroundfetchabort, "backgroundfetchabort") DEF(backgroundfetchclick, "backgroundfetchclick") @@ -104,26 +74,8 @@ DEF(backgroundfetchfail, "backgroundfetchfail") DEF(backgroundfetchsuccess, "backgroundfetchsuccess") -DEF(beforecopy, "beforecopy") - -DEF(beforecreatepolicy, "beforecreatepolicy") - -DEF(beforecut, "beforecut") - -DEF(beforeinput, "beforeinput") - -DEF(beforeinstallprompt, "beforeinstallprompt") - -DEF(beforematch, "beforematch") - -DEF(beforepaste, "beforepaste") - -DEF(beforeprint, "beforeprint") - DEF(beforeunload, "beforeunload") -DEF(beforexrselect, "beforexrselect") - DEF(beginEvent, "beginEvent") DEF(blocked, "blocked") @@ -132,14 +84,10 @@ DEF(blur, "blur") DEF(boundary, "boundary") -DEF(bufferedamountlow, "bufferedamountlow") - DEF(cached, "cached") DEF(cancel, "cancel") -DEF(canmakepayment, "canmakepayment") - DEF(canplay, "canplay") DEF(canplaythrough, "canplaythrough") @@ -148,14 +96,6 @@ DEF(capturehandlechange, "capturehandlechange") DEF(change, "change") -DEF(characterboundsupdate, "characterboundsupdate") - -DEF(characteristicvaluechanged, "characteristicvaluechanged") - -DEF(chargingchange, "chargingchange") - -DEF(chargingtimechange, "chargingtimechange") - DEF(checking, "checking") DEF(click, "click") @@ -174,12 +114,6 @@ DEF(compositionupdate, "compositionupdate") DEF(connect, "connect") -DEF(connecting, "connecting") - -DEF(connectionavailable, "connectionavailable") - -DEF(connectionstatechange, "connectionstatechange") - DEF(contextlost, "contextlost") DEF(contextmenu, "contextmenu") @@ -210,44 +144,14 @@ DEF(dblclick, "dblclick") DEF(defaultsessionstart, "defaultsessionstart") -DEF(devicechange, "devicechange") - -DEF(devicemotion, "devicemotion") - -DEF(deviceorientation, "deviceorientation") - -DEF(deviceorientationabsolute, "deviceorientationabsolute") - -DEF(dischargingtimechange, "dischargingtimechange") - DEF(disconnect, "disconnect") DEF(display, "display") -DEF(dispose, "dispose") - -DEF(downloading, "downloading") - -DEF(dataavailable, "dataavailable") - -DEF(drag, "drag") - -DEF(dragend, "dragend") - -DEF(dragenter, "dragenter") - -DEF(dragleave, "dragleave") - -DEF(dragover, "dragover") - -DEF(dragstart, "dragstart") - DEF(drop, "drop") DEF(durationchange, "durationchange") -DEF(elementtimingbufferfull, "elementtimingbufferfull") - DEF(emptied, "emptied") DEF(encrypted, "encrypted") @@ -260,12 +164,8 @@ DEF(endEvent, "endEvent") DEF(enter, "enter") -DEF(enterpictureinpicture, "enterpictureinpicture") - DEF(error, "error") -DEF(eventtimingbufferfull, "eventtimingbufferfull") - DEF(exit, "exit") DEF(fetch, "fetch") @@ -278,62 +178,16 @@ DEF(focusin, "focusin") DEF(focusout, "focusout") -DEF(foreignfetch, "foreignfetch") - -DEF(formdata, "formdata") - DEF(freeze, "freeze") DEF(fullscreenchange, "fullscreenchange") DEF(fullscreenerror, "fullscreenerror") -DEF(gamepadconnected, "gamepadconnected") - -DEF(gamepaddisconnected, "gamepaddisconnected") - -DEF(gatheringstatechange, "gatheringstatechange") - -DEF(gattserverdisconnected, "gattserverdisconnected") - -DEF(geofenceenter, "geofenceenter") - -DEF(geofenceleave, "geofenceleave") - -DEF(geometrychange, "geometrychange") - -DEF(gesturelongpress, "gesturelongpress") - -DEF(gesturescrollend, "gesturescrollend") - -DEF(gesturescrollstart, "gesturescrollstart") - -DEF(gesturescrollupdate, "gesturescrollupdate") - -DEF(gestureshowpress, "gestureshowpress") - -DEF(gesturetap, "gesturetap") - -DEF(gesturetapdown, "gesturetapdown") - -DEF(gesturetapunconfirmed, "gesturetapunconfirmed") - -DEF(gestureflingstart, "gestureflingstart") - -DEF(gotpointercapture, "gotpointercapture") - DEF(hashchange, "hashchange") DEF(hide, "hide") -DEF(icecandidate, "icecandidate") - -DEF(icecandidateerror, "icecandidateerror") - -DEF(iceconnectionstatechange, "iceconnectionstatechange") - -DEF(icegatheringstatechange, "icegatheringstatechange") - DEF(inactive, "inactive") DEF(input, "input") @@ -372,24 +226,16 @@ DEF(loadend, "loadend") DEF(loading, "loading") -DEF(loadingdone, "loadingdone") - -DEF(loadingerror, "loadingerror") - DEF(loadstart, "loadstart") DEF(lostpointercapture, "lostpointercapture") -DEF(managedconfigurationchange, "managedconfigurationchange") - DEF(mark, "mark") DEF(message, "message") DEF(messageerror, "messageerror") -DEF(midimessage, "midimessage") - DEF(mousedown, "mousedown") DEF(mouseenter, "mouseenter") @@ -414,24 +260,8 @@ DEF(navigateerror, "navigateerror") DEF(navigatesuccess, "navigatesuccess") -DEF(negotiationneeded, "negotiationneeded") - -DEF(nomatch, "nomatch") - -DEF(notificationclick, "notificationclick") - -DEF(notificationclose, "notificationclose") - -DEF(notificationerror, "notificationerror") - DEF(noupdate, "noupdate") -DEF(obsolete, "obsolete") - -DEF(offline, "offline") - -DEF(online, "online") - DEF(open, "open") DEF(orientationchange, "orientationchange") @@ -446,14 +276,6 @@ DEF(paste, "paste") DEF(pause, "pause") -DEF(payerdetailchange, "payerdetailchange") - -DEF(paymentmethodchange, "paymentmethodchange") - -DEF(paymentrequest, "paymentrequest") - -DEF(periodicsync, "periodicsync") - DEF(play, "play") DEF(playing, "playing") @@ -476,18 +298,10 @@ DEF(pointerout, "pointerout") DEF(pointerover, "pointerover") -DEF(pointerrawupdate, "pointerrawupdate") - DEF(pointerup, "pointerup") DEF(popstate, "popstate") -DEF(portalactivate, "portalactivate") - -DEF(prerenderingchange, "prerenderingchange") - -DEF(prioritychange, "prioritychange") - DEF(progress, "progress") DEF(processorerror, "processorerror") @@ -496,10 +310,6 @@ DEF(push, "push") DEF(pushsubscriptionchange, "pushsubscriptionchange") -DEF(quicstream, "quicstream") - -DEF(quotachange, "quotachange") - DEF(ratechange, "ratechange") DEF(reading, "reading") @@ -516,8 +326,6 @@ DEF(release, "release") DEF(remove, "remove") -DEF(removesourcebuffer, "removesourcebuffer") - DEF(removestream, "removestream") DEF(removetrack, "removetrack") @@ -526,18 +334,12 @@ DEF(repeatEvent, "repeatEvent") DEF(reset, "reset") -DEF(resetpose, "resetpose") - DEF(resize, "resize") -DEF(resourcetimingbufferfull, "resourcetimingbufferfull") - DEF(result, "result") DEF(resume, "resume") -DEF(samplebufferfull, "samplebufferfull") - DEF(screenschange, "screenschange") DEF(scroll, "scroll") @@ -546,46 +348,18 @@ DEF(scrollend, "scrollend") DEF(search, "search") -DEF(securitypolicyviolation, "securitypolicyviolation") - DEF(seeked, "seeked") DEF(seeking, "seeking") DEF(select, "select") -DEF(selectedcandidatepairchange, "selectedcandidatepairchange") - -DEF(selectend, "selectend") - DEF(selectionchange, "selectionchange") DEF(selectstart, "selectstart") -DEF(shippingaddresschange, "shippingaddresschange") - -DEF(shippingoptionchange, "shippingoptionchange") - DEF(show, "show") -DEF(signalingstatechange, "signalingstatechange") - -DEF(slotchange, "slotchange") - -DEF(soundend, "soundend") - -DEF(soundstart, "soundstart") - -DEF(sourceclose, "sourceclose") - -DEF(sourceended, "sourceended") - -DEF(sourceopen, "sourceopen") - -DEF(speechend, "speechend") - -DEF(speechstart, "speechstart") - DEF(squeeze, "squeeze") DEF(squeezeend, "squeezeend") @@ -618,12 +392,6 @@ DEF(textupdate, "textupdate") DEF(textformatupdate, "textformatupdate") -DEF(timeout, "timeout") - -DEF(timeupdate, "timeupdate") - -DEF(timezonechange, "timezonechange") - DEF(toggle, "toggle") DEF(tonechange, "tonechange") @@ -636,8 +404,6 @@ DEF(touchmove, "touchmove") DEF(touchstart, "touchstart") -DEF(track, "track") - DEF(transitioncancel, "transitioncancel") DEF(transitionend, "transitionend") @@ -658,38 +424,10 @@ DEF(unmute, "unmute") DEF(update, "update") -DEF(updateend, "updateend") - -DEF(updatefound, "updatefound") - -DEF(updateready, "updateready") - -DEF(updatestart, "updatestart") - -DEF(upgradeneeded, "upgradeneeded") - DEF(versionchange, "versionchange") DEF(visibilitychange, "visibilitychange") -DEF(voiceschanged, "voiceschanged") - -DEF(volumechange, "volumechange") - -DEF(vrdisplayconnect, "vrdisplayconnect") - -DEF(vrdisplaydisconnect, "vrdisplaydisconnect") - -DEF(vrdisplayactivate, "vrdisplayactivate") - -DEF(vrdisplaydeactivate, "vrdisplaydeactivate") - -DEF(vrdisplayblur, "vrdisplayblur") - -DEF(vrdisplayfocus, "vrdisplayfocus") - -DEF(vrdisplaypresentchange, "vrdisplaypresentchange") - DEF(waiting, "waiting") DEF(waitingforkey, "waitingforkey") @@ -700,34 +438,8 @@ DEF(webglcontextlost, "webglcontextlost") DEF(webglcontextrestored, "webglcontextrestored") -DEF(webkitAnimationEnd, "webkitAnimationEnd") - -DEF(webkitAnimationIteration, "webkitAnimationIteration") - -DEF(webkitAnimationStart, "webkitAnimationStart") - -DEF(webkitBeforeTextInserted, "webkitBeforeTextInserted") - -DEF(webkitEditableContentChanged, "webkitEditableContentChanged") - -DEF(webkitTransitionEnd, "webkitTransitionEnd") - -DEF(webkitfullscreenchange, "webkitfullscreenchange") - -DEF(webkitfullscreenerror, "webkitfullscreenerror") - -DEF(webkitspeechchange, "webkitspeechchange") - -DEF(webkitvisibilitychange, "webkitvisibilitychange") - DEF(wheel, "wheel") -DEF(write, "write") - -DEF(writeend, "writeend") - -DEF(writestart, "writestart") - DEF(zoom, "zoom") From 4ee852f24e3d1d7df76ddeac8cfda5970d6e2faf Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 31 Mar 2022 20:13:01 +0800 Subject: [PATCH 043/498] feat: support generate dictionary type code. --- bridge/CMakeLists.txt | 4 + bridge/bindings/qjs/atom_string.cc | 4 - bridge/bindings/qjs/atom_string.h | 74 +++--- bridge/bindings/qjs/converter_impl.h | 4 +- bridge/bindings/qjs/idl_type.h | 2 +- bridge/bindings/qjs/js_based_event_listener.h | 8 + bridge/bindings/qjs/js_event_handler.cc | 188 ++++++++------- bridge/bindings/qjs/js_event_handler.h | 4 + bridge/bindings/qjs/wrapper_type_info.h | 2 +- bridge/core/dom/events/close_event.d.ts | 1 - bridge/core/dom/events/error_event.cc | 15 ++ bridge/core/dom/events/error_event.h | 17 +- bridge/core/dom/events/error_event.ts | 10 + bridge/core/dom/events/error_event_init.d.ts | 7 + bridge/core/dom/events/event.cc | 54 +++-- bridge/core/dom/events/event.h | 18 +- bridge/core/dom/events/event_init.d.ts | 8 + bridge/core/dom/events/event_listener_map.cc | 13 +- bridge/core/dom/events/event_target.h | 2 + bridge/core/fileapi/blob.d.ts | 2 - bridge/core/html/html_canvas_element.d.ts | 3 - .../code_generator/bin/code_generator.js | 12 +- bridge/scripts/code_generator/global.d.ts | 8 + .../code_generator/src/idl/analyzer.ts | 9 + .../code_generator/src/idl/declaration.ts | 6 + .../code_generator/src/idl/generateHeader.ts | 114 ++++----- .../code_generator/src/idl/generateSource.ts | 228 +++++------------- .../src/json/{template.ts => JSONTemplate.ts} | 2 +- .../code_generator/src/json/generator.ts | 8 +- .../static/idl_templates/base.cc.tpl | 62 +++++ .../static/idl_templates/base.h.tpl | 15 ++ .../static/idl_templates/dictionary.cc.tpl | 43 ++++ .../static/idl_templates/dictionary.h.tpl | 30 +++ .../idl_templates/global_function.cc.tpl | 3 + .../idl_templates/global_function.h.tpl | 14 ++ .../static/idl_templates/interface.cc.tpl | 30 +++ .../static/idl_templates/interface.h.tpl | 23 ++ .../static/json_templates/make_names.cc.tpl | 2 +- bridge/scripts/code_generator/tsconfig.json | 3 +- bridge/tsconfig.json | 25 ++ 40 files changed, 664 insertions(+), 413 deletions(-) create mode 100644 bridge/core/dom/events/error_event.ts create mode 100644 bridge/core/dom/events/error_event_init.d.ts create mode 100644 bridge/core/dom/events/event_init.d.ts create mode 100644 bridge/scripts/code_generator/global.d.ts rename bridge/scripts/code_generator/src/json/{template.ts => JSONTemplate.ts} (93%) create mode 100644 bridge/scripts/code_generator/static/idl_templates/base.cc.tpl create mode 100644 bridge/scripts/code_generator/static/idl_templates/base.h.tpl create mode 100644 bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl create mode 100644 bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl create mode 100644 bridge/scripts/code_generator/static/idl_templates/global_function.cc.tpl create mode 100644 bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl create mode 100644 bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl create mode 100644 bridge/scripts/code_generator/static/idl_templates/interface.h.tpl create mode 100644 bridge/tsconfig.json diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index d82b3ebb34..c4bae71507 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -311,6 +311,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_blob.h out/qjs_event.cc out/qjs_event.h + out/qjs_error_event.h + out/qjs_error_event.cc + out/qjs_event_init.h + out/qjs_event_init.cc out/qjs_event_target.cc out/qjs_event_target.h out/event_type_names.h diff --git a/bridge/bindings/qjs/atom_string.cc b/bridge/bindings/qjs/atom_string.cc index 2f0fcd5aaa..8c483613d2 100644 --- a/bridge/bindings/qjs/atom_string.cc +++ b/bridge/bindings/qjs/atom_string.cc @@ -7,9 +7,5 @@ namespace kraken { -JSValue StaticAtomicString::ToQuickJS(JSContext* ctx) const { - return JS_AtomToValue(ctx, atom_); -} - } // namespace kraken diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index 48eef3478a..53d34313ff 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -11,6 +11,7 @@ #include "foundation/macros.h" #include "foundation/native_string.h" #include "native_string_utils.h" +#include "qjs_engine_patch.h" namespace kraken { @@ -20,77 +21,60 @@ namespace kraken { // two String instances because we just check string storage identity. class AtomicString { public: + static AtomicString Empty(JSContext* ctx) { return AtomicString(ctx, JS_ATOM_NULL); }; + static 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; + }; + AtomicString() = default; - AtomicString(JSAtom atom) : atom_(atom) {} + AtomicString(JSContext *ctx, JSAtom atom) : ctx_(ctx), atom_(atom) {}; + AtomicString(JSContext* ctx, const std::string& string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())) {}; + AtomicString(JSContext* ctx, JSValue value) : ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)) {}; + AtomicString(JSAtom atom): atom_(atom), is_static_atom_(true) {}; // Return the undefined string value from atom key. - virtual JSValue ToQuickJS(JSContext* ctx) const = 0; - - bool operator==(const AtomicString& other) const { return other.atom_ == this->atom_; } - - protected: - JSAtom atom_{JS_ATOM_NULL}; -}; - -// AtomicString which holding quickjs built-in atoms string. -// These string are stored in JSRuntime instead of JSContext. -// So it can be used by any JSContext and don't needs to be freed. -class PersistentAtomicString : public AtomicString { - public: - PersistentAtomicString(JSAtom atom): AtomicString(atom) {}; - - JSValue ToQuickJS(JSContext* ctx) const override; -}; - -// PeriodicAtomicString holding string atom key created by JSContext. -// Could be freed when string refer_count set to 0. -class PeriodicAtomicString : public AtomicString { - // Should only allocate on stack. - KRAKEN_DISALLOW_NEW(); - - public: - static PeriodicAtomicString Empty(JSContext* ctx) { return PeriodicAtomicString(ctx); }; - - explicit PeriodicAtomicString(JSContext* ctx) : ctx_(ctx), AtomicString(JS_ATOM_NULL) {} - explicit PeriodicAtomicString(JSContext* ctx, const std::string& string) : ctx_(ctx), AtomicString(JS_NewAtom(ctx, string.c_str())) {} - explicit PeriodicAtomicString(JSContext* ctx, JSAtom atom) : ctx_(ctx), AtomicString(JS_DupAtom(ctx, atom)) {}; - explicit PeriodicAtomicString(JSContext* ctx, JSValue value) : ctx_(ctx), AtomicString(JS_ValueToAtom(ctx, value)){}; - ~PeriodicAtomicString() { JS_FreeAtom(ctx_, atom_); } - - JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); } + JSValue ToQuickJS(JSContext* ctx) const { + return JS_AtomToValue(ctx, atom_); + }; // Copy assignment - PeriodicAtomicString(PeriodicAtomicString const& value) { - if (&value != this) { + AtomicString(AtomicString const& value) { + if (!is_static_atom_ && &value != this) { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; }; - PeriodicAtomicString& operator=(const PeriodicAtomicString& other) { - if (&other != this) { + AtomicString& operator=(const AtomicString& other) { + if (!is_static_atom_ && &other != this) { atom_ = JS_DupAtom(ctx_, other.atom_); } return *this; }; // Move assignment - PeriodicAtomicString(PeriodicAtomicString&& value) noexcept { - if (&value != this) { + AtomicString(AtomicString&& value) noexcept { + if (!is_static_atom_ && &value != this) { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; }; - PeriodicAtomicString& operator=(PeriodicAtomicString&& value) noexcept { - if (&value != this) { + AtomicString& operator=(AtomicString&& value) noexcept { + if (!is_static_atom_ && &value != this) { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; return *this; } - private: - PeriodicAtomicString() = delete; + bool operator==(const AtomicString& other) const { return other.atom_ == this->atom_; } + + protected: + bool is_static_atom_ = false; JSContext* ctx_{nullptr}; + JSAtom atom_{JS_ATOM_NULL}; }; } // namespace kraken diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 73fff320d6..fc0de2948b 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -178,7 +178,7 @@ struct Converter : public ConverterBase { return AtomicString(ctx, value); } - static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(); } + 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); } @@ -341,7 +341,7 @@ struct Converter : public ConverterBase { return toScriptWrappable(value); } - static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } + static JSValue ToValue(JSContext* ctx, ImplType value) { return reinterpret_cast(value)->ToQuickJS(); } }; } // namespace kraken diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 42bc6521db..4f20bc1afb 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -50,7 +50,7 @@ class NativeString; struct IDLDOMString final : public IDLTypeBaseHelper {}; // https://developer.mozilla.org/en-US/docs/Web/API/USVString -struct IDLUSVString final : public IDLTypeBaseHelper {}; +struct IDLUSVString final : public IDLTypeBaseHelper {}; // Object struct IDLObject : public IDLTypeBaseHelper {}; diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index e9c0960ed9..3bc162f113 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -21,6 +21,14 @@ class JSBasedEventListener : public EventListener { // Implements step 2. of "inner invoke". // See: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke void Invoke(ExecutingContext* context, Event* event) final; + + // Implements "get the current value of the event handler". + // https://html.spec.whatwg.org/C/#getting-the-current-value-of-the-event-handler + // Returns v8::Null with firing error event instead of throwing an exception + // on failing to compile the uncompiled script body in eventHandler's value. + // Also, this can return empty because of crbug.com/881688 . + virtual JSValue GetListenerObject(EventTarget&) = 0; + // Returns Functions that handles invoked event or undefined without // throwing any exception. virtual JSValue GetEffectiveFunction(EventTarget&) = 0; diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index e25545abdd..d15f0c2738 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -5,6 +5,8 @@ #include "js_event_handler.h" #include "core/dom/events/error_event.h" +#include "core/dom/events/event_target.h" +#include "event_type_names.h" namespace kraken { @@ -22,6 +24,13 @@ bool JSEventHandler::Matches(const EventListener& other) const { // https://html.spec.whatwg.org/C/#the-event-handler-processing-algorithm void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, ExceptionState& exception_state) { + // Step 1. Let callback be the result of getting the current value of the + // event handler given eventTarget and name. + // Step 2. If callback is null, then return. + JSValue listener_value = GetListenerObject(*event.currentTarget()); + if (JS_IsNull(listener_value)) + return; + // Step 3. Let special error event handling be true if event is an ErrorEvent // object, event's type is error, and event's currentTarget implements the // WindowOrWorkerGlobalScope mixin. Otherwise, let special error event @@ -46,87 +55,86 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // 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.) - HeapVector arguments; - ScriptState* script_state_of_listener = - event_handler_->CallbackRelevantScriptState(); - v8::Isolate* isolate = script_state_of_listener->GetIsolate(); + + 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(script_state_of_listener); - if (error_attribute.IsEmpty() || - error_event->target()->InterfaceName() == event_target_names::kWorker) { - error_attribute = ScriptValue::CreateNull(isolate); - } - arguments = { - ScriptValue(isolate, - ToV8Traits::ToV8(script_state_of_listener, - error_event->message()) - .ToLocalChecked()), - ScriptValue(isolate, - ToV8Traits::ToV8(script_state_of_listener, - error_event->filename()) - .ToLocalChecked()), - ScriptValue(isolate, - ToV8Traits::ToV8(script_state_of_listener, - error_event->lineno()) - .ToLocalChecked()), - ScriptValue(isolate, ToV8Traits::ToV8( - script_state_of_listener, error_event->colno()) - .ToLocalChecked()), - error_attribute}; +// ScriptValue error_attribute = error_event->error(script_state_of_listener); +// if (error_attribute.IsEmpty() || +// error_event->target()->InterfaceName() == event_target_names::kWorker) { +// error_attribute = ScriptValue::CreateNull(isolate); +// } +// arguments = { +// ScriptValue(isolate, +// ToV8Traits::ToV8(script_state_of_listener, +// error_event->message()) +// .ToLocalChecked()), +// ScriptValue(isolate, +// ToV8Traits::ToV8(script_state_of_listener, +// error_event->filename()) +// .ToLocalChecked()), +// ScriptValue(isolate, +// ToV8Traits::ToV8(script_state_of_listener, +// error_event->lineno()) +// .ToLocalChecked()), +// ScriptValue(isolate, ToV8Traits::ToV8( +// script_state_of_listener, error_event->colno()) +// .ToLocalChecked()), +// error_attribute}; } else { - arguments.push_back(ScriptValue(isolate, js_event)); + arguments.emplace_back(ctx, event.ToQuickJS()); } - if (!event_handler_->IsRunnableOrThrowException( - event.ShouldDispatchEvenWhenExecutionContextIsPaused() - ? V8EventHandlerNonNull::IgnorePause::kIgnore - : V8EventHandlerNonNull::IgnorePause::kDontIgnore)) { - return; - } - ScriptValue result; - if (!event_handler_ - ->InvokeWithoutRunnabilityCheck(event.currentTarget(), arguments) - .To(&result) || - isolate->IsExecutionTerminating()) - return; - v8::Local v8_return_value = result.V8Value(); - - // There is nothing to do if |v8_return_value| is null or undefined. - // See Step 5. for more information. - if (v8_return_value->IsNullOrUndefined()) - 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. - String result_for_beforeunload; - if (IsOnBeforeUnloadEventHandler()) { - event_handler_->EvaluateAsPartOfCallback(Bind( - [](v8::Local& v8_return_value, - String& result_for_beforeunload) { - // TODO(yukiy): use |NativeValueTraits|. - V8StringResource native_result( - v8_return_value); - - // |native_result.Prepare()| throws exception if it fails to convert - // |native_result| to String. - if (!native_result.Prepare()) - return; - result_for_beforeunload = native_result; - }, - std::ref(v8_return_value), std::ref(result_for_beforeunload))); - if (!result_for_beforeunload) - return; - } +// if (!event_handler_->IsRunnableOrThrowException( +// event.ShouldDispatchEvenWhenExecutionContextIsPaused() +// ? V8EventHandlerNonNull::IgnorePause::kIgnore +// : V8EventHandlerNonNull::IgnorePause::kDontIgnore)) { +// return; +// } +// ScriptValue result; +// if (!event_handler_ +// ->InvokeWithoutRunnabilityCheck(event.currentTarget(), arguments) +// .To(&result) || +// isolate->IsExecutionTerminating()) +// return; +// v8::Local v8_return_value = result.V8Value(); +// +// // There is nothing to do if |v8_return_value| is null or undefined. +// // See Step 5. for more information. +// if (v8_return_value->IsNullOrUndefined()) +// 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. +// String result_for_beforeunload; +// if (IsOnBeforeUnloadEventHandler()) { +// event_handler_->EvaluateAsPartOfCallback(Bind( +// [](v8::Local& v8_return_value, +// String& result_for_beforeunload) { +// // TODO(yukiy): use |NativeValueTraits|. +// V8StringResource native_result( +// v8_return_value); +// +// // |native_result.Prepare()| throws exception if it fails to convert +// // |native_result| to String. +// if (!native_result.Prepare()) +// return; +// result_for_beforeunload = native_result; +// }, +// std::ref(v8_return_value), std::ref(result_for_beforeunload))); +// if (!result_for_beforeunload) +// return; +// } // Step 5. Process return value as follows: // If event is a BeforeUnloadEvent object and event's type is beforeunload @@ -143,23 +151,23 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // then return value will never be false, since in such cases // return value will have been coerced into either null or a // DOMString. - auto* before_unload_event = DynamicTo(&event); - const bool is_beforeunload_event = - before_unload_event && event.type() == event_type_names::kBeforeunload; - if (is_beforeunload_event) { - if (result_for_beforeunload) { - event.preventDefault(); - if (before_unload_event->returnValue().IsEmpty()) - before_unload_event->setReturnValue(result_for_beforeunload); - } - } else if (!IsOnBeforeUnloadEventHandler()) { - if (special_error_event_handling && v8_return_value->IsBoolean() && - v8_return_value.As()->Value()) - event.preventDefault(); - else if (!special_error_event_handling && v8_return_value->IsBoolean() && - !v8_return_value.As()->Value()) - event.preventDefault(); - } +// auto* before_unload_event = DynamicTo(&event); +// const bool is_beforeunload_event = +// before_unload_event && event.type() == event_type_names::kBeforeunload; +// if (is_beforeunload_event) { +// if (result_for_beforeunload) { +// event.preventDefault(); +// if (before_unload_event->returnValue().IsEmpty()) +// before_unload_event->setReturnValue(result_for_beforeunload); +// } +// } else if (!IsOnBeforeUnloadEventHandler()) { +// if (special_error_event_handling && v8_return_value->IsBoolean() && +// v8_return_value.As()->Value()) +// event.preventDefault(); +// else if (!special_error_event_handling && v8_return_value->IsBoolean() && +// !v8_return_value.As()->Value()) +// event.preventDefault(); +// } } } diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index 5727a7eb17..4ebd156e08 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -35,6 +35,10 @@ class JSEventHandler : public JSBasedEventListener { explicit JSEventHandler(const std::shared_ptr& event_handler, HandlerType type): type_(type), event_handler_(event_handler) {}; + JSValue GetListenerObject(EventTarget&) { + return event_handler_->ToQuickJS(); + } + JSValue GetEffectiveFunction(EventTarget&) { return event_handler_->ToQuickJS(); } diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index b43729aa4b..5d03376bc0 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -12,7 +12,7 @@ namespace kraken { -enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB, JS_CLASS_EVENT, JS_CLASS_EVENTTARGET }; +enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB, JS_CLASS_EVENT, JS_CLASS_ERROREVENT, JS_CLASS_EVENTTARGET }; // 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 diff --git a/bridge/core/dom/events/close_event.d.ts b/bridge/core/dom/events/close_event.d.ts index 6bf9423039..7b5a552c91 100644 --- a/bridge/core/dom/events/close_event.d.ts +++ b/bridge/core/dom/events/close_event.d.ts @@ -1,4 +1,3 @@ -type int64 = number; interface CloseEvent extends Event { readonly code: int64; readonly reason: string; diff --git a/bridge/core/dom/events/error_event.cc b/bridge/core/dom/events/error_event.cc index db1bafeca7..bd175bab7f 100644 --- a/bridge/core/dom/events/error_event.cc +++ b/bridge/core/dom/events/error_event.cc @@ -4,3 +4,18 @@ */ #include "error_event.h" + +namespace kraken { + +ErrorEventInit::ErrorEventInit(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + FillMembersWithQJSObject(ctx, value, exception_state); +} + +bool ErrorEventInit::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { + return false; +} + +void ErrorEventInit::FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state) {} + +} + diff --git a/bridge/core/dom/events/error_event.h b/bridge/core/dom/events/error_event.h index 5ced2fe109..19130af1ac 100644 --- a/bridge/core/dom/events/error_event.h +++ b/bridge/core/dom/events/error_event.h @@ -7,12 +7,27 @@ #define KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ #include "event.h" +#include "bindings/qjs/dictionary_base.h" namespace kraken { -class ErrorEvent : public Event { +class ErrorEventInit : public DictionaryBase { public: + static std::shared_ptr Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + + }; + ErrorEventInit() = delete; + ErrorEventInit(JSContext* ctx, JSValue value, ExceptionState& exception_state); + + bool FillQJSObjectWithMembers(JSContext *ctx, JSValue qjs_dictionary) const override; + void FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state); +}; + +class ErrorEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + public: + static ErrorEvent* Create(ExecutingContext* context, const AtomicString& event_type, ExceptionState& exception_state); private: }; diff --git a/bridge/core/dom/events/error_event.ts b/bridge/core/dom/events/error_event.ts new file mode 100644 index 0000000000..c94949c2dc --- /dev/null +++ b/bridge/core/dom/events/error_event.ts @@ -0,0 +1,10 @@ +import {ErrorEventInit} from "./error_event_init"; + +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/dom/events/error_event_init.d.ts b/bridge/core/dom/events/error_event_init.d.ts new file mode 100644 index 0000000000..1b98ff8445 --- /dev/null +++ b/bridge/core/dom/events/error_event_init.d.ts @@ -0,0 +1,7 @@ +import {EventInit} from "./event_init"; + +// @ts-ignore +@Dictionary() +export interface ErrorEventInit extends EventInit { + readonly error: any; +} diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index a08c054d66..9bbb99c7a8 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -9,32 +9,46 @@ namespace kraken { -Event::Event(ExecutingContext* context) : Event(context, nullptr) {} +Event* Event::From(ExecutingContext* context, NativeEvent* native_event) { + AtomicString event_type = AtomicString::From(context->ctx(), native_event->type); + + auto* event = makeGarbageCollected(context, event_type, native_event->bubbles == 0 ? Bubbles::kNo : Bubbles::kYes, native_event->cancelable == 0 ? Cancelable::kNo : Cancelable::kYes, + ComposedMode::kComposed, native_event->timeStamp); + event->SetTarget(static_cast(native_event->target)); + event->SetCurrentTarget(static_cast(native_event->currentTarget)); + event->default_prevented_ = native_event->defaultPrevented; + return event; +} + +Event::Event(ExecutingContext* context) : type_(AtomicString::Empty(context->ctx())), ScriptWrappable(context->ctx()) {} -Event::Event(ExecutingContext* context, const AtomicString& event_type): type_(event_type), ScriptWrappable(context->ctx()) {} +Event::Event(ExecutingContext* context, const AtomicString& event_type) : type_(event_type), ScriptWrappable(context->ctx()) {} -Event::Event(ExecutingContext* context, NativeEvent* native_event) +Event::Event(ExecutingContext* context, const AtomicString& event_type, Bubbles bubbles, Cancelable cancelable, ComposedMode composed_mode, double time_stamp) : ScriptWrappable(context->ctx()), -#if ANDROID_32_BIT - type_(reinterpret_cast(nativeEvent->type)), - target_(reinterpret_cast(native_event->target)), - current_target_(reinterpret_cast(native_event->currentTarget)), -#else - type_(), - target_(static_cast(native_event->target)), - current_target_(static_cast(native_event->currentTarget)), -#endif - bubbles_(native_event->bubbles), - cancelable_(native_event->cancelable), - time_stamp_(static_cast(native_event->timeStamp)), - default_prevented_(native_event->defaultPrevented) { -} + 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), + prevent_default_called_on_uncancelable_event_(false), + legacy_did_listeners_throw_flag_(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) {} const char* Event::GetHumanReadableName() const { return "Event"; } -void Event::SetType(NativeString* type) { +void Event::SetType(const AtomicString& type) { type_ = type; } @@ -62,7 +76,7 @@ void Event::preventDefault(ExceptionState& exception_state) { default_prevented_ = true; } -void Event::initEvent(std::unique_ptr& event_type, bool bubbles, bool cancelable, ExceptionState& exception_state) { +void Event::initEvent(const AtomicString& event_type, bool bubbles, bool cancelable, ExceptionState& exception_state) { if (IsBeingDispatched()) { return; } @@ -72,7 +86,7 @@ void Event::initEvent(std::unique_ptr& event_type, bool bubbles, b immediate_propagation_stopped_ = false; default_prevented_ = false; - type_ = event_type->clone(); + type_ = event_type; bubbles_ = bubbles; cancelable_ = cancelable; } diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 7f0ee1bee2..d51c3beeb6 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -66,6 +66,11 @@ class Event : public ScriptWrappable { kYes, }; + enum class ComposedMode { + kComposed, + kScoped, + }; + enum PhaseType { kNone = 0, kCapturingPhase = 1, kAtTarget = 2, kBubblingPhase = 3 }; static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; @@ -73,14 +78,12 @@ class Event : public ScriptWrappable { return makeGarbageCollected(context, type); }; - static Event* From(ExecutingContext* context, NativeEvent* native_event) { - return makeGarbageCollected(context, native_event); - } + static Event* From(ExecutingContext* context, NativeEvent* native_event); Event() = delete; explicit Event(ExecutingContext* context); explicit Event(ExecutingContext* context, const AtomicString& event_type); - explicit Event(ExecutingContext* context, NativeEvent* native_event); + explicit Event(ExecutingContext* context, const AtomicString& event_type, Bubbles bubbles, Cancelable cancelable, ComposedMode composed_mode, double timeStamp); const char* GetHumanReadableName() const override; bool propagationStopped() const { return propagation_stopped_; } @@ -89,7 +92,7 @@ class Event : public ScriptWrappable { bool propagationImmediatelyStopped(ExceptionState& exception_state) { return immediate_propagation_stopped_; } bool cancelable() const { return cancelable_; } const AtomicString& type() { return type_; }; - void SetType(NativeString* type); + void SetType(const AtomicString& type); EventTarget* target() const; void SetTarget(EventTarget* target); EventTarget* currentTarget() const; @@ -114,7 +117,7 @@ class Event : public ScriptWrappable { 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(std::unique_ptr& event_type, bool bubbles, bool cancelable, ExceptionState& exception_state); + void initEvent(const AtomicString& event_type, bool bubbles, bool cancelable, ExceptionState& exception_state); bool defaultPrevented() const { return default_prevented_; } void preventDefault(ExceptionState& exception_state); @@ -141,6 +144,7 @@ class Event : public ScriptWrappable { unsigned bubbles_ : 1; unsigned cancelable_ : 1; unsigned composed_ : 1; + double time_stamp_{0.0}; unsigned propagation_stopped_ : 1; unsigned immediate_propagation_stopped_ : 1; @@ -149,8 +153,6 @@ class Event : public ScriptWrappable { unsigned was_initialized_ : 1; unsigned is_trusted_ : 1; - double time_stamp_{0.0}; - uint8_t event_phase_ = PhaseType::kNone; // Whether preventDefault was called on uncancelable event. 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_map.cc b/bridge/core/dom/events/event_listener_map.cc index e2b2433630..dd32fd7318 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -51,7 +51,18 @@ bool EventListenerMap::Contains(const AtomicString& event_type) const { return false; } -bool EventListenerMap::ContainsCapturing(const AtomicString& event_type) const {} +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(); diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 9e69fa418e..c0290293fa 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -81,6 +81,8 @@ class EventTarget : public ScriptWrappable { const char* GetHumanReadableName() const override; + virtual bool IsWindowOrWorkerGlobalScope() const { return false; } + private: }; diff --git a/bridge/core/fileapi/blob.d.ts b/bridge/core/fileapi/blob.d.ts index a15cd6a3ef..468118ef47 100644 --- a/bridge/core/fileapi/blob.d.ts +++ b/bridge/core/fileapi/blob.d.ts @@ -1,5 +1,3 @@ -type int64 = void; - interface Blob { readonly size: number; readonly type: string; diff --git a/bridge/core/html/html_canvas_element.d.ts b/bridge/core/html/html_canvas_element.d.ts index 9e143700f0..eab9f245a9 100644 --- a/bridge/core/html/html_canvas_element.d.ts +++ b/bridge/core/html/html_canvas_element.d.ts @@ -1,6 +1,3 @@ -type double = number; -type int64 = number; - interface CanvasRenderingContext2D { fillStyle: string; direction: string; diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index ffe4529fa1..aa4fc7555f 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -7,7 +7,7 @@ const glob = require('glob'); const fs = require('fs'); const { IDLBlob } = require('../dist/idl/IDLBlob'); const { JSONBlob } = require('../dist/json/JSONBlob'); -const { Template } = require('../dist/json/template'); +const { JSONTemplate } = require('../dist/json/JSONTemplate'); const { analyzer } = require('../dist/idl/analyzer'); const { generateJSONTemplate } = require('../dist/json/generator'); @@ -30,11 +30,11 @@ if (!path.isAbsolute(dist)) { function genCodeFromTypeDefine() { // Generate code from type defines. - let files = glob.sync("**/*.d.ts", { + let typeFiles = glob.sync("**/*.d.ts", { cwd: source, }); - let blobs = files.map(file => { + 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); @@ -61,7 +61,7 @@ function genCodeFromJSONData() { cwd: source }); let templateFiles = glob.sync('**/*.tpl', { - cwd: path.join(__dirname, '../static') + cwd: path.join(__dirname, '../static/json_templates') }); let blobs = jsonFiles.map(file => { @@ -71,7 +71,7 @@ function genCodeFromJSONData() { let templates = templateFiles.map(template => { let filename = template.split('/').slice(-1)[0].replace('.tpl', ''); - return new Template(path.join(path.join(__dirname, '../static'), template), filename); + return new JSONTemplate(path.join(path.join(__dirname, '../static/json_templates'), template), filename); }); for (let i = 0; i < blobs.length; i ++) { @@ -93,5 +93,5 @@ function genCodeFromJSONData() { } } -// genCodeFromTypeDefine(); +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..9e11d1f433 --- /dev/null +++ b/bridge/scripts/code_generator/global.d.ts @@ -0,0 +1,8 @@ +declare type int64 = number; +declare type double = number; + +declare interface Dictionary {} + +declare interface BlobPart {} +declare interface BlobPropertyBag {} +declare function Dictionary() : any; diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 899636bcc7..e7fdb6b9ea 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -2,6 +2,7 @@ import ts, {HeritageClause, ScriptTarget, VariableStatement} from 'typescript'; import {IDLBlob} from './IDLBlob'; import { ClassObject, + ClassObjectKind, FunctionArguments, FunctionArgumentType, FunctionDeclaration, @@ -132,6 +133,14 @@ function walkProgram(statement: ts.Statement) { obj.name = s.name.escapedText.toString(); + if (s.decorators) { + let decoratorExpression = s.decorators[0].expression as ts.CallExpression; + // @ts-ignore + if (decoratorExpression.expression.kind === ts.SyntaxKind.Identifier && decoratorExpression.expression.escapedText === 'Dictionary') { + obj.kind = ClassObjectKind.dictionary; + } + } + s.members.forEach(member => { switch(member.kind) { case ts.SyntaxKind.PropertySignature: { diff --git a/bridge/scripts/code_generator/src/idl/declaration.ts b/bridge/scripts/code_generator/src/idl/declaration.ts index 9cfe9a2773..23be264e71 100644 --- a/bridge/scripts/code_generator/src/idl/declaration.ts +++ b/bridge/scripts/code_generator/src/idl/declaration.ts @@ -33,12 +33,18 @@ export class FunctionDeclaration extends PropsDeclaration { returnType: ParameterType[] = []; } +export enum ClassObjectKind { + interface, + dictionary +} + export class ClassObject { name: string; parent: string; props: PropsDeclaration[] = []; methods: FunctionDeclaration[] = []; construct?: FunctionDeclaration; + kind: ClassObjectKind = ClassObjectKind.interface } export class FunctionObject { diff --git a/bridge/scripts/code_generator/src/idl/generateHeader.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts index a44f7cd613..aed0c19645 100644 --- a/bridge/scripts/code_generator/src/idl/generateHeader.ts +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -1,66 +1,68 @@ -import {ClassObject, FunctionObject, PropsDeclaration} from "./declaration"; -import {uniqBy, snakeCase} from "lodash"; +import {ClassObject, ClassObjectKind, FunctionObject} from "./declaration"; +import _ from "lodash"; import {IDLBlob} from "./IDLBlob"; -import {addIndent, getClassName} from "./utils"; +import {getClassName} from "./utils"; +import fs from 'fs'; +import path from 'path'; +import {generateTypeConverter} from "./generateSource"; -function generateInterfaceAdditionalHeader(blob: IDLBlob, object: any): [string, string, string] { - if (!(object instanceof ClassObject)) { - return ['', '', '']; - } - - let wrapperTypeInfo = `static WrapperTypeInfo* GetWrapperTypeInfo() { - return const_cast(&wrapper_type_info_); - }`; - - let wrapperTypeDefine = `static JSValue ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); - constexpr static const WrapperTypeInfo wrapper_type_info_ = {JS_CLASS_${getClassName(blob).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ConstructorCallback}; -`; +export enum TemplateKind { + globalFunction, + Dictionary, + Interface, + null +} - let installFunctions = `static void InstallPrototypeMethods(ExecutingContext* context); - static void InstallPrototypeProperties(ExecutingContext* context); - static void InstallConstructor(ExecutingContext* context);`; +export function getTemplateKind(object: ClassObject | FunctionObject | null): TemplateKind { + if (object instanceof FunctionObject) { + return TemplateKind.globalFunction; + } else if (object instanceof ClassObject) { + if (object.kind === ClassObjectKind.dictionary) { + return TemplateKind.Dictionary; + } + return TemplateKind.Interface; + } + return TemplateKind.null; +} - return [ - wrapperTypeInfo, - wrapperTypeDefine, - installFunctions - ]; +function readTemplate(name: string) { + return fs.readFileSync(path.join(__dirname, '../../static/idl_templates/' + name + '.h.tpl'), {encoding: 'utf-8'}); } export function generateCppHeader(blob: IDLBlob) { - let classObject = blob.objects.find(object => object instanceof ClassObject); - let interfaceDefines = generateInterfaceAdditionalHeader(blob, classObject); - let haveInterfaceBase = !!classObject; - return `/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_${blob.filename.toUpperCase()}_H -#define KRAKENBRIDGE_${blob.filename.toUpperCase()}_H + const baseTemplate = fs.readFileSync(path.join(__dirname, '../../static/idl_templates/base.h.tpl'), {encoding: 'utf-8'}); + const contents = blob.objects.map(object => { + const templateKind = getTemplateKind(object); + if (templateKind === TemplateKind.null) return ''; -#include -#include "bindings/qjs/wrapper_type_info.h" -#include "bindings/qjs/qjs_interface_bridge.h" -#include "core/${blob.implement}.h" - -namespace kraken { - -class ExecutingContext; - -class QJS${getClassName(blob)} ${haveInterfaceBase ? `: public QJSInterfaceBridge` : 'final'} { - public: - static void Install(ExecutingContext* context); - - ${interfaceDefines[0]} - ${interfaceDefines[1]} - private: - static void InstallGlobalFunctions(ExecutingContext* context); - ${interfaceDefines[2]} -}; - -} + switch(templateKind) { + case TemplateKind.Interface: { + return _.template(readTemplate('interface'))({ + className: getClassName(blob), + blob: blob + }); + } + case TemplateKind.Dictionary: { + let props = (object as ClassObject).props; + return _.template(readTemplate('dictionary'))({ + className: getClassName(blob), + blob: blob, + object: object, + props, + generateTypeConverter: generateTypeConverter + }); + } + case TemplateKind.globalFunction: { + return _.template(readTemplate('global_function'))({ + className: getClassName(blob), + blob: blob + }); + } + } + }); -#endif //KRAKENBRIDGE_${blob.filename.toUpperCase()}T_H -`; + return _.template(baseTemplate)({ + content: contents.join('\n'), + blob: blob + }); } diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 9bf25ef29d..e3f9a4da94 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -9,6 +9,10 @@ import { } 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"; enum PropType { hostObject, @@ -30,7 +34,7 @@ function generateMethodArgumentsCheck(m: FunctionDeclaration) { `; } -function generateTypeConverter(type: ParameterType[]): string { +export function generateTypeConverter(type: ParameterType[]): string { let haveNull = type.some(t => t === FunctionArgumentType.null); let returnValue = ''; @@ -157,13 +161,6 @@ ${optionalArgumentsInit.join('\n')} `; } -function generateGlobalFunctionSource(blob: IDLBlob, object: FunctionObject) { - let body = generateFunctionBody(blob, object.declare); - return `static JSValue ${object.declare.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { -${body} -}`; -} - function generateReturnValueInit(blob: IDLBlob, type: ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { if (type[0] == FunctionArgumentType.void) return ''; @@ -222,169 +219,72 @@ ${addIndent(callBody, 4)} `; } -function generateClassConstructorCallback(blob: IDLBlob, declare: FunctionDeclaration) { - return `JSValue QJS${getClassName(blob)}::ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags) { -${generateFunctionBody(blob, declare, {isConstructor: true})} -} -`; -} - -function generatePropertyGetterCallback(blob: IDLBlob, prop: PropsDeclaration) { - return `static JSValue ${prop.name}AttributeGetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* ${blob.filename} = toScriptWrappable<${getClassName(blob)}>(this_val); - assert(${blob.filename} != nullptr); - return Converter<${generateTypeConverter(prop.type)}>::ToValue(ctx, ${blob.filename}->${prop.name}()); -}`; -} - -function generatePropertySetterCallback(blob: IDLBlob, prop: PropsDeclaration) { - return `static JSValue ${prop.name}AttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - auto* ${blob.filename} = toScriptWrappable<${getClassName(blob)}>(this_val); - ExceptionState exception_state; - auto&& v = Converter<${generateTypeConverter(prop.type)}>::FromValue(ctx, argv[0], exception_state); - if (exception_state.HasException()) { - return exception_state.ToQuickJS(); - } - ${blob.filename}->set${prop.name[0].toUpperCase() + prop.name.slice(1)}(v); -}`; -} - -function generateMethodCallback(blob: IDLBlob, methods: FunctionDeclaration[]): string[] { - return methods.map(method => { - return `static JSValue ${method.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - ${ generateFunctionBody(blob, method, {isInstanceMethod: true}) } -}`; - }); -} - -function generateClassSource(blob: IDLBlob, object: ClassObject) { - let constructorCallback = ''; - if (object.construct) { - constructorCallback = generateClassConstructorCallback(blob, object.construct); - } - let getterCallbacks: string[] = []; - let setterCallbacks: string[] = []; - let methodCallback = generateMethodCallback(blob, object.methods); - - object.props.forEach(prop => { - getterCallbacks.push(generatePropertyGetterCallback(blob, prop)); - if (!prop.readonly) { - setterCallbacks.push(generatePropertySetterCallback(blob, prop)) - } - }); - - return [ - constructorCallback, - getterCallbacks.join('\n'), - setterCallbacks.join('\n'), - methodCallback.join('\n') - ].join('\n'); -} - -function generateInstallGlobalFunctions(blob: IDLBlob, installList: string[]) { - return `void QJS${getClassName(blob)}::InstallGlobalFunctions(ExecutingContext* context) { - std::initializer_list functionConfig { - ${installList.join(',\n')} - }; - - MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); -}`; -} - -function generateConstructorInstaller(blob: IDLBlob) { - return `void QJS${getClassName(blob)}::InstallConstructor(ExecutingContext* context) { - const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); - JSValue constructor = context->contextData()->constructorForType(wrapperTypeInfo); - - std::initializer_list attributeConfig { - {"${getClassName(blob)}", nullptr, nullptr, constructor} - }; - MemberInstaller::InstallAttributes(context, context->Global(), attributeConfig); -}`; -} - -function generatePrototypeMethodsInstaller(blob: IDLBlob, installList: string[]) { - return `void QJS${getClassName(blob)}::InstallPrototypeMethods(ExecutingContext* context) { - const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); - JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); - - std::initializer_list attributesConfig { - ${installList.join(',\n')} - }; - - MemberInstaller::InstallAttributes(context, prototype, attributesConfig); -} -`; -} - -function generatePrototypePropsInstaller(blob: IDLBlob, installList: string[]) { - return `void QJS${getClassName(blob)}::InstallPrototypeProperties(ExecutingContext* context) { - const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); - JSValue prototype = context->contextData()->prototypeForType(wrapperTypeInfo); - - std::initializer_list functionConfig { - ${installList.join(',\n')} - }; - - MemberInstaller::InstallFunctions(context, prototype, functionConfig); -} -`; +function readTemplate(name: string) { + return fs.readFileSync(path.join(__dirname, '../../static/idl_templates/' + name + '.cc.tpl'), {encoding: 'utf-8'}); } export function generateCppSource(blob: IDLBlob) { - let functionInstallList: string[] = []; + let globalFunctionInstallList: string[] = []; let classMethodsInstallList: string[] = []; let classPropsInstallList: string[] = []; let wrapperTypeInfoInit = ''; - - let sources = blob.objects.map(o => { - if (o instanceof FunctionObject) { - functionInstallList.push(` {"${o.declare.name}", ${o.declare.name}, ${o.declare.args.length}}`); - return generateGlobalFunctionSource(blob, o); - } else { - o.props.forEach(prop => { - classMethodsInstallList.push(`{"${prop.name}", ${prop.name}AttributeGetCallback, ${prop.readonly ? 'nullptr' : `${prop.name}AttributeSetCallback`}}`) - }); - o.methods.forEach(method => { - classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) - }); - wrapperTypeInfoInit = `const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::wrapper_type_info_;`; - return generateClassSource(blob, o); + const baseTemplate = fs.readFileSync(path.join(__dirname, '../../static/idl_templates/base.cc.tpl'), {encoding: 'utf-8'}); + + const contents = blob.objects.map(object => { + const templateKind = getTemplateKind(object); + if (templateKind === TemplateKind.null) return ''; + + switch(templateKind) { + case TemplateKind.Interface: { + object = object as ClassObject; + object.props.forEach(prop => { + classMethodsInstallList.push(`{"${prop.name}", ${prop.name}AttributeGetCallback, ${prop.readonly ? 'nullptr' : `${prop.name}AttributeSetCallback`}}`) + }); + object.methods.forEach(method => { + classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) + }); + wrapperTypeInfoInit = ` +const WrapperTypeInfo wrapper_type_info_ {JS_CLASS_${getClassName(blob).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, QJS${getClassName(blob)}::ConstructorCallback}; +const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::wrapper_type_info_;`; + return _.template(readTemplate('interface'))({ + className: getClassName(blob), + blob: blob, + object: object, + generateFunctionBody, + generateTypeConverter + }); + } + case TemplateKind.Dictionary: { + let props = (object as ClassObject).props; + return _.template(readTemplate('dictionary'))({ + className: getClassName(blob), + blob: blob, + props: props, + object: object, + generateTypeConverter + }); + } + case TemplateKind.globalFunction: { + object = object as FunctionObject; + 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 ''; }); - let haveInterfaceDefine = !!blob.objects.find(object => object instanceof ClassObject); - - return `/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "${blob.filename}.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 "core/executing_context.h" - -namespace kraken { - -${wrapperTypeInfoInit} - -${sources.join('\n')} - -void QJS${getClassName(blob)}::Install(ExecutingContext* context) { - InstallGlobalFunctions(context); - ${haveInterfaceDefine ? `InstallConstructor(context); - InstallPrototypeMethods(context); - InstallPrototypeProperties(context)` : ''}; -} - -${generateInstallGlobalFunctions(blob, functionInstallList)} - -${haveInterfaceDefine ? `${generateConstructorInstaller(blob)} -${generatePrototypeMethodsInstaller(blob, classMethodsInstallList)} -${generatePrototypePropsInstaller(blob, classPropsInstallList)}` : ''} -}`; + return _.template(baseTemplate)({ + content: contents.join('\n'), + className: getClassName(blob), + blob: blob, + globalFunctionInstallList, + classPropsInstallList, + classMethodsInstallList, + wrapperTypeInfoInit + }); } diff --git a/bridge/scripts/code_generator/src/json/template.ts b/bridge/scripts/code_generator/src/json/JSONTemplate.ts similarity index 93% rename from bridge/scripts/code_generator/src/json/template.ts rename to bridge/scripts/code_generator/src/json/JSONTemplate.ts index cb76f50731..a3b82df837 100644 --- a/bridge/scripts/code_generator/src/json/template.ts +++ b/bridge/scripts/code_generator/src/json/JSONTemplate.ts @@ -5,7 +5,7 @@ enum TemplateType { body } -export class Template { +export class JSONTemplate { public raw: string; public filename: string; public type: TemplateType; diff --git a/bridge/scripts/code_generator/src/json/generator.ts b/bridge/scripts/code_generator/src/json/generator.ts index 4cdad33792..cb8ae77a55 100644 --- a/bridge/scripts/code_generator/src/json/generator.ts +++ b/bridge/scripts/code_generator/src/json/generator.ts @@ -1,8 +1,8 @@ import {JSONBlob} from './JSONBlob'; -import {Template} from './template'; +import {JSONTemplate} from './JSONTemplate'; import _ from 'lodash'; -function generateHeader(blob: JSONBlob, template: Template): string { +function generateHeader(blob: JSONBlob, template: JSONTemplate): string { let compiled = _.template(template.raw); return compiled({ _: _, @@ -12,7 +12,7 @@ function generateHeader(blob: JSONBlob, template: Template): string { }); } -function generateBody(blob: JSONBlob, template: Template): string { +function generateBody(blob: JSONBlob, template: JSONTemplate): string { let compiled = _.template(template.raw); return compiled({ template_path: blob.source, @@ -21,7 +21,7 @@ function generateBody(blob: JSONBlob, template: Template): string { }); } -export function generateJSONTemplate(blob: JSONBlob, headerTemplate: Template, bodyTemplate?: Template) { +export function generateJSONTemplate(blob: JSONBlob, headerTemplate: JSONTemplate, bodyTemplate?: JSONTemplate) { let header = generateHeader(blob, headerTemplate); let body = bodyTemplate ? generateBody(blob, bodyTemplate) : ''; diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl new file mode 100644 index 0000000000..061f0520f8 --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "<%= blob.filename %>.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 "core/executing_context.h" + +namespace kraken { + +<% if (wrapperTypeInfoInit) { %> +<%= wrapperTypeInfoInit %> +<% } %> +<%= content %> + +<% if (globalFunctionInstallList.length > 0 || classPropsInstallList.length > 0 || classMethodsInstallList.length > 0) { %> +void QJS<%= className %>::Install(ExecutingContext* context) { + InstallGlobalFunctions(context); + <% if(classPropsInstallList.length > 0) { %> InstallPrototypeProperties(context); <% } %> + <% if(classMethodsInstallList.length > 0) { %> InstallPrototypeMethods(context); <% } %> +} + +<% } %> + +<% if(globalFunctionInstallList.length > 0) { %> +void QJS<%= className %>::InstallGlobalFunctions(ExecutingContext* context) { + std::initializer_list 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 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 attributesConfig { + <%= classMethodsInstallList.join(',\n') %> + }; + + MemberInstaller::InstallAttributes(context, prototype, attributesConfig); +} +<% } %> + +} diff --git a/bridge/scripts/code_generator/static/idl_templates/base.h.tpl b/bridge/scripts/code_generator/static/idl_templates/base.h.tpl new file mode 100644 index 0000000000..155c719522 --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/base.h.tpl @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_<%= blob.filename.toUpperCase() %>_H +#define KRAKENBRIDGE_<%= blob.filename.toUpperCase() %>_H + +#include +#include "bindings/qjs/wrapper_type_info.h" +#include "bindings/qjs/qjs_interface_bridge.h" +#include "bindings/qjs/dictionary_base.h" + +<%= content %> + +#endif //KRAKENBRIDGE_<%= blob.filename.toUpperCase() %>T_H diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl new file mode 100644 index 0000000000..e93034724f --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl @@ -0,0 +1,43 @@ +std::unique_ptr<<%= className %>> <%= className %>::Create() { + return std::make_unique<<%= className %>>(); +} +std::unique_ptr<<%= className %>> <%= className %>::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + return std::make_unique<<%= className %>>(ctx, value, exception_state); +} + +<%= className %>::<%= className %>() {} +<%= className %>::<%= className %>(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + FillMembersWithQJSObject(ctx, value, exception_state); +} + +bool <%= className %>::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { + <% if (object.parent) { %> + EventInit::FillQJSObjectWithMembers(ctx, qjs_dictionary); + <% } %> + + if (!JS_IsObject(qjs_dictionary)) { + return false; + } + + <% _.forEach(props, function(prop, index) { %> + JS_SetPropertyStr(ctx, qjs_dictionary, "<%= prop.name %>_", Converter<<%= generateTypeConverter(prop.type) %>>::ToValue(ctx, <%= prop.name %>_)); + <% }); %> + + return true; +} + +void <%= className %>::FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + <% if (object.parent) { %> + EventInit::FillMembersWithQJSObject(ctx, value, exception_state); + <% } %> + + if (!JS_IsObject(value)) { + return; + } + + <% _.forEach(props, function(prop, index) { %> + <%= prop.name %>_ = Converter<<%= generateTypeConverter(prop.type) %>>::FromValue(ctx, value, exception_state); + <% }); %> + + +} diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl new file mode 100644 index 0000000000..a5c894766b --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl @@ -0,0 +1,30 @@ +#include "bindings/qjs/converter_impl.h" + +<% if (object.parent) { %> +#include "qjs_<%= _.snakeCase(object.parent) %>.h" +<% } %> + +namespace kraken { + +class ExecutingContext; + +class <%= className %> : public <%= object.parent ? object.parent : 'DictionaryBase' %> { + public: + static std::unique_ptr<<%= className %>> Create(); + static std::unique_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) { %> + Converter<<%= generateTypeConverter(prop.type) %>>::ImplType <%= prop.name %>() const { return <%= prop.name %>_; } + void set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(Converter<<%= generateTypeConverter(prop.type) %>>::ImplType value) { <%= prop.name %>_ = value; } + <% })); %> +private: + bool FillQJSObjectWithMembers(JSContext *ctx, JSValue qjs_dictionary) const override; + void FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state); + <% _.forEach(props, (function(prop, index) { %> + Converter<<%= generateTypeConverter(prop.type) %>>::ImplType <%= prop.name %>_; + <% })); %> +}; + +} diff --git a/bridge/scripts/code_generator/static/idl_templates/global_function.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/global_function.cc.tpl new file mode 100644 index 0000000000..3abca6a798 --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/global_function.cc.tpl @@ -0,0 +1,3 @@ +static JSValue ${object.declare.name}(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + <%= generateFunctionBody(blob, object.declare) %> +} diff --git a/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl b/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl new file mode 100644 index 0000000000..059bb60cda --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl @@ -0,0 +1,14 @@ +#include "core/<%= blob.implement %>.h" + +namespace kraken { + +class ExecutingContext; + +class QJS<%= className %> final { + public: + static void Install(ExecutingContext* context); + private: + static void InstallGlobalFunctions(ExecutingContext* context); +}; + +} diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl new file mode 100644 index 0000000000..50812c8307 --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -0,0 +1,30 @@ +<% 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}) %> +} +<% } %> + +<% _.forEach(object.methods, function(method, index) { %> +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); + return Converter<<%= generateTypeConverter(prop.type) %>>::ToValue(ctx, <%= blob.filename %>-><%= prop.name %>()); +} +<% if (!prop.readonly) { %> +static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { + auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); + ExceptionState exception_state; + auto&& v = Converter<<%= generateTypeConverter(prop.type) %>>::FromValue(ctx, argv[0], exception_state); + if (exception_state.HasException()) { + return exception_state.ToQuickJS(); + } + <%= blob.filename %>->set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(v); +} +<% } %> +<% }); %> diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl new file mode 100644 index 0000000000..c52a5fdc56 --- /dev/null +++ b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl @@ -0,0 +1,23 @@ +#include "core/<%= blob.implement %>.h" + +namespace kraken { + +class ExecutingContext; + +class QJS<%= className %> : public QJSInterfaceBridge, <%= className%>> { + public: + static void Install(ExecutingContext* context); + static WrapperTypeInfo* GetWrapperTypeInfo() { + return const_cast(&wrapper_type_info_); + } + static JSValue ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); + static const WrapperTypeInfo wrapper_type_info_; + private: + static void InstallGlobalFunctions(ExecutingContext* context); + static void InstallPrototypeMethods(ExecutingContext* context); + static void InstallPrototypeProperties(ExecutingContext* context); + static void InstallConstructor(ExecutingContext* context); +}; + + +} diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index a9a19b15c4..b3bcafe819 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -29,7 +29,7 @@ void Init() { for(size_t i = 0; i < std::size(kNames); i ++) { void* address = reinterpret_cast(&names_storage) + i; - new (address) PersistentAtomicString(kNames[i].atom); + new (address) AtomicString(kNames[i].atom); } }; diff --git a/bridge/scripts/code_generator/tsconfig.json b/bridge/scripts/code_generator/tsconfig.json index 4b8c8add00..9ebac78b8d 100644 --- a/bridge/scripts/code_generator/tsconfig.json +++ b/bridge/scripts/code_generator/tsconfig.json @@ -4,8 +4,7 @@ "target": "es6", "lib": [ "es6", - "es7", - "dom" + "es7" ], "allowJs": false, "moduleResolution": "node", 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" + } +} From d8564d87425a1aaa86ea65d0d1534cb49ac6fd42 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 31 Mar 2022 20:19:24 +0800 Subject: [PATCH 044/498] feat: auto gen event listener options. --- .../qjs/add_event_listener_options.cc | 41 -------------- .../bindings/qjs/add_event_listener_options.h | 53 ------------------- bridge/bindings/qjs/event_listener_options.cc | 36 ------------- bridge/bindings/qjs/event_listener_options.h | 34 ------------ .../events/add_event_listener_options.d.ts | 9 ++++ .../dom/events/event_listener_options.d.ts | 5 ++ 6 files changed, 14 insertions(+), 164 deletions(-) delete mode 100644 bridge/bindings/qjs/add_event_listener_options.cc delete mode 100644 bridge/bindings/qjs/add_event_listener_options.h delete mode 100644 bridge/bindings/qjs/event_listener_options.cc delete mode 100644 bridge/bindings/qjs/event_listener_options.h create mode 100644 bridge/core/dom/events/add_event_listener_options.d.ts create mode 100644 bridge/core/dom/events/event_listener_options.d.ts diff --git a/bridge/bindings/qjs/add_event_listener_options.cc b/bridge/bindings/qjs/add_event_listener_options.cc deleted file mode 100644 index 38d83daeee..0000000000 --- a/bridge/bindings/qjs/add_event_listener_options.cc +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "add_event_listener_options.h" - -namespace kraken { - -AddEventListenerOptions::AddEventListenerOptions() {} - -AddEventListenerOptions::AddEventListenerOptions(JSContext* ctx, JSValue dictionary_value, ExceptionState& exception_state) {} - -bool AddEventListenerOptions::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { - if (!JS_IsObject(qjs_dictionary)) { - return false; - } - - JS_SetPropertyStr(ctx, qjs_dictionary, "passive", JS_NewBool(ctx, member_passive_)); - JS_SetPropertyStr(ctx, qjs_dictionary, "once", JS_NewBool(ctx, member_once_)); - - EventListenerOptions::FillQJSObjectWithMembers(ctx, qjs_dictionary); - - return true; -} - -void AddEventListenerOptions::FillMembersFromQJSObject(JSContext* ctx, JSValue qjs_dictionary) { - if (!JS_IsObject(qjs_dictionary)) { - return; - } - - JSValue passive = JS_GetPropertyStr(ctx, qjs_dictionary, "passive"); - member_passive_ = JS_ToBool(ctx, passive); - JS_FreeValue(ctx, passive); - - JSValue once = JS_GetPropertyStr(ctx, qjs_dictionary, "once"); - member_once_ = JS_ToBool(ctx, once); - JS_FreeValue(ctx, once); -} - -} // namespace kraken diff --git a/bridge/bindings/qjs/add_event_listener_options.h b/bridge/bindings/qjs/add_event_listener_options.h deleted file mode 100644 index 21351bf9e6..0000000000 --- a/bridge/bindings/qjs/add_event_listener_options.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_BINDINGS_QJS_ADD_EVENT_LISTENER_OPTIONS_H_ -#define KRAKENBRIDGE_BINDINGS_QJS_ADD_EVENT_LISTENER_OPTIONS_H_ - -#include "dictionary_base.h" -#include "event_listener_options.h" - -namespace kraken { - -class AddEventListenerOptions : public EventListenerOptions { - public: - static std::unique_ptr Create(JSContext* ctx, JSValue dictionary_value, ExceptionState& exception_state) { - return std::make_unique(ctx, dictionary_value, exception_state); - } - - explicit AddEventListenerOptions(); - explicit AddEventListenerOptions(JSContext* ctx, JSValue dictionary_value, ExceptionState& exception_state); - - bool hasOnce() const { return true; } - bool once() const { return member_once_; } - void setOnce(bool value) { member_once_ = value; } - - bool hasPassive() const { return has_passive_; } - bool passive() const { return member_passive_; } - bool getPassiveOr(bool fallback_value) const { - if (!hasPassive()) { - return fallback_value; - } - return member_passive_; - } - void setPassive(bool value) { - member_passive_ = value; - has_passive_ = true; - } - - protected: - bool FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const override; - - private: - void FillMembersFromQJSObject(JSContext* ctx, JSValue qjs_dictionary); - - bool has_passive_ = false; - bool member_once_{false}; - bool member_passive_; -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_BINDINGS_QJS_ADD_EVENT_LISTENER_OPTIONS_H_ diff --git a/bridge/bindings/qjs/event_listener_options.cc b/bridge/bindings/qjs/event_listener_options.cc deleted file mode 100644 index 902f2ce53a..0000000000 --- a/bridge/bindings/qjs/event_listener_options.cc +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "event_listener_options.h" - -namespace kraken { - -EventListenerOptions::EventListenerOptions() {} - -EventListenerOptions::EventListenerOptions(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - FillMembersFromQJSObject(ctx, value, exception_state); -} - -bool EventListenerOptions::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { - if (!JS_IsObject(qjs_dictionary)) { - return false; - } - - JS_SetPropertyStr(ctx, qjs_dictionary, "capture", JS_NewBool(ctx, capture_)); - - return true; -} - -void EventListenerOptions::FillMembersFromQJSObject(JSContext* ctx, JSValue qjs_dictionary, ExceptionState& exception_state) { - if (!JS_IsObject(qjs_dictionary)) { - return; - } - - JSValue capture = JS_GetPropertyStr(ctx, qjs_dictionary, "capture"); - capture_ = JS_ToBool(ctx, capture); - JS_FreeValue(ctx, capture); -} - -} // namespace kraken diff --git a/bridge/bindings/qjs/event_listener_options.h b/bridge/bindings/qjs/event_listener_options.h deleted file mode 100644 index 107cd019d1..0000000000 --- a/bridge/bindings/qjs/event_listener_options.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_OPTIONS_H_ -#define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_OPTIONS_H_ - -#include "bindings/qjs/dictionary_base.h" -#include "bindings/qjs/exception_state.h" - -namespace kraken { - -class EventListenerOptions : public DictionaryBase { - public: - static std::shared_ptr Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { return std::make_shared(); }; - explicit EventListenerOptions(); - explicit EventListenerOptions(JSContext* ctx, JSValue value, ExceptionState& exception_state); - - bool hasCapture() const { return true; } - bool capture() const { return capture_; } - void setCapture(bool value) { capture_ = value; } - - protected: - bool FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const override; - - private: - bool capture_{false}; - void FillMembersFromQJSObject(JSContext* ctx, JSValue qjs_dictionary, ExceptionState& exception_state); -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_OPTIONS_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/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; +} From d2b739b3bf297fdea6717052140223147ca98116 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 1 Apr 2022 16:51:20 +0800 Subject: [PATCH 045/498] feat: finish error event and js_event_handler. --- bridge/.clang-format | 2 +- bridge/CMakeLists.txt | 10 +- bridge/bindings/qjs/atom_string.h | 7 + bridge/bindings/qjs/js_event_handler.cc | 134 +++++------------- bridge/bindings/qjs/source_location.cc | 16 +++ bridge/bindings/qjs/source_location.h | 37 +++++ bridge/core/dom/events/error_event.cc | 21 --- bridge/core/dom/events/error_event.h | 37 ----- bridge/core/dom/events/error_event_init.d.ts | 7 - bridge/core/dom/events/event.cc | 1 - bridge/core/dom/events/event.h | 6 - bridge/core/{dom => }/events/close_event.cc | 0 bridge/core/{dom => }/events/close_event.d.ts | 0 bridge/core/{dom => }/events/close_event.h | 0 bridge/core/events/error_event.cc | 41 ++++++ bridge/core/events/error_event.h | 47 ++++++ bridge/core/{dom => }/events/error_event.ts | 0 bridge/core/events/error_event_init.d.ts | 11 ++ .../{dom => }/events/event_type_names.json | 0 .../core/{dom => }/events/gesture_event.d.ts | 0 bridge/core/{dom => }/events/input_event.cc | 0 bridge/core/{dom => }/events/input_event.d.ts | 0 bridge/core/{dom => }/events/input_event.h | 0 .../events/intersection_change_event.cc | 0 .../events/intersection_change_event.d.ts | 0 .../events/intersection_change_event.h | 0 bridge/core/{dom => }/events/touch_event.cc | 0 bridge/core/{dom => }/events/touch_event.h | 0 .../code_generator/src/idl/generateSource.ts | 2 +- .../static/idl_templates/dictionary.cc.tpl | 7 +- 30 files changed, 204 insertions(+), 182 deletions(-) create mode 100644 bridge/bindings/qjs/source_location.cc create mode 100644 bridge/bindings/qjs/source_location.h delete mode 100644 bridge/core/dom/events/error_event.cc delete mode 100644 bridge/core/dom/events/error_event.h delete mode 100644 bridge/core/dom/events/error_event_init.d.ts rename bridge/core/{dom => }/events/close_event.cc (100%) rename bridge/core/{dom => }/events/close_event.d.ts (100%) rename bridge/core/{dom => }/events/close_event.h (100%) create mode 100644 bridge/core/events/error_event.cc create mode 100644 bridge/core/events/error_event.h rename bridge/core/{dom => }/events/error_event.ts (100%) create mode 100644 bridge/core/events/error_event_init.d.ts rename bridge/core/{dom => }/events/event_type_names.json (100%) rename bridge/core/{dom => }/events/gesture_event.d.ts (100%) rename bridge/core/{dom => }/events/input_event.cc (100%) rename bridge/core/{dom => }/events/input_event.d.ts (100%) rename bridge/core/{dom => }/events/input_event.h (100%) rename bridge/core/{dom => }/events/intersection_change_event.cc (100%) rename bridge/core/{dom => }/events/intersection_change_event.d.ts (100%) rename bridge/core/{dom => }/events/intersection_change_event.h (100%) rename bridge/core/{dom => }/events/touch_event.cc (100%) rename bridge/core/{dom => }/events/touch_event.h (100%) 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/CMakeLists.txt b/bridge/CMakeLists.txt index c4bae71507..3eb30febf7 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -187,10 +187,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/converter_impl.h bindings/qjs/dictionary_base.cc bindings/qjs/dictionary_base.h - bindings/qjs/event_listener_options.cc - bindings/qjs/event_listener_options.h - bindings/qjs/add_event_listener_options.cc - bindings/qjs/add_event_listener_options.h bindings/qjs/js_based_event_listener.cc bindings/qjs/js_based_event_listener.h bindings/qjs/js_event_handler.cc @@ -201,6 +197,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/binding_initializer.h bindings/qjs/member_installer.cc bindings/qjs/member_installer.h + bindings/qjs/source_location.cc + bindings/qjs/source_location.h bindings/qjs/garbage_collected.h bindings/qjs/script_wrappable.cc bindings/qjs/script_wrappable.h @@ -283,8 +281,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/events/event_target.cc core/dom/events/event_target_impl.cc core/dom/events/event_target_impl.h - core/dom/events/error_event.cc - core/dom/events/error_event.h + core/events/error_event.cc + core/events/error_event.h # core/dom/character_data.cc # core/dom/character_data.h # core/dom/comment.cc diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index 53d34313ff..3103e88619 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -40,6 +40,13 @@ class AtomicString { return JS_AtomToValue(ctx, atom_); }; + std::string ToStdString() const { + const char* buf = JS_AtomToCString(ctx_, atom_); + std::string result = std::string(buf); + JS_FreeCString(ctx_, buf); + return result; + } + // Copy assignment AtomicString(AtomicString const& value) { if (!is_static_atom_ && &value != this) { diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index d15f0c2738..e87e264e8e 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -4,12 +4,13 @@ */ #include "js_event_handler.h" -#include "core/dom/events/error_event.h" +#include "core/events/error_event.h" #include "core/dom/events/event_target.h" #include "event_type_names.h" namespace kraken { + std::unique_ptr JSEventHandler::CreateOrNull(JSContext* ctx, JSValue value, JSEventHandler::HandlerType handler_type) { if (!JS_IsFunction(ctx, value)) { return nullptr; @@ -55,7 +56,6 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // 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(); @@ -64,110 +64,42 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc 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(script_state_of_listener); -// if (error_attribute.IsEmpty() || -// error_event->target()->InterfaceName() == event_target_names::kWorker) { -// error_attribute = ScriptValue::CreateNull(isolate); -// } -// arguments = { -// ScriptValue(isolate, -// ToV8Traits::ToV8(script_state_of_listener, -// error_event->message()) -// .ToLocalChecked()), -// ScriptValue(isolate, -// ToV8Traits::ToV8(script_state_of_listener, -// error_event->filename()) -// .ToLocalChecked()), -// ScriptValue(isolate, -// ToV8Traits::ToV8(script_state_of_listener, -// error_event->lineno()) -// .ToLocalChecked()), -// ScriptValue(isolate, ToV8Traits::ToV8( -// script_state_of_listener, error_event->colno()) -// .ToLocalChecked()), -// error_attribute}; + ScriptValue error_attribute = error_event->error(); + if (error_attribute.IsEmpty()) { + error_attribute = ScriptValue::Empty(event.ctx()); + } + arguments = { + ScriptValue(ctx, Converter::ToValue(ctx, error_event->message())), + ScriptValue(ctx, Converter::ToValue(ctx, error_event->filename())), + ScriptValue(ctx, Converter::ToValue(ctx, error_event->lineno())), + ScriptValue(ctx, Converter::ToValue(ctx, error_event->colno())), + error_attribute + }; } else { arguments.emplace_back(ctx, event.ToQuickJS()); } -// if (!event_handler_->IsRunnableOrThrowException( -// event.ShouldDispatchEvenWhenExecutionContextIsPaused() -// ? V8EventHandlerNonNull::IgnorePause::kIgnore -// : V8EventHandlerNonNull::IgnorePause::kDontIgnore)) { -// return; -// } -// ScriptValue result; -// if (!event_handler_ -// ->InvokeWithoutRunnabilityCheck(event.currentTarget(), arguments) -// .To(&result) || -// isolate->IsExecutionTerminating()) -// return; -// v8::Local v8_return_value = result.V8Value(); -// -// // There is nothing to do if |v8_return_value| is null or undefined. -// // See Step 5. for more information. -// if (v8_return_value->IsNullOrUndefined()) -// 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. -// String result_for_beforeunload; -// if (IsOnBeforeUnloadEventHandler()) { -// event_handler_->EvaluateAsPartOfCallback(Bind( -// [](v8::Local& v8_return_value, -// String& result_for_beforeunload) { -// // TODO(yukiy): use |NativeValueTraits|. -// V8StringResource native_result( -// v8_return_value); -// -// // |native_result.Prepare()| throws exception if it fails to convert -// // |native_result| to String. -// if (!native_result.Prepare()) -// return; -// result_for_beforeunload = native_result; -// }, -// std::ref(v8_return_value), std::ref(result_for_beforeunload))); -// if (!result_for_beforeunload) -// return; -// } + ScriptValue result = event_handler_ + ->Invoke(event.ctx(), arguments.size(), arguments.data()); + if (result.IsException()) { + exception_state.ThrowException(event.ctx(), result.ToQuickJS()); + return; + } - // Step 5. Process return value as follows: - // If event is a BeforeUnloadEvent object and event's type is beforeunload - // If return value is not null, then: - // 1. Set event's canceled flag. - // 2. If event's returnValue attribute's value is the empty string, then - // set event's returnValue attribute's value to return value. - // If special error event handling is true - // If return value is true, then set event's canceled flag. - // Otherwise - // If return value is false, then set event's canceled flag. - // Note: If we've gotten to this "Otherwise" clause because event's type - // is beforeunload but event is not a BeforeUnloadEvent object, - // then return value will never be false, since in such cases - // return value will have been coerced into either null or a - // DOMString. -// auto* before_unload_event = DynamicTo(&event); -// const bool is_beforeunload_event = -// before_unload_event && event.type() == event_type_names::kBeforeunload; -// if (is_beforeunload_event) { -// if (result_for_beforeunload) { -// event.preventDefault(); -// if (before_unload_event->returnValue().IsEmpty()) -// before_unload_event->setReturnValue(result_for_beforeunload); -// } -// } else if (!IsOnBeforeUnloadEventHandler()) { -// if (special_error_event_handling && v8_return_value->IsBoolean() && -// v8_return_value.As()->Value()) -// event.preventDefault(); -// else if (!special_error_event_handling && v8_return_value->IsBoolean() && -// !v8_return_value.As()->Value()) -// event.preventDefault(); -// } + // // 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. } } diff --git a/bridge/bindings/qjs/source_location.cc b/bridge/bindings/qjs/source_location.cc new file mode 100644 index 0000000000..adb6897733 --- /dev/null +++ b/bridge/bindings/qjs/source_location.cc @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "source_location.h" + +namespace kraken { + +std::unique_ptr SourceLocation::Capture(const std::string& url, unsigned int line_number, unsigned int column_number) { + return std::make_unique(url, line_number, column_number); +} + +SourceLocation::SourceLocation(const std::string& url, unsigned int line_number, unsigned int column_number): url_(url), line_number_(line_number), column_number_(column_number) { } + +} diff --git a/bridge/bindings/qjs/source_location.h b/bridge/bindings/qjs/source_location.h new file mode 100644 index 0000000000..792b2e43a1 --- /dev/null +++ b/bridge/bindings/qjs/source_location.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ + +#include +#include + +namespace kraken { + +class ExecutingContext; + +class SourceLocation { + public: + // Zero lineNumber and columnNumber mean unknown. Captures current stack + // trace. + static std::unique_ptr Capture(const std::string& url, unsigned line_number, unsigned column_number); + + SourceLocation(const std::string& url, unsigned line_number, unsigned column_number); + ~SourceLocation(); + + const std::string& Url() const { return url_; } + unsigned LineNumber() const { return line_number_; } + unsigned ColumnNumber() const { return column_number_; } + + private: + std::string url_; + unsigned line_number_; + unsigned column_number_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ diff --git a/bridge/core/dom/events/error_event.cc b/bridge/core/dom/events/error_event.cc deleted file mode 100644 index bd175bab7f..0000000000 --- a/bridge/core/dom/events/error_event.cc +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "error_event.h" - -namespace kraken { - -ErrorEventInit::ErrorEventInit(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - FillMembersWithQJSObject(ctx, value, exception_state); -} - -bool ErrorEventInit::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { - return false; -} - -void ErrorEventInit::FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state) {} - -} - diff --git a/bridge/core/dom/events/error_event.h b/bridge/core/dom/events/error_event.h deleted file mode 100644 index 19130af1ac..0000000000 --- a/bridge/core/dom/events/error_event.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ -#define KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ - -#include "event.h" -#include "bindings/qjs/dictionary_base.h" - -namespace kraken { - -class ErrorEventInit : public DictionaryBase { - public: - static std::shared_ptr Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - - }; - - ErrorEventInit() = delete; - ErrorEventInit(JSContext* ctx, JSValue value, ExceptionState& exception_state); - - bool FillQJSObjectWithMembers(JSContext *ctx, JSValue qjs_dictionary) const override; - void FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state); -}; - -class ErrorEvent : public Event { - DEFINE_WRAPPERTYPEINFO(); - public: - static ErrorEvent* Create(ExecutingContext* context, const AtomicString& event_type, ExceptionState& exception_state); - private: - -}; - -} - -#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ diff --git a/bridge/core/dom/events/error_event_init.d.ts b/bridge/core/dom/events/error_event_init.d.ts deleted file mode 100644 index 1b98ff8445..0000000000 --- a/bridge/core/dom/events/error_event_init.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {EventInit} from "./event_init"; - -// @ts-ignore -@Dictionary() -export interface ErrorEventInit extends EventInit { - readonly error: any; -} diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 9bbb99c7a8..1b8bf2ffe0 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -37,7 +37,6 @@ Event::Event(ExecutingContext* context, const AtomicString& event_type, Bubbles was_initialized_(true), is_trusted_(false), prevent_default_called_on_uncancelable_event_(false), - legacy_did_listeners_throw_flag_(false), fire_only_capture_listeners_at_target_(false), fire_only_non_capture_listeners_at_target_(false), event_phase_(0), diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index d51c3beeb6..134e58058d 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -158,12 +158,6 @@ class Event : public ScriptWrappable { // Whether preventDefault was called on uncancelable event. unsigned prevent_default_called_on_uncancelable_event_ : 1; - // Whether any of listeners have thrown an exception or not. - // Corresponds to |legacyOutputDidListenersThrowFlag| in DOM standard. - // https://dom.spec.whatwg.org/#dispatching-events - // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke - unsigned legacy_did_listeners_throw_flag_ : 1; - unsigned fire_only_capture_listeners_at_target_ : 1; unsigned fire_only_non_capture_listeners_at_target_ : 1; diff --git a/bridge/core/dom/events/close_event.cc b/bridge/core/events/close_event.cc similarity index 100% rename from bridge/core/dom/events/close_event.cc rename to bridge/core/events/close_event.cc diff --git a/bridge/core/dom/events/close_event.d.ts b/bridge/core/events/close_event.d.ts similarity index 100% rename from bridge/core/dom/events/close_event.d.ts rename to bridge/core/events/close_event.d.ts diff --git a/bridge/core/dom/events/close_event.h b/bridge/core/events/close_event.h similarity index 100% rename from bridge/core/dom/events/close_event.h rename to bridge/core/events/close_event.h diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc new file mode 100644 index 0000000000..38815aaeaf --- /dev/null +++ b/bridge/core/events/error_event.cc @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "error_event.h" + +namespace kraken { + +ErrorEvent* ErrorEvent::Create(ExecutingContext* context, const std::string& message) { + return makeGarbageCollected(context, message); +} +ErrorEvent* ErrorEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { + return makeGarbageCollected(context, type, exception_state); +} +ErrorEvent* ErrorEvent::Create(ExecutingContext* context, + const AtomicString& type, + const ErrorEventInit* initializer, + ExceptionState& exception_state) { + return makeGarbageCollected(context, type, initializer, exception_state); +} + +ErrorEvent::ErrorEvent(ExecutingContext* context, const std::string& message) + : Event(context), message_(message), source_location_(std::make_unique("", 0, 0)) {} + +ErrorEvent::ErrorEvent(ExecutingContext* context, const std::string& message, std::unique_ptr location) + : Event(context), message_(message), source_location_(std::move(location)) {} + +ErrorEvent::ErrorEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context), message_(type.ToStdString()), source_location_(std::make_unique("", 0, 0)) {} + +ErrorEvent::ErrorEvent(ExecutingContext* context, + const AtomicString& type, + const ErrorEventInit* initializer, + ExceptionState& exception_state) + : Event(context), + message_(type.ToStdString()), + error_(initializer->error()), + source_location_(std::make_unique(initializer->filename().ToStdString(), initializer->lineno(), initializer->colno())) {} + +} // namespace kraken diff --git a/bridge/core/events/error_event.h b/bridge/core/events/error_event.h new file mode 100644 index 0000000000..31e857b54f --- /dev/null +++ b/bridge/core/events/error_event.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ +#define KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ + +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "core/dom/events/event.h" +#include "qjs_error_event_init.h" + +namespace kraken { + +class ErrorEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + public: + using ImplType = ErrorEvent*; + static ErrorEvent* Create(ExecutingContext* context, const std::string& message); + static ErrorEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + static ErrorEvent* Create(ExecutingContext* context, const AtomicString& type, const ErrorEventInit* initializer, ExceptionState& exception_state); + + explicit ErrorEvent(ExecutingContext* context, const std::string& message); + explicit ErrorEvent(ExecutingContext* context, const std::string& message, std::unique_ptr location); + explicit ErrorEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + explicit ErrorEvent(ExecutingContext* context, const AtomicString& type, const ErrorEventInit* initializer, ExceptionState& exception_state); + + // As |message| is exposed to JavaScript, never return |unsanitized_message_|. + const std::string& message() const { return message_; } + const std::string& filename() const { return source_location_->Url(); } + unsigned lineno() const { return source_location_->LineNumber(); } + unsigned colno() const { return source_location_->ColumnNumber(); } + + ScriptValue error() const { return error_; } + + SourceLocation* Location() const { return source_location_.get(); } + + private: + std::string message_; + std::unique_ptr source_location_{nullptr}; + ScriptValue error_; +}; + +} + +#endif // KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ diff --git a/bridge/core/dom/events/error_event.ts b/bridge/core/events/error_event.ts similarity index 100% rename from bridge/core/dom/events/error_event.ts rename to bridge/core/events/error_event.ts 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/dom/events/event_type_names.json b/bridge/core/events/event_type_names.json similarity index 100% rename from bridge/core/dom/events/event_type_names.json rename to bridge/core/events/event_type_names.json diff --git a/bridge/core/dom/events/gesture_event.d.ts b/bridge/core/events/gesture_event.d.ts similarity index 100% rename from bridge/core/dom/events/gesture_event.d.ts rename to bridge/core/events/gesture_event.d.ts diff --git a/bridge/core/dom/events/input_event.cc b/bridge/core/events/input_event.cc similarity index 100% rename from bridge/core/dom/events/input_event.cc rename to bridge/core/events/input_event.cc diff --git a/bridge/core/dom/events/input_event.d.ts b/bridge/core/events/input_event.d.ts similarity index 100% rename from bridge/core/dom/events/input_event.d.ts rename to bridge/core/events/input_event.d.ts diff --git a/bridge/core/dom/events/input_event.h b/bridge/core/events/input_event.h similarity index 100% rename from bridge/core/dom/events/input_event.h rename to bridge/core/events/input_event.h diff --git a/bridge/core/dom/events/intersection_change_event.cc b/bridge/core/events/intersection_change_event.cc similarity index 100% rename from bridge/core/dom/events/intersection_change_event.cc rename to bridge/core/events/intersection_change_event.cc diff --git a/bridge/core/dom/events/intersection_change_event.d.ts b/bridge/core/events/intersection_change_event.d.ts similarity index 100% rename from bridge/core/dom/events/intersection_change_event.d.ts rename to bridge/core/events/intersection_change_event.d.ts diff --git a/bridge/core/dom/events/intersection_change_event.h b/bridge/core/events/intersection_change_event.h similarity index 100% rename from bridge/core/dom/events/intersection_change_event.h rename to bridge/core/events/intersection_change_event.h diff --git a/bridge/core/dom/events/touch_event.cc b/bridge/core/events/touch_event.cc similarity index 100% rename from bridge/core/dom/events/touch_event.cc rename to bridge/core/events/touch_event.cc diff --git a/bridge/core/dom/events/touch_event.h b/bridge/core/events/touch_event.h similarity index 100% rename from bridge/core/dom/events/touch_event.h rename to bridge/core/events/touch_event.h diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index e3f9a4da94..09c0c5f068 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -244,7 +244,7 @@ export function generateCppSource(blob: IDLBlob) { classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) }); wrapperTypeInfoInit = ` -const WrapperTypeInfo wrapper_type_info_ {JS_CLASS_${getClassName(blob).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, QJS${getClassName(blob)}::ConstructorCallback}; +const WrapperTypeInfo QJS${getClassName(blob)}::wrapper_type_info_ {JS_CLASS_${getClassName(blob).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, QJS${getClassName(blob)}::ConstructorCallback}; const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::wrapper_type_info_;`; return _.template(readTemplate('interface'))({ className: getClassName(blob), diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl index e93034724f..0485078880 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl @@ -36,7 +36,12 @@ void <%= className %>::FillMembersWithQJSObject(JSContext* ctx, JSValue value, E } <% _.forEach(props, function(prop, index) { %> - <%= prop.name %>_ = Converter<<%= generateTypeConverter(prop.type) %>>::FromValue(ctx, value, exception_state); + { + JSValue v = JS_GetPropertyStr(ctx, value, "<%= prop.name %>"); + <%= prop.name %>_ = Converter<<%= generateTypeConverter(prop.type) %>>::FromValue(ctx, v, exception_state); + JS_FreeValue(ctx, v); + } + <% }); %> From 9fc2168c3aa19ae082b034343b3d4b357d334a6e Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 1 Apr 2022 21:02:47 +0800 Subject: [PATCH 046/498] feat: impl event target --- bridge/bindings/qjs/converter_impl.h | 17 ++++++ .../bindings/qjs/js_based_event_listener.cc | 4 +- bridge/bindings/qjs/js_based_event_listener.h | 2 +- bridge/bindings/qjs/js_event_handler.cc | 2 +- bridge/bindings/qjs/js_event_handler.h | 2 + bridge/bindings/qjs/js_event_listener.cc | 24 +++++++++ bridge/bindings/qjs/js_event_listener.h | 39 +++++++++++++- bridge/bindings/qjs/qjs_function.cc | 4 +- bridge/bindings/qjs/qjs_function.h | 6 ++- bridge/bindings/qjs/qjs_interface_bridge.h | 8 ++- bridge/core/dom/events/event.d.ts | 1 + bridge/core/dom/events/event.h | 5 ++ bridge/core/dom/events/event_listener.h | 2 +- bridge/core/dom/events/event_target.cc | 52 +++++++++++++++++-- bridge/core/dom/events/event_target.d.ts | 6 ++- bridge/core/dom/events/event_target.h | 50 +++++++++++------- bridge/scripts/code_generator/global.d.ts | 1 + 17 files changed, 188 insertions(+), 37 deletions(-) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index fc0de2948b..8c20eb1497 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -15,6 +15,7 @@ #include "core/fileapi/blob_property_bag.h" #include "idl_type.h" #include "native_string_utils.h" +#include "js_event_listener.h" namespace kraken { @@ -344,6 +345,22 @@ struct Converter : public ConverterBase { static JSValue ToValue(JSContext* ctx, ImplType value) { return reinterpret_cast(value)->ToQuickJS(); } }; +template<> +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return JSEventListener::CreateOrNull(QJSFunction::Create(ctx, value)); + } +}; + +template<> +struct Converter> : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return Converter::FromValue(ctx, value, exception_state); + } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc index a90afbc1a1..cae9a8c084 100644 --- a/bridge/bindings/qjs/js_based_event_listener.cc +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -9,13 +9,11 @@ namespace kraken { // Implements step 2. of "inner invoke". // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke -void JSBasedEventListener::Invoke(ExecutingContext* context, Event* event) { +void JSBasedEventListener::Invoke(ExecutingContext* context, Event* event, ExceptionState& exception_state) { assert(context); assert(event); if (!context->IsValid()) return; - - ExceptionState exception_state; // Step 10: Call a listener with event's currentTarget as receiver and event // and handle errors if thrown. InvokeInternal(*event->currentTarget(), *event, exception_state); diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index 3bc162f113..1f31592b5a 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -20,7 +20,7 @@ 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) final; + void Invoke(ExecutingContext* context, Event* event, ExceptionState& exception_state); // Implements "get the current value of the event handler". // https://html.spec.whatwg.org/C/#getting-the-current-value-of-the-event-handler diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index e87e264e8e..af17cf2eb8 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -80,7 +80,7 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc } ScriptValue result = event_handler_ - ->Invoke(event.ctx(), arguments.size(), arguments.data()); + ->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), arguments.size(), arguments.data()); if (result.IsException()) { exception_state.ThrowException(event.ctx(), result.ToQuickJS()); return; diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index 4ebd156e08..7f68115243 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -15,6 +15,8 @@ namespace kraken { // 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 diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index 8b21b8acf9..e48ca0e9f9 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -4,3 +4,27 @@ */ #include "js_event_listener.h" +#include "core/dom/events/event_target.h" + +namespace kraken { + +JSEventListener::JSEventListener(std::shared_ptr listener) : event_listener_(listener) {} +JSValue JSEventListener::GetListenerObject(EventTarget&) { + return event_listener_->ToQuickJS(); +} +JSValue JSEventListener::GetEffectiveFunction(EventTarget&) { + return event_listener_->ToQuickJS(); +} +void JSEventListener::InvokeInternal(EventTarget& event_target, Event& event, ExceptionState& exception_state) { + ScriptValue arguments[] = { + ScriptValue(event.ctx(), event.ToQuickJS()) + }; + + ScriptValue result = event_listener_->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), 1, arguments); + if (result.IsException()) { + exception_state.ThrowException(event.ctx(), result.ToQuickJS()); + return; + } +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h index 3f8317ac1f..50ff156092 100644 --- a/bridge/bindings/qjs/js_event_listener.h +++ b/bridge/bindings/qjs/js_event_listener.h @@ -6,6 +6,43 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ -class js_event_listener {}; +#include "js_based_event_listener.h" +#include "foundation/casting.h" + +namespace kraken { + +// |JSEventListener| implements EventListener in the DOM standard. +// https://dom.spec.whatwg.org/#callbackdef-eventlistener +class JSEventListener final : public JSBasedEventListener { + public: + using ImplType = std::shared_ptr; + + // TODO: Support IDL EventListener callbackInterface. + static std::unique_ptr CreateOrNull(std::shared_ptr listener) { + return listener ? std::make_unique(listener) : nullptr; + } + + explicit JSEventListener(std::shared_ptr listener); + + JSValue GetListenerObject(EventTarget&) override; + + JSValue GetEffectiveFunction(EventTarget&) override; + + bool IsJSEventListener() const override { return true; } + + bool Matches(const EventListener& other) const override { + const auto* other_listener = DynamicTo(other); + return other_listener && *event_listener_ == *other_listener->event_listener_; + } + + private: + void InvokeInternal(EventTarget&, + Event&, + ExceptionState& exception_state) override; + + const std::shared_ptr event_listener_; +}; + +} #endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index c75968bc19..8d25710606 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -12,7 +12,7 @@ bool QJSFunction::IsFunction(JSContext* ctx) { return JS_IsFunction(ctx, function_); } -ScriptValue QJSFunction::Invoke(JSContext* ctx, int32_t argc, ScriptValue* arguments) { +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_); @@ -22,7 +22,7 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, int32_t argc, ScriptValue* argum argv[0 + i] = arguments[i].ToQuickJS(); } - JSValue returnValue = JS_Call(ctx, function_, JS_UNDEFINED, argc, argv); + JSValue returnValue = JS_Call(ctx, function_, this_val.ToQuickJS(), argc, argv); // Free the previous duplicated function. JS_FreeValue(ctx, function_); diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 89c0aa46a0..81657eb1ff 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -25,7 +25,11 @@ class QJSFunction { // Performs "invoke". // https://webidl.spec.whatwg.org/#invoke-a-callback-function - ScriptValue Invoke(JSContext* ctx, int32_t argc, ScriptValue* arguments); + 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_); + }; private: JSContext* ctx_{nullptr}; diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index 9a352869cf..b166374ff5 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -6,7 +6,6 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ -#include "core/executing_context.h" #include "script_wrappable.h" namespace kraken { @@ -16,9 +15,14 @@ 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()->prototypeForType(QJST::GetWrapperTypeInfo())); } + static bool HasInstance(ExecutingContext* context, JSValue value); }; +template +bool QJSInterfaceBridge::HasInstance(ExecutingContext* context, JSValue value) { + return false; +} + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ diff --git a/bridge/core/dom/events/event.d.ts b/bridge/core/dom/events/event.d.ts index 8926a05f86..79f4547fdb 100644 --- a/bridge/core/dom/events/event.d.ts +++ b/bridge/core/dom/events/event.d.ts @@ -18,6 +18,7 @@ interface Event { 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. */ diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 134e58058d..74a7716a3f 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -119,6 +119,11 @@ class Event : public ScriptWrappable { 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 WasInitialized() { return was_initialized_; } + + bool isTrusted() const { return is_trusted_; } + void SetTrusted(bool value) { is_trusted_ = value; } + bool defaultPrevented() const { return default_prevented_; } void preventDefault(ExceptionState& exception_state); diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h index 86bbda63cd..4b43eb1579 100644 --- a/bridge/core/dom/events/event_listener.h +++ b/bridge/core/dom/events/event_listener.h @@ -31,7 +31,7 @@ class EventListener { ~EventListener() = default; // Invokes this event listener. - virtual void Invoke(ExecutingContext* context, Event*) = 0; + virtual void Invoke(ExecutingContext* context, Event*, ExceptionState& exception_state) = 0; // Returns true if this implements IDL EventHandler family. virtual bool IsEventHandler() const { return false; } diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 1440b6d026..c212e9ed3e 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -22,25 +22,67 @@ EventTargetData::~EventTargetData() {} void EventTargetData::Trace(GCVisitor* visitor) const {} EventTarget* EventTarget::Create(ExecutingContext* context) { - return makeGarbageCollected(context); + return makeGarbageCollected(context); } EventTarget::EventTarget(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} -bool addEventListener(std::unique_ptr& event_type, const std::shared_ptr& callback, ExceptionState& exception_state) {} +bool EventTarget::addEventListener(const AtomicString& event_type, + const std::shared_ptr& event_listener, + ExceptionState& exception_state) { + return AddEventListenerInternal(event_type, event_listener.get()); +} + +bool EventTarget::removeEventListener(const AtomicString& event_type, + const std::shared_ptr& event_listener, + ExceptionState& exception_state) { + return RemoveEventListenerInternal(event_type, event_listener.get()); +} + +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 (!context()) + 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) != + DispatchEventResult::kCanceledByEventHandler; +} void EventTarget::Trace(GCVisitor* visitor) const {} void EventTarget::Dispose() const {} -const char* EventTarget::GetHumanReadableName() const { - return "EventTarget"; +bool EventTarget::AddEventListenerInternal(const AtomicString& event_type, const EventListener* listener) { + return false; } -bool EventTarget::addEventListener(std::unique_ptr& event_type, const std::shared_ptr& callback, ExceptionState& exception_state) { +bool EventTarget::RemoveEventListenerInternal(const AtomicString& event_type, const EventListener* listener) { return false; } +DispatchEventResult EventTarget::DispatchEventInternal(Event& event) { + return DispatchEventResult::kCanceledByDefaultEventHandler; +} + +const char* EventTarget::GetHumanReadableName() const { + return "EventTarget"; +} } // namespace kraken // namespace kraken::binding::qjs diff --git a/bridge/core/dom/events/event_target.d.ts b/bridge/core/dom/events/event_target.d.ts index 021eae747f..a16d88d8d4 100644 --- a/bridge/core/dom/events/event_target.d.ts +++ b/bridge/core/dom/events/event_target.d.ts @@ -1,6 +1,8 @@ + +// TODO: support options for addEventListener and removeEventListener interface EventTarget { - addEventListener(type: string, callback: EventListener | null): void; + addEventListener(type: string, callback: JSEventListener | null): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: EventListener | null): void; + removeEventListener(type: string, callback: JSEventListener | null): void; new(): EventTarget; } diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index c0290293fa..d1ff43d79b 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -8,6 +8,7 @@ #include "bindings/qjs/qjs_function.h" #include "bindings/qjs/script_wrappable.h" +#include "bindings/qjs/js_event_listener.h" #include "event_listener_map.h" #include "foundation/native_string.h" @@ -20,6 +21,25 @@ void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, namespace kraken { +enum class DispatchEventResult { + // Event was not canceled by event handler or default event handler. + kNotCanceled, + // Event was canceled by event handler; i.e. a script handler calling + // preventDefault. + kCanceledByEventHandler, + // Event was canceled by the default event handler; i.e. executing the default + // action. This result should be used sparingly as it deviates from the DOM + // Event Dispatch model. Default event handlers really shouldn't be invoked + // inside of dispatch. + kCanceledByDefaultEventHandler, + // Event was canceled but suppressed before dispatched to event handler. This + // result should be used sparingly; and its usage likely indicates there is + // potential for a bug. Trusted events may return this code; but untrusted + // events likely should always execute the event handler the developer intends + // to execute. + kCanceledBeforeDispatch, +}; + class EventTargetData final { KRAKEN_DISALLOW_NEW(); @@ -51,28 +71,20 @@ class EventTarget : public ScriptWrappable { EventTarget() = delete; explicit EventTarget(ExecutingContext* context); - bool addEventListener(std::unique_ptr& event_type, const std::shared_ptr& callback, ExceptionState& exception_state); + bool addEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, ExceptionState& exception_state); + bool removeEventListener(const AtomicString& event_type, const std::shared_ptr &event_listener, ExceptionState& exception_state); + bool dispatchEvent(Event* event, ExceptionState& exception_state); void Trace(GCVisitor* visitor) const override; void Dispose() const override; - // virtual bool AddEventListenerInternal(const AtomicString& event_type, - // EventListener*, - // const AddEventListenerOptionsResolved*); - // bool RemoveEventListenerInternal(const AtomicString& event_type, - // const EventListener*, - // const EventListenerOptions*); - // - // // Called when an event listener has been successfully added. - // virtual void AddedEventListener(const AtomicString& event_type, - // RegisteredEventListener&); - // - // // Called when an event listener is removed. The original registration - // // parameters of this event listener are available to be queried. - // virtual void RemovedEventListener(const AtomicString& event_type, - // const RegisteredEventListener&); - // - // virtual DispatchEventResult DispatchEventInternal(Event&); + protected: + + virtual bool AddEventListenerInternal(const AtomicString& event_type, const EventListener* listener); + + bool RemoveEventListenerInternal(const AtomicString& event_type, const EventListener* listener); + + DispatchEventResult DispatchEventInternal(Event& event); // Subclasses should likely not override these themselves; instead, they // should subclass EventTargetWithInlineData. @@ -89,6 +101,8 @@ class EventTarget : public ScriptWrappable { // Provide EventTarget with inlined EventTargetData for improved performance. class EventTargetWithInlineData : public EventTarget { public: + EventTargetWithInlineData(ExecutingContext* context): EventTarget(context) {}; + void Trace(GCVisitor* visitor) const override; protected: diff --git a/bridge/scripts/code_generator/global.d.ts b/bridge/scripts/code_generator/global.d.ts index 9e11d1f433..35cc8ad715 100644 --- a/bridge/scripts/code_generator/global.d.ts +++ b/bridge/scripts/code_generator/global.d.ts @@ -6,3 +6,4 @@ declare interface Dictionary {} declare interface BlobPart {} declare interface BlobPropertyBag {} declare function Dictionary() : any; +declare type JSEventListener = void; From 501932a956d428ca0cf1245edb122d0f543fe57b Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Sat, 2 Apr 2022 19:03:30 +0800 Subject: [PATCH 047/498] feat: finish event target impl. --- bridge/CMakeLists.txt | 4 + bridge/bindings/qjs/atom_string.h | 1 + bridge/bindings/qjs/script_wrappable.cc | 2 +- bridge/bindings/qjs/script_wrappable.h | 2 +- bridge/core/dom/events/event.cc | 10 + bridge/core/dom/events/event.h | 33 ++++ bridge/core/dom/events/event_listener_map.cc | 6 +- bridge/core/dom/events/event_listener_map.h | 7 +- bridge/core/dom/events/event_target.cc | 181 ++++++++++++++++-- bridge/core/dom/events/event_target.d.ts | 7 +- bridge/core/dom/events/event_target.h | 101 +++++++--- .../dom/events/registered_eventListener.cc | 20 +- .../dom/events/registered_eventListener.h | 5 +- bridge/core/executing_context.cc | 4 +- bridge/core/fileapi/blob.cc | 8 +- .../static/idl_templates/dictionary.cc.tpl | 8 +- .../static/idl_templates/dictionary.h.tpl | 4 +- 17 files changed, 333 insertions(+), 70 deletions(-) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 3eb30febf7..a41bd9655f 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -309,6 +309,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_blob.h out/qjs_event.cc out/qjs_event.h + out/qjs_add_event_listener_options.cc + out/qjs_add_event_listener_options.h + out/qjs_event_listener_options.cc + out/qjs_event_listener_options.h out/qjs_error_event.h out/qjs_error_event.cc out/qjs_event_init.h diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index 3103e88619..b12313aac2 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -77,6 +77,7 @@ class AtomicString { } bool operator==(const AtomicString& other) const { return other.atom_ == this->atom_; } + bool operator!=(const AtomicString& other) const { return other.atom_ != this->atom_; }; protected: bool is_static_atom_ = false; diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index ef977fcbb7..4e93768f10 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -64,7 +64,7 @@ void ScriptWrappable::InitializeQuickJSObject() { JS_SetOpaque(jsObject_, this); // Let instance inherit EventTarget prototype methods. - JSValue prototype = context()->contextData()->prototypeForType(wrapperTypeInfo); + JSValue prototype = GetExecutingContext()->contextData()->prototypeForType(wrapperTypeInfo); JS_SetPrototype(ctx_, jsObject_, prototype); wrapped_ = true; diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 13268dd408..5855f1a692 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -41,7 +41,7 @@ class ScriptWrappable : public GarbageCollected { virtual const WrapperTypeInfo* GetWrapperTypeInfo() const = 0; JSValue ToQuickJS(); - FORCE_INLINE ExecutingContext* context() const { return static_cast(JS_GetContextOpaque(ctx_)); }; + FORCE_INLINE ExecutingContext* GetExecutingContext() const { return static_cast(JS_GetContextOpaque(ctx_)); }; FORCE_INLINE JSContext* ctx() const { return ctx_; } private: diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 1b8bf2ffe0..4bc40a598e 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -36,6 +36,7 @@ Event::Event(ExecutingContext* context, const AtomicString& event_type, Bubbles 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), @@ -72,6 +73,11 @@ void Event::SetCurrentTarget(EventTarget* target) { } void Event::preventDefault(ExceptionState& exception_state) { + if (handling_passive_ != PassiveMode::kNotPassive && + handling_passive_ != PassiveMode::kNotPassiveDefault) { + return; + } + default_prevented_ = true; } @@ -90,6 +96,10 @@ void Event::initEvent(const AtomicString& event_type, bool bubbles, bool cancela cancelable_ = cancelable; } +void Event::SetHandlingPassive(PassiveMode mode) { + handling_passive_ = mode; +} + void Event::Trace(GCVisitor* visitor) const { visitor->Trace(target_); visitor->Trace(current_target_); diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 74a7716a3f..d5a555a9a7 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -71,6 +71,21 @@ class Event : public ScriptWrappable { kScoped, }; + + enum class PassiveMode { + // Not passive, default initialized. + kNotPassiveDefault, + // Not passive, explicitly specified. + kNotPassive, + // Passive, explicitly specified. + kPassive, + // Passive, not explicitly specified and forced due to document level + // listener. + kPassiveForcedDocumentLevel, + // Passive, default initialized. + kPassiveDefault, + }; + enum PhaseType { kNone = 0, kCapturingPhase = 1, kAtTarget = 2, kBubblingPhase = 3 }; static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; @@ -101,6 +116,12 @@ class Event : public ScriptWrappable { uint8_t eventPhase() const { return event_phase_; } void SetEventPhase(uint8_t event_phase) { event_phase_ = event_phase; } + // 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) { if (cancel) { @@ -119,14 +140,22 @@ class Event : public ScriptWrappable { 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; @@ -144,6 +173,9 @@ class Event : public ScriptWrappable { void Dispose() const override; protected: + + PassiveMode HandlingPassive() const { return handling_passive_; } + AtomicString type_; unsigned bubbles_ : 1; @@ -158,6 +190,7 @@ class Event : public ScriptWrappable { unsigned was_initialized_ : 1; unsigned is_trusted_ : 1; + PassiveMode handling_passive_; uint8_t event_phase_ = PhaseType::kNone; // Whether preventDefault was called on uncancelable event. diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index dd32fd7318..62f54aeea1 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -23,7 +23,7 @@ static bool AddListenerToVector(EventListenerVector* vector, static bool RemoveListenerFromVector(EventListenerVector* listener_vector, const std::shared_ptr& listener, - const std::shared_ptr& options, + const std::shared_ptr& options, size_t* index_of_removed_listener, RegisteredEventListener* registered_event_listener) { // Do a manual search for the matching listener. It is not @@ -83,7 +83,7 @@ bool EventListenerMap::Add(const AtomicString& event_type, bool EventListenerMap::Remove(const AtomicString& event_type, const std::shared_ptr& listener, - const std::shared_ptr& options, + const std::shared_ptr& options, size_t* index_of_removed_listener, RegisteredEventListener* registered_event_listener) { for (unsigned i = 0; i < entries_.size(); ++i) { @@ -99,7 +99,7 @@ bool EventListenerMap::Remove(const AtomicString& event_type, return false; } -const EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) { +EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) { for (const auto& entry : entries_) { if (entry.first == event_type) return entry.second.get(); diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index 97b83a2ee8..c4141320e6 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -17,6 +17,9 @@ namespace kraken { +class AddEventListenerOptions; +class EventListenerOptions; + using EventListenerVector = std::vector; class EventListenerMap final { @@ -34,10 +37,10 @@ class EventListenerMap final { bool Add(const AtomicString& event_type, const std::shared_ptr& listener, const std::shared_ptr& options, RegisteredEventListener* registered_event_listener); bool Remove(const AtomicString& event_type, const std::shared_ptr& listener, - const std::shared_ptr& options, + const std::shared_ptr& options, size_t* index_of_removed_listener, RegisteredEventListener* registered_event_listener); - const EventListenerVector* Find(const AtomicString& event_type); + EventListenerVector* Find(const AtomicString& event_type); private: // EventListener handlers registered with addEventListener API. diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index c212e9ed3e..ab060a95ff 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -4,6 +4,8 @@ */ #include "event_target.h" +#include "event_type_names.h" +#include "qjs_add_event_listener_options.h" #define PROPAGATION_STOPPED 1 #define PROPAGATION_CONTINUE 0 @@ -14,6 +16,15 @@ namespace kraken { +Event::PassiveMode EventPassiveMode( + const RegisteredEventListener& event_listener) { + if (!event_listener.Passive()) { + return Event::PassiveMode::kNotPassiveDefault; + } + return Event::PassiveMode::kPassiveDefault; +} + + // EventTargetData EventTargetData::EventTargetData() {} @@ -28,31 +39,40 @@ EventTarget* EventTarget::Create(ExecutingContext* context) { EventTarget::EventTarget(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} bool EventTarget::addEventListener(const AtomicString& event_type, - const std::shared_ptr& event_listener, + const std::shared_ptr& event_listener, + const std::shared_ptr& options, ExceptionState& exception_state) { - return AddEventListenerInternal(event_type, event_listener.get()); + return AddEventListenerInternal(event_type, event_listener, options); +} + +bool EventTarget::removeEventListener(const AtomicString& event_type, + const std::shared_ptr& event_listener, + const std::shared_ptr& options, + ExceptionState& exception_state) { + return RemoveEventListenerInternal(event_type, event_listener, options); } bool EventTarget::removeEventListener(const AtomicString& event_type, - const std::shared_ptr& event_listener, + const std::shared_ptr& event_listener, + bool use_capture, ExceptionState& exception_state) { - return RemoveEventListenerInternal(event_type, event_listener.get()); + 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."); + 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."); + exception_state.ThrowException(event->ctx(), ErrorType::InternalError, "The event is already being dispatched."); return false; } - if (!context()) + if (!GetExecutingContext()) return false; event->SetTrusted(false); @@ -60,20 +80,91 @@ bool EventTarget::dispatchEvent(Event* event, ExceptionState& exception_state) { // 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) != - DispatchEventResult::kCanceledByEventHandler; + return DispatchEventInternal(*event) != DispatchEventResult::kCanceledByEventHandler; } void EventTarget::Trace(GCVisitor* visitor) const {} void EventTarget::Dispose() const {} -bool EventTarget::AddEventListenerInternal(const AtomicString& event_type, const EventListener* listener) { - return false; +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::RemoveEventListenerInternal(const AtomicString& event_type, const EventListener* listener) { - return false; +bool EventTarget::AddEventListenerInternal(const AtomicString& event_type, + const std::shared_ptr& listener, + const std::shared_ptr& options) { + if (!listener) + return false; + + RegisteredEventListener registered_listener; + bool added = EnsureEventTargetData().event_listener_map.Add(event_type, listener, options, ®istered_listener); + + return added; +} + +bool EventTarget::RemoveEventListenerInternal(const AtomicString& event_type, + const std::shared_ptr& listener, + const std::shared_ptr& options) { + if (!listener) + return false; + + EventTargetData* d = GetEventTargetData(); + if (!d) + return false; + + size_t index_of_removed_listener; + RegisteredEventListener registered_listener; + + if (!d->event_listener_map.Remove(event_type, listener, options, &index_of_removed_listener, ®istered_listener)) + return false; + + // Notify firing events planning to invoke the listener at 'index' that + // they have one less listener to invoke. + if (d->firing_event_iterators) { + for (const auto& firing_iterator : *d->firing_event_iterators) { + if (event_type != firing_iterator.event_type) + continue; + + if (index_of_removed_listener >= firing_iterator.end) + continue; + + --firing_iterator.end; + // Note that when firing an event listener, + // firingIterator.iterator indicates the next event listener + // that would fire, not the currently firing event + // listener. See EventTarget::fireEventListeners. + if (index_of_removed_listener < firing_iterator.iterator) + --firing_iterator.iterator; + } + } + + return true; } DispatchEventResult EventTarget::DispatchEventInternal(Event& event) { @@ -83,6 +174,66 @@ DispatchEventResult EventTarget::DispatchEventInternal(Event& event) { const char* EventTarget::GetHumanReadableName() const { return "EventTarget"; } + +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(); + 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 listener = registered_listener.Callback(); + // The listener will be retained by Member 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; +} + } // namespace kraken // namespace kraken::binding::qjs diff --git a/bridge/core/dom/events/event_target.d.ts b/bridge/core/dom/events/event_target.d.ts index a16d88d8d4..f60631ffe8 100644 --- a/bridge/core/dom/events/event_target.d.ts +++ b/bridge/core/dom/events/event_target.d.ts @@ -1,8 +1,11 @@ // TODO: support options for addEventListener and removeEventListener +import {AddEventListenerOptions} from "./add_event_listener_options"; +import {EventListenerOptions} from "./event_listener_options"; + interface EventTarget { - addEventListener(type: string, callback: JSEventListener | null): void; + addEventListener(type: string, callback: JSEventListener | null, options?: AddEventListenerOptions): void; + removeEventListener(type: string, callback: JSEventListener | null, options?: EventListenerOptions): void; dispatchEvent(event: Event): boolean; - removeEventListener(type: string, callback: JSEventListener | null): void; new(): EventTarget; } diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index d1ff43d79b..920b01b5ef 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -6,9 +6,9 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H +#include "bindings/qjs/js_event_listener.h" #include "bindings/qjs/qjs_function.h" #include "bindings/qjs/script_wrappable.h" -#include "bindings/qjs/js_event_listener.h" #include "event_listener_map.h" #include "foundation/native_string.h" @@ -21,6 +21,9 @@ void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, namespace kraken { +class AddEventListenerOptions; +class EventListenerOptions; + enum class DispatchEventResult { // Event was not canceled by event handler or default event handler. kNotCanceled, @@ -40,6 +43,20 @@ enum class DispatchEventResult { kCanceledBeforeDispatch, }; +struct FiringEventIterator { + KRAKEN_DISALLOW_NEW(); + + public: + FiringEventIterator(const AtomicString& event_type, size_t& iterator, size_t& end) + : event_type(event_type), iterator(iterator), end(end) {} + + const AtomicString& event_type; + size_t& iterator; + size_t& end; +}; + +using FiringEventIteratorVector = std::vector; + class EventTargetData final { KRAKEN_DISALLOW_NEW(); @@ -51,8 +68,8 @@ class EventTargetData final { void Trace(GCVisitor* visitor) const; - private: - EventListenerMap event_listener_map_; + EventListenerMap event_listener_map; + std::unique_ptr firing_event_iterators; }; // All DOM event targets extend EventTarget. The spec is defined here: @@ -71,18 +88,34 @@ class EventTarget : public ScriptWrappable { EventTarget() = delete; explicit EventTarget(ExecutingContext* context); - bool addEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, ExceptionState& exception_state); - bool removeEventListener(const AtomicString& event_type, const std::shared_ptr &event_listener, ExceptionState& exception_state); + bool addEventListener(const AtomicString& event_type, + const std::shared_ptr& event_listener, + const std::shared_ptr& options, + ExceptionState& exception_state); + bool removeEventListener(const AtomicString& event_type, + const std::shared_ptr& event_listener, + const std::shared_ptr& options, + ExceptionState& exception_state); + bool removeEventListener(const AtomicString& event_type, + const std::shared_ptr& event_listener, + bool use_capture, + ExceptionState& exception_state); bool dispatchEvent(Event* event, ExceptionState& exception_state); void Trace(GCVisitor* visitor) const override; void Dispose() const override; - protected: + DispatchEventResult FireEventListeners(Event&, ExceptionState&); - virtual bool AddEventListenerInternal(const AtomicString& event_type, const EventListener* listener); + static DispatchEventResult GetDispatchEventResult(const Event&); - bool RemoveEventListenerInternal(const AtomicString& event_type, const EventListener* listener); + protected: + virtual bool AddEventListenerInternal(const AtomicString& event_type, + const std::shared_ptr& listener, + const std::shared_ptr& options); + bool RemoveEventListenerInternal(const AtomicString& event_type, + const std::shared_ptr& listener, + const std::shared_ptr& options); DispatchEventResult DispatchEventInternal(Event& event); @@ -96,12 +129,13 @@ class EventTarget : public ScriptWrappable { virtual bool IsWindowOrWorkerGlobalScope() const { return false; } private: + bool FireEventListeners(Event&, EventTargetData*, EventListenerVector&, ExceptionState&); }; // Provide EventTarget with inlined EventTargetData for improved performance. class EventTargetWithInlineData : public EventTarget { public: - EventTargetWithInlineData(ExecutingContext* context): EventTarget(context) {}; + EventTargetWithInlineData(ExecutingContext* context) : EventTarget(context){}; void Trace(GCVisitor* visitor) const override; @@ -118,15 +152,25 @@ class EventTargetWithInlineData : public EventTarget { // |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); } + void setOn##lower_name(EventListener* listener) { \ + SetAttributeEventListener(event_type_names::symbol_name, listener); \ + } -#define DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ - static EventListener* on##lower_name(EventTarget& eventTarget) { return eventTarget.GetAttributeEventListener(event_type_names::symbol_name); } \ - static void setOn##lower_name(EventTarget& eventTarget, EventListener* listener) { eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener); } +#define DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + static EventListener* on##lower_name(EventTarget& eventTarget) { \ + return eventTarget.GetAttributeEventListener(event_type_names::symbol_name); \ + } \ + static void setOn##lower_name(EventTarget& eventTarget, EventListener* listener) { \ + eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener); \ + } -#define DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ - EventListener* on##lower_name() { return GetDocument().GetWindowAttributeEventListener(event_type_names::symbol_name); } \ - void setOn##lower_name(EventListener* listener) { GetDocument().SetWindowAttributeEventListener(event_type_names::symbol_name, listener); } +#define DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ + EventListener* on##lower_name() { \ + return GetDocument().GetWindowAttributeEventListener(event_type_names::symbol_name); \ + } \ + void setOn##lower_name(EventListener* listener) { \ + GetDocument().SetWindowAttributeEventListener(event_type_names::symbol_name, listener); \ + } #define DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ static EventListener* on##lower_name(EventTarget& eventTarget) { \ @@ -146,17 +190,19 @@ class EventTargetWithInlineData : public EventTarget { } // -// 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); +// 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)){}; +// 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}; +// 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 @@ -177,8 +223,8 @@ class EventTargetWithInlineData : public EventTarget { // 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, 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(); // @@ -203,7 +249,8 @@ class EventTargetWithInlineData : public EventTarget { // // 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 +// // 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}; @@ -213,8 +260,8 @@ class EventTargetWithInlineData : public EventTarget { // // 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); +// 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); diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc index 3d211526d8..12236144e7 100644 --- a/bridge/core/dom/events/registered_eventListener.cc +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -4,21 +4,31 @@ */ #include "registered_eventListener.h" +#include "qjs_add_event_listener_options.h" namespace kraken { -RegisteredEventListener::RegisteredEventListener() : use_capture_(false), passive_(false), once_(false), blocked_event_warning_emitted_(false) {} +RegisteredEventListener::RegisteredEventListener() : RegisteredEventListener(nullptr, nullptr) {} -RegisteredEventListener::RegisteredEventListener(const std::shared_ptr& listener, std::shared_ptr options) - : callback_(listener), use_capture_(options->capture()), passive_(options->passive()), once_(options->once()), blocked_event_warning_emitted_(false){}; +RegisteredEventListener::RegisteredEventListener(const std::shared_ptr& listener, + std::shared_ptr options) + : callback_(listener), + use_capture_(options->capture()), + passive_(options->passive()), + passive_specified_(false), + once_(options->once()), + blocked_event_warning_emitted_(false){}; RegisteredEventListener::RegisteredEventListener(const RegisteredEventListener& that) = default; RegisteredEventListener& RegisteredEventListener::operator=(const RegisteredEventListener& that) = default; -void RegisteredEventListener::SetCallback(EventListener* listener) {} +void RegisteredEventListener::SetCallback(const std::shared_ptr& listener) { + callback_ = listener; +} -bool RegisteredEventListener::Matches(const std::shared_ptr& listener, const std::shared_ptr& options) const { +bool RegisteredEventListener::Matches(const std::shared_ptr& listener, + const std::shared_ptr& options) const { // Equality is soley based on the listener and useCapture flags. assert(callback_); assert(listener); diff --git a/bridge/core/dom/events/registered_eventListener.h b/bridge/core/dom/events/registered_eventListener.h index 52bdd5c97d..c931d3e5b6 100644 --- a/bridge/core/dom/events/registered_eventListener.h +++ b/bridge/core/dom/events/registered_eventListener.h @@ -6,13 +6,14 @@ #ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ -#include "bindings/qjs/add_event_listener_options.h" -#include "bindings/qjs/event_listener_options.h" #include "event_listener.h" #include "foundation/macros.h" namespace kraken { +class AddEventListenerOptions; +class EventListenerOptions; + // RegisteredEventListener represents 'event listener' defined in the DOM // standard. https://dom.spec.whatwg.org/#concept-event-listener class RegisteredEventListener final { diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 0598be9f1b..07c619e617 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -140,7 +140,7 @@ bool ExecutingContext::IsValid() const { } void* ExecutingContext::owner() { - assert(!ctx_invalid_ && "context has been released"); + assert(!ctx_invalid_ && "GetExecutingContext has been released"); return owner_; } @@ -166,7 +166,7 @@ JSValue ExecutingContext::Global() { } JSContext* ExecutingContext::ctx() { - assert(!ctx_invalid_ && "context has been released"); + assert(!ctx_invalid_ && "GetExecutingContext has been released"); return script_state_.ctx(); } diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 41e64570d4..5f147e7e9b 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -107,14 +107,14 @@ std::string Blob::type() { } ScriptPromise Blob::arrayBuffer(ExceptionState& exception_state) { - auto* resolver = ScriptPromiseResolver::Create(context()); - new BlobReaderClient(context(), this, resolver, BlobReaderClient::ReadType::kReadAsArrayBuffer); + 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(context()); - new BlobReaderClient(context(), this, resolver, BlobReaderClient::ReadType::kReadAsText); + auto* resolver = ScriptPromiseResolver::Create(GetExecutingContext()); + new BlobReaderClient(GetExecutingContext(), this, resolver, BlobReaderClient::ReadType::kReadAsText); return resolver->Promise(); } diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl index 0485078880..bd8e5bb500 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl @@ -1,8 +1,8 @@ -std::unique_ptr<<%= className %>> <%= className %>::Create() { - return std::make_unique<<%= className %>>(); +std::shared_ptr<<%= className %>> <%= className %>::Create() { + return std::make_shared<<%= className %>>(); } -std::unique_ptr<<%= className %>> <%= className %>::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - return std::make_unique<<%= className %>>(ctx, value, exception_state); +std::shared_ptr<<%= className %>> <%= className %>::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + return std::make_shared<<%= className %>>(ctx, value, exception_state); } <%= className %>::<%= className %>() {} diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl index a5c894766b..06adc9a955 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl @@ -10,8 +10,8 @@ class ExecutingContext; class <%= className %> : public <%= object.parent ? object.parent : 'DictionaryBase' %> { public: - static std::unique_ptr<<%= className %>> Create(); - static std::unique_ptr<<%= className %>> Create(JSContext* ctx, JSValue value, ExceptionState& exception_state); + 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); From df39f18762256567afced81e12e10ba76b74a3ed Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Sat, 2 Apr 2022 11:04:44 +0000 Subject: [PATCH 048/498] Committing clang-format changes --- bridge/bindings/qjs/atom_string.cc | 5 +- bridge/bindings/qjs/atom_string.h | 12 +- bridge/bindings/qjs/converter_impl.h | 70 ++++++--- bridge/bindings/qjs/garbage_collected.h | 3 +- bridge/bindings/qjs/garbage_collected_test.cc | 33 +++-- bridge/bindings/qjs/gc_visitor.h | 1 + .../bindings/qjs/js_based_event_listener.cc | 5 +- bridge/bindings/qjs/js_based_event_listener.h | 7 +- bridge/bindings/qjs/js_event_handler.cc | 29 ++-- bridge/bindings/qjs/js_event_handler.h | 27 ++-- bridge/bindings/qjs/js_event_listener.cc | 7 +- bridge/bindings/qjs/js_event_listener.h | 8 +- bridge/bindings/qjs/macros.h | 14 +- bridge/bindings/qjs/member_installer.cc | 15 +- bridge/bindings/qjs/member_installer.h | 7 +- bridge/bindings/qjs/qjs_engine_patch.cc | 27 ++-- bridge/bindings/qjs/qjs_function.h | 4 +- bridge/bindings/qjs/qjs_interface_bridge.h | 4 +- bridge/bindings/qjs/qjs_patch_test.cc | 6 +- bridge/bindings/qjs/qjs_window.cc | 6 +- bridge/bindings/qjs/rejected_promises.cc | 4 +- bridge/bindings/qjs/script_value.h | 3 +- bridge/bindings/qjs/script_wrappable.cc | 14 +- bridge/bindings/qjs/script_wrappable.h | 4 +- bridge/bindings/qjs/source_location.cc | 9 +- bridge/bindings/qjs/wrapper_type_info.h | 8 +- bridge/core/css/css_style_declaration.cc | 55 +++++-- bridge/core/css/css_style_declaration.h | 7 +- bridge/core/dart_methods.h | 23 ++- bridge/core/dom/comment.cc | 3 +- bridge/core/dom/comment.h | 4 +- bridge/core/dom/document.cc | 66 +++++---- bridge/core/dom/document.h | 4 +- bridge/core/dom/document_fragment.h | 4 +- bridge/core/dom/document_test.cc | 3 +- bridge/core/dom/element.cc | 57 +++++--- bridge/core/dom/element.h | 7 +- bridge/core/dom/element_test.cc | 10 +- bridge/core/dom/events/custom_event.cc | 6 +- bridge/core/dom/events/custom_event.h | 4 +- bridge/core/dom/events/event.cc | 19 ++- bridge/core/dom/events/event.h | 27 ++-- bridge/core/dom/events/event_listener_map.cc | 8 +- bridge/core/dom/events/event_listener_map.h | 5 +- bridge/core/dom/events/event_target.cc | 4 +- bridge/core/dom/events/event_target_test.cc | 19 ++- bridge/core/dom/events/event_test.cc | 3 +- .../dom/events/registered_eventListener.h | 6 +- bridge/core/dom/node.cc | 50 ++++--- bridge/core/dom/node.h | 17 ++- bridge/core/dom/node_test.cc | 7 +- .../core/dom/scripted_animation_controller.cc | 3 +- bridge/core/dom/text_node.h | 4 +- bridge/core/events/error_event.cc | 4 +- bridge/core/events/error_event.h | 13 +- bridge/core/events/touch_event.cc | 12 +- bridge/core/executing_context.cc | 48 +++++-- bridge/core/executing_context.h | 13 +- bridge/core/executing_context_test.cc | 15 +- bridge/core/fileapi/blob.cc | 17 ++- bridge/core/fileapi/blob.h | 18 ++- bridge/core/fileapi/blob_part.h | 13 +- bridge/core/fileapi/blob_property_bag.cc | 4 +- bridge/core/frame/console.cc | 9 +- bridge/core/frame/console.h | 9 +- bridge/core/frame/dom_timer.cc | 3 +- bridge/core/frame/location.h | 4 +- bridge/core/frame/module_callback.h | 5 +- bridge/core/frame/module_listener.h | 3 +- bridge/core/frame/module_manager.cc | 22 ++- bridge/core/frame/module_manager.h | 9 +- bridge/core/frame/window.cc | 24 ++-- bridge/core/frame/window.h | 4 +- .../frame/window_or_worker_global_scope.cc | 25 +++- .../frame/window_or_worker_global_scope.h | 10 +- bridge/core/frame/window_test.cc | 4 +- bridge/core/html/html_all_collection.cc | 3 +- bridge/core/html/html_image_element.cc | 25 ++-- bridge/core/html/html_parser.cc | 3 +- bridge/core/html/html_template_element.cc | 9 +- bridge/core/html/html_template_element.h | 3 +- bridge/core/page.cc | 16 ++- bridge/core/page.h | 10 +- bridge/core/timing/performance.cc | 136 ++++++++++++------ bridge/core/timing/performance.h | 12 +- bridge/foundation/casting.h | 2 +- bridge/foundation/logging.cc | 3 +- bridge/foundation/logging.h | 6 +- bridge/foundation/native_value.cc | 39 +++-- bridge/foundation/native_value.h | 18 ++- bridge/foundation/ui_command_buffer.cc | 6 +- bridge/foundation/ui_command_buffer.h | 9 +- bridge/include/kraken_bridge.h | 6 +- bridge/kraken_bridge.cc | 10 +- bridge/kraken_bridge_test.cc | 3 +- bridge/page.cc | 15 +- bridge/test/kraken_test_context.cc | 59 +++++--- bridge/test/kraken_test_env.cc | 13 +- 98 files changed, 989 insertions(+), 462 deletions(-) diff --git a/bridge/bindings/qjs/atom_string.cc b/bridge/bindings/qjs/atom_string.cc index 8c483613d2..8285eee338 100644 --- a/bridge/bindings/qjs/atom_string.cc +++ b/bridge/bindings/qjs/atom_string.cc @@ -5,7 +5,4 @@ #include "atom_string.h" -namespace kraken { - - -} // namespace kraken +namespace kraken {} // namespace kraken diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index b12313aac2..d4f0c495e1 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -30,15 +30,13 @@ class AtomicString { }; AtomicString() = default; - AtomicString(JSContext *ctx, JSAtom atom) : ctx_(ctx), atom_(atom) {}; - AtomicString(JSContext* ctx, const std::string& string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())) {}; - AtomicString(JSContext* ctx, JSValue value) : ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)) {}; - AtomicString(JSAtom atom): atom_(atom), is_static_atom_(true) {}; + AtomicString(JSContext* ctx, JSAtom atom) : ctx_(ctx), atom_(atom){}; + AtomicString(JSContext* ctx, const std::string& string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())){}; + AtomicString(JSContext* ctx, JSValue value) : ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; + AtomicString(JSAtom atom) : atom_(atom), is_static_atom_(true){}; // Return the undefined string value from atom key. - JSValue ToQuickJS(JSContext* ctx) const { - return JS_AtomToValue(ctx, atom_); - }; + JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); }; std::string ToStdString() const { const char* buf = JS_AtomToCString(ctx_, atom_); diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 8c20eb1497..6c615125d7 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -14,8 +14,8 @@ #include "core/fileapi/blob_part.h" #include "core/fileapi/blob_property_bag.h" #include "idl_type.h" -#include "native_string_utils.h" #include "js_event_listener.h" +#include "native_string_utils.h" namespace kraken { @@ -26,7 +26,8 @@ struct is_shared_ptr> : std::true_type {}; // Optional value for pointer value. template -struct Converter, std::enable_if_t::ImplType>::value>> : public ConverterBase> { +struct Converter, std::enable_if_t::ImplType>::value>> + : public ConverterBase> { using ImplType = typename Converter::ImplType; static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { @@ -36,12 +37,15 @@ struct Converter, std::enable_if_t::FromValue(ctx, value, exception); } - static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, value); } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, value); + } }; // Nullable value for pointer value template -struct Converter, std::enable_if::ImplType>::value>> : public ConverterBase> { +struct Converter, std::enable_if::ImplType>::value>> + : public ConverterBase> { using ImplType = typename Converter::ImplType; static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { @@ -51,11 +55,14 @@ struct Converter, std::enable_if::FromValue(ctx, value, exception_state); } - static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, value); } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, value); + } }; template -struct Converter, std::enable_if_t::ImplType>::value>> : public ConverterBase> { +struct Converter, std::enable_if_t::ImplType>::value>> + : public ConverterBase> { using ImplType = typename Converter::ImplType; static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { @@ -65,12 +72,15 @@ struct Converter, std::enable_if_t::FromValue(ctx, value, exception); } - static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, value); } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, value); + } }; // Optional value for arithmetic value template -struct Converter, std::enable_if_t::ImplType>::value>> : public ConverterBase> { +struct Converter, std::enable_if_t::ImplType>::value>> + : public ConverterBase> { using ImplType = typename Converter::ImplType; static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception) { @@ -80,7 +90,9 @@ struct Converter, std::enable_if_t::FromValue(ctx, value, exception); } - static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, value); } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, value); + } }; // Any @@ -101,7 +113,9 @@ struct Converter> : public ConverterBase return ScriptValue(ctx, value); } - static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { return Converter::ToValue(ctx, std::move(value)); } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, std::move(value)); + } }; template <> @@ -180,9 +194,15 @@ struct Converter : public ConverterBase { } 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, 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()); } }; @@ -194,9 +214,13 @@ struct Converter> : public ConverterBase 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, 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)); } + static JSValue ToValue(JSContext* ctx, typename Converter::ImplType value) { + return Converter::ToValue(ctx, std::move(value)); + } }; template <> @@ -291,8 +315,8 @@ struct Converter : public ConverterBase { }; // EventListener -//template <> -//struct Converter : public ConverterBase { +// template <> +// struct Converter : public ConverterBase { // static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { // assert(!JS_IsException(value)); // if (!JS_IsFunction(ctx, value)) { @@ -303,8 +327,8 @@ struct Converter : public ConverterBase { // } //}; // -//template <> -//struct Converter> : public ConverterBase { +// template <> +// struct Converter> : public ConverterBase { // static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { // assert(!JS_IsException(value)); // if (JS_IsNull(value)) { @@ -342,10 +366,12 @@ struct Converter : public ConverterBase { return toScriptWrappable(value); } - static JSValue ToValue(JSContext* ctx, ImplType value) { return reinterpret_cast(value)->ToQuickJS(); } + static JSValue ToValue(JSContext* ctx, ImplType value) { + return reinterpret_cast(value)->ToQuickJS(); + } }; -template<> +template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); @@ -353,7 +379,7 @@ struct Converter : public ConverterBase { } }; -template<> +template <> struct Converter> : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index f9ee7eb334..65a44c4355 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -22,7 +22,8 @@ class ExecutingContext; /** * 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. + * 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 diff --git a/bridge/bindings/qjs/garbage_collected_test.cc b/bridge/bindings/qjs/garbage_collected_test.cc index 060bb701f2..3badad68e9 100644 --- a/bridge/bindings/qjs/garbage_collected_test.cc +++ b/bridge/bindings/qjs/garbage_collected_test.cc @@ -14,12 +14,14 @@ // 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); +// 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); } +// 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}; @@ -30,7 +32,8 @@ // // class SampleClassInstance : public Instance { // public: -// explicit SampleClassInstance(HostClass* sampleClass) : Instance(sampleClass, "SampleClass", nullptr, kSampleClassId, finalizer){}; +// explicit SampleClassInstance(HostClass* sampleClass) : Instance(sampleClass, "SampleClass", nullptr, kSampleClassId, +// finalizer){}; // // private: // static void finalizer(JSRuntime* rt, JSValue v) { @@ -57,7 +60,8 @@ // ~SampleClass() {} // // private: -// static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, 10); } +// 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}; //}; @@ -297,7 +301,8 @@ // ExoticClassInstance() = delete; // static JSClassExoticMethods methods; // -// explicit ExoticClassInstance(ExoticClass* exoticClass) : Instance(exoticClass, "ExoticClass", &methods, ExoticClass::exoticClassID, finalizer){}; +// 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)); @@ -321,7 +326,8 @@ // delete instance; // }; // -// static int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags) { +// 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; @@ -343,16 +349,19 @@ // return JS_NULL; // }; // }; -// ObjectProperty m_getClassName{m_context, jsObject, "className", ClassNamePropertyDescriptor::getter, ClassNamePropertyDescriptor::setter}; +// 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}; +// 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) { +// JSValue ExoticClass::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) +// { // return (new ExoticClassInstance(this))->jsObject; //} // @@ -538,7 +547,8 @@ // ~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); +// int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int +// flags); // // private: //}; @@ -546,7 +556,8 @@ // 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) { +// int SampleExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, +// int flags) { // return 0; //} // diff --git a/bridge/bindings/qjs/gc_visitor.h b/bridge/bindings/qjs/gc_visitor.h index 6e70f72e7e..549a02392c 100644 --- a/bridge/bindings/qjs/gc_visitor.h +++ b/bridge/bindings/qjs/gc_visitor.h @@ -17,6 +17,7 @@ class ScriptWrappable; class GCVisitor final { KRAKEN_DISALLOW_NEW(); KRAKEN_DISALLOW_IMPLICIT_CONSTRUCTORS(GCVisitor); + public: explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc) : runtime_(rt), markFunc_(markFunc){}; diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc index cae9a8c084..a15b9242b7 100644 --- a/bridge/bindings/qjs/js_based_event_listener.cc +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -13,7 +13,8 @@ void JSBasedEventListener::Invoke(ExecutingContext* context, Event* event, Excep assert(context); assert(event); - if (!context->IsValid()) return; + if (!context->IsValid()) + return; // Step 10: Call a listener with event's currentTarget as receiver and event // and handle errors if thrown. InvokeInternal(*event->currentTarget(), *event, exception_state); @@ -21,4 +22,4 @@ void JSBasedEventListener::Invoke(ExecutingContext* context, Event* event, Excep JSBasedEventListener::JSBasedEventListener() {} -} +} // namespace kraken diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index 1f31592b5a..04cba9fbdf 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -46,12 +46,9 @@ class JSBasedEventListener : public EventListener { // 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; + virtual void InvokeInternal(EventTarget&, Event&, ExceptionState& exception_state) = 0; }; - -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index af17cf2eb8..66ad508aa1 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -4,14 +4,15 @@ */ #include "js_event_handler.h" -#include "core/events/error_event.h" #include "core/dom/events/event_target.h" +#include "core/events/error_event.h" #include "event_type_names.h" namespace kraken { - -std::unique_ptr JSEventHandler::CreateOrNull(JSContext* ctx, JSValue value, JSEventHandler::HandlerType handler_type) { +std::unique_ptr JSEventHandler::CreateOrNull(JSContext* ctx, + JSValue value, + JSEventHandler::HandlerType handler_type) { if (!JS_IsFunction(ctx, value)) { return nullptr; } @@ -36,9 +37,8 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // 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(); + 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 @@ -68,19 +68,16 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc 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 - }; + arguments = {ScriptValue(ctx, Converter::ToValue(ctx, error_event->message())), + ScriptValue(ctx, Converter::ToValue(ctx, error_event->filename())), + ScriptValue(ctx, Converter::ToValue(ctx, error_event->lineno())), + ScriptValue(ctx, Converter::ToValue(ctx, error_event->colno())), error_attribute}; } else { arguments.emplace_back(ctx, event.ToQuickJS()); } - ScriptValue result = event_handler_ - ->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), arguments.size(), arguments.data()); + ScriptValue result = event_handler_->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), + arguments.size(), arguments.data()); if (result.IsException()) { exception_state.ThrowException(event.ctx(), result.ToQuickJS()); return; @@ -102,4 +99,4 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // TODO: special handling for beforeunload event and onerror event. } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index 7f68115243..92f1430dfc 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ -#include "js_based_event_listener.h" #include "foundation/casting.h" +#include "js_based_event_listener.h" namespace kraken { @@ -35,27 +35,20 @@ class JSEventHandler : public JSBasedEventListener { return JS_NULL; } - explicit JSEventHandler(const std::shared_ptr& event_handler, HandlerType type): type_(type), event_handler_(event_handler) {}; + explicit JSEventHandler(const std::shared_ptr& event_handler, HandlerType type) + : type_(type), event_handler_(event_handler){}; - JSValue GetListenerObject(EventTarget&) { - return event_handler_->ToQuickJS(); - } + JSValue GetListenerObject(EventTarget&) { return event_handler_->ToQuickJS(); } - JSValue GetEffectiveFunction(EventTarget&) { - return event_handler_->ToQuickJS(); - } + JSValue GetEffectiveFunction(EventTarget&) { return event_handler_->ToQuickJS(); } // Helper functions for DowncastTraits. bool IsJSEventHandler() const override { return true; } // For checking special types of EventHandler. - bool IsOnErrorEventHandler() const { - return type_ == HandlerType::kOnErrorEventHandler; - } + bool IsOnErrorEventHandler() const { return type_ == HandlerType::kOnErrorEventHandler; } - bool IsOnBeforeUnloadEventHandler() const { - return type_ == HandlerType::kOnBeforeUnloadEventHandler; - } + bool IsOnBeforeUnloadEventHandler() const { return type_ == HandlerType::kOnBeforeUnloadEventHandler; } // EventListener overrides: bool Matches(const EventListener&) const override; @@ -64,14 +57,12 @@ class JSEventHandler : public JSBasedEventListener { // 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; + void InvokeInternal(EventTarget&, Event&, ExceptionState& exception_state) override; std::shared_ptr event_handler_; const HandlerType type_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index e48ca0e9f9..f71271e1fc 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -16,11 +16,10 @@ JSValue JSEventListener::GetEffectiveFunction(EventTarget&) { return event_listener_->ToQuickJS(); } void JSEventListener::InvokeInternal(EventTarget& event_target, Event& event, ExceptionState& exception_state) { - ScriptValue arguments[] = { - ScriptValue(event.ctx(), event.ToQuickJS()) - }; + ScriptValue arguments[] = {ScriptValue(event.ctx(), event.ToQuickJS())}; - ScriptValue result = event_listener_->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), 1, arguments); + ScriptValue result = + event_listener_->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), 1, arguments); if (result.IsException()) { exception_state.ThrowException(event.ctx(), result.ToQuickJS()); return; diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h index 50ff156092..d17a6cb28d 100644 --- a/bridge/bindings/qjs/js_event_listener.h +++ b/bridge/bindings/qjs/js_event_listener.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ -#include "js_based_event_listener.h" #include "foundation/casting.h" +#include "js_based_event_listener.h" namespace kraken { @@ -36,13 +36,11 @@ class JSEventListener final : public JSBasedEventListener { } private: - void InvokeInternal(EventTarget&, - Event&, - ExceptionState& exception_state) override; + void InvokeInternal(EventTarget&, Event&, ExceptionState& exception_state) override; const std::shared_ptr event_listener_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/macros.h b/bridge/bindings/qjs/macros.h index 2f44bb15c8..4a007b32f0 100644 --- a/bridge/bindings/qjs/macros.h +++ b/bridge/bindings/qjs/macros.h @@ -15,14 +15,18 @@ #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_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_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 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 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##_ diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc index 3fc495ce89..99ba8a44c5 100644 --- a/bridge/bindings/qjs/member_installer.cc +++ b/bridge/bindings/qjs/member_installer.cc @@ -19,7 +19,12 @@ int combinePropFlags(JSPropFlag a, JSPropFlag b, JSPropFlag 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) { +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)) { @@ -37,7 +42,9 @@ static JSValue handleCallThisOnProxy(JSContext* ctx, JSValueConst this_val, int return result; } -void MemberInstaller::InstallAttributes(ExecutingContext* context, JSValue root, std::initializer_list config) { +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); @@ -65,7 +72,9 @@ void MemberInstaller::InstallAttributes(ExecutingContext* context, JSValue root, } } -void MemberInstaller::InstallFunctions(ExecutingContext* context, JSValue root, std::initializer_list config) { +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); diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h index 3c4e26cb10..d17b2231c4 100644 --- a/bridge/bindings/qjs/member_installer.h +++ b/bridge/bindings/qjs/member_installer.h @@ -14,7 +14,12 @@ namespace kraken { class ExecutingContext; // Flags for object properties. -enum JSPropFlag { normal = JS_PROP_NORMAL, writable = JS_PROP_WRITABLE, enumerable = JS_PROP_ENUMERABLE, configurable = JS_PROP_CONFIGURABLE }; +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); diff --git a/bridge/bindings/qjs/qjs_engine_patch.cc b/bridge/bindings/qjs/qjs_engine_patch.cc index 95ba544de7..b52474c2c6 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.cc +++ b/bridge/bindings/qjs/qjs_engine_patch.cc @@ -160,9 +160,10 @@ struct JSObject { uint8_t __gc_mark; /* corresponds to header.mark/gc_obj_type */ uint8_t extensible : 1; - uint8_t free_mark : 1; /* only used when freeing objects with cycles */ - uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ - uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed arrays) */ + uint8_t free_mark : 1; /* only used when freeing objects with cycles */ + uint8_t is_exotic : 1; /* TRUE if object has exotic property handlers */ + uint8_t fast_array : 1; /* TRUE if u.array is used for get/put (for JS_CLASS_ARRAY, JS_CLASS_ARGUMENTS and typed + arrays) */ uint8_t is_constructor : 1; /* TRUE if object is a constructor function */ uint8_t is_uncatchable_error : 1; /* if TRUE, error is not catchable */ uint8_t tmp_mark : 1; /* used in JS_WriteObjectRec() */ @@ -187,15 +188,17 @@ struct JSObject { struct JSFloatEnv* float_env; /* JS_CLASS_FLOAT_ENV */ struct JSOperatorSetData* operator_set; /* JS_CLASS_OPERATOR_SET */ #endif - struct JSMapState* map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ - struct JSMapIteratorData* map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ - struct JSArrayIteratorData* array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ - struct JSRegExpStringIteratorData* regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ - struct JSGeneratorData* generator_data; /* JS_CLASS_GENERATOR */ - struct JSProxyData* proxy_data; /* JS_CLASS_PROXY */ - struct JSPromiseData* promise_data; /* JS_CLASS_PROMISE */ - struct JSPromiseFunctionData* promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ - struct JSAsyncFunctionData* async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ + struct JSMapState* map_state; /* JS_CLASS_MAP..JS_CLASS_WEAKSET */ + struct JSMapIteratorData* map_iterator_data; /* JS_CLASS_MAP_ITERATOR, JS_CLASS_SET_ITERATOR */ + struct JSArrayIteratorData* array_iterator_data; /* JS_CLASS_ARRAY_ITERATOR, JS_CLASS_STRING_ITERATOR */ + struct JSRegExpStringIteratorData* regexp_string_iterator_data; /* JS_CLASS_REGEXP_STRING_ITERATOR */ + struct JSGeneratorData* generator_data; /* JS_CLASS_GENERATOR */ + struct JSProxyData* proxy_data; /* JS_CLASS_PROXY */ + struct JSPromiseData* promise_data; /* JS_CLASS_PROMISE */ + struct JSPromiseFunctionData* + promise_function_data; /* JS_CLASS_PROMISE_RESOLVE_FUNCTION, JS_CLASS_PROMISE_REJECT_FUNCTION */ + struct JSAsyncFunctionData* + async_function_data; /* JS_CLASS_ASYNC_FUNCTION_RESOLVE, JS_CLASS_ASYNC_FUNCTION_REJECT */ struct JSAsyncFromSyncIteratorData* async_from_sync_iterator_data; /* JS_CLASS_ASYNC_FROM_SYNC_ITERATOR */ struct JSAsyncGeneratorData* async_generator_data; /* JS_CLASS_ASYNC_GENERATOR */ struct { /* JS_CLASS_BYTECODE_FUNCTION: 12/24 bytes */ diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 81657eb1ff..58afd2d02b 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -15,7 +15,9 @@ namespace kraken { // 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, JSValue function) { + return std::make_shared(ctx, function); + } explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), function_(JS_DupValue(ctx, function)){}; ~QJSFunction() { JS_FreeValue(ctx_, function_); } diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index b166374ff5..c61c7fdf62 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -13,7 +13,9 @@ namespace kraken { template class QJSInterfaceBridge { public: - static T* ToWrappable(ExecutingContext* context, JSValue value) { return HasInstance(context, value) ? toScriptWrappable(value) : nullptr; } + static T* ToWrappable(ExecutingContext* context, JSValue value) { + return HasInstance(context, value) ? toScriptWrappable(value) : nullptr; + } static bool HasInstance(ExecutingContext* context, JSValue value); }; diff --git a/bridge/bindings/qjs/qjs_patch_test.cc b/bridge/bindings/qjs/qjs_patch_test.cc index 0ca9aa4ea9..bd6b5cebf8 100644 --- a/bridge/bindings/qjs/qjs_patch_test.cc +++ b/bridge/bindings/qjs/qjs_patch_test.cc @@ -60,7 +60,8 @@ TEST(JS_NewUnicodeString, fromAscii) { JSRuntime* runtime = JS_NewRuntime(); JSContext* ctx = JS_NewContext(runtime); std::u16string source = u"helloworld"; - JSValue result = JS_NewUnicodeString(runtime, ctx, reinterpret_cast(source.c_str()), source.length()); + JSValue result = + JS_NewUnicodeString(runtime, ctx, reinterpret_cast(source.c_str()), source.length()); const char* str = JS_ToCString(ctx, result); EXPECT_STREQ(str, "helloworld"); @@ -74,7 +75,8 @@ TEST(JS_NewUnicodeString, fromChieseCode) { JSRuntime* runtime = JS_NewRuntime(); JSContext* ctx = JS_NewContext(runtime); std::u16string source = u"a你的名字12345"; - JSValue result = JS_NewUnicodeString(runtime, ctx, reinterpret_cast(source.c_str()), source.length()); + JSValue result = + JS_NewUnicodeString(runtime, ctx, reinterpret_cast(source.c_str()), source.length()); uint32_t length; uint16_t* buffer = JS_ToUnicode(ctx, result, &length); std::u16string bufferString = std::u16string(reinterpret_cast(buffer), length); diff --git a/bridge/bindings/qjs/qjs_window.cc b/bridge/bindings/qjs/qjs_window.cc index 1794afdbec..ef4be8e605 100644 --- a/bridge/bindings/qjs/qjs_window.cc +++ b/bridge/bindings/qjs/qjs_window.cc @@ -37,7 +37,8 @@ static JSValue setTimeout(JSContext* ctx, JSValueConst this_val, int argc, JSVal } 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."); + return JS_ThrowTypeError( + ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); } auto handler = QJSFunction::Create(ctx, callbackValue); @@ -81,7 +82,8 @@ static JSValue setInterval(JSContext* ctx, JSValueConst this_val, int argc, JSVa } 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."); + return JS_ThrowTypeError( + ctx, "Failed to execute 'setTimeout': parameter 2 (timeout) only can be a number or undefined."); } auto handler = QJSFunction::Create(ctx, callbackValue); diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index d4f29e8689..1c67bdd8f9 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -9,7 +9,9 @@ namespace kraken { 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)) {} + : 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); diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 118a729fd6..73af8d9ae9 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -19,7 +19,8 @@ namespace kraken { class ExecutingContext; class WrapperTypeInfo; -// ScriptValue is a stack allocate only QuickJS JSValue wrapper ScriptValuewhich hold all information to hide out QuickJS running details. +// ScriptValue is a stack allocate only QuickJS JSValue wrapper ScriptValuewhich hold all information to hide out +// QuickJS running details. class ScriptValue final { // ScriptValue should only allocate at stack. KRAKEN_DISALLOW_NEW(); diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 4e93768f10..ef25bbfd0a 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -33,8 +33,8 @@ void ScriptWrappable::InitializeQuickJSObject() { 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. + /// 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))); GCVisitor visitor{rt, mark_func}; @@ -47,7 +47,8 @@ void ScriptWrappable::InitializeQuickJSObject() { } /// 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. + /// 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(); @@ -57,9 +58,10 @@ void ScriptWrappable::InitializeQuickJSObject() { JS_NewClass(runtime, wrapperTypeInfo->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. + /// 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_, wrapperTypeInfo->classId); JS_SetOpaque(jsObject_, this); diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 5855f1a692..7494dccdec 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -41,7 +41,9 @@ class ScriptWrappable : public GarbageCollected { virtual const WrapperTypeInfo* GetWrapperTypeInfo() const = 0; JSValue ToQuickJS(); - FORCE_INLINE ExecutingContext* GetExecutingContext() const { return static_cast(JS_GetContextOpaque(ctx_)); }; + FORCE_INLINE ExecutingContext* GetExecutingContext() const { + return static_cast(JS_GetContextOpaque(ctx_)); + }; FORCE_INLINE JSContext* ctx() const { return ctx_; } private: diff --git a/bridge/bindings/qjs/source_location.cc b/bridge/bindings/qjs/source_location.cc index adb6897733..ce03259c15 100644 --- a/bridge/bindings/qjs/source_location.cc +++ b/bridge/bindings/qjs/source_location.cc @@ -7,10 +7,13 @@ namespace kraken { -std::unique_ptr SourceLocation::Capture(const std::string& url, unsigned int line_number, unsigned int column_number) { +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(const std::string& url, unsigned int line_number, unsigned int column_number) + : url_(url), line_number_(line_number), column_number_(column_number) {} -} +} // namespace kraken diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 5d03376bc0..c796d08197 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -12,7 +12,13 @@ namespace kraken { -enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB, JS_CLASS_EVENT, JS_CLASS_ERROREVENT, JS_CLASS_EVENTTARGET }; +enum { + JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, + JS_CLASS_BLOB, + JS_CLASS_EVENT, + JS_CLASS_ERROREVENT, + JS_CLASS_EVENTTARGET +}; // 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 diff --git a/bridge/core/css/css_style_declaration.cc b/bridge/core/css/css_style_declaration.cc index 1fa355becc..d67e9ced4b 100644 --- a/bridge/core/css/css_style_declaration.cc +++ b/bridge/core/css/css_style_declaration.cc @@ -45,14 +45,19 @@ static std::string parseJavaScriptCSSPropertyName(std::string& propertyName) { return result; } -JSValue CSSStyleDeclaration::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +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 eventTargetInstance = + static_cast(JS_GetOpaque(eventTargetValue, EventTarget::classId(eventTargetValue))); auto style = new StyleDeclaration(this, eventTargetInstance); return style->jsObject; } @@ -65,8 +70,11 @@ CSSStyleDeclaration::CSSStyleDeclaration(ExecutionContext* context) : HostClass( IMPL_FUNCTION(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)); + 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]; @@ -82,8 +90,10 @@ IMPL_FUNCTION(CSSStyleDeclaration, setProperty)(JSContext* ctx, JSValue this_val IMPL_FUNCTION(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)); + 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]; @@ -99,8 +109,11 @@ IMPL_FUNCTION(CSSStyleDeclaration, removeProperty)(JSContext* ctx, JSValue this_ IMPL_FUNCTION(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)); + 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); @@ -111,7 +124,12 @@ IMPL_FUNCTION(CSSStyleDeclaration, getPropertyValue)(JSContext* ctx, JSValue thi } StyleDeclaration::StyleDeclaration(CSSStyleDeclaration* cssStyleDeclaration, EventTargetInstance* ownerEventTarget) - : Instance(cssStyleDeclaration, "CSSStyleDeclaration", &m_exoticMethods, CSSStyleDeclaration::kCSSStyleDeclarationClassId, finalize), ownerEventTarget(ownerEventTarget) { + : Instance(cssStyleDeclaration, + "CSSStyleDeclaration", + &m_exoticMethods, + CSSStyleDeclaration::kCSSStyleDeclarationClassId, + finalize), + ownerEventTarget(ownerEventTarget) { JS_DupValue(m_ctx, ownerEventTarget->jsObject); } @@ -125,7 +143,8 @@ bool StyleDeclaration::setProperty(std::string& name, JSValue 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); + m_context->uiCommandBuffer()->addCommand(ownerEventTarget->eventTargetId(), UICommand::setStyle, *args_01, *args_02, + nullptr); } return true; @@ -143,7 +162,8 @@ void StyleDeclaration::removeProperty(std::string& 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); + m_context->uiCommandBuffer()->addCommand(ownerEventTarget->eventTargetId(), UICommand::setStyle, *args_01, *args_02, + nullptr); } } @@ -233,8 +253,14 @@ bool StyleDeclaration::hasObjectProperty(JSContext* ctx, JSValue obj, JSAtom ato } // Property Accessors -int StyleDeclaration::setObjectProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, int flags) { - auto* style = static_cast(JS_GetOpaque(receiver, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); +int StyleDeclaration::setObjectProperty(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->setProperty(name, value); @@ -252,7 +278,8 @@ JSValue StyleDeclaration::getObjectProperty(JSContext* ctx, JSValue obj, JSAtom } JS_FreeValue(ctx, prototype); - auto* style = static_cast(JS_GetOpaque(receiver, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); + 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->getPropertyValue(name); diff --git a/bridge/core/css/css_style_declaration.h b/bridge/core/css/css_style_declaration.h index 40e18149a5..a9e4dce8c9 100644 --- a/bridge/core/css/css_style_declaration.h +++ b/bridge/core/css/css_style_declaration.h @@ -46,7 +46,12 @@ class CSSStyleDeclaration : public GarbageCollected { private: static int hasObjectProperty(JSContext* ctx, JSValueConst obj, JSAtom atom); - static int setObjectProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int flags); + static int setObjectProperty(JSContext* ctx, + JSValueConst obj, + JSAtom atom, + JSValueConst value, + JSValueConst receiver, + int flags); static JSValue getObjectProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); std::unordered_map m_properties; }; diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index c0307c6010..db369d2000 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -20,8 +20,14 @@ namespace kraken { using AsyncCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg); using AsyncRAFCallback = void (*)(void* callbackContext, int32_t contextId, double result, const char* errmsg); using AsyncModuleCallback = void (*)(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json); -using AsyncBlobCallback = void (*)(void* callbackContext, int32_t contextId, const char* error, uint8_t* bytes, int32_t length); -typedef NativeString* (*InvokeModule)(void* callbackContext, int32_t contextId, NativeString* moduleName, NativeString* method, NativeString* params, AsyncModuleCallback callback); +using AsyncBlobCallback = + void (*)(void* callbackContext, int32_t contextId, const char* error, uint8_t* bytes, int32_t length); +typedef NativeString* (*InvokeModule)(void* callbackContext, + int32_t contextId, + NativeString* moduleName, + NativeString* method, + NativeString* params, + AsyncModuleCallback callback); typedef void (*RequestBatchUpdate)(int32_t contextId); typedef void (*ReloadApp)(int32_t contextId); typedef int32_t (*SetTimeout)(void* callbackContext, int32_t contextId, AsyncCallback callback, int32_t timeout); @@ -32,14 +38,23 @@ typedef void (*CancelAnimationFrame)(int32_t contextId, int32_t id); typedef NativeScreen* (*GetScreen)(int32_t contextId); typedef double (*DevicePixelRatio)(int32_t contextId); typedef NativeString* (*PlatformBrightness)(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 (*FlushUICommand)(); typedef void (*InitWindow)(int32_t contextId, void* nativePtr); typedef void (*InitDocument)(int32_t contextId, void* nativePtr); 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 diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc index 11a90c7a8d..4e033739ab 100644 --- a/bridge/core/dom/comment.cc +++ b/bridge/core/dom/comment.cc @@ -48,7 +48,8 @@ IMPL_PROPERTY_GETTER(Comment, length)(JSContext* ctx, JSValue this_val, int argc return JS_NewUint32(ctx, 0); } -CommentInstance::CommentInstance(Comment* comment) : NodeInstance(comment, NodeType::COMMENT_NODE, Comment::classId(), "Comment") { +CommentInstance::CommentInstance(Comment* comment) + : NodeInstance(comment, NodeType::COMMENT_NODE, Comment::classId(), "Comment") { m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createComment, nativeEventTarget); } diff --git a/bridge/core/dom/comment.h b/bridge/core/dom/comment.h index 571c4f119e..0cc079ae75 100644 --- a/bridge/core/dom/comment.h +++ b/bridge/core/dom/comment.h @@ -36,7 +36,9 @@ class Comment : public Node { friend CommentInstance; }; -auto commentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue {}; +auto commentCreator = + [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) + -> JSValue {}; const WrapperTypeInfo commentTypeInfo = {"Comment", &nodeTypeInfo, commentCreator}; diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 46ef9a9744..3ec4c0aad1 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -118,19 +118,23 @@ Document::Document() : Node() { 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_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)); + // 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)); + // 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_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)); @@ -145,25 +149,30 @@ Document::Document() : Node() { // 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)); + // 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)); + // 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)); + // 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)); + // 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_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)); + // return new PopStateEventInstance(PopStateEvent::instance(context), + // reinterpret_cast(nativeEvent)); // }); } } @@ -209,7 +218,8 @@ IMPL_FUNCTION(Document, createElement)(JSContext* ctx, JSValue this_val, int arg 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->context(), tagName); + // JSValue constructor = static_cast(document->prototype())->getElementConstructor(document->context(), + // tagName); // // JSValue element = JS_CallConstructor(ctx, constructor, argc, argv); // return element; @@ -217,7 +227,8 @@ IMPL_FUNCTION(Document, createElement)(JSContext* ctx, JSValue this_val, int arg IMPL_FUNCTION(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."); + 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)); @@ -238,7 +249,9 @@ IMPL_FUNCTION(Document, createComment)(JSContext* ctx, JSValue this_val, int arg IMPL_FUNCTION(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."); + 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)); @@ -270,9 +283,10 @@ IMPL_FUNCTION(Document, getElementById)(JSContext* ctx, JSValue this_val, int ar 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."); + 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)); @@ -306,7 +320,9 @@ JSValue Document::getElementsByTagName(JSContext* ctx, JSValue this_val, int arg 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."); + 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)); @@ -441,7 +457,8 @@ IMPL_PROPERTY_SETTER(Document, body)(JSContext* ctx, JSValue this_val, int argc, } 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 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. @@ -562,7 +579,8 @@ Document::Document(Document* document) : Node(document, NodeType::DOCUMENT_NODE, m_cookie = std::make_unique(); m_eventTargetId = DOCUMENT_TARGET_ID; - m_scriptAnimationController = makeGarbageCollected()->initialize(m_ctx, &ScriptAnimationController::classId); + m_scriptAnimationController = + makeGarbageCollected()->initialize(m_ctx, &ScriptAnimationController::classId); #if FLUTTER_BACKEND getDartMethod()->initDocument(m_context->getContextId(), nativeEventTarget); diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 2d2b8bf5a9..aab91fd9e4 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -81,7 +81,9 @@ class Document : public Node { std::unordered_map elementConstructorMap; }; -auto documentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { return JS_ThrowTypeError(ctx, "Illegal constructor"); }; +auto documentCreator = + [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) + -> JSValue { return JS_ThrowTypeError(ctx, "Illegal constructor"); }; const WrapperTypeInfo documentTypeInfo = {"Document", &nodeTypeInfo, documentCreator}; diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index dadfb93624..b785667765 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -24,7 +24,9 @@ class DocumentFragment : public Node { friend Node; }; -auto documentFragmentCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { +auto documentFragmentCreator = + [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) + -> JSValue { auto* eventTarget = EventTarget::create(ctx); return eventTarget->toQuickJS(); }; diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 6cf6221ae8..19f5d1073a 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -44,7 +44,8 @@ TEST(Document, instanceofNode) { errorCalled = true; }); auto context = bridge->getContext(); - const char* code = "console.log(document instanceof Node, document instanceof Document, document instanceof EventTarget)"; + 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); diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 3a96e6b797..0154066f45 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -70,7 +70,8 @@ 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()); + return JS_ThrowTypeError( + m_ctx, "Failed to execute 'setAttribute' on 'Element': '%s' is not a valid attribute name.", name.c_str()); } if (name == "class") { @@ -167,7 +168,8 @@ JSValue Element::getBoundingClientRect(JSContext* ctx, JSValue this_val, int arg 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"); + return JS_ThrowTypeError(ctx, + "Failed to execute 'hasAttribute' on 'Element': 1 argument required, but only 0 present"); } JSValue nameValue = argv[0]; @@ -190,7 +192,8 @@ JSValue Element::hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu 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); + return JS_ThrowTypeError( + ctx, "Failed to execute 'setAttribute' on 'Element': 2 arguments required, but only %d present", argc); } JSValue nameValue = argv[0]; @@ -223,7 +226,8 @@ JSValue Element::setAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu 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); + element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setAttribute, *args_01, + *args_02, nullptr); JS_FreeValue(ctx, attributeValue); @@ -232,7 +236,8 @@ JSValue Element::setAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu 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"); + return JS_ThrowTypeError(ctx, + "Failed to execute 'getAttribute' on 'Element': 1 argument required, but only 0 present"); } JSValue nameValue = argv[0]; @@ -255,7 +260,8 @@ JSValue Element::getAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu 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"); + return JS_ThrowTypeError( + ctx, "Failed to execute 'removeAttribute' on 'Element': 1 argument required, but only 0 present"); } JSValue nameValue = argv[0]; @@ -275,7 +281,8 @@ JSValue Element::removeAttribute(JSContext* ctx, JSValue this_val, int argc, JSV JS_FreeValue(ctx, targetValue); std::unique_ptr args_01 = stringToNativeString(name); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::removeAttribute, *args_01, nullptr); + element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::removeAttribute, *args_01, + nullptr); } return JS_NULL; @@ -354,7 +361,8 @@ JSValue Element::toBlob(JSContext* ctx, JSValue this_val, int argc, JSValue* arg 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); + 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; @@ -842,59 +850,70 @@ ElementInstance::ElementInstance(Element* element, std::string tagName, bool sho 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)); + 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); + element->m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createElement, *args_01, + nativeEventTarget); } } -JSClassExoticMethods ElementInstance::exoticMethods{nullptr, nullptr, nullptr, nullptr, hasProperty, getProperty, setProperty}; +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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + 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)); + auto* boundingClientRect = + static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); return JS_NewFloat64(ctx, boundingClientRect->m_nativeBoundingClientRect->left); } diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 6a81ed3cf8..6a35d8ba6b 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -141,7 +141,9 @@ class Element : public Node { friend class Node; }; -auto elementCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { +auto elementCreator = + [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) + -> JSValue { if (argc == 0) { return JS_ThrowTypeError(ctx, "Illegal constructor"); } @@ -171,7 +173,8 @@ const WrapperTypeInfo elementTypeInfo = {"Element", &nodeTypeInfo, elementCreato class BoundingClientRect : public GarbageCollected { public: BoundingClientRect() = delete; - explicit BoundingClientRect(ExecutionContext* context, NativeBoundingClientRect* nativeBoundingClientRect) : GarbageCollected(), m_nativeBoundingClientRect(nativeBoundingClientRect){}; + explicit BoundingClientRect(ExecutionContext* context, NativeBoundingClientRect* nativeBoundingClientRect) + : GarbageCollected(), m_nativeBoundingClientRect(nativeBoundingClientRect){}; const char* getHumanReadableName() const override { return "BoundingClientRect"; } diff --git a/bridge/core/dom/element_test.cc b/bridge/core/dom/element_test.cc index b92d2c77ad..8e739bc16b 100644 --- a/bridge/core/dom/element_test.cc +++ b/bridge/core/dom/element_test.cc @@ -61,7 +61,9 @@ TEST(Element, getAttribute) { TEST(Element, setAttributeWithHTML) { bool static errorCalled = false; bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; @@ -69,7 +71,8 @@ TEST(Element, setAttributeWithHTML) { auto context = bridge->getContext(); const char* code = "let div = document.createElement('div');" - "div.innerHTML = '';"; + "div.innerHTML = '';"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); } @@ -123,7 +126,8 @@ TEST(Element, stringifyBoundingClientRect) { bool static logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "{\"x\":10,\"y\":20,\"width\":30,\"height\":40,\"top\":10,\"right\":20,\"bottom\":30,\"left\":40}"); + EXPECT_STREQ(message.c_str(), + "{\"x\":10,\"y\":20,\"width\":30,\"height\":40,\"top\":10,\"right\":20,\"bottom\":30,\"left\":40}"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; diff --git a/bridge/core/dom/events/custom_event.cc b/bridge/core/dom/events/custom_event.cc index 00e66fd6ab..50ef78d3b0 100644 --- a/bridge/core/dom/events/custom_event.cc +++ b/bridge/core/dom/events/custom_event.cc @@ -80,7 +80,8 @@ CustomEvent::CustomEvent(JSValue eventType, JSValue eventInit) : Event(eventType } } -CustomEvent::CustomEvent(NativeCustomEvent* nativeEvent) : m_nativeCustomEvent(nativeEvent), Event(reinterpret_cast(nativeEvent)) { +CustomEvent::CustomEvent(NativeCustomEvent* nativeEvent) + : m_nativeCustomEvent(nativeEvent), Event(reinterpret_cast(nativeEvent)) { m_detail = JS_NewUnicodeString(m_runtime, m_ctx, nativeEvent->detail->string, nativeEvent->detail->length); } @@ -97,7 +98,8 @@ void CustomEvent::dispose() const { IMPL_FUNCTION(CustomEvent, initCustomEvent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'initCustomEvent' on 'CustomEvent': 1 argument required, but only 0 present"); + 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, CustomEvent::classId)); diff --git a/bridge/core/dom/events/custom_event.h b/bridge/core/dom/events/custom_event.h index e8de13d6f7..dc9597e586 100644 --- a/bridge/core/dom/events/custom_event.h +++ b/bridge/core/dom/events/custom_event.h @@ -51,7 +51,9 @@ class CustomEvent : public Event { // friend CustomEvent; //}; -auto customEventCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { +auto customEventCreator = + [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) + -> JSValue { if (argc < 1) { return JS_ThrowTypeError(ctx, "Failed to construct 'CustomEvent': 1 argument required, but only 0 present."); } diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 4bc40a598e..c236333cf3 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -12,8 +12,10 @@ namespace kraken { Event* Event::From(ExecutingContext* context, NativeEvent* native_event) { AtomicString event_type = AtomicString::From(context->ctx(), native_event->type); - auto* event = makeGarbageCollected(context, event_type, native_event->bubbles == 0 ? Bubbles::kNo : Bubbles::kYes, native_event->cancelable == 0 ? Cancelable::kNo : Cancelable::kYes, - ComposedMode::kComposed, native_event->timeStamp); + auto* event = + makeGarbageCollected(context, event_type, native_event->bubbles == 0 ? Bubbles::kNo : Bubbles::kYes, + native_event->cancelable == 0 ? Cancelable::kNo : Cancelable::kYes, + ComposedMode::kComposed, native_event->timeStamp); event->SetTarget(static_cast(native_event->target)); event->SetCurrentTarget(static_cast(native_event->currentTarget)); event->default_prevented_ = native_event->defaultPrevented; @@ -22,9 +24,15 @@ Event* Event::From(ExecutingContext* context, NativeEvent* native_event) { Event::Event(ExecutingContext* context) : type_(AtomicString::Empty(context->ctx())), ScriptWrappable(context->ctx()) {} -Event::Event(ExecutingContext* context, const AtomicString& event_type) : type_(event_type), ScriptWrappable(context->ctx()) {} +Event::Event(ExecutingContext* context, const AtomicString& event_type) + : type_(event_type), ScriptWrappable(context->ctx()) {} -Event::Event(ExecutingContext* context, const AtomicString& event_type, Bubbles bubbles, Cancelable cancelable, ComposedMode composed_mode, double time_stamp) +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), @@ -73,8 +81,7 @@ void Event::SetCurrentTarget(EventTarget* target) { } void Event::preventDefault(ExceptionState& exception_state) { - if (handling_passive_ != PassiveMode::kNotPassive && - handling_passive_ != PassiveMode::kNotPassiveDefault) { + if (handling_passive_ != PassiveMode::kNotPassive && handling_passive_ != PassiveMode::kNotPassiveDefault) { return; } diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index d5a555a9a7..71d8d8a00e 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -7,8 +7,8 @@ #define KRAKENBRIDGE_EVENT_H #include -#include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/atom_string.h" +#include "bindings/qjs/script_wrappable.h" #include "core/executing_context.h" #include "foundation/native_string.h" @@ -17,7 +17,8 @@ namespace kraken { class EventTarget; class ExceptionState; -// Dart generated nativeEvent member are force align to 64-bit system. So all members in NativeEvent should have 64 bit width. +// 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}; @@ -31,7 +32,8 @@ struct NativeEvent { 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. +// 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}; @@ -71,7 +73,6 @@ class Event : public ScriptWrappable { kScoped, }; - enum class PassiveMode { // Not passive, default initialized. kNotPassiveDefault, @@ -90,7 +91,7 @@ class Event : public ScriptWrappable { static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; static Event* Create(ExecutingContext* context, const AtomicString& type) { - return makeGarbageCollected(context, type); + return makeGarbageCollected(context, type); }; static Event* From(ExecutingContext* context, NativeEvent* native_event); @@ -98,7 +99,12 @@ class Event : public ScriptWrappable { Event() = delete; explicit Event(ExecutingContext* context); explicit Event(ExecutingContext* context, const AtomicString& event_type); - 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, + Bubbles bubbles, + Cancelable cancelable, + ComposedMode composed_mode, + double timeStamp); const char* GetHumanReadableName() const override; bool propagationStopped() const { return propagation_stopped_; } @@ -137,12 +143,12 @@ class Event : public ScriptWrappable { 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 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 ImmediatePropagationStopped() const { return immediate_propagation_stopped_; } bool WasInitialized() { return was_initialized_; } void SetHandlingPassive(PassiveMode); @@ -173,7 +179,6 @@ class Event : public ScriptWrappable { void Dispose() const override; protected: - PassiveMode HandlingPassive() const { return handling_passive_; } AtomicString type_; diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index 62f54aeea1..dbd1c74c2f 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -29,7 +29,10 @@ static bool RemoveListenerFromVector(EventListenerVector* listener_vector, // 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); }); + 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; @@ -88,7 +91,8 @@ bool EventListenerMap::Remove(const AtomicString& event_type, RegisteredEventListener* registered_event_listener) { for (unsigned i = 0; i < entries_.size(); ++i) { if (entries_[i].first == event_type) { - bool was_removed = RemoveListenerFromVector(entries_[i].second.get(), listener, options, index_of_removed_listener, registered_event_listener); + bool was_removed = RemoveListenerFromVector(entries_[i].second.get(), listener, options, + index_of_removed_listener, registered_event_listener); if (entries_[i].second->empty()) { entries_.erase(entries_.begin() + i); } diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index c4141320e6..fe4f903755 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -34,7 +34,10 @@ class EventListenerMap final { 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& listener, const std::shared_ptr& options, RegisteredEventListener* registered_event_listener); + bool Add(const AtomicString& event_type, + const std::shared_ptr& listener, + const std::shared_ptr& options, + RegisteredEventListener* registered_event_listener); bool Remove(const AtomicString& event_type, const std::shared_ptr& listener, const std::shared_ptr& options, diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index ab060a95ff..5c24a6ffd9 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -16,15 +16,13 @@ namespace kraken { -Event::PassiveMode EventPassiveMode( - const RegisteredEventListener& event_listener) { +Event::PassiveMode EventPassiveMode(const RegisteredEventListener& event_listener) { if (!event_listener.Passive()) { return Event::PassiveMode::kNotPassiveDefault; } return Event::PassiveMode::kPassiveDefault; } - // EventTargetData EventTargetData::EventTargetData() {} diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index c3a3804998..748f34842b 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -20,7 +20,9 @@ TEST(EventTarget, addEventListener) { 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'));"; + 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); @@ -29,14 +31,17 @@ TEST(EventTarget, addEventListener) { TEST(EventTarget, removeEventListener) { bool static errorCalled = false; bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->getContext(); 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); @@ -55,7 +60,9 @@ TEST(EventTarget, setNoEventTargetProperties) { }); auto context = bridge->getContext(); - const char* code = "let div = document.createElement('div'); div._a = { name: 1}; console.log(div._a); document.body.appendChild(div);"; + 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); } @@ -86,7 +93,9 @@ TEST(EventTarget, propertyEventHandler) { TEST(EventTarget, setUnExpectedAttributeEventHandler) { bool static errorCalled = false; bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = false; }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = false; + }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; diff --git a/bridge/core/dom/events/event_test.cc b/bridge/core/dom/events/event_test.cc index 88f32745f3..b116f6e4d1 100644 --- a/bridge/core/dom/events/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.h b/bridge/core/dom/events/registered_eventListener.h index c931d3e5b6..7592d28287 100644 --- a/bridge/core/dom/events/registered_eventListener.h +++ b/bridge/core/dom/events/registered_eventListener.h @@ -20,7 +20,8 @@ class RegisteredEventListener final { KRAKEN_DISALLOW_NEW() public: RegisteredEventListener(); - RegisteredEventListener(const std::shared_ptr& listener, std::shared_ptr options); + RegisteredEventListener(const std::shared_ptr& listener, + std::shared_ptr options); RegisteredEventListener(const RegisteredEventListener& that); RegisteredEventListener& operator=(const RegisteredEventListener& that); @@ -39,7 +40,8 @@ class RegisteredEventListener final { void SetBlockedEventWarningEmitted() { blocked_event_warning_emitted_ = true; } - bool Matches(const std::shared_ptr& listener, const std::shared_ptr& options) const; + bool Matches(const std::shared_ptr& listener, + const std::shared_ptr& options) const; bool ShouldFire(const Event&) const; diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 8dafea8391..1390c4823c 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -104,7 +104,8 @@ IMPL_FUNCTION(Node, appendChild)(JSContext* ctx, JSValue this_val, int argc, JSV } if (node == self) { - return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': The new child element contains the parent."); + return JS_ThrowTypeError(ctx, + "Failed to execute 'appendChild' on 'Node': The new child element contains the parent."); } if (node->hasNodeFlag(Node::NodeFlag::IsDocumentFragment)) { @@ -130,13 +131,15 @@ IMPL_FUNCTION(Node, remove)(JSContext* ctx, JSValue this_val, int argc, JSValue* } IMPL_FUNCTION(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"); + 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"); + return JS_ThrowTypeError( + ctx, "Uncaught TypeError: Failed to execute 'removeChild' on 'Node': 1st arguments is not object"); } auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); @@ -167,7 +170,8 @@ IMPL_FUNCTION(Node, insertBefore)(JSContext* ctx, JSValue this_val, int argc, JS if (JS_IsObject(referenceNodeValue)) { reference = static_cast(JS_GetOpaque(referenceNodeValue, JSValueGetClassId(referenceNodeValue))); } else if (!JS_IsNull(referenceNodeValue)) { - return JS_ThrowTypeError(ctx, "TypeError: Failed to execute 'insertBefore' on 'Node': parameter 2 is not of type 'Node'"); + return JS_ThrowTypeError( + ctx, "TypeError: Failed to execute 'insertBefore' on 'Node': parameter 2 is not of type 'Node'"); } auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); @@ -197,26 +201,31 @@ IMPL_FUNCTION(Node, insertBefore)(JSContext* ctx, JSValue this_val, int argc, JS IMPL_FUNCTION(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"); + 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"); + 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."); + return JS_ThrowTypeError( + ctx, "Uncaught TypeError: Failed to execute 'replaceChild' on 'Node': 2 arguments is not object."); } auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); auto newChild = static_cast(JS_GetOpaque(newChildValue, JSValueGetClassId(newChildValue))); auto oldChild = static_cast(JS_GetOpaque(oldChildValue, JSValueGetClassId(oldChildValue))); - if (oldChild == nullptr || JS_VALUE_GET_PTR(oldChild->parentNode) != JS_VALUE_GET_PTR(self->jsObject) || oldChild->ownerDocument() != self->ownerDocument()) { - return JS_ThrowTypeError(ctx, "Failed to execute 'replaceChild' on 'Node': The node to be replaced is not a child of this node."); + if (oldChild == nullptr || JS_VALUE_GET_PTR(oldChild->parentNode) != JS_VALUE_GET_PTR(self->jsObject) || + oldChild->ownerDocument() != self->ownerDocument()) { + return JS_ThrowTypeError( + ctx, "Failed to execute 'replaceChild' on 'Node': The node to be replaced is not a child of this node."); } if (newChild == nullptr || newChild->ownerDocument() != self->ownerDocument()) { @@ -267,7 +276,9 @@ JSValue Node::copyNodeValue(JSContext* ctx, Node* node) { std::string tagName = element->getRegisteredTagName(); JSValue tagNameValue = JS_NewString(element->ctx(), tagName.c_str()); JSValue arguments[] = {tagNameValue}; - JSValue newElementValue = JS_CallConstructor(element->context()->ctx(), element->context()->contextData()->constructorForType(&elementTypeInfo), 1, arguments); + JSValue newElementValue = + JS_CallConstructor(element->context()->ctx(), + element->context()->contextData()->constructorForType(&elementTypeInfo), 1, arguments); JS_FreeValue(ctx, tagNameValue); auto* newElement = static_cast(JS_GetOpaque(newElementValue, JSValueGetClassId(newElementValue))); @@ -283,7 +294,8 @@ JSValue Node::copyNodeValue(JSContext* ctx, Node* node) { std::string newNodeEventTargetId = std::to_string(newElement->eventTargetId()); std::unique_ptr args_01 = stringToNativeString(newNodeEventTargetId); - element->context()->uiCommandBuffer()->addCommand(element->eventTargetId(), UICommand::cloneNode, *args_01, nullptr); + element->context()->uiCommandBuffer()->addCommand(element->eventTargetId(), UICommand::cloneNode, *args_01, + nullptr); return newElement->jsObject; } else if (node->nodeType == TEXT_NODE) { @@ -468,7 +480,9 @@ JSValue Node::internalInsertBefore(Node* node, Node* referenceNode) { 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."); + 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; @@ -478,7 +492,8 @@ JSValue Node::internalInsertBefore(Node* node, Node* referenceNode) { 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."); + 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); @@ -491,7 +506,8 @@ JSValue Node::internalInsertBefore(Node* node, Node* referenceNode) { std::unique_ptr args_01 = stringToNativeString(nodeEventTargetId); std::unique_ptr args_02 = stringToNativeString(position); - context()->uiCommandBuffer()->addCommand(referenceNode->eventTargetId(), UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); + context()->uiCommandBuffer()->addCommand(referenceNode->eventTargetId(), UICommand::insertAdjacentNode, *args_01, + *args_02, nullptr); } } @@ -507,7 +523,8 @@ JSValue Node::internalReplaceChild(Node* newChild, Node* oldChild) { 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."); + return JS_ThrowTypeError(m_ctx, + "Failed to execute 'replaceChild' on 'Node': old child is not exist on childNodes."); } newChild->setParentNode(this); @@ -523,7 +540,8 @@ JSValue Node::internalReplaceChild(Node* newChild, Node* oldChild) { std::unique_ptr args_01 = stringToNativeString(newChildEventTargetId); std::unique_ptr args_02 = stringToNativeString(position); - context()->uiCommandBuffer()->addCommand(oldChild->eventTargetId(), UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); + context()->uiCommandBuffer()->addCommand(oldChild->eventTargetId(), UICommand::insertAdjacentNode, *args_01, *args_02, + nullptr); context()->uiCommandBuffer()->addCommand(oldChild->eventTargetId(), UICommand::removeNode, nullptr); diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 98f0af692e..796f5d414c 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -19,7 +19,14 @@ class DocumentFragment; void bindNode(std::unique_ptr& context); -enum NodeType { ELEMENT_NODE = 1, TEXT_NODE = 3, COMMENT_NODE = 8, DOCUMENT_NODE = 9, DOCUMENT_TYPE_NODE = 10, DOCUMENT_FRAGMENT_NODE = 11 }; +enum NodeType { + ELEMENT_NODE = 1, + TEXT_NODE = 3, + COMMENT_NODE = 8, + DOCUMENT_NODE = 9, + DOCUMENT_TYPE_NODE = 10, + DOCUMENT_FRAGMENT_NODE = 11 +}; class Node; class TextNode; @@ -57,7 +64,9 @@ class Node : public EventTarget { 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(); } + 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); } @@ -102,7 +111,9 @@ class Node : public EventTarget { static JSValue copyNodeValue(JSContext* ctx, Node* node); }; -auto nodeCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { return JS_ThrowTypeError(ctx, "Illegal constructor"); }; +auto nodeCreator = + [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) + -> JSValue { return JS_ThrowTypeError(ctx, "Illegal constructor"); }; const WrapperTypeInfo nodeTypeInfo = {"Node", &eventTargetTypeInfo, nodeCreator}; diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 62285d0aa8..e8f8b6cd52 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -20,7 +20,8 @@ TEST(Node, appendChild) { 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); @@ -118,7 +119,9 @@ TEST(Node, ensureDetached) { TEST(Node, replaceBody) { bool static errorCalled = false; bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index 7aa2bfa310..70f41dc30a 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -47,7 +47,8 @@ static void handleRAFTransientCallback(void* ptr, int32_t contextId, double high uint32_t ScriptAnimationController::registerFrameCallback(FrameCallback* frameCallback) { auto* context = static_cast(JS_GetContextOpaque(m_ctx)); - uint32_t requestId = getDartMethod()->requestAnimationFrame(frameCallback, context->getContextId(), handleRAFTransientCallback); + uint32_t requestId = + getDartMethod()->requestAnimationFrame(frameCallback, context->getContextId(), handleRAFTransientCallback); // Register frame callback to collection. m_frameRequestCallbackCollection.registerFrameCallback(requestId, frameCallback); diff --git a/bridge/core/dom/text_node.h b/bridge/core/dom/text_node.h index a3a65afdc9..4aeb075ed8 100644 --- a/bridge/core/dom/text_node.h +++ b/bridge/core/dom/text_node.h @@ -37,7 +37,9 @@ class TextNode : public Node { std::string m_data; }; -auto textNodeCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { +auto textNodeCreator = + [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) + -> JSValue { JSValue textContent = JS_NULL; if (argc == 1) { textContent = argv[0]; diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc index 38815aaeaf..96848198c2 100644 --- a/bridge/core/events/error_event.cc +++ b/bridge/core/events/error_event.cc @@ -36,6 +36,8 @@ ErrorEvent::ErrorEvent(ExecutingContext* context, : Event(context), message_(type.ToStdString()), error_(initializer->error()), - source_location_(std::make_unique(initializer->filename().ToStdString(), initializer->lineno(), initializer->colno())) {} + source_location_(std::make_unique(initializer->filename().ToStdString(), + initializer->lineno(), + initializer->colno())) {} } // namespace kraken diff --git a/bridge/core/events/error_event.h b/bridge/core/events/error_event.h index 31e857b54f..22e8482479 100644 --- a/bridge/core/events/error_event.h +++ b/bridge/core/events/error_event.h @@ -15,16 +15,23 @@ namespace kraken { class ErrorEvent : public Event { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = ErrorEvent*; static ErrorEvent* Create(ExecutingContext* context, const std::string& message); static ErrorEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); - static ErrorEvent* Create(ExecutingContext* context, const AtomicString& type, const ErrorEventInit* initializer, ExceptionState& exception_state); + static ErrorEvent* Create(ExecutingContext* context, + const AtomicString& type, + const ErrorEventInit* initializer, + ExceptionState& exception_state); explicit ErrorEvent(ExecutingContext* context, const std::string& message); explicit ErrorEvent(ExecutingContext* context, const std::string& message, std::unique_ptr location); explicit ErrorEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); - explicit ErrorEvent(ExecutingContext* context, const AtomicString& type, const ErrorEventInit* initializer, ExceptionState& exception_state); + explicit ErrorEvent(ExecutingContext* context, + const AtomicString& type, + const ErrorEventInit* initializer, + ExceptionState& exception_state); // As |message| is exposed to JavaScript, never return |unsanitized_message_|. const std::string& message() const { return message_; } @@ -42,6 +49,6 @@ class ErrorEvent : public Event { ScriptValue error_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ diff --git a/bridge/core/events/touch_event.cc b/bridge/core/events/touch_event.cc index 3cbd03255a..1347e788fd 100644 --- a/bridge/core/events/touch_event.cc +++ b/bridge/core/events/touch_event.cc @@ -14,7 +14,8 @@ void bindTouchEvent(ExecutionContext* context) { context->defineGlobalProperty("TouchEvent", constructor->jsObject); } -TouchList::TouchList(ExecutionContext* context, NativeTouch** touches, int64_t length) : ExoticHostObject(context, "TouchList"), m_touches(touches), _length(length) {} +TouchList::TouchList(ExecutionContext* context, NativeTouch** touches, int64_t length) + : ExoticHostObject(context, "TouchList"), m_touches(touches), _length(length) {} JSValue TouchList::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { std::string key = jsAtomToStdString(ctx, atom); @@ -38,7 +39,8 @@ IMPL_PROPERTY_SETTER(TouchList, length)(JSContext* ctx, JSValue this_val, int ar return JS_NULL; } -Touch::Touch(ExecutionContext* context, NativeTouch* nativeTouch) : HostObject(context, "Touch"), m_nativeTouch(nativeTouch) {} +Touch::Touch(ExecutionContext* context, NativeTouch* nativeTouch) + : HostObject(context, "Touch"), m_nativeTouch(nativeTouch) {} IMPL_PROPERTY_GETTER(Touch, identifier)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* object = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); @@ -226,7 +228,8 @@ IMPL_PROPERTY_GETTER(TouchEvent, targetTouches)(JSContext* ctx, JSValue this_val IMPL_PROPERTY_GETTER(TouchEvent, changedTouches)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* event = static_cast(JS_GetOpaque(this_val, Event::kEventClassID)); auto* nativeEvent = reinterpret_cast(event->nativeEvent); - auto* changedTouchList = new TouchList(event->m_context, nativeEvent->changedTouches, nativeEvent->changedTouchesLength); + auto* changedTouchList = + new TouchList(event->m_context, nativeEvent->changedTouches, nativeEvent->changedTouchesLength); return changedTouchList->jsObject; } @@ -254,6 +257,7 @@ IMPL_PROPERTY_GETTER(TouchEvent, shiftKey)(JSContext* ctx, JSValue this_val, int return JS_NewBool(ctx, nativeEvent->shiftKey ? 1 : 0); } -TouchEventInstance::TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent) : EventInstance(event, nativeEvent) {} +TouchEventInstance::TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent) + : EventInstance(event, nativeEvent) {} } // namespace kraken diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 07c619e617..613f97da2b 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -21,7 +21,9 @@ std::unique_ptr createJSContext(int32_t contextId, const JSExc ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) : context_id_(contextId), handler_(handler), owner_(owner), ctx_invalid_(false), unique_id_(context_unique_id++) { #if ENABLE_PROFILE - auto jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + auto jsContextStartTime = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; nativePerformance.mark(PERF_JS_CONTEXT_INIT_START, jsContextStartTime); nativePerformance.mark(PERF_JS_CONTEXT_INIT_END); @@ -43,9 +45,14 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& JSContext* ctx = script_state_.ctx(); global_object_ = JS_GetGlobalObject(script_state_.ctx()); JSValue windowGetter = JS_NewCFunction( - ctx, [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { return JS_GetGlobalObject(ctx); }, "get", 0); + ctx, + [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { + return JS_GetGlobalObject(ctx); + }, + "get", 0); JSAtom windowKey = JS_NewAtom(ctx, "window"); - JS_DefinePropertyGetSet(ctx, global_object_, windowKey, windowGetter, JS_UNDEFINED, JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); + JS_DefinePropertyGetSet(ctx, global_object_, windowKey, windowGetter, JS_UNDEFINED, + JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); JS_FreeAtom(ctx, windowKey); JS_SetContextOpaque(ctx, this); JS_SetHostPromiseRejectionTracker(script_state_.runtime(), promiseRejectTracker, nullptr); @@ -97,7 +104,10 @@ ExecutingContext* ExecutingContext::From(JSContext* ctx) { return static_cast(JS_GetContextOpaque(ctx)); } -bool ExecutingContext::EvaluateJavaScript(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { +bool ExecutingContext::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(script_state_.ctx(), utf8Code.c_str(), utf8Code.size(), sourceURL, JS_EVAL_TYPE_GLOBAL); DrainPendingPromiseJobs(); @@ -231,8 +241,12 @@ 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); +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; @@ -273,7 +287,10 @@ void ExecutingContext::DispatchGlobalErrorEvent(ExecutingContext* context, JSVal // } } -static void dispatchPromiseRejectionEvent(const char* eventType, ExecutingContext* context, JSValueConst promise, JSValueConst error) { +static void dispatchPromiseRejectionEvent(const char* eventType, + ExecutingContext* context, + JSValueConst promise, + JSValueConst error) { // JSContext* ctx = context->ctx(); // auto* window = static_cast(JS_GetOpaque(context->global(), Window::classId())); // @@ -303,7 +320,9 @@ static void dispatchPromiseRejectionEvent(const char* eventType, ExecutingContex // } } -void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error) { +void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, + JSValueConst promise, + JSValueConst error) { // Trigger onerror event. DispatchGlobalErrorEvent(context, error); @@ -318,11 +337,16 @@ void ExecutingContext::DispatchGlobalRejectionHandledEvent(ExecutingContext* con std::unordered_map ExecutingContext::pluginByteCode{}; -void ExecutingContext::promiseRejectTracker(JSContext* ctx, JSValue promise, JSValue reason, int is_handled, void* opaque) { +void ExecutingContext::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. + // 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 { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index ba9c0cff3c..b596c7cd73 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -103,7 +103,9 @@ class ExecutingContext { struct list_head module_callback_job_list; struct list_head native_function_job_list; - static void DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error); + 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); @@ -111,7 +113,11 @@ class ExecutingContext { static std::unordered_map pluginByteCode; private: - static void promiseRejectTracker(JSContext* ctx, JSValueConst promise, JSValueConst reason, JS_BOOL is_handled, void* opaque); + static void promiseRejectTracker(JSContext* ctx, + JSValueConst promise, + JSValueConst reason, + JS_BOOL is_handled, + void* opaque); // From C++ standard, https://isocpp.org/wiki/faq/dtors#order-dtors-for-members // Members first initialized and destructed at the last. @@ -141,7 +147,8 @@ class ObjectProperty { ObjectProperty() = delete; // Define an property on object with a JSValue. - explicit ObjectProperty(ExecutingContext* context, JSValueConst thisObject, const char* property, JSValue value) : m_value(value) { + explicit ObjectProperty(ExecutingContext* context, JSValueConst thisObject, const char* property, JSValue value) + : m_value(value) { JS_DefinePropertyValueStr(context->ctx(), thisObject, property, value, JS_PROP_ENUMERABLE); } diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index e22ca774fb..73ca6d3aa3 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -98,7 +98,8 @@ TEST(Context, unrejectPromiseWillTriggerUnhandledRejectionEvent) { }; auto bridge = TEST_init(errorHandler); static int logIndex = 0; - static std::string logs[] = {"error event cannot read property 'forceNullError' of null", "unhandled event {promise: Promise {...}, reason: Error {...}} true"}; + static std::string logs[] = {"error event cannot read property 'forceNullError' of null", + "unhandled event {promise: Promise {...}, reason: Error {...}} true"}; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(logs[logIndex++].c_str(), message.c_str()); @@ -167,7 +168,9 @@ TEST(Context, unhandledRejectionEventWillTriggerWhenNotHandled) { static bool logCalled = false; auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; auto bridge = TEST_init(errorHandler); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; std::string code = R"( window.addEventListener('unhandledrejection', event => { @@ -196,7 +199,9 @@ TEST(Context, handledRejectionEventWillTriggerWhenUnHandledRejectHandled) { static bool logCalled = false; auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; auto bridge = TEST_init(errorHandler); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; std::string code = R"( window.addEventListener('unhandledrejection', event => { @@ -314,7 +319,9 @@ TEST(Context, windowInheritEventTarget) { KRAKEN_LOG(VERBOSE) << errmsg; }; auto bridge = TEST_init(errorHandler); - const char* code = "console.log(window.addEventListener, addEventListener, globalThis.addEventListener, window.addEventListener === addEventListener)"; + const char* code = + "console.log(window.addEventListener, addEventListener, globalThis.addEventListener, window.addEventListener === " + "addEventListener)"; bridge->evaluateScript(code, strlen(code), "file://", 0); EXPECT_EQ(errorHandlerExecuted, false); EXPECT_EQ(logCalled, true); diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 5f147e7e9b..acdd57a4c8 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -14,7 +14,8 @@ class BlobReaderClient { public: enum ReadType { kReadAsText, kReadAsArrayBuffer }; - BlobReaderClient(ExecutingContext* context, Blob* blob, ScriptPromiseResolver* resolver, ReadType read_type) : context_(context), blob_(blob), resolver_(resolver), read_type_(read_type) { + BlobReaderClient(ExecutingContext* context, Blob* blob, ScriptPromiseResolver* resolver, ReadType read_type) + : context_(context), blob_(blob), resolver_(resolver), read_type_(read_type) { Start(); }; @@ -52,11 +53,16 @@ Blob* Blob::Create(ExecutingContext* context) { return makeGarbageCollected(context->ctx()); } -Blob* Blob::Create(ExecutingContext* context, std::vector>& data, ExceptionState& exception_state) { +Blob* Blob::Create(ExecutingContext* context, + std::vector>& data, + ExceptionState& exception_state) { return makeGarbageCollected(context->ctx(), data); } -Blob* Blob::Create(ExecutingContext* context, std::vector>& data, std::shared_ptr property, ExceptionState& exception_state) { +Blob* Blob::Create(ExecutingContext* context, + std::vector>& data, + std::shared_ptr property, + ExceptionState& exception_state) { return makeGarbageCollected(context->ctx(), data, property); } @@ -84,7 +90,10 @@ Blob* Blob::slice(int64_t start, int64_t end, ExceptionState& exception_state) { std::unique_ptr contentType = nullptr; return slice(start, end, contentType, exception_state); } -Blob* Blob::slice(int64_t start, int64_t end, std::unique_ptr& content_type, ExceptionState& exception_state) { +Blob* Blob::slice(int64_t start, + int64_t end, + std::unique_ptr& content_type, + ExceptionState& exception_state) { auto* newBlob = makeGarbageCollected(ctx()); std::vector newData; newData.reserve(_data.size() - (end - start)); diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 8406c8d234..6a07ebf608 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -22,13 +22,23 @@ class Blob : public ScriptWrappable { public: static Blob* Create(ExecutingContext* context); - static Blob* Create(ExecutingContext* context, std::vector>& data, ExceptionState& exception_state); - static Blob* Create(ExecutingContext* context, std::vector>& data, std::shared_ptr property, ExceptionState& exception_state); + static Blob* Create(ExecutingContext* context, + std::vector>& data, + ExceptionState& exception_state); + static Blob* Create(ExecutingContext* context, + std::vector>& data, + std::shared_ptr property, + ExceptionState& exception_state); Blob() = delete; explicit Blob(JSContext* ctx) : ScriptWrappable(ctx){}; - explicit Blob(JSContext* ctx, std::vector>& data) : ScriptWrappable(ctx) { PopulateBlobData(data); }; - explicit Blob(JSContext* ctx, std::vector>& data, std::shared_ptr& property) : mime_type_(property->type()), ScriptWrappable(ctx) { + explicit Blob(JSContext* ctx, std::vector>& data) : ScriptWrappable(ctx) { + PopulateBlobData(data); + }; + explicit Blob(JSContext* ctx, + std::vector>& data, + std::shared_ptr& property) + : mime_type_(property->type()), ScriptWrappable(ctx) { PopulateBlobData(data); }; diff --git a/bridge/core/fileapi/blob_part.h b/bridge/core/fileapi/blob_part.h index 67767a5c89..b734bcb802 100644 --- a/bridge/core/fileapi/blob_part.h +++ b/bridge/core/fileapi/blob_part.h @@ -30,10 +30,17 @@ class BlobPart { 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) + 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, 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: diff --git a/bridge/core/fileapi/blob_property_bag.cc b/bridge/core/fileapi/blob_property_bag.cc index 6b8f85523c..422d205041 100644 --- a/bridge/core/fileapi/blob_property_bag.cc +++ b/bridge/core/fileapi/blob_property_bag.cc @@ -7,7 +7,9 @@ namespace kraken { -std::shared_ptr BlobPropertyBag::Create(JSContext* ctx, JSValue value, ExceptionState& exceptionState) { +std::shared_ptr BlobPropertyBag::Create(JSContext* ctx, + JSValue value, + ExceptionState& exceptionState) { auto bag = std::make_shared(); bag->FillMemberFromQuickjsObject(ctx, value, exceptionState); return nullptr; diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 181d6974b8..02b6994284 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -9,14 +9,19 @@ namespace kraken { -void Console::__kraken_print__(ExecutingContext* context, std::unique_ptr& log, std::unique_ptr& level, ExceptionState& exception) { +void Console::__kraken_print__(ExecutingContext* context, + std::unique_ptr& log, + std::unique_ptr& level, + ExceptionState& exception) { std::stringstream stream; std::string buffer = nativeStringToStdString(log.get()); stream << buffer; printLog(context->contextId(), stream, level != nullptr ? nativeStringToStdString(level.get()) : "info", nullptr); } -void Console::__kraken_print__(ExecutingContext* context, std::unique_ptr& log, ExceptionState& exception_state) { +void Console::__kraken_print__(ExecutingContext* context, + std::unique_ptr& log, + ExceptionState& exception_state) { std::stringstream stream; std::string buffer = nativeStringToStdString(log.get()); stream << buffer; diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index af5ffa062f..e8904e9892 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -13,8 +13,13 @@ namespace kraken { class Console final { public: - static void __kraken_print__(ExecutingContext* context, std::unique_ptr& log, std::unique_ptr& level, ExceptionState& exception); - static void __kraken_print__(ExecutingContext* context, std::unique_ptr& log, ExceptionState& exception_state); + static void __kraken_print__(ExecutingContext* context, + std::unique_ptr& log, + std::unique_ptr& level, + ExceptionState& exception); + static void __kraken_print__(ExecutingContext* context, + std::unique_ptr& log, + ExceptionState& exception_state); }; } // namespace kraken diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index ba57516668..67a13b5f0c 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -18,7 +18,8 @@ std::shared_ptr DOMTimer::create(ExecutingContext* context, std::share return std::make_shared(context, callback); } -DOMTimer::DOMTimer(ExecutingContext* context, std::shared_ptr callback) : context_(context), callback_(callback) {} +DOMTimer::DOMTimer(ExecutingContext* context, std::shared_ptr callback) + : context_(context), callback_(callback) {} void DOMTimer::Fire() { if (!callback_->IsFunction(context_->ctx())) diff --git a/bridge/core/frame/location.h b/bridge/core/frame/location.h index c198f525c7..21cc3876b7 100644 --- a/bridge/core/frame/location.h +++ b/bridge/core/frame/location.h @@ -25,7 +25,9 @@ class Location : public GarbageCollected { void dispose() const override; }; -auto locationCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { +auto locationCreator = + [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) + -> JSValue { auto* type = static_cast(JS_GetOpaque(func_obj, JSValueGetClassId(func_obj))); auto* location = Location::create(ctx); auto* context = static_cast(JS_GetContextOpaque(ctx)); diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h index d64ef8a0ee..7268c431f5 100644 --- a/bridge/core/frame/module_callback.h +++ b/bridge/core/frame/module_callback.h @@ -12,8 +12,9 @@ namespace kraken { -// ModuleCallback is an asynchronous callback function, usually from the 4th parameter of `kraken.invokeModule` function. -// When the asynchronous operation on the Dart side ends, the callback is will called and to return to the JS executing environment. +// ModuleCallback is an asynchronous callback function, usually from the 4th parameter of `kraken.invokeModule` +// function. When the asynchronous operation on the Dart side ends, the callback is will called and to return to the JS +// executing environment. class ModuleCallback { public: static std::shared_ptr Create(std::shared_ptr function); diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h index cc8bcf3458..7e7961d1f9 100644 --- a/bridge/core/frame/module_listener.h +++ b/bridge/core/frame/module_listener.h @@ -15,7 +15,8 @@ class ModuleCallbackCoordinator; class ModuleListenerContainer; // ModuleListener is an persistent callback function. Registered from user with `kraken.addModuleListener` method. -// When module event triggered at dart side, All module listener will be invoked and let user to dispatch further operations. +// 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 Create(const std::shared_ptr& function); diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index c50c1eed59..c3582a1fc8 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -22,7 +22,8 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha return; if (moduleContext->callback == nullptr) { - JSValue exception = JS_ThrowTypeError(moduleContext->context->ctx(), "Failed to execute '__kraken_invoke_module__': callback is null."); + JSValue exception = JS_ThrowTypeError(moduleContext->context->ctx(), + "Failed to execute '__kraken_invoke_module__': callback is null."); context->HandleException(&exception); return; } @@ -53,7 +54,10 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha delete moduleContext; } -void handleInvokeModuleUnexpectedCallback(void* callbackContext, int32_t contextId, const char* errmsg, NativeString* json) { +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."); } @@ -88,7 +92,9 @@ std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingC } if (context->dartMethodPtr()->invokeModule == nullptr) { - exception.ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); + exception.ThrowException( + context->ctx(), ErrorType::InternalError, + "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); return nullptr; } @@ -98,9 +104,11 @@ std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingC NativeString* result; if (callback != nullptr) { - result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleTransientCallback); + result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.get(), method.get(), + params.get(), handleInvokeModuleTransientCallback); } else { - result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.get(), method.get(), params.get(), handleInvokeModuleUnexpectedCallback); + result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.get(), method.get(), + params.get(), handleInvokeModuleUnexpectedCallback); } moduleName->free(); @@ -116,7 +124,9 @@ std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingC return std::unique_ptr(result); } -void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, const std::shared_ptr& handler, ExceptionState& exception) { +void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, + const std::shared_ptr& handler, + ExceptionState& exception) { auto listener = ModuleListener::Create(handler); context->ModuleListeners()->AddModuleListener(listener); } diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 5cac35467a..c6a5d9b572 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -14,7 +14,10 @@ namespace kraken { class ModuleManager { public: - static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, std::unique_ptr& moduleName, std::unique_ptr& method, ExceptionState& exception); + static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, + std::unique_ptr& moduleName, + std::unique_ptr& method, + ExceptionState& exception); static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, std::unique_ptr& moduleName, std::unique_ptr& method, @@ -26,7 +29,9 @@ class ModuleManager { ScriptValue& params, std::shared_ptr callback, ExceptionState& exception); - static void __kraken_add_module_listener__(ExecutingContext* context, const std::shared_ptr& handler, ExceptionState& exception); + static void __kraken_add_module_listener__(ExecutingContext* context, + const std::shared_ptr& handler, + ExceptionState& exception); }; } // namespace kraken diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index a006710b64..efbe606c9a 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -100,7 +100,8 @@ IMPL_FUNCTION(Window, postMessage)(JSContext* ctx, JSValue this_val, int argc, J JSValue messageType = JS_NewString(ctx, "message"); JSValue arguments[] = {messageType, messageEventInitValue}; - JSValue messageEventValue = JS_CallConstructor(ctx, MessageEvent::instance(window->m_context)->jsObject, 2, arguments); + 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); @@ -113,7 +114,8 @@ IMPL_FUNCTION(Window, postMessage)(JSContext* ctx, JSValue this_val, int argc, J IMPL_FUNCTION(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."); + return JS_ThrowTypeError(ctx, + "Failed to execute 'requestAnimationFrame': 1 argument required, but only 0 present."); } auto* context = static_cast(JS_GetContextOpaque(ctx)); @@ -122,27 +124,32 @@ IMPL_FUNCTION(Window, requestAnimationFrame)(JSContext* ctx, JSValue this_val, i JSValue callbackValue = argv[0]; if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); + 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."); + return JS_ThrowTypeError(ctx, + "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); } // Flutter backend implements check #if FLUTTER_BACKEND if (getDartMethod()->flushUICommand == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); } // Flush all pending ui messages. getDartMethod()->flushUICommand(); if (getDartMethod()->requestAnimationFrame == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) is not registered."); + 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); + auto* frameCallback = makeGarbageCollected(JS_DupValue(ctx, callbackValue)) + ->initialize(ctx, &FrameCallback::classId); int32_t requestId = window->document()->requestAnimationFrame(frameCallback); @@ -173,7 +180,8 @@ IMPL_FUNCTION(Window, cancelAnimationFrame)(JSContext* ctx, JSValue this_val, in JS_ToInt32(ctx, &id, requestIdValue); if (getDartMethod()->cancelAnimationFrame == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute 'cancelAnimationFrame': dart method (cancelAnimationFrame) is not registered."); + return JS_ThrowTypeError( + ctx, "Failed to execute 'cancelAnimationFrame': dart method (cancelAnimationFrame) is not registered."); } window->document()->cancelAnimationFrame(id); diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index ca33d315e1..4fa17f1755 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -51,7 +51,9 @@ class Window : public EventTarget { friend ExecutionContext; }; -auto windowCreator = [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) -> JSValue { +auto windowCreator = + [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) + -> JSValue { auto* window = Window::create(ctx); return window->toQuickJS(); }; diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index ab125785dd..4176467b7e 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -49,17 +49,22 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e handleTimerCallback(timer, errmsg); } -int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState* exception) { +int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, + std::shared_ptr handler, + int32_t timeout, + ExceptionState* exception) { #if FLUTTER_BACKEND if (context->dartMethodPtr()->setTimeout == nullptr) { - exception->ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); + exception->ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); return -1; } #endif // Create a timer object to keep track timer callback. auto timer = DOMTimer::create(context, handler); - auto timerId = context->dartMethodPtr()->setTimeout(timer.get(), context->contextId(), handleTransientCallback, timeout); + auto timerId = + context->dartMethodPtr()->setTimeout(timer.get(), context->contextId(), handleTransientCallback, timeout); // Register timerId. timer->setTimerId(timerId); @@ -69,16 +74,21 @@ int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, std::shared return timerId; } -int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState* exception) { +int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, + std::shared_ptr handler, + int32_t timeout, + ExceptionState* exception) { if (context->dartMethodPtr()->setInterval == nullptr) { - exception->ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute 'setInterval': dart method (setInterval) is not registered."); + exception->ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'setInterval': dart method (setInterval) is not registered."); return -1; } // Create a timer object to keep track timer callback. auto timer = DOMTimer::create(context, handler); - uint32_t timerId = context->dartMethodPtr()->setInterval(timer.get(), context->contextId(), handlePersistentCallback, timeout); + uint32_t timerId = + context->dartMethodPtr()->setInterval(timer.get(), context->contextId(), handlePersistentCallback, timeout); // Register timerId. timer->setTimerId(timerId); @@ -89,7 +99,8 @@ int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, std::share 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."); + exception->ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); return; } diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h index a3c56fb08b..7f5be4a213 100644 --- a/bridge/core/frame/window_or_worker_global_scope.h +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -14,8 +14,14 @@ namespace kraken { class WindowOrWorkerGlobalScope { public: - static int setTimeout(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState* exception); - static int setInterval(ExecutingContext* context, std::shared_ptr handler, int32_t timeout, ExceptionState* exception); + static int setTimeout(ExecutingContext* context, + std::shared_ptr handler, + int32_t timeout, + ExceptionState* exception); + static int setInterval(ExecutingContext* context, + std::shared_ptr handler, + int32_t timeout, + ExceptionState* exception); static void clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState* exception); }; diff --git a/bridge/core/frame/window_test.cc b/bridge/core/frame/window_test.cc index 92baa1ffde..2c95e0fb42 100644 --- a/bridge/core/frame/window_test.cc +++ b/bridge/core/frame/window_test.cc @@ -30,7 +30,9 @@ TEST(Window, instanceofEventTarget) { TEST(Window, requestAnimationFrame) { auto bridge = TEST_init(); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "456"); }; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "456"); + }; std::string code = R"( requestAnimationFrame(() => { diff --git a/bridge/core/html/html_all_collection.cc b/bridge/core/html/html_all_collection.cc index 0f6894bc77..3fb06081ff 100644 --- a/bridge/core/html/html_all_collection.cc +++ b/bridge/core/html/html_all_collection.cc @@ -29,7 +29,8 @@ // } // // if (!JS_IsObject(argv[0])) { -// return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: first arguments should be a object."); +// return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: first arguments should be a +// object."); // } // // JSValue before = JS_NULL; diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index a7017caf2b..7625f51328 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -19,7 +19,8 @@ // context->defineGlobalProperty("Image", JS_DupValue(context->ctx(), constructor->jsObject)); //} // -// JSValue ImageElement::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +// JSValue ImageElement::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* +// argv) { // auto instance = new ImageElementInstance(this); // return instance->jsObject; //} @@ -33,8 +34,8 @@ // 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::setProperty, *args_01, *args_02, nullptr); -// return JS_NULL; +// element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, +// *args_02, nullptr); return JS_NULL; //} // IMPL_PROPERTY_GETTER(ImageElement, height)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // getDartMethod()->flushUICommand(); @@ -46,8 +47,8 @@ // 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::setProperty, *args_01, *args_02, nullptr); -// return JS_NULL; +// element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, +// *args_02, nullptr); return JS_NULL; //} // IMPL_PROPERTY_GETTER(ImageElement, naturalWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // getDartMethod()->flushUICommand(); @@ -69,8 +70,8 @@ // 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::setProperty, *args_01, *args_02, nullptr); -// return JS_NULL; +// element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, +// *args_02, nullptr); return JS_NULL; //} // IMPL_PROPERTY_GETTER(ImageElement, loading)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // getDartMethod()->flushUICommand(); @@ -82,8 +83,8 @@ // 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::setProperty, *args_01, *args_02, nullptr); -// return JS_NULL; +// element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setProperty, *args_01, +// *args_02, nullptr); return JS_NULL; //} // // ImageElementInstance::ImageElementInstance(ImageElement* element) : ElementInstance(element, "img", true) { @@ -92,9 +93,9 @@ //} // // bool ImageElementInstance::dispatchEvent(EventInstance* event) { -// std::u16string u16EventType = std::u16string(reinterpret_cast(event->nativeEvent->type->string), event->nativeEvent->type->length); -// std::string eventType = toUTF8(u16EventType); -// bool result = EventTargetInstance::dispatchEvent(event); +// std::u16string u16EventType = std::u16string(reinterpret_cast(event->nativeEvent->type->string), +// event->nativeEvent->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) { diff --git a/bridge/core/html/html_parser.cc b/bridge/core/html/html_parser.cc index ff7ec7f317..15016d366f 100644 --- a/bridge/core/html/html_parser.cc +++ b/bridge/core/html/html_parser.cc @@ -19,7 +19,8 @@ inline std::string trim(std::string& str) { return str; } -// Parse html,isHTMLFragment should be false if need to automatically complete html, head, and body when they are missing. +// 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()); diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index 0ebd59faee..2a622154b3 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -19,7 +19,11 @@ void bindTemplateElement(ExecutionContext* context) { context->defineGlobalProperty("HTMLTemplateElement", constructor->jsObject); } -JSValue TemplateElement::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { +JSValue TemplateElement::instanceConstructor(JSContext* ctx, + JSValue func_obj, + JSValue this_val, + int argc, + JSValue* argv) { auto instance = new TemplateElementInstance(this); return instance->jsObject; } @@ -28,7 +32,8 @@ DocumentFragmentInstance* TemplateElementInstance::content() const { return static_cast(JS_GetOpaque(m_content.value(), DocumentFragment::classId())); } -TemplateElementInstance::TemplateElementInstance(TemplateElement* element) : ElementInstance(element, "template", true) { +TemplateElementInstance::TemplateElementInstance(TemplateElement* element) + : ElementInstance(element, "template", true) { setNodeFlag(NodeFlag::IsTemplateElement); } diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index ab25115fb2..492fb88789 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -38,7 +38,8 @@ class TemplateElementInstance : public ElementInstance { 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)}; + ObjectProperty m_content{m_context, jsObject, "content", + JS_CallConstructor(m_ctx, DocumentFragment::instance(m_context)->jsObject, 0, nullptr)}; friend TemplateElement; }; diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 15bd1b1574..c3e0045d2d 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -18,7 +18,8 @@ ConsoleMessageHandler KrakenPage::consoleMessageHandler{nullptr}; kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; -KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { +KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) + : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { m_context = new ExecutingContext( contextId, [](ExecutingContext* context, const char* message) { @@ -40,7 +41,10 @@ bool KrakenPage::parseHTML(const char* code, size_t length) { // return true; } -void KrakenPage::invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* ptr, NativeString* extra) { +void KrakenPage::invokeModuleEvent(const NativeString* moduleName, + const char* eventType, + void* ptr, + NativeString* extra) { // if (!m_context->isValid()) // return; // @@ -52,9 +56,8 @@ void KrakenPage::invokeModuleEvent(const NativeString* moduleName, const char* e // eventObject = event->toQuickJS(); // } // - // JSValue moduleNameValue = JS_NewUnicodeString(m_context->runtime(), m_context->ctx(), moduleName->string, moduleName->length); - // JSValue extraObject = JS_NULL; - // if (extra != nullptr) { + // 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(extra->string), extra->length); // std::string extraString = toUTF8(u16Extra); // extraObject = JS_ParseJSON(m_context->ctx(), extraString.c_str(), extraString.size(), ""); @@ -90,7 +93,8 @@ void KrakenPage::evaluateScript(const NativeString* script, const char* url, int #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(script->string), script->length); + std::u16string patchedCode = std::u16string(u"performance.mark('js_parse_time_end');") + + std::u16string(reinterpret_cast(script->string), script->length); m_context->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); #else m_context->EvaluateJavaScript(script->string, script->length, url, startLine); diff --git a/bridge/core/page.h b/bridge/core/page.h index ccc32e5341..db85dc6dc5 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -22,9 +22,10 @@ using JSBridgeDisposeCallback = void (*)(KrakenPage* bridge); using ConsoleMessageHandler = std::function; /// KrakenPage is class which manage all js objects Create by flutter widget. -/// Every flutter widgets have a corresponding KrakenPage, and all objects created by JavaScript are stored here, -/// and there is no data sharing between objects between different KrakenPages. -/// It's safe to Allocate many KrakenPages at the same times on one thread, but not safe for multi-threads, only one thread can enter to KrakenPage at the same time. +/// Every flutter widgets have a corresponding KrakenPage, and all objects created by JavaScript are stored +/// here, and there is no data sharing between objects between different KrakenPages. It's safe to Allocate many +/// KrakenPages at the same times on one thread, but not safe for multi-threads, only one thread can enter to KrakenPage +/// at the same time. class KrakenPage final { public: static kraken::KrakenPage** pageContextPool; @@ -58,7 +59,8 @@ class KrakenPage final { private: const std::thread::id ownerThreadId; // 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. + // TODO: Raw pointer is dangerous and just works but it's fragile. We needs refactor this for more stable and + // maintainable. ExecutingContext* m_context; JSExceptionHandler m_handler; }; diff --git a/bridge/core/timing/performance.cc b/bridge/core/timing/performance.cc index 7a8cfabc9b..5b1dad0428 100644 --- a/bridge/core/timing/performance.cc +++ b/bridge/core/timing/performance.cc @@ -40,7 +40,9 @@ IMPL_PROPERTY_GETTER(PerformanceEntry, duration)(JSContext* ctx, JSValue this_va IMPL_PROPERTY_GETTER(Performance, timeOrigin)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - int64_t time = std::chrono::duration_cast(performance->m_context->timeOrigin.time_since_epoch()).count(); + int64_t time = + std::chrono::duration_cast(performance->m_context->timeOrigin.time_since_epoch()) + .count(); return JS_NewUint32(ctx, time); } @@ -51,7 +53,9 @@ JSValue Performance::now(JSContext* ctx, JSValue this_val, int argc, JSValue* ar JSValue Performance::toJSON(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* performance = static_cast(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(); + 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)); @@ -59,7 +63,9 @@ JSValue Performance::toJSON(JSContext* ctx, JSValue this_val, int argc, JSValue* return object; } -static JSValue buildPerformanceEntry(const std::string& entryType, ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) { +static JSValue buildPerformanceEntry(const std::string& entryType, + ExecutionContext* context, + NativePerformanceEntry* nativePerformanceEntry) { if (entryType == "mark") { auto* mark = new PerformanceMark(context, nativePerformanceEntry); return mark->jsObject; @@ -153,7 +159,8 @@ JSValue Performance::getEntries(JSContext* ctx, JSValue this_val, int argc, JSVa } JSValue Performance::getEntriesByName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc == 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); + return JS_ThrowTypeError( + ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); } std::string targetName = jsValueToStdString(ctx, argv[0]); @@ -175,7 +182,8 @@ JSValue Performance::getEntriesByName(JSContext* ctx, JSValue this_val, int argc } JSValue Performance::getEntriesByType(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc == 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); + return JS_ThrowTypeError( + ctx, "Failed to execute 'getEntriesByName' on 'Performance': 1 argument required, but only 0 present."); } std::string entryType = jsValueToStdString(ctx, argv[0]); @@ -196,7 +204,8 @@ JSValue Performance::getEntriesByType(JSContext* ctx, JSValue this_val, int argc } JSValue Performance::mark(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc != 1) { - return JS_ThrowTypeError(ctx, "Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present."); + return JS_ThrowTypeError(ctx, + "Failed to execute 'mark' on 'Performance': 1 argument required, but only 0 present."); } auto* performance = static_cast(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); @@ -207,7 +216,8 @@ JSValue Performance::mark(JSContext* ctx, JSValue this_val, int argc, JSValue* a } JSValue Performance::measure(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { if (argc == 0) { - return JS_ThrowTypeError(ctx, "Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 present."); + return JS_ThrowTypeError(ctx, + "Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 present."); } std::string name = jsValueToStdString(ctx, argv[0]); @@ -238,42 +248,64 @@ PerformanceEntry::PerformanceEntry(ExecutionContext* context, NativePerformanceE : HostObject(context, "PerformanceEntry"), m_nativePerformanceEntry(nativePerformanceEntry) {} PerformanceMark::PerformanceMark(ExecutionContext* context, std::string& name, int64_t startTime) - : PerformanceEntry(context, new NativePerformanceEntry(name, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} -PerformanceMark::PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) : PerformanceEntry(context, nativePerformanceEntry) {} -PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, std::string& name, int64_t startTime, int64_t duration) - : PerformanceEntry(context, new NativePerformanceEntry(name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} -PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) : PerformanceEntry(context, nativePerformanceEntry) {} + : PerformanceEntry(context, + new NativePerformanceEntry(name, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} +PerformanceMark::PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) + : PerformanceEntry(context, nativePerformanceEntry) {} +PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, + std::string& name, + int64_t startTime, + int64_t duration) + : PerformanceEntry( + context, + new NativePerformanceEntry(name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID)) {} +PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) + : PerformanceEntry(context, nativePerformanceEntry) {} void NativePerformance::mark(const std::string& markName) { int64_t startTime = std::chrono::duration_cast(system_clock::now().time_since_epoch()).count(); - auto* nativePerformanceEntry = new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; + auto* nativePerformanceEntry = + new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; entries->emplace_back(nativePerformanceEntry); } void NativePerformance::mark(const std::string& markName, int64_t startTime) { - auto* nativePerformanceEntry = new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; + auto* nativePerformanceEntry = + new NativePerformanceEntry{markName, "mark", startTime, 0, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; entries->emplace_back(nativePerformanceEntry); } Performance::Performance(ExecutionContext* context) : HostObject(context, "Performance") {} -void Performance::internalMeasure(const std::string& name, const std::string& startMark, const std::string& endMark, JSValue* exception) { +void Performance::internalMeasure(const std::string& name, + const std::string& startMark, + const std::string& endMark, + JSValue* exception) { auto entries = getFullEntries(); if (!startMark.empty() && !endMark.empty()) { - size_t startMarkCount = std::count_if(entries.begin(), entries.end(), [&startMark](NativePerformanceEntry* entry) -> bool { return entry->name == startMark; }); + size_t startMarkCount = + std::count_if(entries.begin(), entries.end(), + [&startMark](NativePerformanceEntry* entry) -> bool { return entry->name == startMark; }); if (startMarkCount == 0) { - *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not exist.", startMark.c_str()); + *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not exist.", + startMark.c_str()); return; } - size_t endMarkCount = std::count_if(entries.begin(), entries.end(), [&endMark](NativePerformanceEntry* entry) -> bool { return entry->name == endMark; }); + size_t endMarkCount = + std::count_if(entries.begin(), entries.end(), + [&endMark](NativePerformanceEntry* entry) -> bool { return entry->name == endMark; }); if (endMarkCount == 0) { - *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not exist.", endMark.c_str()); + *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not exist.", + endMark.c_str()); return; } if (startMarkCount != endMarkCount) { - *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s and %s does not appear the same number of times", startMark.c_str(), endMark.c_str()); + *exception = JS_ThrowTypeError( + m_ctx, + "Failed to execute 'measure' on 'Performance': The mark %s and %s does not appear the same number of times", + startMark.c_str(), endMark.c_str()); return; } @@ -281,11 +313,14 @@ void Performance::internalMeasure(const std::string& name, const std::string& st auto endIt = std::begin(entries); for (size_t i = 0; i < startMarkCount; i++) { - auto startEntry = std::find_if(startIt, entries.end(), [&startMark](NativePerformanceEntry* entry) -> bool { return entry->name == startMark; }); + auto startEntry = std::find_if(startIt, entries.end(), [&startMark](NativePerformanceEntry* entry) -> bool { + return entry->name == startMark; + }); bool isStartEntryHasUniqueId = (*startEntry)->uniqueId != PERFORMANCE_ENTRY_NONE_UNIQUE_ID; - auto endEntryComparator = [&endMark, &startEntry, isStartEntryHasUniqueId](NativePerformanceEntry* entry) -> bool { + auto endEntryComparator = [&endMark, &startEntry, + isStartEntryHasUniqueId](NativePerformanceEntry* entry) -> bool { if (isStartEntryHasUniqueId) { return entry->uniqueId == (*startEntry)->uniqueId && entry->name == endMark; } @@ -296,12 +331,14 @@ void Performance::internalMeasure(const std::string& name, const std::string& st if (endEntry == entries.end()) { size_t startIndex = startEntry - entries.begin(); - assert_m(false, ("Can not get endEntry. startIndex: " + std::to_string(startIndex) + " startMark: " + startMark + " endMark: " + endMark)); + assert_m(false, ("Can not get endEntry. startIndex: " + std::to_string(startIndex) + + " startMark: " + startMark + " endMark: " + endMark)); } int64_t duration = (*endEntry)->startTime - (*startEntry)->startTime; int64_t startTime = std::chrono::duration_cast(system_clock::now().time_since_epoch()).count(); - auto* nativePerformanceEntry = new NativePerformanceEntry{name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; + auto* nativePerformanceEntry = + new NativePerformanceEntry{name, "measure", startTime, duration, PERFORMANCE_ENTRY_NONE_UNIQUE_ID}; m_nativePerformance.entries->emplace_back(nativePerformanceEntry); startIt = ++startEntry; endIt = ++endEntry; @@ -352,21 +389,31 @@ std::vector Performance::getFullEntries() { void Performance::measureSummary(JSValue* exception) { internalMeasure(PERF_WIDGET_CREATION_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_INIT_END, exception); - internalMeasure(PERF_CONTROLLER_PROPERTIES_INIT_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_PROPERTY_INIT, exception); - internalMeasure(PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST, PERF_VIEW_CONTROLLER_INIT_START, PERF_VIEW_CONTROLLER_PROPERTY_INIT, exception); + internalMeasure(PERF_CONTROLLER_PROPERTIES_INIT_COST, PERF_CONTROLLER_INIT_START, PERF_CONTROLLER_PROPERTY_INIT, + exception); + internalMeasure(PERF_VIEW_CONTROLLER_PROPERTIES_INIT_COST, PERF_VIEW_CONTROLLER_INIT_START, + PERF_VIEW_CONTROLLER_PROPERTY_INIT, exception); internalMeasure(PERF_BRIDGE_INIT_COST, PERF_BRIDGE_INIT_START, PERF_BRIDGE_INIT_END, exception); - internalMeasure(PERF_BRIDGE_REGISTER_DART_METHOD_COST, PERF_BRIDGE_REGISTER_DART_METHOD_START, PERF_BRIDGE_REGISTER_DART_METHOD_END, exception); + internalMeasure(PERF_BRIDGE_REGISTER_DART_METHOD_COST, PERF_BRIDGE_REGISTER_DART_METHOD_START, + PERF_BRIDGE_REGISTER_DART_METHOD_END, exception); internalMeasure(PERF_CREATE_VIEWPORT_COST, PERF_CREATE_VIEWPORT_START, PERF_CREATE_VIEWPORT_END, exception); - internalMeasure(PERF_ELEMENT_MANAGER_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, PERF_ELEMENT_MANAGER_INIT_END, exception); - internalMeasure(PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, PERF_ELEMENT_MANAGER_PROPERTY_INIT, exception); + internalMeasure(PERF_ELEMENT_MANAGER_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, PERF_ELEMENT_MANAGER_INIT_END, + exception); + internalMeasure(PERF_ELEMENT_MANAGER_PROPERTIES_INIT_COST, PERF_ELEMENT_MANAGER_INIT_START, + PERF_ELEMENT_MANAGER_PROPERTY_INIT, exception); internalMeasure(PERF_ROOT_ELEMENT_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, PERF_ROOT_ELEMENT_INIT_END, exception); - internalMeasure(PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, PERF_ROOT_ELEMENT_PROPERTY_INIT, exception); + internalMeasure(PERF_ROOT_ELEMENT_PROPERTIES_INIT_COST, PERF_ROOT_ELEMENT_INIT_START, PERF_ROOT_ELEMENT_PROPERTY_INIT, + exception); internalMeasure(PERF_JS_CONTEXT_INIT_COST, PERF_JS_CONTEXT_INIT_START, PERF_JS_CONTEXT_INIT_END, exception); - internalMeasure(PERF_JS_HOST_CLASS_GET_PROPERTY_COST, PERF_JS_HOST_CLASS_GET_PROPERTY_START, PERF_JS_HOST_CLASS_GET_PROPERTY_END, exception); - internalMeasure(PERF_JS_HOST_CLASS_SET_PROPERTY_COST, PERF_JS_HOST_CLASS_SET_PROPERTY_START, PERF_JS_HOST_CLASS_SET_PROPERTY_END, exception); + internalMeasure(PERF_JS_HOST_CLASS_GET_PROPERTY_COST, PERF_JS_HOST_CLASS_GET_PROPERTY_START, + PERF_JS_HOST_CLASS_GET_PROPERTY_END, exception); + internalMeasure(PERF_JS_HOST_CLASS_SET_PROPERTY_COST, PERF_JS_HOST_CLASS_SET_PROPERTY_START, + PERF_JS_HOST_CLASS_SET_PROPERTY_END, exception); internalMeasure(PERF_JS_HOST_CLASS_INIT_COST, PERF_JS_HOST_CLASS_INIT_START, PERF_JS_HOST_CLASS_INIT_END, exception); - internalMeasure(PERF_JS_NATIVE_FUNCTION_CALL_COST, PERF_JS_NATIVE_FUNCTION_CALL_START, PERF_JS_NATIVE_FUNCTION_CALL_END, exception); - internalMeasure(PERF_JS_NATIVE_METHOD_INIT_COST, PERF_JS_NATIVE_METHOD_INIT_START, PERF_JS_NATIVE_METHOD_INIT_END, exception); + internalMeasure(PERF_JS_NATIVE_FUNCTION_CALL_COST, PERF_JS_NATIVE_FUNCTION_CALL_START, + PERF_JS_NATIVE_FUNCTION_CALL_END, exception); + internalMeasure(PERF_JS_NATIVE_METHOD_INIT_COST, PERF_JS_NATIVE_METHOD_INIT_START, PERF_JS_NATIVE_METHOD_INIT_END, + exception); internalMeasure(PERF_JS_POLYFILL_INIT_COST, PERF_JS_POLYFILL_INIT_START, PERF_JS_POLYFILL_INIT_END, exception); internalMeasure(PERF_JS_BUNDLE_LOAD_COST, PERF_JS_BUNDLE_LOAD_START, PERF_JS_BUNDLE_LOAD_END, exception); internalMeasure(PERF_JS_BUNDLE_EVAL_COST, PERF_JS_BUNDLE_EVAL_START, PERF_JS_BUNDLE_EVAL_END, exception); @@ -374,9 +421,11 @@ void Performance::measureSummary(JSValue* exception) { internalMeasure(PERF_CREATE_ELEMENT_COST, PERF_CREATE_ELEMENT_START, PERF_CREATE_ELEMENT_END, exception); internalMeasure(PERF_CREATE_TEXT_NODE_COST, PERF_CREATE_TEXT_NODE_START, PERF_CREATE_TEXT_NODE_END, exception); internalMeasure(PERF_CREATE_COMMENT_COST, PERF_CREATE_COMMENT_START, PERF_CREATE_COMMENT_END, exception); - internalMeasure(PERF_DISPOSE_EVENT_TARGET_COST, PERF_DISPOSE_EVENT_TARGET_START, PERF_DISPOSE_EVENT_TARGET_END, exception); + internalMeasure(PERF_DISPOSE_EVENT_TARGET_COST, PERF_DISPOSE_EVENT_TARGET_START, PERF_DISPOSE_EVENT_TARGET_END, + exception); internalMeasure(PERF_ADD_EVENT_COST, PERF_ADD_EVENT_START, PERF_ADD_EVENT_END, exception); - internalMeasure(PERF_INSERT_ADJACENT_NODE_COST, PERF_INSERT_ADJACENT_NODE_START, PERF_INSERT_ADJACENT_NODE_END, exception); + internalMeasure(PERF_INSERT_ADJACENT_NODE_COST, PERF_INSERT_ADJACENT_NODE_START, PERF_INSERT_ADJACENT_NODE_END, + exception); internalMeasure(PERF_REMOVE_NODE_COST, PERF_REMOVE_NODE_START, PERF_REMOVE_NODE_END, exception); internalMeasure(PERF_SET_STYLE_COST, PERF_SET_STYLE_START, PERF_SET_STYLE_END, exception); internalMeasure(PERF_SET_PROPERTIES_COST, PERF_SET_PROPERTIES_START, PERF_SET_PROPERTIES_END, exception); @@ -387,11 +436,13 @@ void Performance::measureSummary(JSValue* exception) { internalMeasure(PERF_SILVER_LAYOUT_COST, PERF_SILVER_LAYOUT_START, PERF_SILVER_LAYOUT_END, exception); internalMeasure(PERF_PAINT_COST, PERF_PAINT_START, PERF_PAINT_END, exception); internalMeasure(PERF_DOM_FORCE_LAYOUT_COST, PERF_DOM_FORCE_LAYOUT_START, PERF_DOM_FORCE_LAYOUT_END, exception); - internalMeasure(PERF_DOM_FLUSH_UI_COMMAND_COST, PERF_DOM_FLUSH_UI_COMMAND_START, PERF_DOM_FLUSH_UI_COMMAND_END, exception); + internalMeasure(PERF_DOM_FLUSH_UI_COMMAND_COST, PERF_DOM_FLUSH_UI_COMMAND_START, PERF_DOM_FLUSH_UI_COMMAND_END, + exception); internalMeasure(PERF_JS_PARSE_TIME_COST, PERF_JS_PARSE_TIME_START, PERF_JS_PARSE_TIME_END, exception); } -std::vector findAllMeasures(const std::vector& entries, const std::string& targetName) { +std::vector findAllMeasures(const std::vector& entries, + const std::string& targetName) { std::vector resultEntries; for (auto entry : entries) { @@ -474,14 +525,17 @@ JSValue Performance::__kraken_navigation_summary__(JSContext* ctx, JSValue this_ GET_COST(paint, PERF_PAINT_COST); GET_COST(domForceLayout, PERF_DOM_FORCE_LAYOUT_COST); GET_COST(domFlushUICommand, PERF_DOM_FLUSH_UI_COMMAND_COST); - GET_COST_WITH_DECREASE(jsHostClassGetProperty, PERF_JS_HOST_CLASS_GET_PROPERTY_COST, domForceLayoutCost + domFlushUICommandCost) + GET_COST_WITH_DECREASE(jsHostClassGetProperty, PERF_JS_HOST_CLASS_GET_PROPERTY_COST, + domForceLayoutCost + domFlushUICommandCost) GET_COST(jsHostClassSetProperty, PERF_JS_HOST_CLASS_SET_PROPERTY_COST); GET_COST(jsHostClassInit, PERF_JS_HOST_CLASS_INIT_COST); GET_COST(jsNativeFunction, PERF_JS_NATIVE_FUNCTION_CALL_COST); GET_COST_WITH_DECREASE(jsBundleEval, PERF_JS_BUNDLE_EVAL_COST, domForceLayoutCost + domFlushUICommandCost); - double initBundleCost = jsBundleLoadCost + jsBundleEvalCost + flushUiCommandCost + createElementCost + createTextNodeCost + createCommentCost + disposeEventTargetCost + addEventCost + - insertAdjacentNodeCost + removeNodeCost + setStyleCost + setPropertiesCost + removePropertiesCost; + double initBundleCost = jsBundleLoadCost + jsBundleEvalCost + flushUiCommandCost + createElementCost + + createTextNodeCost + createCommentCost + disposeEventTargetCost + addEventCost + + insertAdjacentNodeCost + removeNodeCost + setStyleCost + setPropertiesCost + + removePropertiesCost; // layout and paint measure are not correct. double renderingCost = flexLayoutCost + flowLayoutCost + intrinsicLayoutCost + silverLayoutCost + paintCost; double totalCost = widgetCreationCost + initBundleCost; diff --git a/bridge/core/timing/performance.h b/bridge/core/timing/performance.h index 7a9d63479e..fcea718fa7 100644 --- a/bridge/core/timing/performance.h +++ b/bridge/core/timing/performance.h @@ -128,7 +128,12 @@ namespace kraken { void bindPerformance(ExecutionContext* context); struct NativePerformanceEntry { - NativePerformanceEntry(const std::string& name, const std::string& entryType, int64_t startTime, int64_t duration, int64_t uniqueId) : startTime(startTime), duration(duration), uniqueId(uniqueId) { + NativePerformanceEntry(const std::string& name, + const std::string& entryType, + int64_t startTime, + int64_t duration, + int64_t uniqueId) + : startTime(startTime), duration(duration), uniqueId(uniqueId) { this->name = new char[name.size() + 1]; this->entryType = new char[entryType.size() + 1]; strcpy(this->name, name.data()); @@ -203,7 +208,10 @@ class Performance : public HostObject { DEFINE_READONLY_PROPERTY(timeOrigin); private: - void internalMeasure(const std::string& name, const std::string& startMark, const std::string& endMark, JSValue* exception); + void internalMeasure(const std::string& name, + const std::string& startMark, + const std::string& endMark, + JSValue* exception); double internalNow(); std::vector getFullEntries(); diff --git a/bridge/foundation/casting.h b/bridge/foundation/casting.h index 885fafdc7f..521f105058 100644 --- a/bridge/foundation/casting.h +++ b/bridge/foundation/casting.h @@ -77,6 +77,6 @@ Derived* DynamicTo(Base& from) { return IsA(from) ? &To(from) : nullptr; } -} +} // namespace kraken #endif // KRAKENBRIDGE_FOUNDATION_CASTING_H_ diff --git a/bridge/foundation/logging.cc b/bridge/foundation/logging.cc index 9fe4b217c4..29b0a4ed68 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -43,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 << ". "; } diff --git a/bridge/foundation/logging.h b/bridge/foundation/logging.h index 43364b3ca6..6690ad3327 100644 --- a/bridge/foundation/logging.h +++ b/bridge/foundation/logging.h @@ -13,11 +13,13 @@ #define KRAKEN_LAZY_STREAM(stream, condition) !(condition) ? (void)0 : ::kraken::LogMessageVoidify() & (stream) -#define KRAKEN_EAT_STREAM_PARAMETERS(ignored) true || (ignored) ? (void)0 : ::LogMessageVoidify() & ::LogMessage(::LOG_FATAL, 0, 0, nullptr).stream() +#define KRAKEN_EAT_STREAM_PARAMETERS(ignored) \ + true || (ignored) ? (void)0 : ::LogMessageVoidify() & ::LogMessage(::LOG_FATAL, 0, 0, nullptr).stream() #define KRAKEN_LOG(severity) KRAKEN_LAZY_STREAM(KRAKEN_LOG_STREAM(severity), true) -#define KRAKEN_CHECK(condition) KRAKEN_LAZY_STREAM(::kraken::LogMessage(::kraken::FATAL, __FILE__, __LINE__, #condition).stream(), !(condition)) +#define KRAKEN_CHECK(condition) \ + KRAKEN_LAZY_STREAM(::kraken::LogMessage(::kraken::FATAL, __FILE__, __LINE__, #condition).stream(), !(condition)) namespace kraken { diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 292c51de80..d63a78af3d 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -74,7 +74,10 @@ NativeValue Native_NewJSON(ExecutingContext* context, JSValue& value) { return result; } -void call_native_function(NativeFunctionContext* functionContext, int32_t argc, NativeValue* argv, NativeValue* returnValue) { +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++) { @@ -132,7 +135,8 @@ NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { return Native_NewNull(); } -NativeFunctionContext::NativeFunctionContext(ExecutingContext* context, JSValue callback) : m_context(context), m_ctx(context->ctx()), m_callback(callback), call(call_native_function) { +NativeFunctionContext::NativeFunctionContext(ExecutingContext* 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); }; @@ -142,7 +146,12 @@ NativeFunctionContext::~NativeFunctionContext() { JS_FreeValue(m_ctx, m_callback); } -static JSValue anonymousFunction(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { +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))); // @@ -177,9 +186,9 @@ void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int // 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(); + // 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); @@ -190,7 +199,12 @@ void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int // list_del(&promiseContext->link); } -static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) { +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); // @@ -225,9 +239,8 @@ JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { // auto* string = static_cast(value.u.ptr); // if (string == nullptr) // return JS_NULL; - // JSValue returnedValue = JS_NewUnicodeString(context->runtime(), context->ctx(), string->string, string->length); - // string->free(); - // return returnedValue; + // JSValue returnedValue = JS_NewUnicodeString(context->runtime(), context->ctx(), string->string, + // string->length); string->free(); return returnedValue; } case NativeTag::TAG_INT: { return JS_NewUint32(context->ctx(), value.u.int64); @@ -253,7 +266,8 @@ JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { // 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; + // return (new CanvasRenderingContext2D(context, + // static_cast(ptr)))->jsObject; // } else if (ptrType == static_cast(JSPointerType::NativeEventTarget)) { // auto* nativeEventTarget = static_cast(ptr); // return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); @@ -272,7 +286,8 @@ JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { } std::string nativeStringToStdString(NativeString* nativeString) { - std::u16string u16EventType = std::u16string(reinterpret_cast(nativeString->string), nativeString->length); + std::u16string u16EventType = + std::u16string(reinterpret_cast(nativeString->string), nativeString->length); return toUTF8(u16EventType); } diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 67db971556..a62f9317d1 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -26,7 +26,13 @@ enum NativeTag { TAG_ASYNC_FUNCTION = 8, }; -enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeEventTarget = 4 }; +enum class JSPointerType { + AsyncContextContext = 0, + NativeFunctionContext = 1, + NativeBoundingClientRect = 2, + NativeCanvasRenderingContext2D = 3, + NativeEventTarget = 4 +}; class ExecutingContext; @@ -42,9 +48,15 @@ struct NativeValue { struct NativeFunctionContext; -using CallNativeFunction = void (*)(NativeFunctionContext* functionContext, int32_t argc, NativeValue* argv, NativeValue* returnValue); +using CallNativeFunction = void (*)(NativeFunctionContext* functionContext, + int32_t argc, + NativeValue* argv, + NativeValue* returnValue); -static void call_native_function(NativeFunctionContext* functionContext, int32_t argc, NativeValue* argv, NativeValue* returnValue); +static void call_native_function(NativeFunctionContext* functionContext, + int32_t argc, + NativeValue* argv, + NativeValue* returnValue); struct NativeFunctionContext { CallNativeFunction call; diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index da3ac9290d..f732491207 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -45,7 +45,11 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01 queue.emplace_back(item); } -void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01, NativeString& args_02, void* nativePtr) { +void UICommandBuffer::addCommand(int32_t id, + int32_t type, + NativeString& args_01, + NativeString& args_02, + void* nativePtr) { #if FLUTTER_BACKEND if (!update_batched) { m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index d15027c9af..2e0b4bc3e8 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -41,8 +41,13 @@ struct UICommandItem { id(id), nativePtr(reinterpret_cast(nativePtr)){}; UICommandItem(int32_t id, int32_t type, NativeString args_01, void* nativePtr) - : type(type), string_01(reinterpret_cast(args_01.string)), args_01_length(args_01.length), id(id), nativePtr(reinterpret_cast(nativePtr)){}; - UICommandItem(int32_t id, int32_t type, void* nativePtr) : type(type), id(id), nativePtr(reinterpret_cast(nativePtr)){}; + : type(type), + string_01(reinterpret_cast(args_01.string)), + args_01_length(args_01.length), + id(id), + nativePtr(reinterpret_cast(nativePtr)){}; + UICommandItem(int32_t id, int32_t type, void* nativePtr) + : type(type), id(id), nativePtr(reinterpret_cast(nativePtr)){}; int32_t type; int32_t id; int32_t args_01_length{0}; diff --git a/bridge/include/kraken_bridge.h b/bridge/include/kraken_bridge.h index 0cce47d4f5..8461032186 100644 --- a/bridge/include/kraken_bridge.h +++ b/bridge/include/kraken_bridge.h @@ -49,7 +49,11 @@ void parseHTML(int32_t contextId, const char* code, int32_t length); KRAKEN_EXPORT_C void reloadJsContext(int32_t contextId); KRAKEN_EXPORT_C -void invokeModuleEvent(int32_t contextId, NativeString* module, const char* eventType, void* event, NativeString* extra); +void invokeModuleEvent(int32_t contextId, + NativeString* module, + const char* eventType, + void* event, + NativeString* extra); KRAKEN_EXPORT_C void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length); KRAKEN_EXPORT_C diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 6a208fc1aa..2063d82ad9 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -99,7 +99,9 @@ int32_t allocateNewPage(int32_t targetContextId) { } assert(kraken::KrakenPage::pageContextPool[targetContextId] == nullptr && - (std::string("can not Allocate page at index") + std::to_string(targetContextId) + std::string(": page have already exist.")).c_str()); + (std::string("can not Allocate page at index") + std::to_string(targetContextId) + + std::string(": page have already exist.")) + .c_str()); auto* page = new kraken::KrakenPage(targetContextId, nullptr); kraken::KrakenPage::pageContextPool[targetContextId] = page; return targetContextId; @@ -149,7 +151,11 @@ void reloadJsContext(int32_t contextId) { kraken::KrakenPage::pageContextPool[contextId] = newContext; } -void invokeModuleEvent(int32_t contextId, kraken::NativeString* moduleName, const char* eventType, void* event, kraken::NativeString* extra) { +void invokeModuleEvent(int32_t contextId, + kraken::NativeString* moduleName, + const char* eventType, + void* event, + kraken::NativeString* extra) { assert(checkPage(contextId) && "invokeEventListener: contextId is not valid"); auto context = static_cast(getPage(contextId)); context->invokeModuleEvent(moduleName, eventType, event, extra); diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index 5ae02b9729..13d04053f1 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -8,7 +8,8 @@ #include "bindings/qjs/native_string_utils.h" #include "kraken_test_context.h" -std::unordered_map testContextPool = std::unordered_map(); +std::unordered_map testContextPool = + std::unordered_map(); void initTestFramework(int32_t contextId) { auto* page = static_cast(getPage(contextId)); diff --git a/bridge/page.cc b/bridge/page.cc index 0b6358bf64..890381c805 100644 --- a/bridge/page.cc +++ b/bridge/page.cc @@ -54,7 +54,9 @@ kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId) { #if ENABLE_PROFILE - auto jsContextStartTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + auto jsContextStartTime = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) + .count(); #endif m_context = new ExecutionContext(contextId, handler, this); @@ -142,7 +144,10 @@ bool KrakenPage::parseHTML(const char* code, size_t length) { return true; } -void KrakenPage::invokeModuleEvent(NativeString* moduleName, const char* eventType, void* rawEvent, NativeString* extra) { +void KrakenPage::invokeModuleEvent(NativeString* moduleName, + const char* eventType, + void* rawEvent, + NativeString* extra) { if (!m_context->isValid()) return; @@ -154,7 +159,8 @@ void KrakenPage::invokeModuleEvent(NativeString* moduleName, const char* eventTy eventObject = eventInstance->jsObject; } - JSValue moduleNameValue = JS_NewUnicodeString(m_context->runtime(), m_context->ctx(), moduleName->string, moduleName->length); + 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(extra->string), extra->length); @@ -192,7 +198,8 @@ void KrakenPage::evaluateScript(const NativeString* script, const char* url, int #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(script->string), script->length); + std::u16string patchedCode = std::u16string(u"performance.mark('js_parse_time_end');") + + std::u16string(reinterpret_cast(script->string), script->length); m_context->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); #else m_context->evaluateJavaScript(script->string, script->length, url, startLine); diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index 1c788307c0..bc5b113047 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -23,7 +23,10 @@ KrakenTestContext::KrakenTestContext(ExecutingContext* context) : m_context(cont // init_list_head(&image_link); } -bool KrakenTestContext::evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { +bool KrakenTestContext::evaluateTestScripts(const uint16_t* code, + size_t codeLength, + const char* sourceURL, + int startLine) { if (!m_context->IsValid()) return false; return m_context->EvaluateJavaScript(code, codeLength, sourceURL, startLine); @@ -59,28 +62,34 @@ static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int arg // auto* context = static_cast(JS_GetContextOpaque(ctx)); // // if (!JS_IsObject(blobValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be + // an Blob object."); // } // auto blob = static_cast(JS_GetOpaque(blobValue, kraken::Blob::kBlobClassID)); // // if (blob == nullptr) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be + // an Blob object."); // } // // if (!JS_IsString(screenShotValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be an string."); + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be + // an string."); // } // // if (!JS_IsObject(callbackValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is + // not an function."); // } // // if (!JS_IsFunction(ctx, callbackValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is + // not an function."); // } // // if (getDartMethod()->matchImageSnapshot == nullptr) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); + // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method + // (matchImageSnapshot) is not registered."); // } // // std::unique_ptr screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); @@ -94,14 +103,13 @@ static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int arg // // 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); + // 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); + // JSValue returnValue = JS_Call(ctx, callbackContext->callback, callbackContext->context->global(), 2, + // arguments); callbackContext->context->handleException(&returnValue); JS_FreeValue(ctx, errmsgValue); // } // // callbackContext->context->drainPendingPromiseJobs(); @@ -109,7 +117,8 @@ static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int arg // list_del(&callbackContext->link); // }; // - // getDartMethod()->matchImageSnapshot(callbackContext, context->getContextId(), blob->bytes(), blob->size(), screenShotNativeString.get(), fn); + // getDartMethod()->matchImageSnapshot(callbackContext, context->getContextId(), blob->bytes(), blob->size(), + // screenShotNativeString.get(), fn); return JS_NULL; } @@ -117,7 +126,8 @@ static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSVa auto* context = ExecutingContext::From(ctx); #if FLUTTER_BACKEND if (context->dartMethodPtr()->environment == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_environment__': dart method (environment) is not registered."); + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_environment__': dart method (environment) is not registered."); } const char* env = context->dartMethodPtr()->environment(); return JS_ParseJSON(ctx, env, strlen(env), ""); @@ -129,17 +139,20 @@ static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSVa static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* context = static_cast(JS_GetContextOpaque(ctx)); if (context->dartMethodPtr()->simulatePointer == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_simulate_pointer__': dart method(simulatePointer) is not registered."); } JSValue inputArrayValue = argv[0]; if (!JS_IsObject(inputArrayValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); + return JS_ThrowTypeError(ctx, + "Failed to execute '__kraken_simulate_pointer__': first arguments should be an array."); } JSValue pointerValue = argv[1]; if (!JS_IsNumber(pointerValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); + return JS_ThrowTypeError(ctx, + "Failed to execute '__kraken_simulate_pointer__': second arguments should be an number."); } uint32_t length; @@ -189,13 +202,15 @@ static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* context = static_cast(JS_GetContextOpaque(ctx)); if (context->dartMethodPtr()->simulateInputText == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_simulate_keypress__': dart method(simulateInputText) is not registered."); } JSValue& charStringValue = argv[0]; if (!JS_IsString(charStringValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); + return JS_ThrowTypeError(ctx, + "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); } std::unique_ptr nativeString = kraken::jsValueToNativeString(ctx, charStringValue); @@ -240,7 +255,8 @@ static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int arg struct ExecuteCallbackContext { ExecuteCallbackContext() = delete; - explicit ExecuteCallbackContext(ExecutingContext* context, ExecuteCallback executeCallback) : executeCallback(executeCallback), context(context){}; + explicit ExecuteCallbackContext(ExecutingContext* context, ExecuteCallback executeCallback) + : executeCallback(executeCallback), context(context){}; ExecuteCallback executeCallback; ExecutingContext* context; }; @@ -253,7 +269,8 @@ void KrakenTestContext::invokeExecuteTest(ExecuteCallback executeCallback) { // return; // } // - // auto done = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) -> JSValue { + // 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(JS_GetOpaque(proxyObject, 1)); diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 8c2a1880d0..b8fa403932 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -60,7 +60,12 @@ static void unlink_callback(JSThreadState* ts, JSFrameCallback* th) { ts->os_frameCallbacks.erase(th->callbackId); } -NativeString* TEST_invokeModule(void* callbackContext, int32_t contextId, NativeString* moduleName, NativeString* method, NativeString* params, AsyncModuleCallback callback) { +NativeString* TEST_invokeModule(void* callbackContext, + int32_t contextId, + NativeString* moduleName, + NativeString* method, + NativeString* params, + AsyncModuleCallback callback) { std::string module = nativeStringToStdString(moduleName); if (module == "throwError") { @@ -159,7 +164,11 @@ NativeString* TEST_platformBrightness(int32_t contextId) { return nullptr; } -void TEST_toBlob(void* callbackContext, int32_t contextId, AsyncBlobCallback blobCallback, int32_t elementId, double devicePixelRatio) {} +void TEST_toBlob(void* callbackContext, + int32_t contextId, + AsyncBlobCallback blobCallback, + int32_t elementId, + double devicePixelRatio) {} void TEST_flushUICommand() {} From c9165f07ec1317b0ddc400b811d0e21f0bdb5531 Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 3 Apr 2022 13:36:04 +0800 Subject: [PATCH 049/498] feat: add static built-in-string --- bridge/bindings/qjs/atom_string.h | 12 +- bridge/bindings/qjs/converter_impl.h | 12 +- bridge/bindings/qjs/js_event_handler.h | 4 +- bridge/bindings/qjs/native_string_utils.cc | 8 +- bridge/bindings/qjs/to_quickjs.h | 4 +- bridge/core/built_in_string.json | 229 +++++ bridge/core/dom/events/event_target.h | 4 +- .../dom/events/registered_eventListener.cc | 3 +- .../dom/events/registered_eventListener.h | 2 +- .../{error_event.ts => error_event.d.ts} | 0 bridge/core/executing_context.cc | 9 - bridge/core/executing_context.h | 2 - bridge/core/fileapi/blob.cc | 8 +- bridge/core/fileapi/blob.h | 2 +- bridge/core/frame/console.cc | 13 +- bridge/core/frame/console.h | 7 +- bridge/core/frame/dom_timer.cc | 2 +- bridge/core/frame/module_manager.cc | 42 +- bridge/core/frame/module_manager.h | 19 +- bridge/core/page.cc | 2 +- bridge/foundation/native_string.cc | 14 +- bridge/foundation/native_string.h | 13 +- bridge/foundation/native_value.cc | 2 +- bridge/foundation/ui_command_buffer.h | 12 +- .../code_generator/src/idl/generateHeader.ts | 49 +- .../static/idl_templates/dictionary.h.tpl | 2 +- .../static/json_templates/make_names.cc.tpl | 11 +- .../static/json_templates/make_names.h.tpl | 8 +- .../static/json_templates/qjs_atom.h.tpl | 6 +- bridge/third_party/quickjs/built_in_string.h | 918 ++++++++++++++++++ bridge/third_party/quickjs/event_type_names.h | 832 ++++++++++++---- bridge/third_party/quickjs/quickjs.h | 2 +- 32 files changed, 1922 insertions(+), 331 deletions(-) create mode 100644 bridge/core/built_in_string.json rename bridge/core/events/{error_event.ts => error_event.d.ts} (100%) create mode 100644 bridge/third_party/quickjs/built_in_string.h diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index d4f0c495e1..2481366cfd 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -23,7 +23,7 @@ class AtomicString { public: static AtomicString Empty(JSContext* ctx) { return AtomicString(ctx, JS_ATOM_NULL); }; static AtomicString From(JSContext* ctx, NativeString* native_string) { - JSValue str = JS_NewUnicodeString(ctx, native_string->string, native_string->length); + JSValue str = JS_NewUnicodeString(ctx, native_string->string(), native_string->length()); auto result = AtomicString(ctx, str); JS_FreeValue(ctx, str); return result; @@ -38,13 +38,21 @@ class AtomicString { // Return the undefined string value from atom key. JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); }; - std::string ToStdString() const { + [[nodiscard]] std::string ToStdString() const { const char* buf = JS_AtomToCString(ctx_, atom_); std::string result = std::string(buf); JS_FreeCString(ctx_, buf); return result; } + [[nodiscard]] std::unique_ptr 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); + } + // Copy assignment AtomicString(AtomicString const& value) { if (!is_static_atom_ && &value != this) { diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 6c615125d7..cbb5b75176 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -16,6 +16,7 @@ #include "idl_type.h" #include "js_event_listener.h" #include "native_string_utils.h" +#include "qjs_event_init.h" namespace kraken { @@ -195,10 +196,10 @@ struct Converter : public ConverterBase { 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); + 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); + 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); @@ -387,6 +388,13 @@ struct Converter> : public ConverterBase +struct Converter : public ConverterBase { + static ImplType FromValue() { + + } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index 92f1430dfc..c936789f74 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -38,9 +38,9 @@ class JSEventHandler : public JSBasedEventListener { explicit JSEventHandler(const std::shared_ptr& event_handler, HandlerType type) : type_(type), event_handler_(event_handler){}; - JSValue GetListenerObject(EventTarget&) { return event_handler_->ToQuickJS(); } + JSValue GetListenerObject(EventTarget&) override { return event_handler_->ToQuickJS(); } - JSValue GetEffectiveFunction(EventTarget&) { return event_handler_->ToQuickJS(); } + JSValue GetEffectiveFunction(EventTarget&) override { return event_handler_->ToQuickJS(); } // Helper functions for DowncastTraits. bool IsJSEventHandler() const override { return true; } diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index d8205503fb..e13de37abf 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -20,9 +20,7 @@ std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue valu uint32_t length; uint16_t* buffer = JS_ToUnicode(ctx, value, &length); - std::unique_ptr ptr = std::make_unique(); - ptr->string = buffer; - ptr->length = length; + std::unique_ptr ptr = std::make_unique(buffer, length); if (!isValueString) { JS_FreeValue(ctx, value); @@ -33,9 +31,7 @@ std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue valu 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(); + NativeString tmp{reinterpret_cast(utf16.c_str()), static_cast(utf16.size())}; return std::unique_ptr(tmp.clone()); } diff --git a/bridge/bindings/qjs/to_quickjs.h b/bridge/bindings/qjs/to_quickjs.h index 2de59de433..1e13f60a3a 100644 --- a/bridge/bindings/qjs/to_quickjs.h +++ b/bridge/bindings/qjs/to_quickjs.h @@ -34,10 +34,10 @@ 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); + return JS_NewUnicodeString(ctx, str->string(), str->length()); } inline JSValue toQuickJS(JSContext* ctx, NativeString* str) { - return JS_NewUnicodeString(ctx, str->string, str->length); + return JS_NewUnicodeString(ctx, str->string(), str->length()); } // ScriptWrapper diff --git a/bridge/core/built_in_string.json b/bridge/core/built_in_string.json new file mode 100644 index 0000000000..f19973c20f --- /dev/null +++ b/bridge/core/built_in_string.json @@ -0,0 +1,229 @@ +{ + "metadata": { + "templates": ["make_names", "qjs_atom"] + }, + "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"], + ["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"], + ["defineProperties", "defineProperties"], + ["apply", "apply"], + ["join", "join"], + ["concat", "concat"], + ["split", "split"], + ["construct", "construct"], + ["getPrototypeOf", "getPrototypeOf"], + ["setPrototypeOf", "setPrototypeOf"], + ["isExtensible", "isExtensible"], + ["preventExtensions", "preventExtensions"], + ["has", "has"], + ["deleteProperty", "deleteProperty"], + ["defineProperty", "defineProperty"], + ["getOwnPropertyDescriptor", "getOwnPropertyDescriptor"], + ["ownKeys", "ownKeys"], + ["add", "add"], + ["done", "done"], + ["next", "next"], + ["values", "values"], + ["source", "source"], + ["flags", "flags"], + ["global", "global"], + ["unicode", "unicode"], + ["raw", "raw"], + ["new_target", "new.target"], + ["this_active_func", "this.active_func"], + ["home_object", ""], + ["computed_field", ""], + ["static_computed_field", ""], + ["class_fields_init", ""], + ["brand", ""], + ["hash_constructor", "#constructor"], + ["as", "as"], + ["from", "from"], + ["meta", "meta"], + ["_default_", "*default*"], + ["_star_", "*"], + ["Module", "Module"], + ["then", "then"], + ["resolve", "resolve"], + ["reject", "reject"], + ["promise", "promise"], + ["proxy", "proxy"], + ["revoke", "revoke"], + ["async", "async"], + ["exec", "exec"], + ["groups", "groups"], + ["status", "status"], + ["reason", "reason"], + ["globalThis", "globalThis"], + ["bigint", "bigint"], + ["bigfloat", "bigfloat"], + ["bigdecimal", "bigdecimal"], + ["roundingMode", "roundingMode"], + ["maximumSignificantDigits", "maximumSignificantDigits"], + ["maximumFractionDigits", "maximumFractionDigits"], + ["not_equal", "not-equal"], + ["timed_out", "timed-out"], + ["ok", "ok"], + ["toJSON", "toJSON"], + ["Object", "Object"], + ["Array", "Array"], + ["Error", "Error"], + ["Number", "Number"], + ["String", "String"], + ["Boolean", "Boolean"], + ["Symbol", "Symbol"], + ["Arguments", "Arguments"], + ["Math", "Math"], + ["JSON", "JSON"], + ["Date", "Date"], + ["Function", "Function"], + ["GeneratorFunction", "GeneratorFunction"], + ["ForInIterator", "ForInIterator"], + ["RegExp", "RegExp"], + ["ArrayBuffer", "ArrayBuffer"], + ["SharedArrayBuffer", "SharedArrayBuffer"], + ["Uint8ClampedArray", "Uint8ClampedArray"], + ["Int8Array", "Int8Array"], + ["Uint8Array", "Uint8Array"], + ["Int16Array", "Int16Array"], + ["Uint16Array", "Uint16Array"], + ["Int32Array", "Int32Array"], + ["Uint32Array", "Uint32Array"], + ["BigInt64Array", "BigInt64Array"], + ["BigUint64Array", "BigUint64Array"], + ["Float32Array", "Float32Array"], + ["Float64Array", "Float64Array"], + ["DataView", "DataView"], + ["BigInt", "BigInt"], + ["BigFloat", "BigFloat"], + ["BigFloatEnv", "BigFloatEnv"], + ["BigDecimal", "BigDecimal"], + ["OperatorSet", "OperatorSet"], + ["Operators", "Operators"], + ["Map", "Map"], + ["Set", "Set"], + ["WeakMap", "WeakMap"], + ["WeakSet", "WeakSet"], + ["Map_Iterator", "Map Iterator"], + ["Set_Iterator", "Set Iterator"], + ["Array_Iterator", "Array Iterator"], + ["String_Iterator", "String Iterator"], + ["RegExp_String_Iterator", "RegExp String Iterator"], + ["Generator", "Generator"], + ["Proxy", "Proxy"], + ["Promise", "Promise"], + ["PromiseResolveFunction", "PromiseResolveFunction"], + ["PromiseRejectFunction", "PromiseRejectFunction"], + ["AsyncFunction", "AsyncFunction"], + ["AsyncFunctionResolve", "AsyncFunctionResolve"], + ["AsyncFunctionReject", "AsyncFunctionReject"], + ["AsyncGeneratorFunction", "AsyncGeneratorFunction"], + ["AsyncGenerator", "AsyncGenerator"], + ["EvalError", "EvalError"], + ["RangeError", "RangeError"], + ["ReferenceError", "ReferenceError"], + ["SyntaxError", "SyntaxError"], + ["TypeError", "TypeError"], + ["URIError", "URIError"], + ["InternalError", "InternalError"], + ["Private_brand", ""], + ["Symbol_toPrimitive", "Symbol.toPrimitive"], + ["Symbol_iterator", "Symbol.iterator"], + ["Symbol_match", "Symbol.match"], + ["Symbol_matchAll", "Symbol.matchAll"], + ["Symbol_replace", "Symbol.replace"], + ["Symbol_search", "Symbol.search"], + ["Symbol_split", "Symbol.split"], + ["Symbol_toStringTag", "Symbol.toStringTag"], + ["Symbol_isConcatSpreadable", "Symbol.isConcatSpreadable"], + ["Symbol_hasInstance", "Symbol.hasInstance"], + ["Symbol_species", "Symbol.species"], + ["Symbol_unscopables", "Symbol.unscopables"], + ["Symbol_asyncIterator", "Symbol.asyncIterator"], + ["Symbol_operatorSet", "Symbol.operatorSet"] + ] +} diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 920b01b5ef..fd32ed537b 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -109,6 +109,8 @@ class EventTarget : public ScriptWrappable { static DispatchEventResult GetDispatchEventResult(const Event&); + virtual bool IsWindowOrWorkerGlobalScope() const { return false; } + protected: virtual bool AddEventListenerInternal(const AtomicString& event_type, const std::shared_ptr& listener, @@ -126,8 +128,6 @@ class EventTarget : public ScriptWrappable { const char* GetHumanReadableName() const override; - virtual bool IsWindowOrWorkerGlobalScope() const { return false; } - private: bool FireEventListeners(Event&, EventTargetData*, EventListenerVector&, ExceptionState&); }; diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc index 12236144e7..cf62f6e5b7 100644 --- a/bridge/core/dom/events/registered_eventListener.cc +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -15,7 +15,6 @@ RegisteredEventListener::RegisteredEventListener(const std::shared_ptrcapture()), passive_(options->passive()), - passive_specified_(false), once_(options->once()), blocked_event_warning_emitted_(false){}; @@ -23,7 +22,7 @@ RegisteredEventListener::RegisteredEventListener(const RegisteredEventListener& RegisteredEventListener& RegisteredEventListener::operator=(const RegisteredEventListener& that) = default; -void RegisteredEventListener::SetCallback(const std::shared_ptr& listener) { +void RegisteredEventListener::SetCallback(const std::shared_ptr& listener) { callback_ = listener; } diff --git a/bridge/core/dom/events/registered_eventListener.h b/bridge/core/dom/events/registered_eventListener.h index 7592d28287..04d0d77571 100644 --- a/bridge/core/dom/events/registered_eventListener.h +++ b/bridge/core/dom/events/registered_eventListener.h @@ -26,7 +26,7 @@ class RegisteredEventListener final { RegisteredEventListener& operator=(const RegisteredEventListener& that); const std::shared_ptr Callback() const { return callback_; } - std::shared_ptr Callback() { return callback_; } + void SetCallback(const std::shared_ptr& listener); void SetCallback(EventListener* listener); diff --git a/bridge/core/events/error_event.ts b/bridge/core/events/error_event.d.ts similarity index 100% rename from bridge/core/events/error_event.ts rename to bridge/core/events/error_event.d.ts diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 613f97da2b..a5b3817729 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -370,15 +370,6 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { // return &pending_promises_; //} -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; -} // An lock free context validator. bool isContextValid(int32_t contextId) { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index b596c7cd73..87daab0c5b 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -160,8 +160,6 @@ class ObjectProperty { std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); -void buildUICommandArgs(JSContext* ctx, JSValue key, NativeString& args_01); - // JS array operation utilities. void arrayPushValue(JSContext* ctx, JSValue array, JSValue val); void arrayInsert(JSContext* ctx, JSValue array, uint32_t start, JSValue targetValue); diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index acdd57a4c8..85265927d6 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -7,6 +7,7 @@ #include #include "bindings/qjs/script_promise_resolver.h" #include "core/executing_context.h" +#include "built_in_string.h" namespace kraken { @@ -87,19 +88,18 @@ 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) { - std::unique_ptr contentType = nullptr; - return slice(start, end, contentType, exception_state); + return slice(start, end, AtomicString::Empty(ctx()), exception_state); } Blob* Blob::slice(int64_t start, int64_t end, - std::unique_ptr& content_type, + const AtomicString& content_type, ExceptionState& exception_state) { auto* newBlob = makeGarbageCollected(ctx()); std::vector 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 != nullptr ? nativeStringToStdString(content_type.get()) : mime_type_; + newBlob->mime_type_ = content_type != built_in_string::kempty_string ? content_type.ToStdString() : mime_type_; return newBlob; } diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 6a07ebf608..a8752ede06 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -57,7 +57,7 @@ class Blob : public ScriptWrappable { 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, std::unique_ptr& content_type, ExceptionState& exception_state); + Blob* slice(int64_t start, int64_t end, const AtomicString& content_type, ExceptionState& exception_state); std::string StringResult(); ArrayBufferData ArrayBufferResult(); diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 02b6994284..9044e44cab 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -6,24 +6,25 @@ #include "console.h" #include #include "foundation/logging.h" +#include "built_in_string.h" namespace kraken { void Console::__kraken_print__(ExecutingContext* context, - std::unique_ptr& log, - std::unique_ptr& level, + const AtomicString& log, + const AtomicString& level, ExceptionState& exception) { std::stringstream stream; - std::string buffer = nativeStringToStdString(log.get()); + std::string buffer = log.ToStdString(); stream << buffer; - printLog(context->contextId(), stream, level != nullptr ? nativeStringToStdString(level.get()) : "info", nullptr); + printLog(context->contextId(), stream, level != built_in_string::kempty_string ? level.ToStdString() : "info", nullptr); } void Console::__kraken_print__(ExecutingContext* context, - std::unique_ptr& log, + const AtomicString& log, ExceptionState& exception_state) { std::stringstream stream; - std::string buffer = nativeStringToStdString(log.get()); + std::string buffer = log.ToStdString(); stream << buffer; printLog(context->contextId(), stream, "info", nullptr); } diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index e8904e9892..1949f0b00b 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -7,6 +7,7 @@ #define KRAKE_CONSOLE_H #include "bindings/qjs/script_value.h" +#include "bindings/qjs/atom_string.h" #include "core/executing_context.h" namespace kraken { @@ -14,11 +15,11 @@ namespace kraken { class Console final { public: static void __kraken_print__(ExecutingContext* context, - std::unique_ptr& log, - std::unique_ptr& level, + const AtomicString& log, + const AtomicString& level, ExceptionState& exception); static void __kraken_print__(ExecutingContext* context, - std::unique_ptr& log, + const AtomicString& log, ExceptionState& exception_state); }; diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index 67a13b5f0c..297bb53329 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -25,7 +25,7 @@ void DOMTimer::Fire() { if (!callback_->IsFunction(context_->ctx())) return; - ScriptValue returnValue = callback_->Invoke(context_->ctx(), 0, nullptr); + ScriptValue returnValue = callback_->Invoke(context_->ctx(), ScriptValue::Empty(context_->ctx()), 0, nullptr); if (returnValue.IsException()) { context_->HandleException(&returnValue); diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index c3582a1fc8..210c093a65 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -33,16 +33,16 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha if (errmsg != nullptr) { ScriptValue errorObject = ScriptValue::createErrorObject(ctx, errmsg); ScriptValue arguments[] = {errorObject}; - ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); + ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 1, arguments); if (returnValue.IsException()) { context->HandleException(&returnValue); } } else { - std::u16string argumentString = std::u16string(reinterpret_cast(json->string), json->length); + std::u16string argumentString = std::u16string(reinterpret_cast(json->string()), json->length()); std::string utf8Arguments = toUTF8(argumentString); ScriptValue jsonObject = ScriptValue::createJSONObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); ScriptValue arguments[] = {jsonObject}; - ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, 1, arguments); + ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 1, arguments); if (returnValue.IsException()) { context->HandleException(&returnValue); } @@ -61,25 +61,25 @@ void handleInvokeModuleUnexpectedCallback(void* callbackContext, static_assert("Unexpected module callback, please check your invokeModule implementation on the dart side."); } -std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr& moduleName, - std::unique_ptr& method, +AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, ExceptionState& exception) { ScriptValue empty = ScriptValue::Empty(context->ctx()); return __kraken_invoke_module__(context, moduleName, method, empty, nullptr, exception); } -std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr& moduleName, - std::unique_ptr& method, +AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, ScriptValue& paramsValue, ExceptionState& exception) { return __kraken_invoke_module__(context, moduleName, method, paramsValue, nullptr, exception); } -std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr& moduleName, - std::unique_ptr& method, +AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, ScriptValue& paramsValue, std::shared_ptr callback, ExceptionState& exception) { @@ -87,7 +87,7 @@ std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingC if (!paramsValue.IsEmpty()) { params = paramsValue.ToJSONStringify(&exception).toNativeString(); if (exception.HasException()) { - return nullptr; + return AtomicString::Empty(context->ctx()); } } @@ -95,7 +95,7 @@ std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingC exception.ThrowException( context->ctx(), ErrorType::InternalError, "Failed to execute '__kraken_invoke_module__': dart method (invokeModule) is not registered."); - return nullptr; + return AtomicString::Empty(context->ctx()); } auto moduleCallback = ModuleCallback::Create(callback); @@ -104,24 +104,18 @@ std::unique_ptr ModuleManager::__kraken_invoke_module__(ExecutingC NativeString* result; if (callback != nullptr) { - result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.get(), method.get(), + result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.ToNativeString().get(), method.ToNativeString().get(), params.get(), handleInvokeModuleTransientCallback); } else { - result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.get(), method.get(), + result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.ToNativeString().get(), method.ToNativeString().get(), params.get(), handleInvokeModuleUnexpectedCallback); } - moduleName->free(); - method->free(); - if (params != nullptr) { - params->free(); - } - if (result == nullptr) { - return nullptr; + return AtomicString::Empty(context->ctx()); } - return std::unique_ptr(result); + return AtomicString::From(context->ctx(), result); } void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index c6a5d9b572..31066d7d69 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -6,6 +6,7 @@ #ifndef KRAKENBRIDGE_MODULE_MANAGER_H #define KRAKENBRIDGE_MODULE_MANAGER_H +#include "bindings/qjs/atom_string.h" #include "bindings/qjs/exception_state.h" #include "bindings/qjs/qjs_function.h" #include "module_callback.h" @@ -14,18 +15,18 @@ namespace kraken { class ModuleManager { public: - static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr& moduleName, - std::unique_ptr& method, + static AtomicString __kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, ExceptionState& exception); - static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr& moduleName, - std::unique_ptr& method, + static AtomicString __kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, ScriptValue& params, ExceptionState& exception); - static std::unique_ptr __kraken_invoke_module__(ExecutingContext* context, - std::unique_ptr& moduleName, - std::unique_ptr& method, + static AtomicString __kraken_invoke_module__(ExecutingContext* context, + const AtomicString& moduleName, + const AtomicString& method, ScriptValue& params, std::shared_ptr callback, ExceptionState& exception); diff --git a/bridge/core/page.cc b/bridge/core/page.cc index c3e0045d2d..9cb60a9959 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -97,7 +97,7 @@ void KrakenPage::evaluateScript(const NativeString* script, const char* url, int std::u16string(reinterpret_cast(script->string), script->length); m_context->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); #else - m_context->EvaluateJavaScript(script->string, script->length, url, startLine); + m_context->EvaluateJavaScript(script->string(), script->length(), url, startLine); #endif } diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc index 1df95b89c3..a2f3659aa9 100644 --- a/bridge/foundation/native_string.cc +++ b/bridge/foundation/native_string.cc @@ -9,17 +9,17 @@ namespace kraken { NativeString* NativeString::clone() { - auto* newNativeString = new NativeString(); - auto* newString = new uint16_t[length]; + auto* newNativeString = new NativeString(nullptr, 0); + auto* newString = new uint16_t[length_]; - memcpy(newString, string, length * sizeof(uint16_t)); - newNativeString->string = newString; - newNativeString->length = length; + memcpy(newString, string_, length_ * sizeof(uint16_t)); + newNativeString->string_ = newString; + newNativeString->length_ = length_; return newNativeString; } -void NativeString::free() { - delete[] string; +NativeString::~NativeString() { + delete[] string_; } } // namespace kraken diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h index 5fd8ee84b1..ffbf3cd6b2 100644 --- a/bridge/foundation/native_string.h +++ b/bridge/foundation/native_string.h @@ -11,11 +11,16 @@ namespace kraken { struct NativeString { - const uint16_t* string; - uint32_t length; - + NativeString(const uint16_t* string, uint32_t length): string_(string), length_(length) {}; + ~NativeString(); NativeString* clone(); - void free(); + + inline const uint16_t* string() const{return string_;} + inline uint32_t length() const { return length_; } + + private: + const uint16_t* string_; + uint32_t length_; }; } // namespace kraken diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index d63a78af3d..91df84d767 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -287,7 +287,7 @@ JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { std::string nativeStringToStdString(NativeString* nativeString) { std::u16string u16EventType = - std::u16string(reinterpret_cast(nativeString->string), nativeString->length); + std::u16string(reinterpret_cast(nativeString->string()), nativeString->length()); return toUTF8(u16EventType); } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 2e0b4bc3e8..6c68097711 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -34,16 +34,16 @@ enum UICommand { struct UICommandItem { UICommandItem(int32_t id, int32_t type, NativeString args_01, NativeString args_02, void* nativePtr) : type(type), - string_01(reinterpret_cast(args_01.string)), - args_01_length(args_01.length), - string_02(reinterpret_cast(args_02.string)), - args_02_length(args_02.length), + string_01(reinterpret_cast(args_01.string())), + args_01_length(args_01.length()), + string_02(reinterpret_cast(args_02.string())), + args_02_length(args_02.length()), id(id), nativePtr(reinterpret_cast(nativePtr)){}; UICommandItem(int32_t id, int32_t type, NativeString args_01, void* nativePtr) : type(type), - string_01(reinterpret_cast(args_01.string)), - args_01_length(args_01.length), + string_01(reinterpret_cast(args_01.string())), + args_01_length(args_01.length()), id(id), nativePtr(reinterpret_cast(nativePtr)){}; UICommandItem(int32_t id, int32_t type, void* nativePtr) diff --git a/bridge/scripts/code_generator/src/idl/generateHeader.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts index aed0c19645..35cd05b9f7 100644 --- a/bridge/scripts/code_generator/src/idl/generateHeader.ts +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -31,32 +31,49 @@ function readTemplate(name: string) { export function generateCppHeader(blob: IDLBlob) { const baseTemplate = fs.readFileSync(path.join(__dirname, '../../static/idl_templates/base.h.tpl'), {encoding: 'utf-8'}); + let headerOptions = { + interface: false, + dictionary: false, + global_function: false, + }; const contents = blob.objects.map(object => { const templateKind = getTemplateKind(object); if (templateKind === TemplateKind.null) return ''; switch(templateKind) { case TemplateKind.Interface: { - return _.template(readTemplate('interface'))({ - className: getClassName(blob), - blob: blob - }); + if (!headerOptions.interface) { + headerOptions.interface = true; + return _.template(readTemplate('interface'))({ + className: getClassName(blob), + blob: blob + }); + } + return ''; } case TemplateKind.Dictionary: { - let props = (object as ClassObject).props; - return _.template(readTemplate('dictionary'))({ - className: getClassName(blob), - blob: blob, - object: object, - props, - generateTypeConverter: generateTypeConverter - }); + if (!headerOptions.dictionary) { + headerOptions.dictionary = true; + let props = (object as ClassObject).props; + return _.template(readTemplate('dictionary'))({ + className: getClassName(blob), + blob: blob, + object: object, + props, + generateTypeConverter: generateTypeConverter + }); + } + return ''; } case TemplateKind.globalFunction: { - return _.template(readTemplate('global_function'))({ - className: getClassName(blob), - blob: blob - }); + if (!headerOptions.global_function) { + headerOptions.global_function = true; + return _.template(readTemplate('global_function'))({ + className: getClassName(blob), + blob: blob + }); + } + return ''; } } }); diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl index 06adc9a955..868a2ca393 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl @@ -1,4 +1,3 @@ -#include "bindings/qjs/converter_impl.h" <% if (object.parent) { %> #include "qjs_<%= _.snakeCase(object.parent) %>.h" @@ -10,6 +9,7 @@ class ExecutingContext; 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 %>(); diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index b3bcafe819..ed2aa9e546 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -6,11 +6,16 @@ #include "<%= name %>.h" namespace kraken { -namespace event_type_names { +namespace <%= name %> { void* names_storage[kNamesCount * ((sizeof(AtomicString) + sizeof(void *) - 1) / sizeof(void *))]; -<% _.forEach(data, function(name, index) { %>const AtomicString& k<%= name[0].toUpperCase() + name.slice(1) %> = reinterpret_cast(&names_storage)[<%= index %>]; +<% _.forEach(data, function(name, index) { %> +<% if (_.isArray(name)) { %> +const AtomicString& k<%= name[0] %> = reinterpret_cast(&names_storage)[<%= index %>]; +<% } else { %> +const AtomicString& k<%= name[0].toUpperCase() + name.slice(1) %> = reinterpret_cast(&names_storage)[<%= index %>]; +<% } %> <% }) %> void Init() { @@ -23,7 +28,7 @@ void Init() { }; static const NameEntry kNames[] = { - <% _.forEach(data, function(name) { %>{ JS_ATOM_<%= name %> }, + <% _.forEach(data, function(name) { %>{ JS_ATOM_<%= name[0] %> }, <% }); %> }; diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl index 4417d607f4..38e474e7d4 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl @@ -10,11 +10,11 @@ #include "bindings/qjs/atom_string.h" namespace kraken { -namespace event_type_names { +namespace <%= name %> { -<% _.forEach(data, function(name, index) { %> - extern const AtomicString& k<%= name[0].toUpperCase() + name.slice(1) %>; - <% }) %> +<% _.forEach(data, function(name, index) { %><% if (_.isArray(name)) { %>extern const AtomicString& k<%= name[0] %>; +<% } else { %>extern const AtomicString& k<%= name[0].toUpperCase() + name.slice(1) %>; +<% } %><% }) %> constexpr unsigned kNamesCount = <%= data.length %>; diff --git a/bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl b/bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl index 7f32b18783..16fa63100c 100644 --- a/bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl @@ -26,7 +26,11 @@ #ifdef DEF <% _.forEach(data, function(name) { %> -DEF(<%= name %>, "<%= name %>") +<% if (_.isArray(name)) { %> + DEF(<%= name[0] %>, "<%= name[1] %>") +<% } else { %> + DEF(<%= name %>, "<%= name %>") +<% } %> <% }); %> #endif /* DEF */ diff --git a/bridge/third_party/quickjs/built_in_string.h b/bridge/third_party/quickjs/built_in_string.h new file mode 100644 index 0000000000..dbc80e479e --- /dev/null +++ b/bridge/third_party/quickjs/built_in_string.h @@ -0,0 +1,918 @@ +/* +* QuickJS atom definitions +* +* Copyright (c) 2017-2018 Fabrice Bellard +* Copyright (c) 2017-2018 Charlie Gordon +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +*/ + +#ifdef DEF + + + + DEF(null, "null") + + + + DEF(false, "false") + + + + DEF(true, "true") + + + + DEF(if, "if") + + + + DEF(else, "else") + + + + DEF(return, "return") + + + + DEF(var, "var") + + + + DEF(this, "this") + + + + DEF(delete, "delete") + + + + DEF(void, "void") + + + + DEF(typeof, "typeof") + + + + DEF(new, "new") + + + + DEF(in, "in") + + + + DEF(instanceof, "instanceof") + + + + DEF(do, "do") + + + + DEF(while, "while") + + + + DEF(for, "for") + + + + DEF(break, "break") + + + + DEF(continue, "continue") + + + + DEF(switch, "switch") + + + + DEF(case, "case") + + + + DEF(default, "default") + + + + DEF(throw, "throw") + + + + DEF(try, "try") + + + + DEF(catch, "catch") + + + + DEF(finally, "finally") + + + + DEF(function, "function") + + + + DEF(debugger, "debugger") + + + + DEF(with, "with") + + + + DEF(class, "class") + + + + DEF(const, "const") + + + + DEF(enum, "enum") + + + + DEF(export, "export") + + + + DEF(extends, "extends") + + + + DEF(import, "import") + + + + DEF(super, "super") + + + + DEF(implements, "implements") + + + + DEF(interface, "interface") + + + + DEF(let, "let") + + + + DEF(package, "package") + + + + DEF(private, "private") + + + + DEF(protected, "protected") + + + + DEF(public, "public") + + + + DEF(static, "static") + + + + DEF(yield, "yield") + + + + DEF(await, "await") + + + + DEF(empty_string, "") + + + + DEF(length, "length") + + + + DEF(fileName, "fileName") + + + + DEF(lineNumber, "lineNumber") + + + + DEF(errors, "errors") + + + + DEF(stack, "stack") + + + + DEF(name, "name") + + + + DEF(toString, "toString") + + + + DEF(toLocaleString, "toLocaleString") + + + + DEF(valueOf, "valueOf") + + + + DEF(eval, "eval") + + + + DEF(prototype, "prototype") + + + + DEF(constructor, "constructor") + + + + DEF(configurable, "configurable") + + + + DEF(writable, "writable") + + + + DEF(enumerable, "enumerable") + + + + DEF(value, "value") + + + + DEF(get, "get") + + + + DEF(set, "set") + + + + DEF(of, "of") + + + + DEF(__proto__, "__proto__") + + + + DEF(undefined, "undefined") + + + + DEF(number, "number") + + + + DEF(boolean, "boolean") + + + + DEF(string, "string") + + + + DEF(object, "object") + + + + DEF(symbol, "symbol") + + + + DEF(integer, "integer") + + + + DEF(unknown, "unknown") + + + + DEF(arguments, "arguments") + + + + DEF(callee, "callee") + + + + DEF(caller, "caller") + + + + DEF(_eval_, "") + + + + DEF(_ret_, "") + + + + DEF(_var_, "") + + + + DEF(_arg_var_, "") + + + + DEF(_with_, "") + + + + DEF(lastIndex, "lastIndex") + + + + DEF(target, "target") + + + + DEF(index, "index") + + + + DEF(defineProperties, "defineProperties") + + + + DEF(apply, "apply") + + + + DEF(join, "join") + + + + DEF(concat, "concat") + + + + DEF(split, "split") + + + + DEF(construct, "construct") + + + + DEF(getPrototypeOf, "getPrototypeOf") + + + + DEF(setPrototypeOf, "setPrototypeOf") + + + + DEF(isExtensible, "isExtensible") + + + + DEF(preventExtensions, "preventExtensions") + + + + DEF(has, "has") + + + + DEF(deleteProperty, "deleteProperty") + + + + DEF(defineProperty, "defineProperty") + + + + DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor") + + + + DEF(ownKeys, "ownKeys") + + + + DEF(add, "add") + + + + DEF(done, "done") + + + + DEF(next, "next") + + + + DEF(values, "values") + + + + DEF(source, "source") + + + + DEF(flags, "flags") + + + + DEF(global, "global") + + + + DEF(unicode, "unicode") + + + + DEF(raw, "raw") + + + + DEF(new_target, "new.target") + + + + DEF(this_active_func, "this.active_func") + + + + DEF(home_object, "") + + + + DEF(computed_field, "") + + + + DEF(static_computed_field, "") + + + + DEF(class_fields_init, "") + + + + DEF(brand, "") + + + + DEF(hash_constructor, "#constructor") + + + + DEF(as, "as") + + + + DEF(from, "from") + + + + DEF(meta, "meta") + + + + DEF(_default_, "*default*") + + + + DEF(_star_, "*") + + + + DEF(Module, "Module") + + + + DEF(then, "then") + + + + DEF(resolve, "resolve") + + + + DEF(reject, "reject") + + + + DEF(promise, "promise") + + + + DEF(proxy, "proxy") + + + + DEF(revoke, "revoke") + + + + DEF(async, "async") + + + + DEF(exec, "exec") + + + + DEF(groups, "groups") + + + + DEF(status, "status") + + + + DEF(reason, "reason") + + + + DEF(globalThis, "globalThis") + + + + DEF(bigint, "bigint") + + + + DEF(bigfloat, "bigfloat") + + + + DEF(bigdecimal, "bigdecimal") + + + + DEF(roundingMode, "roundingMode") + + + + DEF(maximumSignificantDigits, "maximumSignificantDigits") + + + + DEF(maximumFractionDigits, "maximumFractionDigits") + + + + DEF(not_equal, "not-equal") + + + + DEF(timed_out, "timed-out") + + + + DEF(ok, "ok") + + + + DEF(toJSON, "toJSON") + + + + DEF(Object, "Object") + + + + DEF(Array, "Array") + + + + DEF(Error, "Error") + + + + DEF(Number, "Number") + + + + DEF(String, "String") + + + + DEF(Boolean, "Boolean") + + + + DEF(Symbol, "Symbol") + + + + DEF(Arguments, "Arguments") + + + + DEF(Math, "Math") + + + + DEF(JSON, "JSON") + + + + DEF(Date, "Date") + + + + DEF(Function, "Function") + + + + DEF(GeneratorFunction, "GeneratorFunction") + + + + DEF(ForInIterator, "ForInIterator") + + + + DEF(RegExp, "RegExp") + + + + DEF(ArrayBuffer, "ArrayBuffer") + + + + DEF(SharedArrayBuffer, "SharedArrayBuffer") + + + + DEF(Uint8ClampedArray, "Uint8ClampedArray") + + + + DEF(Int8Array, "Int8Array") + + + + DEF(Uint8Array, "Uint8Array") + + + + DEF(Int16Array, "Int16Array") + + + + DEF(Uint16Array, "Uint16Array") + + + + DEF(Int32Array, "Int32Array") + + + + DEF(Uint32Array, "Uint32Array") + + + + DEF(BigInt64Array, "BigInt64Array") + + + + DEF(BigUint64Array, "BigUint64Array") + + + + DEF(Float32Array, "Float32Array") + + + + DEF(Float64Array, "Float64Array") + + + + DEF(DataView, "DataView") + + + + DEF(BigInt, "BigInt") + + + + DEF(BigFloat, "BigFloat") + + + + DEF(BigFloatEnv, "BigFloatEnv") + + + + DEF(BigDecimal, "BigDecimal") + + + + DEF(OperatorSet, "OperatorSet") + + + + DEF(Operators, "Operators") + + + + DEF(Map, "Map") + + + + DEF(Set, "Set") + + + + DEF(WeakMap, "WeakMap") + + + + DEF(WeakSet, "WeakSet") + + + + DEF(Map_Iterator, "Map Iterator") + + + + DEF(Set_Iterator, "Set Iterator") + + + + DEF(Array_Iterator, "Array Iterator") + + + + DEF(String_Iterator, "String Iterator") + + + + DEF(RegExp_String_Iterator, "RegExp String Iterator") + + + + DEF(Generator, "Generator") + + + + DEF(Proxy, "Proxy") + + + + DEF(Promise, "Promise") + + + + DEF(PromiseResolveFunction, "PromiseResolveFunction") + + + + DEF(PromiseRejectFunction, "PromiseRejectFunction") + + + + DEF(AsyncFunction, "AsyncFunction") + + + + DEF(AsyncFunctionResolve, "AsyncFunctionResolve") + + + + DEF(AsyncFunctionReject, "AsyncFunctionReject") + + + + DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction") + + + + DEF(AsyncGenerator, "AsyncGenerator") + + + + DEF(EvalError, "EvalError") + + + + DEF(RangeError, "RangeError") + + + + DEF(ReferenceError, "ReferenceError") + + + + DEF(SyntaxError, "SyntaxError") + + + + DEF(TypeError, "TypeError") + + + + DEF(URIError, "URIError") + + + + DEF(InternalError, "InternalError") + + + + DEF(Private_brand, "") + + + + DEF(Symbol_toPrimitive, "Symbol.toPrimitive") + + + + DEF(Symbol_iterator, "Symbol.iterator") + + + + DEF(Symbol_match, "Symbol.match") + + + + DEF(Symbol_matchAll, "Symbol.matchAll") + + + + DEF(Symbol_replace, "Symbol.replace") + + + + DEF(Symbol_search, "Symbol.search") + + + + DEF(Symbol_split, "Symbol.split") + + + + DEF(Symbol_toStringTag, "Symbol.toStringTag") + + + + DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") + + + + DEF(Symbol_hasInstance, "Symbol.hasInstance") + + + + DEF(Symbol_species, "Symbol.species") + + + + DEF(Symbol_unscopables, "Symbol.unscopables") + + + + DEF(Symbol_asyncIterator, "Symbol.asyncIterator") + + + + DEF(Symbol_operatorSet, "Symbol.operatorSet") + + + +#endif /* DEF */ diff --git a/bridge/third_party/quickjs/event_type_names.h b/bridge/third_party/quickjs/event_type_names.h index 53b41d7082..0cf9b65075 100644 --- a/bridge/third_party/quickjs/event_type_names.h +++ b/bridge/third_party/quickjs/event_type_names.h @@ -26,421 +26,837 @@ #ifdef DEF -DEF(DOMActivate, "DOMActivate") -DEF(DOMCharacterDataModified, "DOMCharacterDataModified") + DEF(DOMActivate, "DOMActivate") -DEF(DOMContentLoaded, "DOMContentLoaded") -DEF(DOMFocusIn, "DOMFocusIn") -DEF(DOMFocusOut, "DOMFocusOut") + DEF(DOMCharacterDataModified, "DOMCharacterDataModified") -DEF(DOMNodeInserted, "DOMNodeInserted") -DEF(DOMNodeInsertedIntoDocument, "DOMNodeInsertedIntoDocument") -DEF(DOMNodeRemoved, "DOMNodeRemoved") + DEF(DOMContentLoaded, "DOMContentLoaded") -DEF(DOMNodeRemovedFromDocument, "DOMNodeRemovedFromDocument") -DEF(DOMSubtreeModified, "DOMSubtreeModified") -DEF(abort, "abort") + DEF(DOMFocusIn, "DOMFocusIn") -DEF(abortpayment, "abortpayment") -DEF(activate, "activate") -DEF(active, "active") + DEF(DOMFocusOut, "DOMFocusOut") -DEF(addsourcebuffer, "addsourcebuffer") -DEF(addtrack, "addtrack") -DEF(animationcancel, "animationcancel") + DEF(DOMNodeInserted, "DOMNodeInserted") -DEF(animationend, "animationend") -DEF(animationiteration, "animationiteration") -DEF(animationstart, "animationstart") + DEF(DOMNodeInsertedIntoDocument, "DOMNodeInsertedIntoDocument") -DEF(backgroundfetchabort, "backgroundfetchabort") -DEF(backgroundfetchclick, "backgroundfetchclick") -DEF(backgroundfetchfail, "backgroundfetchfail") + DEF(DOMNodeRemoved, "DOMNodeRemoved") -DEF(backgroundfetchsuccess, "backgroundfetchsuccess") -DEF(beforeunload, "beforeunload") -DEF(beginEvent, "beginEvent") + DEF(DOMNodeRemovedFromDocument, "DOMNodeRemovedFromDocument") -DEF(blocked, "blocked") -DEF(blur, "blur") -DEF(boundary, "boundary") + DEF(DOMSubtreeModified, "DOMSubtreeModified") -DEF(cached, "cached") -DEF(cancel, "cancel") -DEF(canplay, "canplay") + DEF(abort, "abort") -DEF(canplaythrough, "canplaythrough") -DEF(capturehandlechange, "capturehandlechange") -DEF(change, "change") + DEF(abortpayment, "abortpayment") -DEF(checking, "checking") -DEF(click, "click") -DEF(close, "close") + DEF(activate, "activate") -DEF(closing, "closing") -DEF(complete, "complete") -DEF(compositionend, "compositionend") + DEF(active, "active") -DEF(compositionstart, "compositionstart") -DEF(compositionupdate, "compositionupdate") -DEF(connect, "connect") + DEF(addsourcebuffer, "addsourcebuffer") -DEF(contextlost, "contextlost") -DEF(contextmenu, "contextmenu") -DEF(contextrestored, "contextrestored") + DEF(addtrack, "addtrack") -DEF(controllerchange, "controllerchange") -DEF(cookiechange, "cookiechange") -DEF(copy, "copy") + DEF(animationcancel, "animationcancel") -DEF(contentdelete, "contentdelete") -DEF(crossoriginmessage, "crossoriginmessage") -DEF(currentscreenchange, "currentscreenchange") + DEF(animationend, "animationend") -DEF(cuechange, "cuechange") -DEF(currententrychange, "currententrychange") -DEF(cut, "cut") + DEF(animationiteration, "animationiteration") -DEF(datachannel, "datachannel") -DEF(dblclick, "dblclick") -DEF(defaultsessionstart, "defaultsessionstart") + DEF(animationstart, "animationstart") -DEF(disconnect, "disconnect") -DEF(display, "display") -DEF(drop, "drop") + DEF(backgroundfetchabort, "backgroundfetchabort") -DEF(durationchange, "durationchange") -DEF(emptied, "emptied") -DEF(encrypted, "encrypted") + DEF(backgroundfetchclick, "backgroundfetchclick") -DEF(end, "end") -DEF(ended, "ended") -DEF(endEvent, "endEvent") + DEF(backgroundfetchfail, "backgroundfetchfail") -DEF(enter, "enter") -DEF(error, "error") -DEF(exit, "exit") + DEF(backgroundfetchsuccess, "backgroundfetchsuccess") -DEF(fetch, "fetch") -DEF(finish, "finish") -DEF(focus, "focus") + DEF(beforeunload, "beforeunload") -DEF(focusin, "focusin") -DEF(focusout, "focusout") -DEF(freeze, "freeze") + DEF(beginEvent, "beginEvent") -DEF(fullscreenchange, "fullscreenchange") -DEF(fullscreenerror, "fullscreenerror") -DEF(hashchange, "hashchange") + DEF(blocked, "blocked") -DEF(hide, "hide") -DEF(inactive, "inactive") -DEF(input, "input") + DEF(blur, "blur") -DEF(inputreport, "inputreport") -DEF(inputsourceschange, "inputsourceschange") -DEF(install, "install") + DEF(boundary, "boundary") -DEF(interfacerequest, "interfacerequest") -DEF(invalid, "invalid") -DEF(keydown, "keydown") + DEF(cached, "cached") -DEF(keypress, "keypress") -DEF(keystatuseschange, "keystatuseschange") -DEF(keyup, "keyup") + DEF(cancel, "cancel") -DEF(languagechange, "languagechange") -DEF(leavepictureinpicture, "leavepictureinpicture") -DEF(levelchange, "levelchange") + DEF(canplay, "canplay") -DEF(load, "load") -DEF(loadeddata, "loadeddata") -DEF(loadedmetadata, "loadedmetadata") + DEF(canplaythrough, "canplaythrough") -DEF(loadend, "loadend") -DEF(loading, "loading") -DEF(loadstart, "loadstart") + DEF(capturehandlechange, "capturehandlechange") -DEF(lostpointercapture, "lostpointercapture") -DEF(mark, "mark") -DEF(message, "message") + DEF(change, "change") -DEF(messageerror, "messageerror") -DEF(mousedown, "mousedown") -DEF(mouseenter, "mouseenter") + DEF(checking, "checking") -DEF(mouseleave, "mouseleave") -DEF(mousemove, "mousemove") -DEF(mouseout, "mouseout") + DEF(click, "click") -DEF(mouseover, "mouseover") -DEF(mouseup, "mouseup") -DEF(mousewheel, "mousewheel") + DEF(close, "close") -DEF(mute, "mute") -DEF(navigate, "navigate") -DEF(navigateerror, "navigateerror") + DEF(closing, "closing") -DEF(navigatesuccess, "navigatesuccess") -DEF(noupdate, "noupdate") -DEF(open, "open") + DEF(complete, "complete") -DEF(orientationchange, "orientationchange") -DEF(overscroll, "overscroll") -DEF(pagehide, "pagehide") + DEF(compositionend, "compositionend") -DEF(pageshow, "pageshow") -DEF(paste, "paste") -DEF(pause, "pause") + DEF(compositionstart, "compositionstart") -DEF(play, "play") -DEF(playing, "playing") -DEF(pointercancel, "pointercancel") + DEF(compositionupdate, "compositionupdate") -DEF(pointerdown, "pointerdown") -DEF(pointerenter, "pointerenter") -DEF(pointerleave, "pointerleave") + DEF(connect, "connect") -DEF(pointerlockchange, "pointerlockchange") -DEF(pointerlockerror, "pointerlockerror") -DEF(pointermove, "pointermove") + DEF(contextlost, "contextlost") -DEF(pointerout, "pointerout") -DEF(pointerover, "pointerover") -DEF(pointerup, "pointerup") + DEF(contextmenu, "contextmenu") -DEF(popstate, "popstate") -DEF(progress, "progress") -DEF(processorerror, "processorerror") + DEF(contextrestored, "contextrestored") -DEF(push, "push") -DEF(pushsubscriptionchange, "pushsubscriptionchange") -DEF(ratechange, "ratechange") + DEF(controllerchange, "controllerchange") -DEF(reading, "reading") -DEF(readingerror, "readingerror") -DEF(readystatechange, "readystatechange") + DEF(cookiechange, "cookiechange") -DEF(reflectionchange, "reflectionchange") -DEF(rejectionhandled, "rejectionhandled") -DEF(release, "release") + DEF(copy, "copy") -DEF(remove, "remove") -DEF(removestream, "removestream") -DEF(removetrack, "removetrack") + DEF(contentdelete, "contentdelete") -DEF(repeatEvent, "repeatEvent") -DEF(reset, "reset") -DEF(resize, "resize") + DEF(crossoriginmessage, "crossoriginmessage") -DEF(result, "result") -DEF(resume, "resume") -DEF(screenschange, "screenschange") + DEF(currentscreenchange, "currentscreenchange") -DEF(scroll, "scroll") -DEF(scrollend, "scrollend") -DEF(search, "search") + DEF(cuechange, "cuechange") -DEF(seeked, "seeked") -DEF(seeking, "seeking") -DEF(select, "select") + DEF(currententrychange, "currententrychange") -DEF(selectionchange, "selectionchange") -DEF(selectstart, "selectstart") -DEF(show, "show") + DEF(cut, "cut") -DEF(squeeze, "squeeze") -DEF(squeezeend, "squeezeend") -DEF(squeezestart, "squeezestart") + DEF(datachannel, "datachannel") -DEF(stalled, "stalled") -DEF(start, "start") -DEF(stop, "stop") + DEF(dblclick, "dblclick") -DEF(statechange, "statechange") -DEF(storage, "storage") -DEF(submit, "submit") + DEF(defaultsessionstart, "defaultsessionstart") -DEF(success, "success") -DEF(suspend, "suspend") -DEF(sync, "sync") + DEF(disconnect, "disconnect") -DEF(terminate, "terminate") -DEF(textInput, "textInput") -DEF(textupdate, "textupdate") + DEF(display, "display") -DEF(textformatupdate, "textformatupdate") -DEF(toggle, "toggle") -DEF(tonechange, "tonechange") + DEF(drop, "drop") -DEF(touchcancel, "touchcancel") -DEF(touchend, "touchend") -DEF(touchmove, "touchmove") + DEF(durationchange, "durationchange") -DEF(touchstart, "touchstart") -DEF(transitioncancel, "transitioncancel") -DEF(transitionend, "transitionend") + DEF(emptied, "emptied") -DEF(transitionrun, "transitionrun") -DEF(transitionstart, "transitionstart") -DEF(typechange, "typechange") + DEF(encrypted, "encrypted") -DEF(uncapturederror, "uncapturederror") -DEF(unhandledrejection, "unhandledrejection") -DEF(unload, "unload") + DEF(end, "end") -DEF(unmute, "unmute") -DEF(update, "update") -DEF(versionchange, "versionchange") + DEF(ended, "ended") -DEF(visibilitychange, "visibilitychange") -DEF(waiting, "waiting") -DEF(waitingforkey, "waitingforkey") + DEF(endEvent, "endEvent") -DEF(webglcontextcreationerror, "webglcontextcreationerror") -DEF(webglcontextlost, "webglcontextlost") -DEF(webglcontextrestored, "webglcontextrestored") + DEF(enter, "enter") -DEF(wheel, "wheel") -DEF(zoom, "zoom") + + DEF(error, "error") + + + + DEF(exit, "exit") + + + + DEF(fetch, "fetch") + + + + DEF(finish, "finish") + + + + DEF(focus, "focus") + + + + DEF(focusin, "focusin") + + + + DEF(focusout, "focusout") + + + + DEF(freeze, "freeze") + + + + DEF(fullscreenchange, "fullscreenchange") + + + + DEF(fullscreenerror, "fullscreenerror") + + + + DEF(hashchange, "hashchange") + + + + DEF(hide, "hide") + + + + DEF(inactive, "inactive") + + + + DEF(input, "input") + + + + DEF(inputreport, "inputreport") + + + + DEF(inputsourceschange, "inputsourceschange") + + + + DEF(install, "install") + + + + DEF(interfacerequest, "interfacerequest") + + + + DEF(invalid, "invalid") + + + + DEF(keydown, "keydown") + + + + DEF(keypress, "keypress") + + + + DEF(keystatuseschange, "keystatuseschange") + + + + DEF(keyup, "keyup") + + + + DEF(languagechange, "languagechange") + + + + DEF(leavepictureinpicture, "leavepictureinpicture") + + + + DEF(levelchange, "levelchange") + + + + DEF(load, "load") + + + + DEF(loadeddata, "loadeddata") + + + + DEF(loadedmetadata, "loadedmetadata") + + + + DEF(loadend, "loadend") + + + + DEF(loading, "loading") + + + + DEF(loadstart, "loadstart") + + + + DEF(lostpointercapture, "lostpointercapture") + + + + DEF(mark, "mark") + + + + DEF(message, "message") + + + + DEF(messageerror, "messageerror") + + + + DEF(mousedown, "mousedown") + + + + DEF(mouseenter, "mouseenter") + + + + DEF(mouseleave, "mouseleave") + + + + DEF(mousemove, "mousemove") + + + + DEF(mouseout, "mouseout") + + + + DEF(mouseover, "mouseover") + + + + DEF(mouseup, "mouseup") + + + + DEF(mousewheel, "mousewheel") + + + + DEF(mute, "mute") + + + + DEF(navigate, "navigate") + + + + DEF(navigateerror, "navigateerror") + + + + DEF(navigatesuccess, "navigatesuccess") + + + + DEF(noupdate, "noupdate") + + + + DEF(open, "open") + + + + DEF(orientationchange, "orientationchange") + + + + DEF(overscroll, "overscroll") + + + + DEF(pagehide, "pagehide") + + + + DEF(pageshow, "pageshow") + + + + DEF(paste, "paste") + + + + DEF(pause, "pause") + + + + DEF(play, "play") + + + + DEF(playing, "playing") + + + + DEF(pointercancel, "pointercancel") + + + + DEF(pointerdown, "pointerdown") + + + + DEF(pointerenter, "pointerenter") + + + + DEF(pointerleave, "pointerleave") + + + + DEF(pointerlockchange, "pointerlockchange") + + + + DEF(pointerlockerror, "pointerlockerror") + + + + DEF(pointermove, "pointermove") + + + + DEF(pointerout, "pointerout") + + + + DEF(pointerover, "pointerover") + + + + DEF(pointerup, "pointerup") + + + + DEF(popstate, "popstate") + + + + DEF(progress, "progress") + + + + DEF(processorerror, "processorerror") + + + + DEF(push, "push") + + + + DEF(pushsubscriptionchange, "pushsubscriptionchange") + + + + DEF(ratechange, "ratechange") + + + + DEF(reading, "reading") + + + + DEF(readingerror, "readingerror") + + + + DEF(readystatechange, "readystatechange") + + + + DEF(reflectionchange, "reflectionchange") + + + + DEF(rejectionhandled, "rejectionhandled") + + + + DEF(release, "release") + + + + DEF(remove, "remove") + + + + DEF(removestream, "removestream") + + + + DEF(removetrack, "removetrack") + + + + DEF(repeatEvent, "repeatEvent") + + + + DEF(reset, "reset") + + + + DEF(resize, "resize") + + + + DEF(result, "result") + + + + DEF(resume, "resume") + + + + DEF(screenschange, "screenschange") + + + + DEF(scroll, "scroll") + + + + DEF(scrollend, "scrollend") + + + + DEF(search, "search") + + + + DEF(seeked, "seeked") + + + + DEF(seeking, "seeking") + + + + DEF(select, "select") + + + + DEF(selectionchange, "selectionchange") + + + + DEF(selectstart, "selectstart") + + + + DEF(show, "show") + + + + DEF(squeeze, "squeeze") + + + + DEF(squeezeend, "squeezeend") + + + + DEF(squeezestart, "squeezestart") + + + + DEF(stalled, "stalled") + + + + DEF(start, "start") + + + + DEF(stop, "stop") + + + + DEF(statechange, "statechange") + + + + DEF(storage, "storage") + + + + DEF(submit, "submit") + + + + DEF(success, "success") + + + + DEF(suspend, "suspend") + + + + DEF(sync, "sync") + + + + DEF(terminate, "terminate") + + + + DEF(textInput, "textInput") + + + + DEF(textupdate, "textupdate") + + + + DEF(textformatupdate, "textformatupdate") + + + + DEF(toggle, "toggle") + + + + DEF(tonechange, "tonechange") + + + + DEF(touchcancel, "touchcancel") + + + + DEF(touchend, "touchend") + + + + DEF(touchmove, "touchmove") + + + + DEF(touchstart, "touchstart") + + + + DEF(transitioncancel, "transitioncancel") + + + + DEF(transitionend, "transitionend") + + + + DEF(transitionrun, "transitionrun") + + + + DEF(transitionstart, "transitionstart") + + + + DEF(typechange, "typechange") + + + + DEF(uncapturederror, "uncapturederror") + + + + DEF(unhandledrejection, "unhandledrejection") + + + + DEF(unload, "unload") + + + + DEF(unmute, "unmute") + + + + DEF(update, "update") + + + + DEF(versionchange, "versionchange") + + + + DEF(visibilitychange, "visibilitychange") + + + + DEF(waiting, "waiting") + + + + DEF(waitingforkey, "waitingforkey") + + + + DEF(webglcontextcreationerror, "webglcontextcreationerror") + + + + DEF(webglcontextlost, "webglcontextlost") + + + + DEF(webglcontextrestored, "webglcontextrestored") + + + + DEF(wheel, "wheel") + + + + DEF(zoom, "zoom") + #endif /* DEF */ diff --git a/bridge/third_party/quickjs/quickjs.h b/bridge/third_party/quickjs/quickjs.h index 9a743b932e..b9c5e455a3 100644 --- a/bridge/third_party/quickjs/quickjs.h +++ b/bridge/third_party/quickjs/quickjs.h @@ -427,7 +427,7 @@ void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt); enum { __JS_ATOM_NULL = JS_ATOM_NULL, #define DEF(name, str) JS_ATOM_ ## name, -#include "quickjs-atom.h" +#include "built_in_string.h" #include "event_type_names.h" #undef DEF JS_ATOM_END, From 7677ee769fb16302b655f091d2b5781f6bff1c55 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Sun, 3 Apr 2022 05:36:56 +0000 Subject: [PATCH 050/498] Committing clang-format changes --- bridge/bindings/qjs/converter_impl.h | 6 ++---- bridge/core/executing_context.cc | 1 - bridge/core/fileapi/blob.cc | 7 ++----- bridge/core/frame/console.cc | 9 ++++----- bridge/core/frame/console.h | 6 ++---- bridge/core/frame/module_manager.cc | 30 +++++++++++++++------------- bridge/core/frame/module_manager.h | 24 +++++++++++----------- bridge/foundation/native_string.h | 4 ++-- 8 files changed, 40 insertions(+), 47 deletions(-) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index cbb5b75176..a9772227d8 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -388,11 +388,9 @@ struct Converter> : public ConverterBase +template <> struct Converter : public ConverterBase { - static ImplType FromValue() { - - } + static ImplType FromValue() {} }; } // namespace kraken diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index a5b3817729..09db93b03a 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -370,7 +370,6 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { // return &pending_promises_; //} - // An lock free context validator. bool isContextValid(int32_t contextId) { if (contextId > running_context_list) diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 85265927d6..dddefe0276 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -6,8 +6,8 @@ #include "blob.h" #include #include "bindings/qjs/script_promise_resolver.h" -#include "core/executing_context.h" #include "built_in_string.h" +#include "core/executing_context.h" namespace kraken { @@ -90,10 +90,7 @@ Blob* Blob::slice(int64_t start, ExceptionState& exception_state) { Blob* Blob::slice(int64_t start, int64_t end, ExceptionState& exception_state) { return slice(start, end, AtomicString::Empty(ctx()), exception_state); } -Blob* Blob::slice(int64_t start, - int64_t end, - const AtomicString& content_type, - ExceptionState& exception_state) { +Blob* Blob::slice(int64_t start, int64_t end, const AtomicString& content_type, ExceptionState& exception_state) { auto* newBlob = makeGarbageCollected(ctx()); std::vector newData; newData.reserve(_data.size() - (end - start)); diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 9044e44cab..8ac18f27d3 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -5,8 +5,8 @@ #include "console.h" #include -#include "foundation/logging.h" #include "built_in_string.h" +#include "foundation/logging.h" namespace kraken { @@ -17,12 +17,11 @@ void Console::__kraken_print__(ExecutingContext* context, std::stringstream stream; std::string buffer = log.ToStdString(); stream << buffer; - printLog(context->contextId(), stream, level != built_in_string::kempty_string ? level.ToStdString() : "info", nullptr); + printLog(context->contextId(), stream, level != built_in_string::kempty_string ? level.ToStdString() : "info", + nullptr); } -void Console::__kraken_print__(ExecutingContext* context, - const AtomicString& log, - ExceptionState& exception_state) { +void Console::__kraken_print__(ExecutingContext* context, const AtomicString& log, ExceptionState& exception_state) { std::stringstream stream; std::string buffer = log.ToStdString(); stream << buffer; diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index 1949f0b00b..e085c26735 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -6,8 +6,8 @@ #ifndef KRAKE_CONSOLE_H #define KRAKE_CONSOLE_H -#include "bindings/qjs/script_value.h" #include "bindings/qjs/atom_string.h" +#include "bindings/qjs/script_value.h" #include "core/executing_context.h" namespace kraken { @@ -18,9 +18,7 @@ class Console final { const AtomicString& log, const AtomicString& level, ExceptionState& exception); - static void __kraken_print__(ExecutingContext* context, - const AtomicString& log, - ExceptionState& exception_state); + static void __kraken_print__(ExecutingContext* context, const AtomicString& log, ExceptionState& exception_state); }; } // namespace kraken diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 210c093a65..60d5edb8e6 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -62,27 +62,27 @@ void handleInvokeModuleUnexpectedCallback(void* callbackContext, } AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ExceptionState& exception) { + const AtomicString& moduleName, + const AtomicString& method, + ExceptionState& exception) { ScriptValue empty = ScriptValue::Empty(context->ctx()); return __kraken_invoke_module__(context, moduleName, method, empty, nullptr, exception); } AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& paramsValue, - ExceptionState& exception) { + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& paramsValue, + ExceptionState& exception) { return __kraken_invoke_module__(context, moduleName, method, paramsValue, nullptr, exception); } AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& paramsValue, - std::shared_ptr callback, - ExceptionState& exception) { + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& paramsValue, + std::shared_ptr callback, + ExceptionState& exception) { std::unique_ptr params; if (!paramsValue.IsEmpty()) { params = paramsValue.ToJSONStringify(&exception).toNativeString(); @@ -104,10 +104,12 @@ AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, NativeString* result; if (callback != nullptr) { - result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.ToNativeString().get(), method.ToNativeString().get(), + result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), + moduleName.ToNativeString().get(), method.ToNativeString().get(), params.get(), handleInvokeModuleTransientCallback); } else { - result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), moduleName.ToNativeString().get(), method.ToNativeString().get(), + result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), + moduleName.ToNativeString().get(), method.ToNativeString().get(), params.get(), handleInvokeModuleUnexpectedCallback); } diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 31066d7d69..0c6ebe90f6 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -16,20 +16,20 @@ namespace kraken { class ModuleManager { public: static AtomicString __kraken_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ExceptionState& exception); + const AtomicString& moduleName, + const AtomicString& method, + ExceptionState& exception); static AtomicString __kraken_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& params, - ExceptionState& exception); + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& params, + ExceptionState& exception); static AtomicString __kraken_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& params, - std::shared_ptr callback, - ExceptionState& exception); + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& params, + std::shared_ptr callback, + ExceptionState& exception); static void __kraken_add_module_listener__(ExecutingContext* context, const std::shared_ptr& handler, ExceptionState& exception); diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h index ffbf3cd6b2..85442c30a3 100644 --- a/bridge/foundation/native_string.h +++ b/bridge/foundation/native_string.h @@ -11,11 +11,11 @@ namespace kraken { struct NativeString { - NativeString(const uint16_t* string, uint32_t length): string_(string), length_(length) {}; + NativeString(const uint16_t* string, uint32_t length) : string_(string), length_(length){}; ~NativeString(); NativeString* clone(); - inline const uint16_t* string() const{return string_;} + inline const uint16_t* string() const { return string_; } inline uint32_t length() const { return length_; } private: From 8a7b70c3804bcff72cf1c4000c27ea3d7f98b445 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Wed, 6 Apr 2022 18:22:40 +0800 Subject: [PATCH 051/498] fix: fix compile. --- bridge/CMakeLists.txt | 6 +++ bridge/bindings/qjs/converter_impl.h | 32 ++++++++++++- bridge/bindings/qjs/generated_code_helper.h | 13 +++++ bridge/bindings/qjs/js_event_handler.cc | 3 +- bridge/core/dom/events/event.cc | 7 +++ bridge/core/dom/events/event.d.ts | 5 ++ bridge/core/dom/events/event.h | 10 +++- bridge/core/dom/events/event_listener_map.cc | 2 + bridge/core/dom/events/event_target.cc | 17 +++++++ bridge/core/dom/events/event_target.h | 9 +++- bridge/core/events/error_event.cc | 4 +- bridge/core/events/error_event.h | 4 +- .../code_generator/src/idl/generateHeader.ts | 11 +++-- .../code_generator/src/idl/generateSource.ts | 48 +++++++++++++------ .../code_generator/src/idl/generator.ts | 30 +++++++++++- .../static/idl_templates/base.cc.tpl | 15 +++++- .../static/idl_templates/base.h.tpl | 3 +- .../static/idl_templates/dictionary.cc.tpl | 4 +- .../static/idl_templates/dictionary.h.tpl | 9 ++-- .../static/idl_templates/interface.cc.tpl | 1 + .../static/idl_templates/interface.h.tpl | 10 ++-- .../static/json_templates/make_names.cc.tpl | 4 +- .../static/json_templates/make_names.h.tpl | 2 +- 23 files changed, 204 insertions(+), 45 deletions(-) create mode 100644 bridge/bindings/qjs/generated_code_helper.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index a41bd9655f..021c9f00af 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -221,6 +221,7 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/script_promise_resolver.h bindings/qjs/atom_string.cc bindings/qjs/atom_string.h + bindings/qjs/generated_code_helper.h bindings/qjs/exception_state.cc bindings/qjs/exception_state.h bindings/qjs/gc_visitor.cc @@ -279,6 +280,7 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/events/event.cc core/dom/events/event_target.h core/dom/events/event_target.cc + core/dom/events/event_listener_map.cc core/dom/events/event_target_impl.cc core/dom/events/event_target_impl.h core/events/error_event.cc @@ -315,12 +317,16 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_event_listener_options.h out/qjs_error_event.h out/qjs_error_event.cc + out/qjs_error_event_init.h + out/qjs_error_event_init.cc out/qjs_event_init.h out/qjs_event_init.cc out/qjs_event_target.cc out/qjs_event_target.h out/event_type_names.h out/event_type_names.cc + out/built_in_string.cc + out/built_in_string.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index a9772227d8..f7a3729c35 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -17,6 +17,9 @@ #include "js_event_listener.h" #include "native_string_utils.h" #include "qjs_event_init.h" +#include "qjs_error_event_init.h" +#include "qjs_event_listener_options.h" +#include "qjs_add_event_listener_options.h" namespace kraken { @@ -390,7 +393,34 @@ struct Converter> : public ConverterBase struct Converter : public ConverterBase { - static ImplType FromValue() {} + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return EventInit::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 ErrorEventInit::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 AddEventListenerOptions::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 EventListenerOptions::Create(ctx, value, exception_state); + } }; } // namespace kraken diff --git a/bridge/bindings/qjs/generated_code_helper.h b/bridge/bindings/qjs/generated_code_helper.h new file mode 100644 index 0000000000..31b96d8493 --- /dev/null +++ b/bridge/bindings/qjs/generated_code_helper.h @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_GENERATED_CODE_HELPER_H +#define KRAKENBRIDGE_GENERATED_CODE_HELPER_H + +#include "bindings/qjs/qjs_interface_bridge.h" +#include "bindings/qjs/dictionary_base.h" +#include "atom_string.h" +#include "script_value.h" + +#endif diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index 66ad508aa1..a64dac1911 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -4,6 +4,7 @@ */ #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" @@ -37,7 +38,7 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // 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 && + 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: diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index c236333cf3..8c7554e3d9 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -27,6 +27,13 @@ Event::Event(ExecutingContext* context) : type_(AtomicString::Empty(context->ctx Event::Event(ExecutingContext* context, const AtomicString& event_type) : type_(event_type), ScriptWrappable(context->ctx()) {} +Event::Event(ExecutingContext* context, const AtomicString& type, const std::shared_ptr& init) + : ScriptWrappable(context->ctx()), + type_(type), + bubbles_(init->bubbles()), + cancelable_(init->cancelable()), + composed_(init->composed()) {} + Event::Event(ExecutingContext* context, const AtomicString& event_type, Bubbles bubbles, diff --git a/bridge/core/dom/events/event.d.ts b/bridge/core/dom/events/event.d.ts index 79f4547fdb..0a1eb5a935 100644 --- a/bridge/core/dom/events/event.d.ts +++ b/bridge/core/dom/events/event.d.ts @@ -1,3 +1,6 @@ +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. @@ -41,4 +44,6 @@ interface Event { * 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 index 71d8d8a00e..0ca5b9c810 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -11,6 +11,7 @@ #include "bindings/qjs/script_wrappable.h" #include "core/executing_context.h" #include "foundation/native_string.h" +#include "qjs_event_init.h" namespace kraken { @@ -90,15 +91,22 @@ class Event : public ScriptWrappable { enum PhaseType { kNone = 0, kCapturingPhase = 1, kAtTarget = 2, kBubblingPhase = 3 }; static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; - static Event* Create(ExecutingContext* context, const AtomicString& type) { + static Event* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { return makeGarbageCollected(context, type); }; + static Event* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& init, + ExceptionState& exception_state) { + return makeGarbageCollected(context, type, init); + }; static Event* From(ExecutingContext* context, NativeEvent* native_event); Event() = delete; explicit Event(ExecutingContext* context); explicit Event(ExecutingContext* context, const AtomicString& event_type); + explicit Event(ExecutingContext* context, const AtomicString& type, const std::shared_ptr& init); explicit Event(ExecutingContext* context, const AtomicString& event_type, Bubbles bubbles, diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index dbd1c74c2f..2b4289916c 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -7,6 +7,8 @@ namespace kraken { +EventListenerMap::EventListenerMap() {} + static bool AddListenerToVector(EventListenerVector* vector, const std::shared_ptr& listener, const std::shared_ptr& options, diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 5c24a6ffd9..6b96e54110 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -4,6 +4,7 @@ */ #include "event_target.h" +#include "bindings/qjs/converter_impl.h" #include "event_type_names.h" #include "qjs_add_event_listener_options.h" @@ -43,6 +44,18 @@ bool EventTarget::addEventListener(const AtomicString& event_type, return AddEventListenerInternal(event_type, event_listener, options); } +bool EventTarget::addEventListener(const AtomicString& event_type, + const std::shared_ptr& event_listener, + ExceptionState& exception_state) { + std::shared_ptr options = AddEventListenerOptions::Create(); + return AddEventListenerInternal(event_type, event_listener, options); +} + +bool EventTarget::removeEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, ExceptionState& exception_state) { + std::shared_ptr options = EventListenerOptions::Create(); + return RemoveEventListenerInternal(event_type, event_listener, options); +} + bool EventTarget::removeEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, const std::shared_ptr& options, @@ -232,6 +245,10 @@ bool EventTarget::FireEventListeners(Event& event, return fired_listener; } +void EventTargetWithInlineData::Trace(GCVisitor* visitor) const { + EventTarget::Trace(visitor); +} + } // namespace kraken // namespace kraken::binding::qjs diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index fd32ed537b..42f74eebee 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -92,6 +92,12 @@ class EventTarget : public ScriptWrappable { const std::shared_ptr& event_listener, const std::shared_ptr& options, ExceptionState& exception_state); + bool addEventListener(const AtomicString& event_type, + const std::shared_ptr& event_listener, + ExceptionState& exception_state); + bool removeEventListener(const AtomicString& event_type, + const std::shared_ptr& event_listener, + ExceptionState& exception_state); bool removeEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, const std::shared_ptr& options, @@ -135,7 +141,8 @@ class EventTarget : public ScriptWrappable { // Provide EventTarget with inlined EventTargetData for improved performance. class EventTargetWithInlineData : public EventTarget { public: - EventTargetWithInlineData(ExecutingContext* context) : EventTarget(context){}; + EventTargetWithInlineData() = delete; + explicit EventTargetWithInlineData(ExecutingContext* context) : EventTarget(context){}; void Trace(GCVisitor* visitor) const override; diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc index 96848198c2..d62f381e0f 100644 --- a/bridge/core/events/error_event.cc +++ b/bridge/core/events/error_event.cc @@ -15,7 +15,7 @@ ErrorEvent* ErrorEvent::Create(ExecutingContext* context, const AtomicString& ty } ErrorEvent* ErrorEvent::Create(ExecutingContext* context, const AtomicString& type, - const ErrorEventInit* initializer, + const std::shared_ptr& initializer, ExceptionState& exception_state) { return makeGarbageCollected(context, type, initializer, exception_state); } @@ -31,7 +31,7 @@ ErrorEvent::ErrorEvent(ExecutingContext* context, const AtomicString& type, Exce ErrorEvent::ErrorEvent(ExecutingContext* context, const AtomicString& type, - const ErrorEventInit* initializer, + const std::shared_ptr& initializer, ExceptionState& exception_state) : Event(context), message_(type.ToStdString()), diff --git a/bridge/core/events/error_event.h b/bridge/core/events/error_event.h index 22e8482479..52777d916d 100644 --- a/bridge/core/events/error_event.h +++ b/bridge/core/events/error_event.h @@ -22,7 +22,7 @@ class ErrorEvent : public Event { static ErrorEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); static ErrorEvent* Create(ExecutingContext* context, const AtomicString& type, - const ErrorEventInit* initializer, + const std::shared_ptr& initializer, ExceptionState& exception_state); explicit ErrorEvent(ExecutingContext* context, const std::string& message); @@ -30,7 +30,7 @@ class ErrorEvent : public Event { explicit ErrorEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); explicit ErrorEvent(ExecutingContext* context, const AtomicString& type, - const ErrorEventInit* initializer, + const std::shared_ptr& initializer, ExceptionState& exception_state); // As |message| is exposed to JavaScript, never return |unsanitized_message_|. diff --git a/bridge/scripts/code_generator/src/idl/generateHeader.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts index 35cd05b9f7..500a0c83ad 100644 --- a/bridge/scripts/code_generator/src/idl/generateHeader.ts +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -4,7 +4,8 @@ import {IDLBlob} from "./IDLBlob"; import {getClassName} from "./utils"; import fs from 'fs'; import path from 'path'; -import {generateTypeConverter} from "./generateSource"; +import {generateTypeConverter, generateTypeValue} from "./generateSource"; +import {GenerateOptions} from "./generator"; export enum TemplateKind { globalFunction, @@ -29,7 +30,7 @@ function readTemplate(name: string) { return fs.readFileSync(path.join(__dirname, '../../static/idl_templates/' + name + '.h.tpl'), {encoding: 'utf-8'}); } -export function generateCppHeader(blob: IDLBlob) { +export function generateCppHeader(blob: IDLBlob, options: GenerateOptions) { const baseTemplate = fs.readFileSync(path.join(__dirname, '../../static/idl_templates/base.h.tpl'), {encoding: 'utf-8'}); let headerOptions = { interface: false, @@ -46,7 +47,9 @@ export function generateCppHeader(blob: IDLBlob) { headerOptions.interface = true; return _.template(readTemplate('interface'))({ className: getClassName(blob), - blob: blob + blob: blob, + object, + ...options }); } return ''; @@ -60,7 +63,7 @@ export function generateCppHeader(blob: IDLBlob) { blob: blob, object: object, props, - generateTypeConverter: generateTypeConverter + generateTypeValue: generateTypeValue }); } return ''; diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 09c0c5f068..a2616faf76 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -5,7 +5,6 @@ import { FunctionArgumentType, FunctionDeclaration, FunctionObject, - PropsDeclaration, } from "./declaration"; import {addIndent, getClassName} from "./utils"; import {ParameterType} from "./analyzer"; @@ -13,6 +12,7 @@ import _ from 'lodash'; import fs from 'fs'; import path from 'path'; import {getTemplateKind, TemplateKind} from "./generateHeader"; +import {GenerateOptions} from "./generator"; enum PropType { hostObject, @@ -34,6 +34,30 @@ function generateMethodArgumentsCheck(m: FunctionDeclaration) { `; } +export function generateTypeValue(type: ParameterType[]): string { + switch(type[0]) { + case FunctionArgumentType.int64: { + return 'int64_t'; + } + case FunctionArgumentType.int32: { + return 'int32_t'; + } + case FunctionArgumentType.void: { + return 'void'; + } + case FunctionArgumentType.boolean: { + return 'bool'; + } + case FunctionArgumentType.dom_string: { + return 'AtomicString'; + } + case FunctionArgumentType.any: { + return 'ScriptValue'; + } + } + return ''; +} + export function generateTypeConverter(type: ParameterType[]): string { let haveNull = type.some(t => t === FunctionArgumentType.null); let returnValue = ''; @@ -223,11 +247,7 @@ function readTemplate(name: string) { return fs.readFileSync(path.join(__dirname, '../../static/idl_templates/' + name + '.cc.tpl'), {encoding: 'utf-8'}); } -export function generateCppSource(blob: IDLBlob) { - let globalFunctionInstallList: string[] = []; - let classMethodsInstallList: string[] = []; - let classPropsInstallList: string[] = []; - let wrapperTypeInfoInit = ''; +export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { const baseTemplate = fs.readFileSync(path.join(__dirname, '../../static/idl_templates/base.cc.tpl'), {encoding: 'utf-8'}); const contents = blob.objects.map(object => { @@ -238,12 +258,15 @@ export function generateCppSource(blob: IDLBlob) { case TemplateKind.Interface: { object = object as ClassObject; object.props.forEach(prop => { - classMethodsInstallList.push(`{"${prop.name}", ${prop.name}AttributeGetCallback, ${prop.readonly ? 'nullptr' : `${prop.name}AttributeSetCallback`}}`) + options.classMethodsInstallList.push(`{"${prop.name}", ${prop.name}AttributeGetCallback, ${prop.readonly ? 'nullptr' : `${prop.name}AttributeSetCallback`}}`) }); object.methods.forEach(method => { - classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) + options.classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) }); - wrapperTypeInfoInit = ` + if (object.construct) { + options.constructorInstallList.push(`{"${getClassName(blob)}", nullptr, nullptr, constructor}`) + } + options.wrapperTypeInfoInit = ` const WrapperTypeInfo QJS${getClassName(blob)}::wrapper_type_info_ {JS_CLASS_${getClassName(blob).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, QJS${getClassName(blob)}::ConstructorCallback}; const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::wrapper_type_info_;`; return _.template(readTemplate('interface'))({ @@ -266,7 +289,7 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass } case TemplateKind.globalFunction: { object = object as FunctionObject; - globalFunctionInstallList.push(` {"${object.declare.name}", ${object.declare.name}, ${object.declare.args.length}}`); + options.globalFunctionInstallList.push(` {"${object.declare.name}", ${object.declare.name}, ${object.declare.args.length}}`); return _.template(readTemplate('global_function'))({ className: getClassName(blob), blob: blob, @@ -282,9 +305,6 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass content: contents.join('\n'), className: getClassName(blob), blob: blob, - globalFunctionInstallList, - classPropsInstallList, - classMethodsInstallList, - wrapperTypeInfoInit + ...options }); } diff --git a/bridge/scripts/code_generator/src/idl/generator.ts b/bridge/scripts/code_generator/src/idl/generator.ts index 0e01c4d22b..2a712dc1ce 100644 --- a/bridge/scripts/code_generator/src/idl/generator.ts +++ b/bridge/scripts/code_generator/src/idl/generator.ts @@ -2,9 +2,35 @@ 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 wrapperTypeInfoInit = ''; + + return { + globalFunctionInstallList, + classPropsInstallList, + classMethodsInstallList, + constructorInstallList, + wrapperTypeInfoInit + }; +} + +export type GenerateOptions = { + globalFunctionInstallList: string[]; + classMethodsInstallList: string[]; + constructorInstallList: string[]; + classPropsInstallList: string[]; + wrapperTypeInfoInit: string; +}; + export function generatorSource(blob: IDLBlob) { - let header = generateCppHeader(blob); - let source = generateCppSource(blob); + let options = generateSupportedOptions(); + + let source = generateCppSource(blob, options); + let header = generateCppHeader(blob, options); return { header, source diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl index 061f0520f8..d2b14f5010 100644 --- a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl @@ -19,9 +19,10 @@ namespace kraken { <% if (globalFunctionInstallList.length > 0 || classPropsInstallList.length > 0 || classMethodsInstallList.length > 0) { %> void QJS<%= className %>::Install(ExecutingContext* context) { - InstallGlobalFunctions(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); <% } %> } <% } %> @@ -59,4 +60,16 @@ void QJS<%= className %>::InstallPrototypeMethods(ExecutingContext* context) { } <% } %> +<% if (constructorInstallList.length > 0) { %> +void QJS<%= className %>::InstallConstructor(ExecutingContext* context) { + const WrapperTypeInfo* wrapperTypeInfo = GetWrapperTypeInfo(); + JSValue constructor = context->contextData()->constructorForType(wrapperTypeInfo); + + std::initializer_list attributeConfig { + <%= constructorInstallList.join(',\n') %> + }; + MemberInstaller::InstallAttributes(context, context->Global(), attributeConfig); +} +<% } %> + } diff --git a/bridge/scripts/code_generator/static/idl_templates/base.h.tpl b/bridge/scripts/code_generator/static/idl_templates/base.h.tpl index 155c719522..a865cd3923 100644 --- a/bridge/scripts/code_generator/static/idl_templates/base.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/base.h.tpl @@ -7,8 +7,7 @@ #include #include "bindings/qjs/wrapper_type_info.h" -#include "bindings/qjs/qjs_interface_bridge.h" -#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/generated_code_helper.h" <%= content %> diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl index bd8e5bb500..3d4c3384b6 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl @@ -12,7 +12,7 @@ std::shared_ptr<<%= className %>> <%= className %>::Create(JSContext* ctx, JSVal bool <%= className %>::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const { <% if (object.parent) { %> - EventInit::FillQJSObjectWithMembers(ctx, qjs_dictionary); + <%= object.parent %>::FillQJSObjectWithMembers(ctx, qjs_dictionary); <% } %> if (!JS_IsObject(qjs_dictionary)) { @@ -28,7 +28,7 @@ bool <%= className %>::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dict void <%= className %>::FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state) { <% if (object.parent) { %> - EventInit::FillMembersWithQJSObject(ctx, value, exception_state); + <%= object.parent %>::FillMembersWithQJSObject(ctx, value, exception_state); <% } %> if (!JS_IsObject(value)) { diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl index 868a2ca393..cecbe09048 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl @@ -6,6 +6,7 @@ namespace kraken { class ExecutingContext; +class ExceptionState; class <%= className %> : public <%= object.parent ? object.parent : 'DictionaryBase' %> { public: @@ -16,14 +17,14 @@ class <%= className %> : public <%= object.parent ? object.parent : 'DictionaryB explicit <%= className %>(JSContext* ctx, JSValue value, ExceptionState& exception_state); <% _.forEach(props, (function(prop, index) { %> - Converter<<%= generateTypeConverter(prop.type) %>>::ImplType <%= prop.name %>() const { return <%= prop.name %>_; } - void set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(Converter<<%= generateTypeConverter(prop.type) %>>::ImplType value) { <%= prop.name %>_ = value; } + <%= generateTypeValue(prop.type) %> <%= prop.name %>() const { return <%= prop.name %>_; } + void set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(<%= generateTypeValue(prop.type) %> value) { <%= prop.name %>_ = value; } <% })); %> -private: bool FillQJSObjectWithMembers(JSContext *ctx, JSValue qjs_dictionary) const override; void FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state); +private: <% _.forEach(props, (function(prop, index) { %> - Converter<<%= generateTypeConverter(prop.type) %>>::ImplType <%= prop.name %>_; + <%= generateTypeValue(prop.type) %> <%= prop.name %>_; <% })); %> }; diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index 50812c8307..fb57fb6a47 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -25,6 +25,7 @@ static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst return exception_state.ToQuickJS(); } <%= blob.filename %>->set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(v); + return JS_DupValue(ctx, argv[0]); } <% } %> <% }); %> diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl index c52a5fdc56..71c0dd824f 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl @@ -10,13 +10,13 @@ class QJS<%= className %> : public QJSInterfaceBridge, <%= c static WrapperTypeInfo* GetWrapperTypeInfo() { return const_cast(&wrapper_type_info_); } - static JSValue ConstructorCallback(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv, int flags); + <% 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: - static void InstallGlobalFunctions(ExecutingContext* context); - static void InstallPrototypeMethods(ExecutingContext* context); - static void InstallPrototypeProperties(ExecutingContext* context); - static void InstallConstructor(ExecutingContext* context); + <% 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); <% } %> }; diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index ed2aa9e546..b32d8fc51d 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -14,7 +14,7 @@ void* names_storage[kNamesCount * ((sizeof(AtomicString) + sizeof(void *) - 1) / <% if (_.isArray(name)) { %> const AtomicString& k<%= name[0] %> = reinterpret_cast(&names_storage)[<%= index %>]; <% } else { %> -const AtomicString& k<%= name[0].toUpperCase() + name.slice(1) %> = reinterpret_cast(&names_storage)[<%= index %>]; +const AtomicString& k<%= name %> = reinterpret_cast(&names_storage)[<%= index %>]; <% } %> <% }) %> @@ -28,7 +28,7 @@ void Init() { }; static const NameEntry kNames[] = { - <% _.forEach(data, function(name) { %>{ JS_ATOM_<%= name[0] %> }, + <% _.forEach(data, function(name) { %><% if (Array.isArray(name)) { %>{ JS_ATOM_<%= name[0] %> },<% } else { %>{ JS_ATOM_<%= name %> },<% } %> <% }); %> }; diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl index 38e474e7d4..fa410efd49 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl @@ -13,7 +13,7 @@ namespace kraken { namespace <%= name %> { <% _.forEach(data, function(name, index) { %><% if (_.isArray(name)) { %>extern const AtomicString& k<%= name[0] %>; -<% } else { %>extern const AtomicString& k<%= name[0].toUpperCase() + name.slice(1) %>; +<% } else { %>extern const AtomicString& k<%= name %>; <% } %><% }) %> constexpr unsigned kNamesCount = <%= data.length %>; From c50ddebdee6e9df0e8b6b589ae624e03bbf7f501 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Wed, 6 Apr 2022 21:50:39 +0800 Subject: [PATCH 052/498] feat: pre init string atomc works. --- bridge/CMakeLists.txt | 2 +- bridge/bindings/qjs/atom_string.h | 14 +- bridge/bindings/qjs/source_location.cc | 2 + bridge/core/built_in_string.json | 4 +- bridge/core/events/event_type_names.json | 2 +- bridge/core/executing_context.cc | 14 + bridge/core/executing_context_test.cc | 16 +- bridge/kraken_bridge_test.cc | 2 +- .../code_generator/bin/code_generator.js | 4 - .../static/json_templates/make_names.cc.tpl | 26 +- .../static/json_templates/make_names.h.tpl | 3 +- .../static/json_templates/qjs_atom.h.tpl | 36 - bridge/test/kraken_test_context.cc | 1 - bridge/third_party/quickjs/built_in_string.h | 918 ------------------ bridge/third_party/quickjs/event_type_names.h | 862 ---------------- bridge/third_party/quickjs/quickjs-atom.h | 10 +- bridge/third_party/quickjs/quickjs.c | 14 +- bridge/third_party/quickjs/quickjs.h | 4 +- 18 files changed, 69 insertions(+), 1865 deletions(-) delete mode 100644 bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl delete mode 100644 bridge/third_party/quickjs/built_in_string.h delete mode 100644 bridge/third_party/quickjs/event_type_names.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 021c9f00af..5076d630d4 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -65,7 +65,7 @@ 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 () diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index 2481366cfd..c24eb65b69 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -33,7 +33,9 @@ class AtomicString { AtomicString(JSContext* ctx, JSAtom atom) : ctx_(ctx), atom_(atom){}; AtomicString(JSContext* ctx, const std::string& string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())){}; AtomicString(JSContext* ctx, JSValue value) : ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; - AtomicString(JSAtom atom) : atom_(atom), is_static_atom_(true){}; + ~AtomicString() { + JS_FreeAtom(ctx_, atom_); + }; // Return the undefined string value from atom key. JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); }; @@ -55,13 +57,13 @@ class AtomicString { // Copy assignment AtomicString(AtomicString const& value) { - if (!is_static_atom_ && &value != this) { + if (&value != this) { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; }; AtomicString& operator=(const AtomicString& other) { - if (!is_static_atom_ && &other != this) { + if (&other != this) { atom_ = JS_DupAtom(ctx_, other.atom_); } return *this; @@ -69,13 +71,13 @@ class AtomicString { // Move assignment AtomicString(AtomicString&& value) noexcept { - if (!is_static_atom_ && &value != this) { + if (&value != this) { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; }; AtomicString& operator=(AtomicString&& value) noexcept { - if (!is_static_atom_ && &value != this) { + if (&value != this) { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; @@ -86,11 +88,11 @@ class AtomicString { bool operator!=(const AtomicString& other) const { return other.atom_ != this->atom_; }; protected: - bool is_static_atom_ = false; JSContext* ctx_{nullptr}; JSAtom atom_{JS_ATOM_NULL}; }; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ diff --git a/bridge/bindings/qjs/source_location.cc b/bridge/bindings/qjs/source_location.cc index ce03259c15..2cd5561492 100644 --- a/bridge/bindings/qjs/source_location.cc +++ b/bridge/bindings/qjs/source_location.cc @@ -16,4 +16,6 @@ std::unique_ptr SourceLocation::Capture(const std::string& url, SourceLocation::SourceLocation(const std::string& url, unsigned int line_number, unsigned int column_number) : url_(url), line_number_(line_number), column_number_(column_number) {} +SourceLocation::~SourceLocation() {} + } // namespace kraken diff --git a/bridge/core/built_in_string.json b/bridge/core/built_in_string.json index f19973c20f..3dee453081 100644 --- a/bridge/core/built_in_string.json +++ b/bridge/core/built_in_string.json @@ -1,6 +1,6 @@ { "metadata": { - "templates": ["make_names", "qjs_atom"] + "templates": ["make_names"] }, "data": [ ["null", "null"], @@ -53,6 +53,7 @@ ["length", "length"], ["fileName", "fileName"], ["lineNumber", "lineNumber"], + ["message", "message"], ["errors", "errors"], ["stack", "stack"], ["name", "name"], @@ -89,6 +90,7 @@ ["lastIndex", "lastIndex"], ["target", "target"], ["index", "index"], + ["input", "input"], ["defineProperties", "defineProperties"], ["apply", "apply"], ["join", "join"], diff --git a/bridge/core/events/event_type_names.json b/bridge/core/events/event_type_names.json index 85b7ff5cb5..bd242b2b3c 100644 --- a/bridge/core/events/event_type_names.json +++ b/bridge/core/events/event_type_names.json @@ -1,7 +1,7 @@ { "annotation": "Simplified from https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/events/event_type_names.json5", "metadata": { - "templates": ["make_names", "qjs_atom"] + "templates": ["make_names"] }, "data": [ "DOMActivate", diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 09db93b03a..556411eee4 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -5,6 +5,10 @@ #include "executing_context.h" #include "polyfill.h" +#include "built_in_string.h" +#include "event_type_names.h" + +#include "foundation/logging.h" namespace kraken { @@ -57,6 +61,11 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& JS_SetContextOpaque(ctx, this); JS_SetHostPromiseRejectionTracker(script_state_.runtime(), promiseRejectTracker, nullptr); + // PreInit Strings. + built_in_string::Init(ctx); + event_type_names::Init(ctx); + + // Register all built-in native bindings. InstallBindings(this); @@ -80,6 +89,11 @@ ExecutingContext::~ExecutingContext() { valid_contexts[context_id_] = false; ctx_invalid_ = true; + // Dispose pre-built-in strings. + built_in_string::Dispose(); + event_type_names::Dispose(); + + // Free unreleased native_functions. { struct list_head *el, *el1; diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 73ca6d3aa3..23412f44ba 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -350,10 +350,10 @@ 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 = kraken::jsValueToNativeString(bridge->getContext()->ctx(), str); - EXPECT_EQ(nativeString->length, 10); + 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); } @@ -363,9 +363,9 @@ TEST(jsValueToNativeString, unicodeChinese) { JSValue str = JS_NewString(bridge->getContext()->ctx(), "这是你的优乐美"); std::unique_ptr nativeString = kraken::jsValueToNativeString(bridge->getContext()->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); } @@ -375,9 +375,9 @@ TEST(jsValueToNativeString, emoji) { JSValue str = JS_NewString(bridge->getContext()->ctx(), "……🤪"); std::unique_ptr nativeString = kraken::jsValueToNativeString(bridge->getContext()->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); } diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index 13d04053f1..a29c261154 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -19,7 +19,7 @@ void initTestFramework(int32_t contextId) { int8_t evaluateTestScripts(int32_t contextId, kraken::NativeString* code, const char* bundleFilename, int startLine) { auto testContext = testContextPool[contextId]; - return testContext->evaluateTestScripts(code->string, code->length, bundleFilename, startLine); + return testContext->evaluateTestScripts(code->string(), code->length(), bundleFilename, startLine); } void executeTest(int32_t contextId, ExecuteCallback executeCallback) { diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index aa4fc7555f..6ab6007a55 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -82,10 +82,6 @@ function genCodeFromJSONData() { let result = generateJSONTemplate(blobs[i], targetTemplateHeaderData, targetTemplateBodyData); let dist = blob.dist; - if (targetTemplate === 'qjs_atom') { - dist = path.join(__dirname, '../../../third_party/quickjs') - } - let genFilePath = path.join(dist, blob.filename); fs.writeFileSync(genFilePath + '.h', result.header); result.source && fs.writeFileSync(genFilePath + '.cc', result.source); diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index b32d8fc51d..0193a3c28b 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -10,33 +10,37 @@ namespace <%= name %> { void* names_storage[kNamesCount * ((sizeof(AtomicString) + sizeof(void *) - 1) / sizeof(void *))]; -<% _.forEach(data, function(name, index) { %> -<% if (_.isArray(name)) { %> -const AtomicString& k<%= name[0] %> = reinterpret_cast(&names_storage)[<%= index %>]; -<% } else { %> -const AtomicString& k<%= name %> = reinterpret_cast(&names_storage)[<%= index %>]; -<% } %> +<% _.forEach(data, function(name, index) { %><% if (_.isArray(name)) { %>const AtomicString& k<%= name[0] %> = reinterpret_cast(&names_storage)[<%= index %>]; +<% } else { %>const AtomicString& k<%= name %> = reinterpret_cast(&names_storage)[<%= index %>];<% } %> <% }) %> -void Init() { +void Init(JSContext* ctx) { static bool is_loaded = false; if (is_loaded) return; is_loaded = true; struct NameEntry { - JSAtom atom; - }; + const char* str; + }; static const NameEntry kNames[] = { - <% _.forEach(data, function(name) { %><% if (Array.isArray(name)) { %>{ JS_ATOM_<%= name[0] %> },<% } else { %>{ JS_ATOM_<%= name %> },<% } %> + <% _.forEach(data, function(name) { %><% if (Array.isArray(name)) { %>{ "<%= name[0] %>" },<% } else { %>{ "<%= name %>" },<% } %> <% }); %> }; for(size_t i = 0; i < std::size(kNames); i ++) { void* address = reinterpret_cast(&names_storage) + i; - new (address) AtomicString(kNames[i].atom); + new (address) AtomicString(ctx, kNames[i].str); } }; +void Dispose(){ + for(size_t i = 0; i < kNamesCount; i ++) { + AtomicString* atomic_string = reinterpret_cast(&names_storage) + i; + atomic_string->~AtomicString(); + } +}; + + } } // kraken diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl index fa410efd49..af5dc530f5 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl @@ -18,7 +18,8 @@ namespace <%= name %> { constexpr unsigned kNamesCount = <%= data.length %>; -void Init(); +void Init(JSContext* ctx); +void Dispose(); } diff --git a/bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl b/bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl deleted file mode 100644 index 16fa63100c..0000000000 --- a/bridge/scripts/code_generator/static/json_templates/qjs_atom.h.tpl +++ /dev/null @@ -1,36 +0,0 @@ -/* -* QuickJS atom definitions -* -* Copyright (c) 2017-2018 Fabrice Bellard -* Copyright (c) 2017-2018 Charlie Gordon -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in -* all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -* THE SOFTWARE. -*/ - -#ifdef DEF - -<% _.forEach(data, function(name) { %> -<% if (_.isArray(name)) { %> - DEF(<%= name[0] %>, "<%= name[1] %>") -<% } else { %> - DEF(<%= name %>, "<%= name %>") -<% } %> -<% }); %> - -#endif /* DEF */ diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index bc5b113047..416cb6d063 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -216,7 +216,6 @@ static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc std::unique_ptr nativeString = kraken::jsValueToNativeString(ctx, charStringValue); void* p = static_cast(nativeString.get()); context->dartMethodPtr()->simulateInputText(static_cast(p)); - nativeString->free(); return JS_NULL; }; diff --git a/bridge/third_party/quickjs/built_in_string.h b/bridge/third_party/quickjs/built_in_string.h deleted file mode 100644 index dbc80e479e..0000000000 --- a/bridge/third_party/quickjs/built_in_string.h +++ /dev/null @@ -1,918 +0,0 @@ -/* -* QuickJS atom definitions -* -* Copyright (c) 2017-2018 Fabrice Bellard -* Copyright (c) 2017-2018 Charlie Gordon -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in -* all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -* THE SOFTWARE. -*/ - -#ifdef DEF - - - - DEF(null, "null") - - - - DEF(false, "false") - - - - DEF(true, "true") - - - - DEF(if, "if") - - - - DEF(else, "else") - - - - DEF(return, "return") - - - - DEF(var, "var") - - - - DEF(this, "this") - - - - DEF(delete, "delete") - - - - DEF(void, "void") - - - - DEF(typeof, "typeof") - - - - DEF(new, "new") - - - - DEF(in, "in") - - - - DEF(instanceof, "instanceof") - - - - DEF(do, "do") - - - - DEF(while, "while") - - - - DEF(for, "for") - - - - DEF(break, "break") - - - - DEF(continue, "continue") - - - - DEF(switch, "switch") - - - - DEF(case, "case") - - - - DEF(default, "default") - - - - DEF(throw, "throw") - - - - DEF(try, "try") - - - - DEF(catch, "catch") - - - - DEF(finally, "finally") - - - - DEF(function, "function") - - - - DEF(debugger, "debugger") - - - - DEF(with, "with") - - - - DEF(class, "class") - - - - DEF(const, "const") - - - - DEF(enum, "enum") - - - - DEF(export, "export") - - - - DEF(extends, "extends") - - - - DEF(import, "import") - - - - DEF(super, "super") - - - - DEF(implements, "implements") - - - - DEF(interface, "interface") - - - - DEF(let, "let") - - - - DEF(package, "package") - - - - DEF(private, "private") - - - - DEF(protected, "protected") - - - - DEF(public, "public") - - - - DEF(static, "static") - - - - DEF(yield, "yield") - - - - DEF(await, "await") - - - - DEF(empty_string, "") - - - - DEF(length, "length") - - - - DEF(fileName, "fileName") - - - - DEF(lineNumber, "lineNumber") - - - - DEF(errors, "errors") - - - - DEF(stack, "stack") - - - - DEF(name, "name") - - - - DEF(toString, "toString") - - - - DEF(toLocaleString, "toLocaleString") - - - - DEF(valueOf, "valueOf") - - - - DEF(eval, "eval") - - - - DEF(prototype, "prototype") - - - - DEF(constructor, "constructor") - - - - DEF(configurable, "configurable") - - - - DEF(writable, "writable") - - - - DEF(enumerable, "enumerable") - - - - DEF(value, "value") - - - - DEF(get, "get") - - - - DEF(set, "set") - - - - DEF(of, "of") - - - - DEF(__proto__, "__proto__") - - - - DEF(undefined, "undefined") - - - - DEF(number, "number") - - - - DEF(boolean, "boolean") - - - - DEF(string, "string") - - - - DEF(object, "object") - - - - DEF(symbol, "symbol") - - - - DEF(integer, "integer") - - - - DEF(unknown, "unknown") - - - - DEF(arguments, "arguments") - - - - DEF(callee, "callee") - - - - DEF(caller, "caller") - - - - DEF(_eval_, "") - - - - DEF(_ret_, "") - - - - DEF(_var_, "") - - - - DEF(_arg_var_, "") - - - - DEF(_with_, "") - - - - DEF(lastIndex, "lastIndex") - - - - DEF(target, "target") - - - - DEF(index, "index") - - - - DEF(defineProperties, "defineProperties") - - - - DEF(apply, "apply") - - - - DEF(join, "join") - - - - DEF(concat, "concat") - - - - DEF(split, "split") - - - - DEF(construct, "construct") - - - - DEF(getPrototypeOf, "getPrototypeOf") - - - - DEF(setPrototypeOf, "setPrototypeOf") - - - - DEF(isExtensible, "isExtensible") - - - - DEF(preventExtensions, "preventExtensions") - - - - DEF(has, "has") - - - - DEF(deleteProperty, "deleteProperty") - - - - DEF(defineProperty, "defineProperty") - - - - DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor") - - - - DEF(ownKeys, "ownKeys") - - - - DEF(add, "add") - - - - DEF(done, "done") - - - - DEF(next, "next") - - - - DEF(values, "values") - - - - DEF(source, "source") - - - - DEF(flags, "flags") - - - - DEF(global, "global") - - - - DEF(unicode, "unicode") - - - - DEF(raw, "raw") - - - - DEF(new_target, "new.target") - - - - DEF(this_active_func, "this.active_func") - - - - DEF(home_object, "") - - - - DEF(computed_field, "") - - - - DEF(static_computed_field, "") - - - - DEF(class_fields_init, "") - - - - DEF(brand, "") - - - - DEF(hash_constructor, "#constructor") - - - - DEF(as, "as") - - - - DEF(from, "from") - - - - DEF(meta, "meta") - - - - DEF(_default_, "*default*") - - - - DEF(_star_, "*") - - - - DEF(Module, "Module") - - - - DEF(then, "then") - - - - DEF(resolve, "resolve") - - - - DEF(reject, "reject") - - - - DEF(promise, "promise") - - - - DEF(proxy, "proxy") - - - - DEF(revoke, "revoke") - - - - DEF(async, "async") - - - - DEF(exec, "exec") - - - - DEF(groups, "groups") - - - - DEF(status, "status") - - - - DEF(reason, "reason") - - - - DEF(globalThis, "globalThis") - - - - DEF(bigint, "bigint") - - - - DEF(bigfloat, "bigfloat") - - - - DEF(bigdecimal, "bigdecimal") - - - - DEF(roundingMode, "roundingMode") - - - - DEF(maximumSignificantDigits, "maximumSignificantDigits") - - - - DEF(maximumFractionDigits, "maximumFractionDigits") - - - - DEF(not_equal, "not-equal") - - - - DEF(timed_out, "timed-out") - - - - DEF(ok, "ok") - - - - DEF(toJSON, "toJSON") - - - - DEF(Object, "Object") - - - - DEF(Array, "Array") - - - - DEF(Error, "Error") - - - - DEF(Number, "Number") - - - - DEF(String, "String") - - - - DEF(Boolean, "Boolean") - - - - DEF(Symbol, "Symbol") - - - - DEF(Arguments, "Arguments") - - - - DEF(Math, "Math") - - - - DEF(JSON, "JSON") - - - - DEF(Date, "Date") - - - - DEF(Function, "Function") - - - - DEF(GeneratorFunction, "GeneratorFunction") - - - - DEF(ForInIterator, "ForInIterator") - - - - DEF(RegExp, "RegExp") - - - - DEF(ArrayBuffer, "ArrayBuffer") - - - - DEF(SharedArrayBuffer, "SharedArrayBuffer") - - - - DEF(Uint8ClampedArray, "Uint8ClampedArray") - - - - DEF(Int8Array, "Int8Array") - - - - DEF(Uint8Array, "Uint8Array") - - - - DEF(Int16Array, "Int16Array") - - - - DEF(Uint16Array, "Uint16Array") - - - - DEF(Int32Array, "Int32Array") - - - - DEF(Uint32Array, "Uint32Array") - - - - DEF(BigInt64Array, "BigInt64Array") - - - - DEF(BigUint64Array, "BigUint64Array") - - - - DEF(Float32Array, "Float32Array") - - - - DEF(Float64Array, "Float64Array") - - - - DEF(DataView, "DataView") - - - - DEF(BigInt, "BigInt") - - - - DEF(BigFloat, "BigFloat") - - - - DEF(BigFloatEnv, "BigFloatEnv") - - - - DEF(BigDecimal, "BigDecimal") - - - - DEF(OperatorSet, "OperatorSet") - - - - DEF(Operators, "Operators") - - - - DEF(Map, "Map") - - - - DEF(Set, "Set") - - - - DEF(WeakMap, "WeakMap") - - - - DEF(WeakSet, "WeakSet") - - - - DEF(Map_Iterator, "Map Iterator") - - - - DEF(Set_Iterator, "Set Iterator") - - - - DEF(Array_Iterator, "Array Iterator") - - - - DEF(String_Iterator, "String Iterator") - - - - DEF(RegExp_String_Iterator, "RegExp String Iterator") - - - - DEF(Generator, "Generator") - - - - DEF(Proxy, "Proxy") - - - - DEF(Promise, "Promise") - - - - DEF(PromiseResolveFunction, "PromiseResolveFunction") - - - - DEF(PromiseRejectFunction, "PromiseRejectFunction") - - - - DEF(AsyncFunction, "AsyncFunction") - - - - DEF(AsyncFunctionResolve, "AsyncFunctionResolve") - - - - DEF(AsyncFunctionReject, "AsyncFunctionReject") - - - - DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction") - - - - DEF(AsyncGenerator, "AsyncGenerator") - - - - DEF(EvalError, "EvalError") - - - - DEF(RangeError, "RangeError") - - - - DEF(ReferenceError, "ReferenceError") - - - - DEF(SyntaxError, "SyntaxError") - - - - DEF(TypeError, "TypeError") - - - - DEF(URIError, "URIError") - - - - DEF(InternalError, "InternalError") - - - - DEF(Private_brand, "") - - - - DEF(Symbol_toPrimitive, "Symbol.toPrimitive") - - - - DEF(Symbol_iterator, "Symbol.iterator") - - - - DEF(Symbol_match, "Symbol.match") - - - - DEF(Symbol_matchAll, "Symbol.matchAll") - - - - DEF(Symbol_replace, "Symbol.replace") - - - - DEF(Symbol_search, "Symbol.search") - - - - DEF(Symbol_split, "Symbol.split") - - - - DEF(Symbol_toStringTag, "Symbol.toStringTag") - - - - DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") - - - - DEF(Symbol_hasInstance, "Symbol.hasInstance") - - - - DEF(Symbol_species, "Symbol.species") - - - - DEF(Symbol_unscopables, "Symbol.unscopables") - - - - DEF(Symbol_asyncIterator, "Symbol.asyncIterator") - - - - DEF(Symbol_operatorSet, "Symbol.operatorSet") - - - -#endif /* DEF */ diff --git a/bridge/third_party/quickjs/event_type_names.h b/bridge/third_party/quickjs/event_type_names.h deleted file mode 100644 index 0cf9b65075..0000000000 --- a/bridge/third_party/quickjs/event_type_names.h +++ /dev/null @@ -1,862 +0,0 @@ -/* -* QuickJS atom definitions -* -* Copyright (c) 2017-2018 Fabrice Bellard -* Copyright (c) 2017-2018 Charlie Gordon -* -* Permission is hereby granted, free of charge, to any person obtaining a copy -* of this software and associated documentation files (the "Software"), to deal -* in the Software without restriction, including without limitation the rights -* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -* copies of the Software, and to permit persons to whom the Software is -* furnished to do so, subject to the following conditions: -* -* The above copyright notice and this permission notice shall be included in -* all copies or substantial portions of the Software. -* -* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -* THE SOFTWARE. -*/ - -#ifdef DEF - - - - DEF(DOMActivate, "DOMActivate") - - - - DEF(DOMCharacterDataModified, "DOMCharacterDataModified") - - - - DEF(DOMContentLoaded, "DOMContentLoaded") - - - - DEF(DOMFocusIn, "DOMFocusIn") - - - - DEF(DOMFocusOut, "DOMFocusOut") - - - - DEF(DOMNodeInserted, "DOMNodeInserted") - - - - DEF(DOMNodeInsertedIntoDocument, "DOMNodeInsertedIntoDocument") - - - - DEF(DOMNodeRemoved, "DOMNodeRemoved") - - - - DEF(DOMNodeRemovedFromDocument, "DOMNodeRemovedFromDocument") - - - - DEF(DOMSubtreeModified, "DOMSubtreeModified") - - - - DEF(abort, "abort") - - - - DEF(abortpayment, "abortpayment") - - - - DEF(activate, "activate") - - - - DEF(active, "active") - - - - DEF(addsourcebuffer, "addsourcebuffer") - - - - DEF(addtrack, "addtrack") - - - - DEF(animationcancel, "animationcancel") - - - - DEF(animationend, "animationend") - - - - DEF(animationiteration, "animationiteration") - - - - DEF(animationstart, "animationstart") - - - - DEF(backgroundfetchabort, "backgroundfetchabort") - - - - DEF(backgroundfetchclick, "backgroundfetchclick") - - - - DEF(backgroundfetchfail, "backgroundfetchfail") - - - - DEF(backgroundfetchsuccess, "backgroundfetchsuccess") - - - - DEF(beforeunload, "beforeunload") - - - - DEF(beginEvent, "beginEvent") - - - - DEF(blocked, "blocked") - - - - DEF(blur, "blur") - - - - DEF(boundary, "boundary") - - - - DEF(cached, "cached") - - - - DEF(cancel, "cancel") - - - - DEF(canplay, "canplay") - - - - DEF(canplaythrough, "canplaythrough") - - - - DEF(capturehandlechange, "capturehandlechange") - - - - DEF(change, "change") - - - - DEF(checking, "checking") - - - - DEF(click, "click") - - - - DEF(close, "close") - - - - DEF(closing, "closing") - - - - DEF(complete, "complete") - - - - DEF(compositionend, "compositionend") - - - - DEF(compositionstart, "compositionstart") - - - - DEF(compositionupdate, "compositionupdate") - - - - DEF(connect, "connect") - - - - DEF(contextlost, "contextlost") - - - - DEF(contextmenu, "contextmenu") - - - - DEF(contextrestored, "contextrestored") - - - - DEF(controllerchange, "controllerchange") - - - - DEF(cookiechange, "cookiechange") - - - - DEF(copy, "copy") - - - - DEF(contentdelete, "contentdelete") - - - - DEF(crossoriginmessage, "crossoriginmessage") - - - - DEF(currentscreenchange, "currentscreenchange") - - - - DEF(cuechange, "cuechange") - - - - DEF(currententrychange, "currententrychange") - - - - DEF(cut, "cut") - - - - DEF(datachannel, "datachannel") - - - - DEF(dblclick, "dblclick") - - - - DEF(defaultsessionstart, "defaultsessionstart") - - - - DEF(disconnect, "disconnect") - - - - DEF(display, "display") - - - - DEF(drop, "drop") - - - - DEF(durationchange, "durationchange") - - - - DEF(emptied, "emptied") - - - - DEF(encrypted, "encrypted") - - - - DEF(end, "end") - - - - DEF(ended, "ended") - - - - DEF(endEvent, "endEvent") - - - - DEF(enter, "enter") - - - - DEF(error, "error") - - - - DEF(exit, "exit") - - - - DEF(fetch, "fetch") - - - - DEF(finish, "finish") - - - - DEF(focus, "focus") - - - - DEF(focusin, "focusin") - - - - DEF(focusout, "focusout") - - - - DEF(freeze, "freeze") - - - - DEF(fullscreenchange, "fullscreenchange") - - - - DEF(fullscreenerror, "fullscreenerror") - - - - DEF(hashchange, "hashchange") - - - - DEF(hide, "hide") - - - - DEF(inactive, "inactive") - - - - DEF(input, "input") - - - - DEF(inputreport, "inputreport") - - - - DEF(inputsourceschange, "inputsourceschange") - - - - DEF(install, "install") - - - - DEF(interfacerequest, "interfacerequest") - - - - DEF(invalid, "invalid") - - - - DEF(keydown, "keydown") - - - - DEF(keypress, "keypress") - - - - DEF(keystatuseschange, "keystatuseschange") - - - - DEF(keyup, "keyup") - - - - DEF(languagechange, "languagechange") - - - - DEF(leavepictureinpicture, "leavepictureinpicture") - - - - DEF(levelchange, "levelchange") - - - - DEF(load, "load") - - - - DEF(loadeddata, "loadeddata") - - - - DEF(loadedmetadata, "loadedmetadata") - - - - DEF(loadend, "loadend") - - - - DEF(loading, "loading") - - - - DEF(loadstart, "loadstart") - - - - DEF(lostpointercapture, "lostpointercapture") - - - - DEF(mark, "mark") - - - - DEF(message, "message") - - - - DEF(messageerror, "messageerror") - - - - DEF(mousedown, "mousedown") - - - - DEF(mouseenter, "mouseenter") - - - - DEF(mouseleave, "mouseleave") - - - - DEF(mousemove, "mousemove") - - - - DEF(mouseout, "mouseout") - - - - DEF(mouseover, "mouseover") - - - - DEF(mouseup, "mouseup") - - - - DEF(mousewheel, "mousewheel") - - - - DEF(mute, "mute") - - - - DEF(navigate, "navigate") - - - - DEF(navigateerror, "navigateerror") - - - - DEF(navigatesuccess, "navigatesuccess") - - - - DEF(noupdate, "noupdate") - - - - DEF(open, "open") - - - - DEF(orientationchange, "orientationchange") - - - - DEF(overscroll, "overscroll") - - - - DEF(pagehide, "pagehide") - - - - DEF(pageshow, "pageshow") - - - - DEF(paste, "paste") - - - - DEF(pause, "pause") - - - - DEF(play, "play") - - - - DEF(playing, "playing") - - - - DEF(pointercancel, "pointercancel") - - - - DEF(pointerdown, "pointerdown") - - - - DEF(pointerenter, "pointerenter") - - - - DEF(pointerleave, "pointerleave") - - - - DEF(pointerlockchange, "pointerlockchange") - - - - DEF(pointerlockerror, "pointerlockerror") - - - - DEF(pointermove, "pointermove") - - - - DEF(pointerout, "pointerout") - - - - DEF(pointerover, "pointerover") - - - - DEF(pointerup, "pointerup") - - - - DEF(popstate, "popstate") - - - - DEF(progress, "progress") - - - - DEF(processorerror, "processorerror") - - - - DEF(push, "push") - - - - DEF(pushsubscriptionchange, "pushsubscriptionchange") - - - - DEF(ratechange, "ratechange") - - - - DEF(reading, "reading") - - - - DEF(readingerror, "readingerror") - - - - DEF(readystatechange, "readystatechange") - - - - DEF(reflectionchange, "reflectionchange") - - - - DEF(rejectionhandled, "rejectionhandled") - - - - DEF(release, "release") - - - - DEF(remove, "remove") - - - - DEF(removestream, "removestream") - - - - DEF(removetrack, "removetrack") - - - - DEF(repeatEvent, "repeatEvent") - - - - DEF(reset, "reset") - - - - DEF(resize, "resize") - - - - DEF(result, "result") - - - - DEF(resume, "resume") - - - - DEF(screenschange, "screenschange") - - - - DEF(scroll, "scroll") - - - - DEF(scrollend, "scrollend") - - - - DEF(search, "search") - - - - DEF(seeked, "seeked") - - - - DEF(seeking, "seeking") - - - - DEF(select, "select") - - - - DEF(selectionchange, "selectionchange") - - - - DEF(selectstart, "selectstart") - - - - DEF(show, "show") - - - - DEF(squeeze, "squeeze") - - - - DEF(squeezeend, "squeezeend") - - - - DEF(squeezestart, "squeezestart") - - - - DEF(stalled, "stalled") - - - - DEF(start, "start") - - - - DEF(stop, "stop") - - - - DEF(statechange, "statechange") - - - - DEF(storage, "storage") - - - - DEF(submit, "submit") - - - - DEF(success, "success") - - - - DEF(suspend, "suspend") - - - - DEF(sync, "sync") - - - - DEF(terminate, "terminate") - - - - DEF(textInput, "textInput") - - - - DEF(textupdate, "textupdate") - - - - DEF(textformatupdate, "textformatupdate") - - - - DEF(toggle, "toggle") - - - - DEF(tonechange, "tonechange") - - - - DEF(touchcancel, "touchcancel") - - - - DEF(touchend, "touchend") - - - - DEF(touchmove, "touchmove") - - - - DEF(touchstart, "touchstart") - - - - DEF(transitioncancel, "transitioncancel") - - - - DEF(transitionend, "transitionend") - - - - DEF(transitionrun, "transitionrun") - - - - DEF(transitionstart, "transitionstart") - - - - DEF(typechange, "typechange") - - - - DEF(uncapturederror, "uncapturederror") - - - - DEF(unhandledrejection, "unhandledrejection") - - - - DEF(unload, "unload") - - - - DEF(unmute, "unmute") - - - - DEF(update, "update") - - - - DEF(versionchange, "versionchange") - - - - DEF(visibilitychange, "visibilitychange") - - - - DEF(waiting, "waiting") - - - - DEF(waitingforkey, "waitingforkey") - - - - DEF(webglcontextcreationerror, "webglcontextcreationerror") - - - - DEF(webglcontextlost, "webglcontextlost") - - - - DEF(webglcontextrestored, "webglcontextrestored") - - - - DEF(wheel, "wheel") - - - - DEF(zoom, "zoom") - - - -#endif /* DEF */ diff --git a/bridge/third_party/quickjs/quickjs-atom.h b/bridge/third_party/quickjs/quickjs-atom.h index d8f71dc16e..4c2279452a 100644 --- a/bridge/third_party/quickjs/quickjs-atom.h +++ b/bridge/third_party/quickjs/quickjs-atom.h @@ -1,6 +1,6 @@ /* * QuickJS atom definitions - * + * * Copyright (c) 2017-2018 Fabrice Bellard * Copyright (c) 2017-2018 Charlie Gordon * @@ -81,7 +81,7 @@ DEF(empty_string, "") DEF(length, "length") DEF(fileName, "fileName") DEF(lineNumber, "lineNumber") -//DEF(message, "message") +DEF(message, "message") DEF(errors, "errors") DEF(stack, "stack") DEF(name, "name") @@ -118,7 +118,7 @@ DEF(_with_, "") DEF(lastIndex, "lastIndex") DEF(target, "target") DEF(index, "index") -//DEF(input, "input") +DEF(input, "input") DEF(defineProperties, "defineProperties") DEF(apply, "apply") DEF(join, "join") @@ -202,7 +202,7 @@ DEF(RegExp, "RegExp") DEF(ArrayBuffer, "ArrayBuffer") DEF(SharedArrayBuffer, "SharedArrayBuffer") /* must keep same order as class IDs for typed arrays */ -DEF(Uint8ClampedArray, "Uint8ClampedArray") +DEF(Uint8ClampedArray, "Uint8ClampedArray") DEF(Int8Array, "Int8Array") DEF(Uint8Array, "Uint8Array") DEF(Int16Array, "Int16Array") @@ -269,5 +269,5 @@ DEF(Symbol_asyncIterator, "Symbol.asyncIterator") #ifdef CONFIG_BIGNUM DEF(Symbol_operatorSet, "Symbol.operatorSet") #endif - + #endif /* DEF */ diff --git a/bridge/third_party/quickjs/quickjs.c b/bridge/third_party/quickjs/quickjs.c index 1b5a4c9bf3..e9492219f5 100644 --- a/bridge/third_party/quickjs/quickjs.c +++ b/bridge/third_party/quickjs/quickjs.c @@ -2572,14 +2572,15 @@ static int JS_InitAtoms(JSRuntime *rt) rt->atom_count = 0; rt->atom_size = 0; rt->atom_free_index = 0; - if (JS_ResizeAtomHash(rt, 256)) /* there are at least 195 predefined atoms */ + if (JS_ResizeAtomHash(rt, 1024)) /* there are at least 195 predefined atoms */ return -1; p = js_atom_init; + for(i = 1; i < JS_ATOM_END; i++) { if (i == JS_ATOM_Private_brand) atom_type = JS_ATOM_TYPE_PRIVATE; - else if (i >= JS_ATOM_Symbol_toPrimitive) + else if (i >= JS_ATOM_Symbol_toPrimitive && i <= JS_ATOM_Symbol_asyncIterator) atom_type = JS_ATOM_TYPE_SYMBOL; else atom_type = JS_ATOM_TYPE_STRING; @@ -2688,7 +2689,8 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) } /* try and locate an already registered atom */ len = str->len; - h = hash_string(str, atom_type); + /* only in extreme case will str has zero hash, we accept extra hash calc in that case. */ + h = str->hash != 0 ? str->hash : hash_string(str, atom_type); h &= JS_ATOM_HASH_MASK; h1 = h & (rt->atom_hash_size - 1); i = rt->atom_hash[h1]; @@ -2723,7 +2725,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type) 4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092 preallocating space for predefined atoms (at least 195). */ - new_size = max_int(211, rt->atom_size * 3 / 2); + new_size = max_int(1066, rt->atom_size * 3 / 2); if (new_size > JS_ATOM_MAX) goto fail; /* XXX: should use realloc2 to use slack space */ @@ -35951,7 +35953,7 @@ static JSAtom find_atom(JSContext *ctx, const char *name) len = strlen(name) - 1; /* We assume 8 bit non null strings, which is the case for these symbols */ - for(atom = JS_ATOM_Symbol_toPrimitive; atom < JS_ATOM_END; atom++) { + for(atom = JS_ATOM_Symbol_toPrimitive; atom <= JS_ATOM_Symbol_asyncIterator; atom++) { JSAtomStruct *p = ctx->rt->atom_array[atom]; JSString *str = p; if (str->len == len && !memcmp(str->u.str8, name, len)) @@ -51024,7 +51026,7 @@ void JS_AddIntrinsicBaseObjects(JSContext *ctx) ctx->class_proto[JS_CLASS_SYMBOL]); JS_SetPropertyFunctionList(ctx, obj, js_symbol_funcs, countof(js_symbol_funcs)); - for(i = JS_ATOM_Symbol_toPrimitive; i < JS_ATOM_END; i++) { + for(i = JS_ATOM_Symbol_toPrimitive; i <= JS_ATOM_Symbol_asyncIterator; i++) { char buf[ATOM_GET_STR_BUF_SIZE]; const char *str, *p; str = JS_AtomGetStr(ctx, buf, sizeof(buf), i); diff --git a/bridge/third_party/quickjs/quickjs.h b/bridge/third_party/quickjs/quickjs.h index b9c5e455a3..6481dcf9ed 100644 --- a/bridge/third_party/quickjs/quickjs.h +++ b/bridge/third_party/quickjs/quickjs.h @@ -427,8 +427,7 @@ void JS_DumpMemoryUsage(FILE *fp, const JSMemoryUsage *s, JSRuntime *rt); enum { __JS_ATOM_NULL = JS_ATOM_NULL, #define DEF(name, str) JS_ATOM_ ## name, -#include "built_in_string.h" -#include "event_type_names.h" +#include "quickjs-atom.h" #undef DEF JS_ATOM_END, }; @@ -438,7 +437,6 @@ enum { static const char js_atom_init[] = #define DEF(name, str) str "\0" #include "quickjs-atom.h" -#include "event_type_names.h" #undef DEF ; From 56872ac5358c578b1c1e15741f797273074f3007 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Wed, 6 Apr 2022 13:51:24 +0000 Subject: [PATCH 053/498] Committing clang-format changes --- bridge/bindings/qjs/atom_string.h | 5 +---- bridge/bindings/qjs/converter_impl.h | 12 ++++++------ bridge/bindings/qjs/generated_code_helper.h | 4 ++-- bridge/core/dom/events/event_target.cc | 4 +++- bridge/core/executing_context.cc | 4 +--- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index c24eb65b69..d0aec8d148 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -33,9 +33,7 @@ class AtomicString { AtomicString(JSContext* ctx, JSAtom atom) : ctx_(ctx), atom_(atom){}; AtomicString(JSContext* ctx, const std::string& string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())){}; AtomicString(JSContext* ctx, JSValue value) : ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; - ~AtomicString() { - JS_FreeAtom(ctx_, atom_); - }; + ~AtomicString() { JS_FreeAtom(ctx_, atom_); }; // Return the undefined string value from atom key. JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); }; @@ -92,7 +90,6 @@ class AtomicString { JSAtom atom_{JS_ATOM_NULL}; }; - } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index f7a3729c35..9ce7ec13a9 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -16,10 +16,10 @@ #include "idl_type.h" #include "js_event_listener.h" #include "native_string_utils.h" -#include "qjs_event_init.h" +#include "qjs_add_event_listener_options.h" #include "qjs_error_event_init.h" +#include "qjs_event_init.h" #include "qjs_event_listener_options.h" -#include "qjs_add_event_listener_options.h" namespace kraken { @@ -399,15 +399,15 @@ struct Converter : public ConverterBase { } }; -template<> -struct Converter: public ConverterBase { +template <> +struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); return ErrorEventInit::Create(ctx, value, exception_state); } }; -template<> +template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); @@ -415,7 +415,7 @@ struct Converter : public ConverterBase +template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); diff --git a/bridge/bindings/qjs/generated_code_helper.h b/bridge/bindings/qjs/generated_code_helper.h index 31b96d8493..0358cf2356 100644 --- a/bridge/bindings/qjs/generated_code_helper.h +++ b/bridge/bindings/qjs/generated_code_helper.h @@ -5,9 +5,9 @@ #ifndef KRAKENBRIDGE_GENERATED_CODE_HELPER_H #define KRAKENBRIDGE_GENERATED_CODE_HELPER_H -#include "bindings/qjs/qjs_interface_bridge.h" -#include "bindings/qjs/dictionary_base.h" #include "atom_string.h" +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/qjs_interface_bridge.h" #include "script_value.h" #endif diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 6b96e54110..58506309a6 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -51,7 +51,9 @@ bool EventTarget::addEventListener(const AtomicString& event_type, return AddEventListenerInternal(event_type, event_listener, options); } -bool EventTarget::removeEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, ExceptionState& exception_state) { +bool EventTarget::removeEventListener(const AtomicString& event_type, + const std::shared_ptr& event_listener, + ExceptionState& exception_state) { std::shared_ptr options = EventListenerOptions::Create(); return RemoveEventListenerInternal(event_type, event_listener, options); } diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 556411eee4..6ec2e724ca 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -4,9 +4,9 @@ */ #include "executing_context.h" -#include "polyfill.h" #include "built_in_string.h" #include "event_type_names.h" +#include "polyfill.h" #include "foundation/logging.h" @@ -65,7 +65,6 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& built_in_string::Init(ctx); event_type_names::Init(ctx); - // Register all built-in native bindings. InstallBindings(this); @@ -93,7 +92,6 @@ ExecutingContext::~ExecutingContext() { built_in_string::Dispose(); event_type_names::Dispose(); - // Free unreleased native_functions. { struct list_head *el, *el1; From d5ebd76cfbed97a42a6bf9986610fe6a7edbf72c Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 6 Apr 2022 23:16:30 +0800 Subject: [PATCH 054/498] fix: fix string over free. --- bridge/bindings/qjs/atom_string.h | 16 ++++++++++++---- bridge/bindings/qjs/qjs_engine_patch.h | 9 +++++++++ bridge/core/executing_context.cc | 8 -------- bridge/core/executing_context_test.cc | 11 +++++++++-- bridge/core/script_state.cc | 13 +++++++++++++ .../static/json_templates/make_names.cc.tpl | 4 ---- 6 files changed, 43 insertions(+), 18 deletions(-) diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h index d0aec8d148..f56c52cdd3 100644 --- a/bridge/bindings/qjs/atom_string.h +++ b/bridge/bindings/qjs/atom_string.h @@ -30,10 +30,12 @@ class AtomicString { }; AtomicString() = default; - AtomicString(JSContext* ctx, JSAtom atom) : ctx_(ctx), atom_(atom){}; - AtomicString(JSContext* ctx, const std::string& string) : ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())){}; - AtomicString(JSContext* ctx, JSValue value) : ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; - ~AtomicString() { JS_FreeAtom(ctx_, atom_); }; + AtomicString(JSContext* ctx, JSAtom atom) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(atom){}; + AtomicString(JSContext* ctx, const std::string& string) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())){}; + AtomicString(JSContext* ctx, JSValue value) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; + ~AtomicString() { + JS_FreeAtomRT(runtime_, atom_); + }; // Return the undefined string value from atom key. JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); }; @@ -59,11 +61,14 @@ class AtomicString { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; + runtime_ = value.runtime_; }; AtomicString& operator=(const AtomicString& other) { if (&other != this) { atom_ = JS_DupAtom(ctx_, other.atom_); } + runtime_ = other.runtime_; + ctx_ = other.ctx_; return *this; }; @@ -73,12 +78,14 @@ class AtomicString { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; + runtime_ = value.runtime_; }; AtomicString& operator=(AtomicString&& value) noexcept { if (&value != this) { atom_ = JS_DupAtom(ctx_, value.atom_); } ctx_ = value.ctx_; + runtime_ = value.runtime_; return *this; } @@ -87,6 +94,7 @@ class AtomicString { protected: JSContext* ctx_{nullptr}; + JSRuntime* runtime_{nullptr}; JSAtom atom_{JS_ATOM_NULL}; }; diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h index 99f75b1de9..baceb1294d 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -99,6 +99,15 @@ enum { extern "C" { #endif +static inline bool __JS_AtomIsConst(JSAtom v) +{ +#if defined(DUMP_LEAKS) && DUMP_LEAKS > 1 + return (int32_t)v <= 0; +#else + return (int32_t)v < JS_ATOM_END; +#endif +} + uint16_t* JS_ToUnicode(JSContext* ctx, JSValueConst value, uint32_t* length); JSValue JS_NewUnicodeString(JSContext* ctx, const uint16_t* code, uint32_t length); JSClassID JSValueGetClassId(JSValue); diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 6ec2e724ca..1e23164327 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -61,10 +61,6 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& JS_SetContextOpaque(ctx, this); JS_SetHostPromiseRejectionTracker(script_state_.runtime(), promiseRejectTracker, nullptr); - // PreInit Strings. - built_in_string::Init(ctx); - event_type_names::Init(ctx); - // Register all built-in native bindings. InstallBindings(this); @@ -88,10 +84,6 @@ ExecutingContext::~ExecutingContext() { valid_contexts[context_id_] = false; ctx_invalid_ = true; - // Dispose pre-built-in strings. - built_in_string::Dispose(); - event_type_names::Dispose(); - // Free unreleased native_functions. { struct list_head *el, *el1; diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 23412f44ba..f34fac73df 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -11,8 +11,15 @@ using namespace kraken; TEST(Context, isValid) { - auto bridge = TEST_init(); - EXPECT_EQ(bridge->getContext()->IsValid(), true); + { + auto bridge = TEST_init(); + EXPECT_EQ(bridge->getContext()->IsValid(), true); + } + { + auto bridge = TEST_init(); + EXPECT_EQ(bridge->getContext()->IsValid(), true); + } + } TEST(Context, evalWithError) { diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index b4da1091b0..5b97e2e07d 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -4,6 +4,8 @@ */ #include "script_state.h" +#include "built_in_string.h" +#include "event_type_names.h" namespace kraken { @@ -12,12 +14,19 @@ std::atomic runningContexts{0}; ScriptState::ScriptState() { runningContexts++; + bool first_loaded = false; if (runtime_ == nullptr) { runtime_ = JS_NewRuntime(); + first_loaded = true; } // Avoid stack overflow when running in multiple threads. JS_UpdateStackTop(runtime_); ctx_ = JS_NewContext(runtime_); + + if (first_loaded) { + built_in_string::Init(ctx_); + event_type_names::Init(ctx_); + } } JSRuntime* ScriptState::runtime() { @@ -32,6 +41,10 @@ ScriptState::~ScriptState() { #if DUMP_LEAKS if (--runningContexts == 0) { + // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. + built_in_string::Dispose(); + event_type_names::Dispose(); + JS_FreeRuntime(runtime_); runtime_ = nullptr; } diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index 0193a3c28b..cdb5ba84f1 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -15,10 +15,6 @@ void* names_storage[kNamesCount * ((sizeof(AtomicString) + sizeof(void *) - 1) / <% }) %> void Init(JSContext* ctx) { - static bool is_loaded = false; - if (is_loaded) return; - is_loaded = true; - struct NameEntry { const char* str; }; From 750f34db90d7e519f9fb331c18e9cb9d0828b04d Mon Sep 17 00:00:00 2001 From: andycall Date: Thu, 7 Apr 2022 00:06:31 +0800 Subject: [PATCH 055/498] test: add atomic_string test suits. --- bridge/CMakeLists.txt | 4 +- bridge/bindings/qjs/atom_string.cc | 8 -- bridge/bindings/qjs/atom_string.h | 103 -------------- bridge/bindings/qjs/atomic_string.cc | 72 ++++++++++ bridge/bindings/qjs/atomic_string.h | 58 ++++++++ bridge/bindings/qjs/atomic_string_test.cc | 126 ++++++++++++++++++ bridge/bindings/qjs/converter_impl.h | 2 +- bridge/bindings/qjs/generated_code_helper.h | 2 +- bridge/bindings/qjs/native_string_utils.cc | 2 +- bridge/core/dom/events/event.h | 2 +- bridge/core/dom/events/event_listener_map.h | 2 +- bridge/core/frame/console.h | 2 +- bridge/core/frame/module_manager.h | 2 +- bridge/foundation/native_string.cc | 11 +- bridge/foundation/native_string.h | 5 +- .../static/json_templates/make_names.cc.tpl | 2 +- .../static/json_templates/make_names.h.tpl | 2 +- bridge/test/test.cmake | 1 + 18 files changed, 274 insertions(+), 132 deletions(-) delete mode 100644 bridge/bindings/qjs/atom_string.cc delete mode 100644 bridge/bindings/qjs/atom_string.h create mode 100644 bridge/bindings/qjs/atomic_string.cc create mode 100644 bridge/bindings/qjs/atomic_string.h create mode 100644 bridge/bindings/qjs/atomic_string_test.cc diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 5076d630d4..d2230982c0 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -219,8 +219,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/qjs_interface_bridge.h bindings/qjs/script_promise_resolver.cc bindings/qjs/script_promise_resolver.h - bindings/qjs/atom_string.cc - bindings/qjs/atom_string.h + bindings/qjs/atomic_string.cc + bindings/qjs/atomic_string.h bindings/qjs/generated_code_helper.h bindings/qjs/exception_state.cc bindings/qjs/exception_state.h diff --git a/bridge/bindings/qjs/atom_string.cc b/bridge/bindings/qjs/atom_string.cc deleted file mode 100644 index 8285eee338..0000000000 --- a/bridge/bindings/qjs/atom_string.cc +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "atom_string.h" - -namespace kraken {} // namespace kraken diff --git a/bridge/bindings/qjs/atom_string.h b/bridge/bindings/qjs/atom_string.h deleted file mode 100644 index f56c52cdd3..0000000000 --- a/bridge/bindings/qjs/atom_string.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ -#define KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ - -#include -#include -#include "foundation/macros.h" -#include "foundation/native_string.h" -#include "native_string_utils.h" -#include "qjs_engine_patch.h" - -namespace kraken { - -// An AtomicString instance represents a string, and multiple AtomicString -// instances can share their string storage if the strings are -// identical. Comparing two AtomicString instances is much faster than comparing -// two String instances because we just check string storage identity. -class AtomicString { - public: - static AtomicString Empty(JSContext* ctx) { return AtomicString(ctx, JS_ATOM_NULL); }; - static 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; - }; - - AtomicString() = default; - AtomicString(JSContext* ctx, JSAtom atom) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(atom){}; - AtomicString(JSContext* ctx, const std::string& string) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())){}; - AtomicString(JSContext* ctx, JSValue value) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; - ~AtomicString() { - JS_FreeAtomRT(runtime_, atom_); - }; - - // Return the undefined string value from atom key. - JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); }; - - [[nodiscard]] std::string ToStdString() const { - const char* buf = JS_AtomToCString(ctx_, atom_); - std::string result = std::string(buf); - JS_FreeCString(ctx_, buf); - return result; - } - - [[nodiscard]] std::unique_ptr 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); - } - - // Copy assignment - AtomicString(AtomicString const& value) { - if (&value != this) { - atom_ = JS_DupAtom(ctx_, value.atom_); - } - ctx_ = value.ctx_; - runtime_ = value.runtime_; - }; - AtomicString& operator=(const AtomicString& other) { - if (&other != this) { - atom_ = JS_DupAtom(ctx_, other.atom_); - } - runtime_ = other.runtime_; - ctx_ = other.ctx_; - return *this; - }; - - // Move assignment - AtomicString(AtomicString&& value) noexcept { - if (&value != this) { - atom_ = JS_DupAtom(ctx_, value.atom_); - } - ctx_ = value.ctx_; - runtime_ = value.runtime_; - }; - AtomicString& operator=(AtomicString&& value) noexcept { - if (&value != this) { - atom_ = JS_DupAtom(ctx_, value.atom_); - } - ctx_ = value.ctx_; - runtime_ = value.runtime_; - return *this; - } - - 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}; - JSAtom atom_{JS_ATOM_NULL}; -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_BINDINGS_QJS_ATOM_STRING_H_ diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc new file mode 100644 index 0000000000..fa300b6de2 --- /dev/null +++ b/bridge/bindings/qjs/atomic_string.cc @@ -0,0 +1,72 @@ +/* +* Copyright (C) 2021-present The Kraken authors. All rights reserved. +*/ + +#include "atomic_string.h" +#include "built_in_string.h" + +namespace kraken { + + +AtomicString AtomicString::Empty(JSContext* ctx) { + AtomicString tmp = built_in_string::kempty_string; + return tmp; +} + +AtomicString AtomicString::From(JSContext* ctx, NativeString* native_string) { + JSValue str = JS_NewUnicodeString(ctx, native_string->string(), native_string->length()); + auto result = AtomicString(ctx, str); + JS_FreeValue(ctx, str); + return result; +} + +std::string AtomicString::ToStdString() const { + const char* buf = JS_AtomToCString(ctx_, atom_); + std::string result = std::string(buf); + JS_FreeCString(ctx_, buf); + return result; +} + + +std::unique_ptr AtomicString::ToNativeString() const { + JSValue stringValue = JS_AtomToValue(ctx_, atom_); + uint32_t length; + uint16_t* bytes = JS_ToUnicode(ctx_, stringValue, &length); + JS_FreeValue(ctx_, stringValue); + return std::make_unique(bytes, length); +} + +AtomicString::AtomicString(const AtomicString& value) { + if (&value != this) { + atom_ = JS_DupAtom(value.ctx_, value.atom_); + } + ctx_ = value.ctx_; + runtime_ = value.runtime_; +} + +AtomicString& AtomicString::operator=(const AtomicString& other) { + if (&other != this) { + atom_ = JS_DupAtom(other.ctx_, other.atom_); + } + runtime_ = other.runtime_; + ctx_ = other.ctx_; + return *this; +} + +AtomicString::AtomicString(AtomicString&& value) noexcept { + if (&value != this) { + atom_ = JS_DupAtom(value.ctx_, value.atom_); + } + ctx_ = value.ctx_; + runtime_ = value.runtime_; +} + +AtomicString& AtomicString::operator=(AtomicString&& value) noexcept { + if (&value != this) { + atom_ = JS_DupAtom(value.ctx_, value.atom_); + } + ctx_ = value.ctx_; + runtime_ = value.runtime_; + return *this; +} +} // namespace kraken diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h new file mode 100644 index 0000000000..3023252d04 --- /dev/null +++ b/bridge/bindings/qjs/atomic_string.h @@ -0,0 +1,58 @@ +/* +* Copyright (C) 2021-present The Kraken authors. All rights reserved. +*/ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ + +#include +#include +#include "foundation/macros.h" +#include "foundation/native_string.h" +#include "native_string_utils.h" +#include "qjs_engine_patch.h" + +namespace kraken { + +// An AtomicString instance represents a string, and multiple AtomicString +// instances can share their string storage if the strings are +// identical. Comparing two AtomicString instances is much faster than comparing +// two String instances because we just check string storage identity. +class AtomicString { + public: + static AtomicString Empty(JSContext* ctx); + static AtomicString From(JSContext* ctx, NativeString* native_string); + + AtomicString() = default; + AtomicString(JSContext* ctx, const std::string& string) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())){}; + AtomicString(JSContext* ctx, JSValue value) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; + ~AtomicString() { + JS_FreeAtomRT(runtime_, atom_); + }; + + // Return the undefined string value from atom key. + JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); }; + + [[nodiscard]] std::string ToStdString() const; + [[nodiscard]] std::unique_ptr ToNativeString() 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}; + JSAtom atom_{JS_ATOM_NULL}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ diff --git a/bridge/bindings/qjs/atomic_string_test.cc b/bridge/bindings/qjs/atomic_string_test.cc new file mode 100644 index 0000000000..f03cf721db --- /dev/null +++ b/bridge/bindings/qjs/atomic_string_test.cc @@ -0,0 +1,126 @@ +/* +* Copyright (C) 2021-present The Kraken authors. All rights reserved. +*/ + +#include +#include +#include "gtest/gtest.h" +#include "qjs_engine_patch.h" +#include "atomic_string.h" +#include "native_string_utils.h" +#include "event_type_names.h" +#include "built_in_string.h" + +using namespace kraken; + +using TestCallback = void(*)(JSContext* ctx); + +void TestAtomicString(TestCallback callback) { + JSRuntime* runtime = JS_NewRuntime(); + JSContext* ctx = JS_NewContext(runtime); + + built_in_string::Init(ctx); + + callback(ctx); + + JS_FreeContext(ctx); + + built_in_string::Dispose(); + JS_FreeRuntime(runtime); +} + +TEST(AtomicString, Empty) { + TestAtomicString([](JSContext* ctx) { + AtomicString atomic_string = AtomicString::Empty(ctx); + EXPECT_STREQ(atomic_string.ToStdString().c_str(), ""); + }); +} + +TEST(AtomicString, FromNativeString) { + TestAtomicString([](JSContext* ctx) { + auto nativeString = stringToNativeString("helloworld"); + AtomicString value = AtomicString::From(ctx, nativeString.get()); + + EXPECT_STREQ(value.ToStdString().c_str(), "helloworld"); + }); +} + +TEST(AtomicString, CreateFromStdString) { + TestAtomicString([](JSContext* ctx) { + AtomicString&& value = AtomicString(ctx, "helloworld"); + EXPECT_STREQ(value.ToStdString().c_str(), "helloworld"); + }); +} + +TEST(AtomicString, CreateFromJSValue) { + TestAtomicString([](JSContext* ctx) { + JSValue string = JS_NewString(ctx, "helloworld"); + AtomicString&& value = AtomicString(ctx, string); + EXPECT_STREQ(value.ToStdString().c_str(), "helloworld"); + JS_FreeValue(ctx, string); + }); +} + +TEST(AtomicString, ToQuickJS) { + TestAtomicString([](JSContext* ctx) { + AtomicString&& value = AtomicString(ctx, "helloworld"); + JSValue qjs_value = value.ToQuickJS(ctx); + const char* buffer = JS_ToCString(ctx, qjs_value); + EXPECT_STREQ(buffer, "helloworld"); + }); +} + +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; + 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; + if (1 + 1 == 2) { + str = AtomicString(ctx, "helloworld"); + } + EXPECT_STREQ(str.ToStdString().c_str(), "helloworld"); + }); +} diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 9ce7ec13a9..1e289ede65 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -7,7 +7,7 @@ #define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ #include -#include "atom_string.h" +#include "atomic_string.h" #include "converter.h" #include "core/dom/events/event.h" #include "core/dom/events/event_target.h" diff --git a/bridge/bindings/qjs/generated_code_helper.h b/bridge/bindings/qjs/generated_code_helper.h index 0358cf2356..9bf8d3bf52 100644 --- a/bridge/bindings/qjs/generated_code_helper.h +++ b/bridge/bindings/qjs/generated_code_helper.h @@ -5,7 +5,7 @@ #ifndef KRAKENBRIDGE_GENERATED_CODE_HELPER_H #define KRAKENBRIDGE_GENERATED_CODE_HELPER_H -#include "atom_string.h" +#include "atomic_string.h" #include "bindings/qjs/dictionary_base.h" #include "bindings/qjs/qjs_interface_bridge.h" #include "script_value.h" diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index e13de37abf..7b2f569fdd 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -32,7 +32,7 @@ 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::unique_ptr(tmp.clone()); + return std::make_unique(tmp.string(), tmp.length()); } std::unique_ptr atomToNativeString(JSContext* ctx, JSAtom atom) { diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 0ca5b9c810..4bd5e7d04b 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -7,7 +7,7 @@ #define KRAKENBRIDGE_EVENT_H #include -#include "bindings/qjs/atom_string.h" +#include "bindings/qjs/atomic_string.h" #include "bindings/qjs/script_wrappable.h" #include "core/executing_context.h" #include "foundation/native_string.h" diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index fe4f903755..03bcd50ccd 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -10,7 +10,7 @@ #include -#include "bindings/qjs/atom_string.h" +#include "bindings/qjs/atomic_string.h" #include "event_listener.h" #include "foundation/macros.h" #include "registered_eventListener.h" diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index e085c26735..461d0c0915 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -6,7 +6,7 @@ #ifndef KRAKE_CONSOLE_H #define KRAKE_CONSOLE_H -#include "bindings/qjs/atom_string.h" +#include "bindings/qjs/atomic_string.h" #include "bindings/qjs/script_value.h" #include "core/executing_context.h" diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 0c6ebe90f6..360b5c896f 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -6,7 +6,7 @@ #ifndef KRAKENBRIDGE_MODULE_MANAGER_H #define KRAKENBRIDGE_MODULE_MANAGER_H -#include "bindings/qjs/atom_string.h" +#include "bindings/qjs/atomic_string.h" #include "bindings/qjs/exception_state.h" #include "bindings/qjs/qjs_function.h" #include "module_callback.h" diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc index a2f3659aa9..e503fb59d8 100644 --- a/bridge/foundation/native_string.cc +++ b/bridge/foundation/native_string.cc @@ -8,14 +8,9 @@ namespace kraken { -NativeString* NativeString::clone() { - auto* newNativeString = new NativeString(nullptr, 0); - auto* newString = new uint16_t[length_]; - - memcpy(newString, string_, length_ * sizeof(uint16_t)); - newNativeString->string_ = newString; - newNativeString->length_ = length_; - return newNativeString; +NativeString::NativeString(const uint16_t* string, uint32_t length) : length_(length) { + string_ = static_cast(malloc(length * sizeof(uint16_t))); + memcpy((void*)string_, string, length * sizeof(uint16_t)); } NativeString::~NativeString() { diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h index 85442c30a3..be5f24c5bb 100644 --- a/bridge/foundation/native_string.h +++ b/bridge/foundation/native_string.h @@ -7,13 +7,14 @@ #define KRAKENBRIDGE_NATIVE_STRING_H #include +#include +#include namespace kraken { struct NativeString { - NativeString(const uint16_t* string, uint32_t length) : string_(string), length_(length){}; + NativeString(const uint16_t* string, uint32_t length); ~NativeString(); - NativeString* clone(); inline const uint16_t* string() const { return string_; } inline uint32_t length() const { return length_; } diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index cdb5ba84f1..8ecb83a1ac 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -20,7 +20,7 @@ void Init(JSContext* ctx) { }; static const NameEntry kNames[] = { - <% _.forEach(data, function(name) { %><% if (Array.isArray(name)) { %>{ "<%= name[0] %>" },<% } else { %>{ "<%= name %>" },<% } %> + <% _.forEach(data, function(name) { %><% if (Array.isArray(name)) { %>{ "<%= name[1] %>" },<% } else { %>{ "<%= name %>" },<% } %> <% }); %> }; diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl index af5dc530f5..504fcc1463 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl @@ -7,7 +7,7 @@ #ifndef <%= _.snakeCase(name).toUpperCase() %>_H_ #define <%= _.snakeCase(name).toUpperCase() %>_H_ -#include "bindings/qjs/atom_string.h" +#include "bindings/qjs/atomic_string.h" namespace kraken { namespace <%= name %> { diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index ccb1870030..5af9164ff0 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -16,6 +16,7 @@ list(APPEND KRAKEN_TEST_SOURCE list(APPEND KRAKEN_UNIT_TEST_SOURCE ./test/kraken_test_env.cc ./test/kraken_test_env.h + ./bindings/qjs/atomic_string_test.cc ./core/executing_context_test.cc ./core/frame/console_test.cc ./core/frame/module_manager_test.cc From 357906a39bdea9c9ed7a13fe2d56836d2aa886bf Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Wed, 6 Apr 2022 16:07:26 +0000 Subject: [PATCH 056/498] Committing clang-format changes --- bridge/bindings/qjs/atomic_string.cc | 6 ++--- bridge/bindings/qjs/atomic_string.h | 14 +++++------ bridge/bindings/qjs/atomic_string_test.cc | 29 +++++++---------------- bridge/bindings/qjs/qjs_engine_patch.h | 3 +-- bridge/core/executing_context_test.cc | 1 - 5 files changed, 19 insertions(+), 34 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index fa300b6de2..c3ba71aec0 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -1,13 +1,12 @@ /* -* Copyright (C) 2021-present The Kraken authors. All rights reserved. -*/ + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ #include "atomic_string.h" #include "built_in_string.h" namespace kraken { - AtomicString AtomicString::Empty(JSContext* ctx) { AtomicString tmp = built_in_string::kempty_string; return tmp; @@ -27,7 +26,6 @@ std::string AtomicString::ToStdString() const { return result; } - std::unique_ptr AtomicString::ToNativeString() const { JSValue stringValue = JS_AtomToValue(ctx_, atom_); uint32_t length; diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 3023252d04..4f7595a0af 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2021-present The Kraken authors. All rights reserved. -*/ + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ #ifndef KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ #define KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ @@ -24,11 +24,11 @@ class AtomicString { static AtomicString From(JSContext* ctx, NativeString* native_string); AtomicString() = default; - AtomicString(JSContext* ctx, const std::string& string) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())){}; - AtomicString(JSContext* ctx, JSValue value) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; - ~AtomicString() { - JS_FreeAtomRT(runtime_, atom_); - }; + AtomicString(JSContext* ctx, const std::string& string) + : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())){}; + AtomicString(JSContext* ctx, JSValue value) + : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; + ~AtomicString() { JS_FreeAtomRT(runtime_, atom_); }; // Return the undefined string value from atom key. JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); }; diff --git a/bridge/bindings/qjs/atomic_string_test.cc b/bridge/bindings/qjs/atomic_string_test.cc index f03cf721db..1964d20564 100644 --- a/bridge/bindings/qjs/atomic_string_test.cc +++ b/bridge/bindings/qjs/atomic_string_test.cc @@ -1,19 +1,19 @@ /* -* Copyright (C) 2021-present The Kraken authors. All rights reserved. -*/ + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ +#include "atomic_string.h" #include #include +#include "built_in_string.h" +#include "event_type_names.h" #include "gtest/gtest.h" -#include "qjs_engine_patch.h" -#include "atomic_string.h" #include "native_string_utils.h" -#include "event_type_names.h" -#include "built_in_string.h" +#include "qjs_engine_patch.h" using namespace kraken; -using TestCallback = void(*)(JSContext* ctx); +using TestCallback = void (*)(JSContext* ctx); void TestAtomicString(TestCallback callback) { JSRuntime* runtime = JS_NewRuntime(); @@ -77,19 +77,8 @@ TEST(AtomicString, 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 ++) { + 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]); } }); diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h index baceb1294d..79d7a72a16 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -99,8 +99,7 @@ enum { extern "C" { #endif -static inline bool __JS_AtomIsConst(JSAtom v) -{ +static inline bool __JS_AtomIsConst(JSAtom v) { #if defined(DUMP_LEAKS) && DUMP_LEAKS > 1 return (int32_t)v <= 0; #else diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index f34fac73df..041f676983 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -19,7 +19,6 @@ TEST(Context, isValid) { auto bridge = TEST_init(); EXPECT_EQ(bridge->getContext()->IsValid(), true); } - } TEST(Context, evalWithError) { From 7c0e587caa2e1fb08c3910995e5bb0c27939534f Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 7 Apr 2022 09:35:42 +0800 Subject: [PATCH 057/498] feat: add script_value test. --- bridge/bindings/qjs/converter_impl.h | 2 +- bridge/bindings/qjs/js_event_handler.cc | 2 +- bridge/bindings/qjs/js_event_listener.cc | 2 +- bridge/bindings/qjs/qjs_function.cc | 4 +- bridge/bindings/qjs/script_value.cc | 54 +++++++++++++--- bridge/bindings/qjs/script_value.h | 46 ++++---------- bridge/bindings/qjs/script_value_test.cc | 80 ++++++++++++++++++++++++ bridge/bindings/qjs/script_wrappable.cc | 2 +- bridge/core/executing_context.cc | 2 +- bridge/core/frame/module_manager.cc | 6 +- bridge/test/test.cmake | 1 + 11 files changed, 147 insertions(+), 54 deletions(-) create mode 100644 bridge/bindings/qjs/script_value_test.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 1e289ede65..06b7d3e7d9 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -107,7 +107,7 @@ struct Converter : public ConverterBase { return ScriptValue(ctx, value); } - static JSValue ToValue(JSContext* ctx, ScriptValue value) { return value.ToQuickJS(); } + static JSValue ToValue(JSContext* ctx, ScriptValue value) { return value.QJSValue(); } }; template <> diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index a64dac1911..77c7990a01 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -80,7 +80,7 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc ScriptValue result = event_handler_->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), arguments.size(), arguments.data()); if (result.IsException()) { - exception_state.ThrowException(event.ctx(), result.ToQuickJS()); + exception_state.ThrowException(event.ctx(), result.QJSValue()); return; } diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index f71271e1fc..f39d930bf9 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -21,7 +21,7 @@ void JSEventListener::InvokeInternal(EventTarget& event_target, Event& event, Ex ScriptValue result = event_listener_->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), 1, arguments); if (result.IsException()) { - exception_state.ThrowException(event.ctx(), result.ToQuickJS()); + exception_state.ThrowException(event.ctx(), result.QJSValue()); return; } } diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 8d25710606..8277a9a1eb 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -19,10 +19,10 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int JSValue argv[std::max(1, argc)]; for (int i = 0; i < argc; i++) { - argv[0 + i] = arguments[i].ToQuickJS(); + argv[0 + i] = arguments[i].QJSValue(); } - JSValue returnValue = JS_Call(ctx, function_, this_val.ToQuickJS(), argc, argv); + JSValue returnValue = JS_Call(ctx, function_, this_val.QJSValue(), argc, argv); // Free the previous duplicated function. JS_FreeValue(ctx, function_); diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index ebe7ed01fb..3a8671a7b4 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -11,7 +11,7 @@ namespace kraken { -ScriptValue ScriptValue::createErrorObject(JSContext* ctx, const char* errmsg) { +ScriptValue ScriptValue::CreateErrorObject(JSContext* ctx, const char* errmsg) { JS_ThrowInternalError(ctx, "%s", errmsg); JSValue errorObject = JS_GetException(ctx); ScriptValue result = ScriptValue(ctx, errorObject); @@ -19,7 +19,7 @@ ScriptValue ScriptValue::createErrorObject(JSContext* ctx, const char* errmsg) { return result; } -ScriptValue ScriptValue::createJSONObject(JSContext* ctx, const char* jsonString, size_t length) { +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); @@ -30,11 +30,40 @@ ScriptValue ScriptValue::Empty(JSContext* ctx) { return ScriptValue(ctx); } -JSValue ScriptValue::ToQuickJS() const { +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) { +ScriptValue ScriptValue::ToJSONStringify(ExceptionState* exception) const { JSValue stringifyedValue = JS_JSONStringify(ctx_, value_, JS_NULL, JS_NULL); ScriptValue result = ScriptValue(ctx_); // JS_JSONStringify may return JS_EXCEPTION if object is not valid. Return JS_EXCEPTION and let quickjs to handle it. @@ -47,12 +76,8 @@ ScriptValue ScriptValue::ToJSONStringify(ExceptionState* exception) { return result; } -std::unique_ptr ScriptValue::toNativeString() { - return jsValueToNativeString(ctx_, value_); -} - -std::string ScriptValue::toCString() { - return jsValueToStdString(ctx_, value_); +AtomicString ScriptValue::ToString() const { + return AtomicString(ctx_, value_); } bool ScriptValue::IsException() { @@ -63,6 +88,15 @@ bool ScriptValue::IsEmpty() { return JS_IsNull(value_) || JS_IsUndefined(value_); } +bool ScriptValue::IsObject() { + return JS_IsObject(value_); +} + + +bool ScriptValue::IsString() { + return JS_IsString(value_); +} + void ScriptValue::Trace(GCVisitor* visitor) { visitor->Trace(value_); } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 73af8d9ae9..ebfcde380c 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -13,6 +13,7 @@ #include "foundation/macros.h" #include "foundation/native_string.h" #include "gc_visitor.h" +#include "atomic_string.h" namespace kraken { @@ -27,11 +28,9 @@ class ScriptValue final { public: // Create an errorObject from string error message. - static ScriptValue createErrorObject(JSContext* ctx, const char* errmsg); + 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 from NativeString - static ScriptValue fromNativeString(JSContext* ctx, NativeString* nativeString); + static ScriptValue CreateJsonObject(JSContext* ctx, const char* jsonString, size_t length); // Create an empty ScriptValue; static ScriptValue Empty(JSContext* ctx); @@ -41,45 +40,24 @@ class ScriptValue final { ScriptValue() = default; // Copy and assignment - ScriptValue(ScriptValue const& value) { - if (&value != this) { - value_ = JS_DupValue(ctx_, value.value_); - } - ctx_ = value.ctx_; - }; - ScriptValue& operator=(const ScriptValue& value) { - if (&value != this) { - value_ = JS_DupValue(ctx_, value.value_); - } - ctx_ = value.ctx_; - return *this; - } + ScriptValue(ScriptValue const& value); + ScriptValue& operator=(const ScriptValue& value); // Move operations - ScriptValue(ScriptValue&& value) noexcept { - if (&value != this) { - value_ = JS_DupValue(ctx_, value.value_); - } - ctx_ = value.ctx_; - }; - ScriptValue& operator=(ScriptValue&& value) noexcept { - if (&value != this) { - value_ = JS_DupValue(ctx_, value.value_); - } - ctx_ = value.ctx_; - return *this; - } + ScriptValue(ScriptValue&& value) noexcept; + ScriptValue& operator=(ScriptValue&& value) noexcept; ~ScriptValue() { JS_FreeValue(ctx_, value_); }; - JSValue ToQuickJS() const; + JSValue QJSValue() const; // Create a new ScriptValue from call JSON.stringify to current value. - ScriptValue ToJSONStringify(ExceptionState* exception); - std::unique_ptr toNativeString(); - std::string toCString(); + ScriptValue ToJSONStringify(ExceptionState* exception) const; + AtomicString ToString() const; bool IsException(); bool IsEmpty(); + bool IsObject(); + bool IsString(); void Trace(GCVisitor* visitor); diff --git a/bridge/bindings/qjs/script_value_test.cc b/bridge/bindings/qjs/script_value_test.cc new file mode 100644 index 0000000000..2f4dcb1388 --- /dev/null +++ b/bridge/bindings/qjs/script_value_test.cc @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "atomic_string.h" +#include +#include +#include "script_value.h" +#include "gtest/gtest.h" + +using namespace kraken; + +using TestCallback = void (*)(JSContext* ctx); + +void TestScriptValue(TestCallback callback) { + JSRuntime* runtime = JS_NewRuntime(); + JSContext* ctx = JS_NewContext(runtime); + + callback(ctx); + + JS_FreeContext(ctx); + JS_FreeRuntime(runtime); +} + + +TEST(ScriptValue, createErrorObject) { + TestScriptValue([](JSContext* ctx) { + ScriptValue value = ScriptValue::CreateErrorObject(ctx, "error"); + EXPECT_EQ(JS_IsError(ctx, value.QJSValue()), true); + }); +} + +TEST(ScriptValue, CreateJsonObject) { + TestScriptValue([](JSContext* ctx) { + std::string code = "{\"name\": 1}"; + ScriptValue value = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); + EXPECT_EQ(value.IsObject(), true); + }); +} + +TEST(ScriptValue, Empty) { + TestScriptValue([](JSContext* ctx) { + ScriptValue empty = ScriptValue::Empty(ctx); + EXPECT_EQ(empty.IsEmpty(), true); + }); +} + +TEST(ScriptValue, ToString) { + TestScriptValue([](JSContext* ctx) { + std::string code = "{\"name\": 1}"; + ScriptValue json = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); + AtomicString string = json.ToString(); + EXPECT_STREQ(string.ToStdString().c_str(), "[object Object]"); + }); +} + +TEST(ScriptValue, CopyAssignment) { + TestScriptValue([](JSContext* ctx) { + std::string code = "{\"name\":1}"; + ScriptValue json = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); + struct P { + ScriptValue value; + }; + P p; + p.value = json; + EXPECT_STREQ(p.value.ToJSONStringify(nullptr).ToString().ToStdString().c_str(), code.c_str()); + }); +} + +TEST(ScriptValue, MoveAssignment) { + TestScriptValue([](JSContext* ctx) { + ScriptValue other; + { + std::string code = "{\"name\":1}"; + other = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); + } + + EXPECT_STREQ(other.ToJSONStringify(nullptr).ToString().ToStdString().c_str(), "{\"name\":1}"); + }); +} diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index ef25bbfd0a..f7e26927f3 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -25,7 +25,7 @@ void ScriptWrappable::InitializeQuickJSObject() { auto* wrapperTypeInfo = GetWrapperTypeInfo(); JSRuntime* runtime = runtime_; - /// ClassId should be a static ToQuickJS to make sure JSClassDef when this class are created at the first class. + /// ClassId should be a static QJSValue to make sure JSClassDef when this class are created at the first class. if (!JS_HasClassId(runtime, wrapperTypeInfo->classId)) { /// Basic template to describe the behavior about this class. JSClassDef def{}; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 1e23164327..4a8565e941 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -171,7 +171,7 @@ bool ExecutingContext::HandleException(JSValue* exc) { } bool ExecutingContext::HandleException(ScriptValue* exc) { - JSValue value = exc->ToQuickJS(); + JSValue value = exc->QJSValue(); return HandleException(&value); } diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 60d5edb8e6..63f6e3101d 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -31,7 +31,7 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha JSContext* ctx = moduleContext->context->ctx(); if (errmsg != nullptr) { - ScriptValue errorObject = ScriptValue::createErrorObject(ctx, errmsg); + ScriptValue errorObject = ScriptValue::CreateErrorObject(ctx, errmsg); ScriptValue arguments[] = {errorObject}; ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 1, arguments); if (returnValue.IsException()) { @@ -40,7 +40,7 @@ void handleInvokeModuleTransientCallback(void* ptr, int32_t contextId, const cha } else { std::u16string argumentString = std::u16string(reinterpret_cast(json->string()), json->length()); std::string utf8Arguments = toUTF8(argumentString); - ScriptValue jsonObject = ScriptValue::createJSONObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); + ScriptValue jsonObject = ScriptValue::CreateJsonObject(ctx, utf8Arguments.c_str(), utf8Arguments.size()); ScriptValue arguments[] = {jsonObject}; ScriptValue returnValue = moduleContext->callback->value()->Invoke(ctx, ScriptValue::Empty(ctx), 1, arguments); if (returnValue.IsException()) { @@ -85,7 +85,7 @@ AtomicString ModuleManager::__kraken_invoke_module__(ExecutingContext* context, ExceptionState& exception) { std::unique_ptr params; if (!paramsValue.IsEmpty()) { - params = paramsValue.ToJSONStringify(&exception).toNativeString(); + params = paramsValue.ToJSONStringify(&exception).ToString().ToNativeString(); if (exception.HasException()) { return AtomicString::Empty(context->ctx()); } diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 5af9164ff0..f469264e76 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -17,6 +17,7 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./test/kraken_test_env.cc ./test/kraken_test_env.h ./bindings/qjs/atomic_string_test.cc + ./bindings/qjs/script_value_test.cc ./core/executing_context_test.cc ./core/frame/console_test.cc ./core/frame/module_manager_test.cc From 04c5289da51764299bbbb616daaa86c18c7ac848 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 7 Apr 2022 11:43:25 +0800 Subject: [PATCH 058/498] feat: fix event target. --- bridge/CMakeLists.txt | 2 +- bridge/bindings/qjs/binding_initializer.cc | 4 +++ bridge/bindings/qjs/garbage_collected.h | 11 +------- bridge/bindings/qjs/js_event_handler.cc | 4 +++ bridge/bindings/qjs/js_event_handler.h | 3 ++ bridge/bindings/qjs/js_event_listener.cc | 10 +++++-- bridge/bindings/qjs/js_event_listener.h | 2 ++ bridge/bindings/qjs/qjs_function.cc | 5 ++++ bridge/bindings/qjs/qjs_function.h | 7 ++++- bridge/bindings/qjs/script_wrappable.cc | 7 +++-- bridge/bindings/qjs/script_wrappable.h | 4 +++ bridge/core/dom/events/event.cc | 7 ++--- bridge/core/dom/events/event.h | 1 - bridge/core/dom/events/event_listener.h | 2 ++ bridge/core/dom/events/event_listener_map.cc | 8 ++++++ bridge/core/dom/events/event_listener_map.h | 2 ++ bridge/core/dom/events/event_target.cc | 28 +++++++++++-------- bridge/core/dom/events/event_target.h | 5 +--- bridge/core/dom/events/event_target_test.cc | 3 +- .../dom/events/registered_eventListener.cc | 6 +++- .../dom/events/registered_eventListener.h | 2 ++ bridge/core/fileapi/blob.cc | 1 - bridge/core/fileapi/blob.h | 1 - bridge/test/test.cmake | 1 + 24 files changed, 85 insertions(+), 41 deletions(-) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index d2230982c0..dfd31459f7 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -65,7 +65,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") endif() if (ENABLE_ASAN) - add_compile_options(-fsanitize=address -fno-omit-frame-pointer) + add_compile_options(-fsanitize=address -fno-omit-frame-pointer -O1) add_link_options(-fsanitize=address -fno-omit-frame-pointer) endif () diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 80a1c18c29..f28bc56614 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -9,6 +9,8 @@ #include "qjs_console.h" #include "qjs_module_manager.h" #include "qjs_window.h" +#include "qjs_event_target.h" +#include "qjs_event.h" namespace kraken { @@ -16,6 +18,8 @@ void InstallBindings(ExecutingContext* context) { QJSWindow::installGlobalFunctions(context); QJSModuleManager::Install(context); QJSConsole::Install(context); + QJSEventTarget::Install(context); + QJSEvent::Install(context); } } // namespace kraken diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index 65a44c4355..c21412a1c4 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -38,22 +38,12 @@ class GarbageCollected { 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(GCVisitor* visitor) 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. @@ -64,6 +54,7 @@ class GarbageCollected { protected: GarbageCollected(){}; + ~GarbageCollected() = default; friend class MakeGarbageCollectedTrait; }; diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index 77c7990a01..8cab5ae583 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -100,4 +100,8 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // TODO: special handling for beforeunload event and onerror event. } +void JSEventHandler::Trace(GCVisitor* visitor) const { + +} + } // namespace kraken diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index c936789f74..5dc852ba93 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -53,6 +53,9 @@ class JSEventHandler : public JSBasedEventListener { // EventListener overrides: bool Matches(const EventListener&) const override; + void Trace(GCVisitor* visitor) const override; + + private: // JSBasedEventListener override: // Performs "The event handler processing algorithm" diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index f39d930bf9..d81e5d4a25 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -16,14 +16,20 @@ JSValue JSEventListener::GetEffectiveFunction(EventTarget&) { return event_listener_->ToQuickJS(); } void JSEventListener::InvokeInternal(EventTarget& event_target, Event& event, ExceptionState& exception_state) { - ScriptValue arguments[] = {ScriptValue(event.ctx(), event.ToQuickJS())}; + ScriptValue arguments[] = { + event.ToValue() + }; ScriptValue result = - event_listener_->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), 1, arguments); + event_listener_->Invoke(event.ctx(), event_target.ToValue(), 1, arguments); if (result.IsException()) { exception_state.ThrowException(event.ctx(), result.QJSValue()); return; } } +void JSEventListener::Trace(GCVisitor* visitor) const { + event_listener_->Trace(visitor); +} + } // namespace kraken diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h index d17a6cb28d..82626c673f 100644 --- a/bridge/bindings/qjs/js_event_listener.h +++ b/bridge/bindings/qjs/js_event_listener.h @@ -35,6 +35,8 @@ class JSEventListener final : public JSBasedEventListener { return other_listener && *event_listener_ == *other_listener->event_listener_; } + void Trace(GCVisitor* visitor) const override; + private: void InvokeInternal(EventTarget&, Event&, ExceptionState& exception_state) override; diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 8277a9a1eb..871c29a80a 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -29,4 +29,9 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int return ScriptValue(ctx, returnValue); } + +void QJSFunction::Trace(GCVisitor* visitor) const { + visitor->Trace(function_); +} + } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 58afd2d02b..9429eba96c 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -19,7 +19,10 @@ class QJSFunction { return std::make_shared(ctx, function); } explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), function_(JS_DupValue(ctx, function)){}; - ~QJSFunction() { JS_FreeValue(ctx_, function_); } + // This safe to free function_ at GC stage. + ~QJSFunction() { + JS_FreeValue(ctx_, function_); + } bool IsFunction(JSContext* ctx); @@ -33,6 +36,8 @@ class QJSFunction { return JS_VALUE_GET_PTR(function_) == JS_VALUE_GET_PTR(other.function_); }; + void Trace(GCVisitor* visitor) const; + private: JSContext* ctx_{nullptr}; JSValue function_{JS_NULL}; diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index f7e26927f3..fc892fa5f0 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -21,6 +21,10 @@ JSValue ScriptWrappable::ToQuickJS() { return jsObject_; } +ScriptValue ScriptWrappable::ToValue() { + return ScriptValue(ctx_, jsObject_); +} + void ScriptWrappable::InitializeQuickJSObject() { auto* wrapperTypeInfo = GetWrapperTypeInfo(); JSRuntime* runtime = runtime_; @@ -51,8 +55,7 @@ void ScriptWrappable::InitializeQuickJSObject() { /// completed. def.finalizer = [](JSRuntime* rt, JSValue val) { auto* object = static_cast(JS_GetOpaque(val, JSValueGetClassId(val))); - object->Dispose(); - free(object); + delete object; }; JS_NewClass(runtime, wrapperTypeInfo->classId, &def); diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 7494dccdec..80f3373600 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -12,6 +12,8 @@ namespace kraken { +class ScriptValue; + // 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. @@ -36,11 +38,13 @@ class ScriptWrappable : public GarbageCollected { ScriptWrappable() = delete; explicit ScriptWrappable(JSContext* ctx); + virtual ~ScriptWrappable() = default; // Returns the WrapperTypeInfo of the instance. virtual const WrapperTypeInfo* GetWrapperTypeInfo() const = 0; JSValue ToQuickJS(); + ScriptValue ToValue(); FORCE_INLINE ExecutingContext* GetExecutingContext() const { return static_cast(JS_GetContextOpaque(ctx_)); }; diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 8c7554e3d9..a138281c01 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -22,10 +22,10 @@ Event* Event::From(ExecutingContext* context, NativeEvent* native_event) { return event; } -Event::Event(ExecutingContext* context) : type_(AtomicString::Empty(context->ctx())), ScriptWrappable(context->ctx()) {} +Event::Event(ExecutingContext* context) : Event(context, AtomicString::Empty(context->ctx())) {} Event::Event(ExecutingContext* context, const AtomicString& event_type) - : type_(event_type), ScriptWrappable(context->ctx()) {} + : 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& init) : ScriptWrappable(context->ctx()), @@ -115,9 +115,6 @@ void Event::SetHandlingPassive(PassiveMode mode) { } void Event::Trace(GCVisitor* visitor) const { - visitor->Trace(target_); - visitor->Trace(current_target_); } -void Event::Dispose() const {} } // namespace kraken diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 4bd5e7d04b..7715bde853 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -184,7 +184,6 @@ class Event : public ScriptWrappable { bool FireOnlyNonCaptureListenersAtTarget() const { return fire_only_non_capture_listeners_at_target_; } void Trace(GCVisitor* visitor) const override; - void Dispose() const override; protected: PassiveMode HandlingPassive() const { return handling_passive_; } diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h index 4b43eb1579..c57e210b17 100644 --- a/bridge/core/dom/events/event_listener.h +++ b/bridge/core/dom/events/event_listener.h @@ -48,6 +48,8 @@ class EventListener { // 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; diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index 2b4289916c..5347b7f3aa 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -114,4 +114,12 @@ EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) { return nullptr; } +void EventListenerMap::Trace(GCVisitor* visitor) const { + for(const auto& entry: entries_) { + for(auto& listener : *entry.second) { + listener.Trace(visitor); + } + } +} + } // namespace kraken diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index 03bcd50ccd..d8b3f8486e 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -45,6 +45,8 @@ class EventListenerMap final { RegisteredEventListener* registered_event_listener); EventListenerVector* Find(const AtomicString& event_type); + void Trace(GCVisitor* visitor) const; + private: // EventListener handlers registered with addEventListener API. // We use vector instead of hashMap because diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 58506309a6..af7ed7d717 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -27,9 +27,14 @@ Event::PassiveMode EventPassiveMode(const RegisteredEventListener& event_listene // EventTargetData EventTargetData::EventTargetData() {} -EventTargetData::~EventTargetData() {} +EventTargetData::~EventTargetData() { + KRAKEN_LOG(VERBOSE) << "DISPOSE"; +} -void EventTargetData::Trace(GCVisitor* visitor) const {} +void EventTargetData::Trace(GCVisitor* visitor) const { + KRAKEN_LOG(VERBOSE) << "TRACE"; + event_listener_map.Trace(visitor); +} EventTarget* EventTarget::Create(ExecutingContext* context) { return makeGarbageCollected(context); @@ -93,13 +98,9 @@ bool EventTarget::dispatchEvent(Event* event, ExceptionState& exception_state) { // 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) != DispatchEventResult::kCanceledByEventHandler; + return DispatchEventInternal(*event, exception_state) != DispatchEventResult::kCanceledByEventHandler; } -void EventTarget::Trace(GCVisitor* visitor) const {} - -void EventTarget::Dispose() const {} - DispatchEventResult EventTarget::FireEventListeners(Event& event, ExceptionState& exception_state) { assert(event.WasInitialized()); @@ -180,8 +181,13 @@ bool EventTarget::RemoveEventListenerInternal(const AtomicString& event_type, return true; } -DispatchEventResult EventTarget::DispatchEventInternal(Event& event) { - return DispatchEventResult::kCanceledByDefaultEventHandler; +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; } const char* EventTarget::GetHumanReadableName() const { @@ -241,14 +247,14 @@ bool EventTarget::FireEventListeners(Event& event, event.SetHandlingPassive(Event::PassiveMode::kNotPassive); - assert(i < size); + 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 kraken diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 42f74eebee..6f11e701ea 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -108,9 +108,6 @@ class EventTarget : public ScriptWrappable { ExceptionState& exception_state); bool dispatchEvent(Event* event, ExceptionState& exception_state); - void Trace(GCVisitor* visitor) const override; - void Dispose() const override; - DispatchEventResult FireEventListeners(Event&, ExceptionState&); static DispatchEventResult GetDispatchEventResult(const Event&); @@ -125,7 +122,7 @@ class EventTarget : public ScriptWrappable { const std::shared_ptr& listener, const std::shared_ptr& options); - DispatchEventResult DispatchEventInternal(Event& event); + DispatchEventResult DispatchEventInternal(Event& event, ExceptionState& exception_state); // Subclasses should likely not override these themselves; instead, they // should subclass EventTargetWithInlineData. diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index 748f34842b..ceb88fff4a 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -6,7 +6,8 @@ #include "event_target.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" + +using namespace kraken; TEST(EventTarget, addEventListener) { bool static errorCalled = false; diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc index cf62f6e5b7..3085cbd841 100644 --- a/bridge/core/dom/events/registered_eventListener.cc +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -8,7 +8,7 @@ namespace kraken { -RegisteredEventListener::RegisteredEventListener() : RegisteredEventListener(nullptr, nullptr) {} +RegisteredEventListener::RegisteredEventListener() = default; RegisteredEventListener::RegisteredEventListener(const std::shared_ptr& listener, std::shared_ptr options) @@ -50,6 +50,10 @@ bool RegisteredEventListener::ShouldFire(const Event& event) const { 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()); diff --git a/bridge/core/dom/events/registered_eventListener.h b/bridge/core/dom/events/registered_eventListener.h index 04d0d77571..ac995f6d58 100644 --- a/bridge/core/dom/events/registered_eventListener.h +++ b/bridge/core/dom/events/registered_eventListener.h @@ -45,6 +45,8 @@ class RegisteredEventListener final { bool ShouldFire(const Event&) const; + void Trace(GCVisitor* visitor) const; + private: std::shared_ptr callback_; unsigned use_capture_ : 1; diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index dddefe0276..3493fbeec0 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -79,7 +79,6 @@ const char* Blob::GetHumanReadableName() const { return "Blob"; } void Blob::Trace(GCVisitor* visitor) const {} -void Blob::Dispose() const {} Blob* Blob::slice(ExceptionState& exception_state) { return slice(0, _data.size(), exception_state); diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index a8752ede06..b8d1741859 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -64,7 +64,6 @@ class Blob : public ScriptWrappable { const char* GetHumanReadableName() const override; void Trace(GCVisitor* visitor) const override; - void Dispose() const override; protected: void PopulateBlobData(std::vector>& data); diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index f469264e76..62b8f08b7d 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -21,6 +21,7 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./core/executing_context_test.cc ./core/frame/console_test.cc ./core/frame/module_manager_test.cc + ./core/dom/events/event_target_test.cc # ./bindings/qjs/bom/timer_test.cc # ./bindings/qjs/qjs_patch_test.cc # ./bindings/qjs/garbage_collected_test.cc From 48d63cc6fa5700ef644d407d0bba6ad072cc5998 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 7 Apr 2022 15:49:31 +0800 Subject: [PATCH 059/498] feat: add node list and node headers. --- bridge/CMakeLists.txt | 12 + bridge/bindings/qjs/converter_impl.h | 11 +- bridge/bindings/qjs/wrapper_type_info.h | 3 +- bridge/core/dom/child_node_list.cc | 55 ++ bridge/core/dom/child_node_list.h | 56 ++ bridge/core/dom/collection_index_cache.h | 192 ++++++ bridge/core/dom/container_node.cc | 5 + bridge/core/dom/container_node.h | 411 ++++++++++++ bridge/core/dom/events/event_target.cc | 2 +- bridge/core/dom/events/event_target.h | 2 +- bridge/core/dom/node.cc | 587 +----------------- bridge/core/dom/node.d.ts | 127 ++++ bridge/core/dom/node.h | 484 ++++++++++++--- bridge/core/dom/node_list.h | 36 ++ bridge/foundation/ui_command_buffer.h | 2 +- .../code_generator/src/idl/generateSource.ts | 2 +- 16 files changed, 1308 insertions(+), 679 deletions(-) create mode 100644 bridge/core/dom/child_node_list.cc create mode 100644 bridge/core/dom/child_node_list.h create mode 100644 bridge/core/dom/collection_index_cache.h create mode 100644 bridge/core/dom/container_node.cc create mode 100644 bridge/core/dom/container_node.h create mode 100644 bridge/core/dom/node.d.ts create mode 100644 bridge/core/dom/node_list.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index dfd31459f7..cb26d51354 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -283,6 +283,16 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/events/event_listener_map.cc core/dom/events/event_target_impl.cc core/dom/events/event_target_impl.h + core/dom/node.cc + core/dom/node.h + core/dom/collection_index_cache.h + core/dom/child_node_list.cc + core/dom/child_node_list.h + core/dom/node_lists_node_data.cc + core/dom/node_lists_node_data.h + core/dom/node_list.h + core/dom/container_node.cc + core/dom/container_node.h core/events/error_event.cc core/events/error_event.h # core/dom/character_data.cc @@ -323,6 +333,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_event_init.cc out/qjs_event_target.cc out/qjs_event_target.h + out/qjs_node.h + out/qjs_node.cc out/event_type_names.h out/event_type_names.cc out/built_in_string.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 06b7d3e7d9..b40657f065 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -20,6 +20,7 @@ #include "qjs_error_event_init.h" #include "qjs_event_init.h" #include "qjs_event_listener_options.h" +#include "qjs_node.h" namespace kraken { @@ -48,7 +49,7 @@ struct Converter, std::enable_if_t -struct Converter, std::enable_if::ImplType>::value>> +struct Converter, std::enable_if_t::ImplType>::value>> : public ConverterBase> { using ImplType = typename Converter::ImplType; @@ -423,6 +424,14 @@ struct Converter : public ConverterBase +struct Converter : public ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return toScriptWrappable(value); + } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index c796d08197..9b3f4cf461 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -17,7 +17,8 @@ enum { JS_CLASS_BLOB, JS_CLASS_EVENT, JS_CLASS_ERROREVENT, - JS_CLASS_EVENTTARGET + JS_CLASS_EVENTTARGET, + JS_CLASS_NODE }; // This struct provides a way to store a bunch of information that is helpful diff --git a/bridge/core/dom/child_node_list.cc b/bridge/core/dom/child_node_list.cc new file mode 100644 index 0000000000..c2a96c5b11 --- /dev/null +++ b/bridge/core/dom/child_node_list.cc @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "child_node_list.h" + +namespace kraken { + +ChildNodeList::ChildNodeList(JSContext* ctx, ContainerNode* parent) : parent_(parent), NodeList(ctx) {} +ChildNodeList::~ChildNodeList() = default; + +Node* ChildNodeList::VirtualOwnerNode() const { + return &OwnerNode(); +} + + +Node* ChildNodeList::item(unsigned index) const { + return collection_index_cache_.NodeAt(*this, index); +} + +Node* ChildNodeList::TraverseForwardToOffset(unsigned offset, + Node& current_node, + unsigned& current_offset) const { + assert(current_offset < offset); + assert(OwnerNode().childNodes() == this); + assert(&OwnerNode() == current_node.parentNode()); + for (Node* next = current_node.nextSibling(); next; + next = next->nextSibling()) { + if (++current_offset == offset) + return next; + } + return nullptr; +} + +Node* ChildNodeList::TraverseBackwardToOffset(unsigned offset, + Node& current_node, + unsigned& current_offset) const { + assert(current_offset > offset); + assert(OwnerNode().childNodes() == this); + assert(&OwnerNode() == current_node.parentNode()); + for (Node* previous = current_node.previousSibling(); previous; + previous = previous->previousSibling()) { + if (--current_offset == offset) + return previous; + } + return nullptr; +} + +void ChildNodeList::Trace(GCVisitor* visitor) const { + visitor->Trace(parent_); + collection_index_cache_.Trace(visitor); + NodeList::Trace(visitor); +} + +} diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h new file mode 100644 index 0000000000..7175ba4cd0 --- /dev/null +++ b/bridge/core/dom/child_node_list.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ +#define KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ + +#include "bindings/qjs/gc_visitor.h" +#include "node_list.h" +#include "collection_index_cache.h" +#include "container_node.h" + +namespace kraken { + +class ChildNodeList : public NodeList { + public: + explicit ChildNodeList(JSContext* ctx, ContainerNode* root_node); + ~ChildNodeList() override; + + // DOM API. + unsigned length() const override { + return collection_index_cache_.NodeCount(*this); + } + + Node* item(unsigned index) const override; + + // Non-DOM API. + void InvalidateCache() { collection_index_cache_.Invalidate(); } + ContainerNode& OwnerNode() const { return *parent_; } + + 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; + + ContainerNode* parent_; + mutable CollectionIndexCache collection_index_cache_; +}; + +} + +#endif // KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ diff --git a/bridge/core/dom/collection_index_cache.h b/bridge/core/dom/collection_index_cache.h new file mode 100644 index 0000000000..bd75a1222d --- /dev/null +++ b/bridge/core/dom/collection_index_cache.h @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ +#define KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ + +#include +#include "foundation/macros.h" +#include "bindings/qjs/gc_visitor.h" + +namespace kraken { + +template +class CollectionIndexCache { + KRAKEN_DISALLOW_NEW(); + + public: + CollectionIndexCache(); + + bool IsEmpty(const Collection& collection) { + if (IsCachedNodeCountValid()) + return !CachedNodeCount(); + if (CachedNode()) + return false; + return !NodeAt(collection, 0); + } + + bool HasExactlyOneNode(const Collection& collection) { + if (IsCachedNodeCountValid()) + return CachedNodeCount() == 1; + if (CachedNode()) + return !CachedNodeIndex() && !NodeAt(collection, 1); + return NodeAt(collection, 0) && !NodeAt(collection, 1); + } + + unsigned NodeCount(const Collection&); + NodeType* NodeAt(const Collection&, unsigned index); + + void Invalidate(); + + void NodeInserted(); + void NodeRemoved(); + + virtual void Trace(GCVisitor* visitor) const { visitor->Trace(current_node_); } + + protected: + FORCE_INLINE NodeType* CachedNode() const { return current_node_; } + 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); + + NodeType* current_node_; + unsigned cached_node_count_; + unsigned cached_node_index_ : 31; + unsigned is_length_cache_valid_ : 1; +}; + +template +CollectionIndexCache::CollectionIndexCache() + : current_node_(nullptr), cached_node_count_(0), cached_node_index_(0), is_length_cache_valid_(false) {} + +template +void CollectionIndexCache::Invalidate() { + current_node_ = nullptr; + is_length_cache_valid_ = false; +} + +template +void CollectionIndexCache::NodeInserted() { + cached_node_count_++; + current_node_ = nullptr; +} + +template +void CollectionIndexCache::NodeRemoved() { + cached_node_count_--; + current_node_ = nullptr; +} + +template +inline unsigned CollectionIndexCache::NodeCount(const Collection& collection) { + if (IsCachedNodeCountValid()) + return CachedNodeCount(); + + NodeAt(collection, UINT_MAX); + assert(IsCachedNodeCountValid()); + + return CachedNodeCount(); +} + +template +inline NodeType* CollectionIndexCache::NodeAt(const Collection& collection, unsigned index) { + if (IsCachedNodeCountValid() && index >= CachedNodeCount()) + return nullptr; + + if (CachedNode()) { + if (index > CachedNodeIndex()) + return NodeAfterCachedNode(collection, index); + if (index < CachedNodeIndex()) + return NodeBeforeCachedNode(collection, index); + return CachedNode(); + } + + // No valid cache yet, let's find the first matching element. + NodeType* first_node = collection.TraverseToFirst(); + if (!first_node) { + // The collection is empty. + SetCachedNodeCount(0); + return nullptr; + } + SetCachedNode(first_node, 0); + return index ? NodeAfterCachedNode(collection, index) : first_node; +} + +template +inline NodeType* CollectionIndexCache::NodeBeforeCachedNode(const Collection& collection, + unsigned index) { + assert(CachedNode()); // Cache should be valid. + unsigned current_index = CachedNodeIndex(); + assert(current_index > index); + + // Determine if we should traverse from the beginning of the collection + // instead of the cached node. + bool first_is_closer = index < current_index - index; + if (first_is_closer || !collection.CanTraverseBackward()) { + NodeType* first_node = collection.TraverseToFirst(); + assert(first_node); + SetCachedNode(first_node, 0); + return index ? NodeAfterCachedNode(collection, index) : first_node; + } + + // Backward traversal from the cached node to the requested index. + assert(collection.CanTraverseBackward()); + NodeType* current_node = collection.TraverseBackwardToOffset(index, *CachedNode(), current_index); + assert(current_node); + SetCachedNode(current_node, current_index); + return current_node; +} + +template +inline NodeType* CollectionIndexCache::NodeAfterCachedNode(const Collection& collection, + unsigned index) { + assert(CachedNode()); // Cache should be valid. + unsigned current_index = CachedNodeIndex(); + assert(current_index < index); + + // Determine if we should traverse from the end of the collection instead of + // the cached node. + bool last_is_closer = IsCachedNodeCountValid() && CachedNodeCount() - index < index - current_index; + if (last_is_closer && collection.CanTraverseBackward()) { + NodeType* last_item = collection.TraverseToLast(); + assert(last_item); + SetCachedNode(last_item, CachedNodeCount() - 1); + if (index < CachedNodeCount() - 1) + return NodeBeforeCachedNode(collection, index); + return last_item; + } + + // Forward traversal from the cached node to the requested index. + NodeType* current_node = collection.TraverseForwardToOffset(index, *CachedNode(), current_index); + if (!current_node) { + // Did not find the node. On plus side, we now know the length. + if (IsCachedNodeCountValid()) + assert(current_index + 1 == CachedNodeCount()); + SetCachedNodeCount(current_index + 1); + return nullptr; + } + SetCachedNode(current_node, current_index); + return current_node; +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc new file mode 100644 index 0000000000..e96b330d00 --- /dev/null +++ b/bridge/core/dom/container_node.cc @@ -0,0 +1,5 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "container_node.h" diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h new file mode 100644 index 0000000000..8ce80c3486 --- /dev/null +++ b/bridge/core/dom/container_node.h @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ +#define KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ + +#include "node.h" +#include "bindings/qjs/gc_visitor.h" + +namespace kraken { + +class ContainerNode : public Node { + ~ContainerNode() override; + + Node* firstChild() const { return first_child_; } + Node* lastChild() const { return last_child_; } + bool hasChildren() const { return first_child_; } + bool HasChildren() const { return first_child_; } + + bool HasOneChild() const { + return first_child_ && !first_child_->nextSibling(); + } + bool HasOneTextChild() const { + return HasOneChild() && first_child_->IsTextNode(); + } + bool HasChildCount(unsigned) const; + + HTMLCollection* Children(); + + unsigned CountChildren() const; + + Element* QuerySelector(const AtomicString& selectors, ExceptionState&); + Element* QuerySelector(const AtomicString& selectors); + StaticElementList* QuerySelectorAll(const AtomicString& selectors, + ExceptionState&); + StaticElementList* QuerySelectorAll(const AtomicString& selectors); + + Node* InsertBefore(Node* new_child, Node* ref_child, ExceptionState&); + Node* InsertBefore(Node* new_child, Node* ref_child); + Node* ReplaceChild(Node* new_child, Node* old_child, ExceptionState&); + Node* ReplaceChild(Node* new_child, Node* old_child); + Node* RemoveChild(Node* child, ExceptionState&); + Node* RemoveChild(Node* child); + 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; + + Element* getElementById(const AtomicString& id) const; + HTMLCollection* getElementsByTagName(const AtomicString&); + HTMLCollection* getElementsByTagNameNS(const AtomicString& namespace_uri, + const AtomicString& local_name); + NodeList* getElementsByName(const AtomicString& element_name); + HTMLCollection* getElementsByClassName(const AtomicString& class_names); + RadioNodeList* GetRadioNodeList(const AtomicString&, + bool only_match_img_elements = false); + + // These methods are only used during parsing. + // They don't send DOM mutation events or accept DocumentFragments. + void ParserAppendChild(Node*); + void ParserRemoveChild(Node&); + void ParserInsertBefore(Node* new_child, Node& ref_child); + void ParserTakeAllChildrenFrom(ContainerNode&); + + void RemoveChildren( + SubtreeModificationAction = kDispatchSubtreeModifiedEvent); + + void CloneChildNodesFrom(const ContainerNode&, CloneChildrenFlag); + + void AttachLayoutTree(AttachContext&) override; + void DetachLayoutTree(bool performing_reattach = false) override; + PhysicalRect BoundingBox() const final; + void SetFocused(bool, mojom::blink::FocusType) override; + void SetHasFocusWithinUpToAncestor(bool, Node* ancestor); + void FocusStateChanged(); + void FocusVisibleStateChanged(); + void FocusWithinStateChanged(); + void SetDragged(bool) override; + void RemovedFrom(ContainerNode& insertion_point) override; + + bool ChildrenOrSiblingsAffectedByFocus() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocus); + } + void SetChildrenOrSiblingsAffectedByFocus() { + SetRestyleFlag(DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocus); + } + + bool ChildrenOrSiblingsAffectedByFocusVisible() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocusVisible); + } + void SetChildrenOrSiblingsAffectedByFocusVisible() { + SetRestyleFlag( + DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocusVisible); + } + + bool ChildrenOrSiblingsAffectedByFocusWithin() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocusWithin); + } + void SetChildrenOrSiblingsAffectedByFocusWithin() { + SetRestyleFlag( + DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocusWithin); + } + + bool ChildrenOrSiblingsAffectedByHover() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenOrSiblingsAffectedByHover); + } + void SetChildrenOrSiblingsAffectedByHover() { + SetRestyleFlag(DynamicRestyleFlags::kChildrenOrSiblingsAffectedByHover); + } + + bool ChildrenOrSiblingsAffectedByActive() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenOrSiblingsAffectedByActive); + } + void SetChildrenOrSiblingsAffectedByActive() { + SetRestyleFlag(DynamicRestyleFlags::kChildrenOrSiblingsAffectedByActive); + } + + bool ChildrenOrSiblingsAffectedByDrag() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenOrSiblingsAffectedByDrag); + } + void SetChildrenOrSiblingsAffectedByDrag() { + SetRestyleFlag(DynamicRestyleFlags::kChildrenOrSiblingsAffectedByDrag); + } + + bool ChildrenAffectedByFirstChildRules() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenAffectedByFirstChildRules); + } + void SetChildrenAffectedByFirstChildRules() { + SetRestyleFlag(DynamicRestyleFlags::kChildrenAffectedByFirstChildRules); + } + + bool ChildrenAffectedByLastChildRules() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenAffectedByLastChildRules); + } + void SetChildrenAffectedByLastChildRules() { + SetRestyleFlag(DynamicRestyleFlags::kChildrenAffectedByLastChildRules); + } + + bool ChildrenAffectedByDirectAdjacentRules() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenAffectedByDirectAdjacentRules); + } + void SetChildrenAffectedByDirectAdjacentRules() { + SetRestyleFlag(DynamicRestyleFlags::kChildrenAffectedByDirectAdjacentRules); + } + + bool ChildrenAffectedByIndirectAdjacentRules() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenAffectedByIndirectAdjacentRules); + } + void SetChildrenAffectedByIndirectAdjacentRules() { + SetRestyleFlag( + DynamicRestyleFlags::kChildrenAffectedByIndirectAdjacentRules); + } + + bool ChildrenAffectedByForwardPositionalRules() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenAffectedByForwardPositionalRules); + } + void SetChildrenAffectedByForwardPositionalRules() { + SetRestyleFlag( + DynamicRestyleFlags::kChildrenAffectedByForwardPositionalRules); + } + + bool ChildrenAffectedByBackwardPositionalRules() const { + return HasRestyleFlag( + DynamicRestyleFlags::kChildrenAffectedByBackwardPositionalRules); + } + void SetChildrenAffectedByBackwardPositionalRules() { + SetRestyleFlag( + DynamicRestyleFlags::kChildrenAffectedByBackwardPositionalRules); + } + + bool AffectedByFirstChildRules() const { + return HasRestyleFlag(DynamicRestyleFlags::kAffectedByFirstChildRules); + } + void SetAffectedByFirstChildRules() { + SetRestyleFlag(DynamicRestyleFlags::kAffectedByFirstChildRules); + } + + bool AffectedByLastChildRules() const { + return HasRestyleFlag(DynamicRestyleFlags::kAffectedByLastChildRules); + } + void SetAffectedByLastChildRules() { + SetRestyleFlag(DynamicRestyleFlags::kAffectedByLastChildRules); + } + + bool NeedsAdjacentStyleRecalc() const; + + // FIXME: These methods should all be renamed to something better than + // "check", since it's not clear that they alter the style bits of siblings + // and children. + enum SiblingCheckType { + kFinishedParsingChildren, + kSiblingElementInserted, + kSiblingElementRemoved + }; + void CheckForSiblingStyleChanges(SiblingCheckType, + Element* changed_element, + Node* node_before_change, + Node* node_after_change); + void RecalcDescendantStyles(const StyleRecalcChange, + const StyleRecalcContext&); + void RebuildChildrenLayoutTrees(WhitespaceAttacher&); + void RebuildLayoutTreeForChild(Node* child, WhitespaceAttacher&); + + // ----------------------------------------------------------------------------- + // Notification of document structure changes (see core/dom/node.h for more + // notification methods) + + enum class ChildrenChangeType : uint8_t { + kElementInserted, + kNonElementInserted, + kElementRemoved, + kNonElementRemoved, + kAllChildrenRemoved, + kTextChanged + }; + enum class ChildrenChangeSource : uint8_t { kAPI, kParser }; + struct ChildrenChange { + STACK_ALLOCATED(); + + public: + static ChildrenChange ForInsertion(Node& node, + Node* unchanged_previous, + Node* unchanged_next, + ChildrenChangeSource by_parser) { + ChildrenChange change = {node.IsElementNode() + ? ChildrenChangeType::kElementInserted + : ChildrenChangeType::kNonElementInserted, + by_parser, + &node, + unchanged_previous, + unchanged_next, + {}, + String()}; + return change; + } + + static ChildrenChange ForRemoval(Node& node, + Node* previous_sibling, + Node* next_sibling, + ChildrenChangeSource by_parser) { + ChildrenChange change = {node.IsElementNode() + ? ChildrenChangeType::kElementRemoved + : ChildrenChangeType::kNonElementRemoved, + by_parser, + &node, + previous_sibling, + next_sibling, + {}, + String()}; + return change; + } + + bool IsChildInsertion() const { + return type == ChildrenChangeType::kElementInserted || + type == ChildrenChangeType::kNonElementInserted; + } + bool IsChildRemoval() const { + return type == ChildrenChangeType::kElementRemoved || + type == ChildrenChangeType::kNonElementRemoved; + } + bool IsChildElementChange() const { + return type == ChildrenChangeType::kElementInserted || + type == ChildrenChangeType::kElementRemoved; + } + + bool ByParser() const { return by_parser == ChildrenChangeSource::kParser; } + + ChildrenChangeType type; + ChildrenChangeSource by_parser; + Node* sibling_changed = nullptr; + // |siblingBeforeChange| is + // - siblingChanged.previousSibling before node removal + // - siblingChanged.previousSibling after single node insertion + // - previousSibling of the first inserted node after multiple node + // insertion + Node* sibling_before_change = nullptr; + // |siblingAfterChange| is + // - siblingChanged.nextSibling before node removal + // - siblingChanged.nextSibling after single node insertion + // - nextSibling of the last inserted node after multiple node insertion. + Node* sibling_after_change = nullptr; + // List of removed nodes for ChildrenChangeType::kAllChildrenRemoved. + // Only populated if ChildrenChangedAllChildrenRemovedNeedsList() returns + // true. + HeapVector> removed_nodes; + // |old_text| is mostly empty, only used for text node changes. + const String& old_text; + }; + + // Notifies the node that it's list of children have changed (either by adding + // or removing child nodes), or a child node that is of the type + // kCdataSectionNode, kTextNode or kCommentNode has changed its value. + // + // ChildrenChanged() implementations may modify the DOM tree, and may dispatch + // synchronous events. + virtual void ChildrenChanged(const ChildrenChange&); + + // Provides ChildrenChange::removed_nodes for kAllChildrenRemoved. + virtual bool ChildrenChangedAllChildrenRemovedNeedsList() const; + + virtual bool ChildrenCanHaveStyle() const { return true; } + + void Trace(Visitor*) const override; + + protected: + ContainerNode(TreeScope*, ConstructionType = kCreateContainer); + + // |attr_name| and |owner_element| are only used for element attribute + // modifications. |ChildrenChange| is either nullptr or points to a + // ChildNode::ChildrenChange structure that describes the changes in the tree. + // If non-null, blink may preserve caches that aren't affected by the change. + void InvalidateNodeListCachesInAncestors(const QualifiedName* attr_name, + Element* attribute_owner_element, + const ChildrenChange*); + + void SetFirstChild(Node* child) { + first_child_ = child; + } + void SetLastChild(Node* child) { + last_child_ = child; + } + + // Utility functions for NodeListsNodeData API. + template + Collection* EnsureCachedCollection(CollectionType); + template + Collection* EnsureCachedCollection(CollectionType, const AtomicString& name); + template + Collection* EnsureCachedCollection(CollectionType, + const AtomicString& namespace_uri, + const AtomicString& local_name); + template + Collection* CachedCollection(CollectionType); + + 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. + + NodeListsNodeData& EnsureNodeLists(); + 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); + void DidInsertNodeVector( + const NodeVector&, + Node* next, + const 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 WillRemoveChildren(); + void WillRemoveChild(Node& child); + void RemoveDetachedChildrenInContainer(ContainerNode&); + void AddChildNodesToDeletionQueue(Node*&, Node*&, ContainerNode&); + + void NotifyNodeInserted(Node&, + ChildrenChangeSource = ChildrenChangeSource::kAPI); + void NotifyNodeInsertedInternal( + Node&, + NodeVector& post_insertion_notification_targets); + void NotifyNodeRemoved(Node&); + + bool HasRestyleFlag(DynamicRestyleFlags mask) const { + return HasRareData() && HasRestyleFlagInternal(mask); + } + bool HasRestyleFlags() const { + return HasRareData() && HasRestyleFlagsInternal(); + } + void SetRestyleFlag(DynamicRestyleFlags); + bool HasRestyleFlagInternal(DynamicRestyleFlags) const; + bool HasRestyleFlagsInternal() const; + + bool RecheckNodeInsertionStructuralPrereq(const NodeVector&, + const Node* next, + ExceptionState&); + inline bool CheckParserAcceptChild(const Node& new_child) const; + inline bool IsHostIncludingInclusiveAncestorOfThis(const Node&, + ExceptionState&) const; + inline bool IsChildTypeAllowed(const Node& child) const; + + Node* first_child_; + Node* last_child_; +}; + +} + +#endif // KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index af7ed7d717..d21040ba35 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -36,7 +36,7 @@ void EventTargetData::Trace(GCVisitor* visitor) const { event_listener_map.Trace(visitor); } -EventTarget* EventTarget::Create(ExecutingContext* context) { +EventTarget* EventTarget::Create(ExecutingContext* context, ExceptionState& exception_state) { return makeGarbageCollected(context); } diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 6f11e701ea..40363c778c 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -83,7 +83,7 @@ class EventTarget : public ScriptWrappable { public: using ImplType = EventTarget*; - static EventTarget* Create(ExecutingContext* context); + static EventTarget* Create(ExecutingContext* context, ExceptionState& exception_state); EventTarget() = delete; explicit EventTarget(ExecutingContext* context); diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 1390c4823c..778cc4a9be 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -1,6 +1,5 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. + * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ #include "node.h" @@ -13,588 +12,8 @@ namespace kraken { -void bindNode(std::unique_ptr& context) { - auto* contextData = context->contextData(); - JSValue constructor = Node::constructor(context.get()); - JSValue prototype = Node::prototype(context.get()); - - // Install methods to Node.prototype. - INSTALL_FUNCTION(Node, prototype, cloneNode, 1); - INSTALL_FUNCTION(Node, prototype, appendChild, 1); - INSTALL_FUNCTION(Node, prototype, remove, 0); - INSTALL_FUNCTION(Node, prototype, removeChild, 1); - INSTALL_FUNCTION(Node, prototype, insertBefore, 2); - INSTALL_FUNCTION(Node, prototype, replaceChild, 2); - - context->defineGlobalProperty("Node", constructor); -} - -JSValue Node::constructor(ExecutionContext* context) { - return context->contextData()->constructorForType(&nodeTypeInfo); -} - -JSValue Node::prototype(ExecutionContext* context) { - return context->contextData()->prototypeForType(&nodeTypeInfo); -} - -Node* Node::create(JSContext* ctx) { - return nullptr; -} - -JSClassID Node::classId{0}; - -IMPL_FUNCTION(Node, cloneNode)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(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 (self->nodeType == NodeType::ELEMENT_NODE) { - JSValue newElementValue = copyNodeValue(ctx, self); - auto* newElement = static_cast(JS_GetOpaque(newElementValue, JSValueGetClassId(newElementValue))); - - if (deep) { - traverseCloneNode(ctx, self, newElement); - } - return newElement->jsObject; - } else if (self->nodeType == NodeType::TEXT_NODE) { - auto textNode = static_cast(self); - JSValue newTextNode = copyNodeValue(ctx, static_cast(textNode)); - return newTextNode; - } else if (self->nodeType == NodeType::DOCUMENT_FRAGMENT_NODE) { - JSValue newFragmentValue = JS_CallConstructor(ctx, DocumentFragment::constructor(self->context()), 0, nullptr); - auto* newFragment = static_cast(JS_GetOpaque(newFragmentValue, JSValueGetClassId(newFragmentValue))); - - if (deep) { - traverseCloneNode(ctx, self, newFragment); - } - - return newFragmentValue; - } - return JS_NULL; -} - -IMPL_FUNCTION(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 self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - if (self == 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* node = static_cast(JS_GetOpaque(nodeValue, JSValueGetClassId(nodeValue))); - - if (node == nullptr || node->ownerDocument() != self->ownerDocument()) { - return JS_ThrowTypeError(ctx, "Failed to execute 'appendChild' on 'Node': first arguments should be an Node type."); - } - - if (node == self) { - return JS_ThrowTypeError(ctx, - "Failed to execute 'appendChild' on 'Node': The new child element contains the parent."); - } - - if (node->hasNodeFlag(Node::NodeFlag::IsDocumentFragment)) { - size_t len = arrayGetLength(ctx, node->childNodes); - for (int i = 0; i < len; i++) { - JSValue n = JS_GetPropertyUint32(ctx, node->childNodes, i); - self->internalAppendChild(static_cast(JS_GetOpaque(n, JSValueGetClassId(n)))); - JS_FreeValue(ctx, n); - } - - JS_SetPropertyStr(ctx, node->childNodes, "length", JS_NewUint32(ctx, 0)); - } else { - self->ensureDetached(node); - self->internalAppendChild(node); - } - - return JS_DupValue(ctx, node->jsObject); -} -IMPL_FUNCTION(Node, remove)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - self->internalRemove(); - return JS_UNDEFINED; -} -IMPL_FUNCTION(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 self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - auto node = static_cast(JS_GetOpaque(nodeValue, JSValueGetClassId(nodeValue))); - - if (node == nullptr || node->ownerDocument() != self->ownerDocument()) { - return JS_ThrowTypeError(ctx, "Failed to execute 'removeChild' on 'Node': 1st arguments is not a Node object."); - } - - auto removedNode = self->internalRemoveChild(node); - return JS_DupValue(ctx, removedNode->jsObject); -} - -IMPL_FUNCTION(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."); - } - - Node* reference = nullptr; - - if (JS_IsObject(referenceNodeValue)) { - reference = static_cast(JS_GetOpaque(referenceNodeValue, JSValueGetClassId(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 self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - auto node = static_cast(JS_GetOpaque(nodeValue, JSValueGetClassId(nodeValue))); - - if (node == nullptr || node->ownerDocument() != self->ownerDocument()) { - return JS_ThrowTypeError(ctx, "Failed to execute 'insertBefore' on 'Node': parameter 1 is not of type 'Node'"); - } - - if (node->hasNodeFlag(Node::NodeFlag::IsDocumentFragment)) { - size_t len = arrayGetLength(ctx, node->childNodes); - for (int i = 0; i < len; i++) { - JSValue n = JS_GetPropertyUint32(ctx, node->childNodes, i); - self->internalInsertBefore(static_cast(JS_GetOpaque(n, JSValueGetClassId(n))), reference); - JS_FreeValue(ctx, n); - } - - // Clear fragment childNodes reference. - JS_SetPropertyStr(ctx, node->childNodes, "length", JS_NewUint32(ctx, 0)); - } else { - self->ensureDetached(node); - self->internalInsertBefore(node, reference); - } - - return JS_NULL; -} - -IMPL_FUNCTION(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 self = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - auto newChild = static_cast(JS_GetOpaque(newChildValue, JSValueGetClassId(newChildValue))); - auto oldChild = static_cast(JS_GetOpaque(oldChildValue, JSValueGetClassId(oldChildValue))); - - if (oldChild == nullptr || JS_VALUE_GET_PTR(oldChild->parentNode) != JS_VALUE_GET_PTR(self->jsObject) || - oldChild->ownerDocument() != self->ownerDocument()) { - return JS_ThrowTypeError( - ctx, "Failed to execute 'replaceChild' on 'Node': The node to be replaced is not a child of this node."); - } - - if (newChild == nullptr || newChild->ownerDocument() != self->ownerDocument()) { - return JS_ThrowTypeError(ctx, "Failed to execute 'replaceChild' on 'Node': The new node is not a type of node."); - } - - if (newChild->hasNodeFlag(Node::NodeFlag::IsDocumentFragment)) { - size_t len = arrayGetLength(ctx, newChild->childNodes); - for (int i = 0; i < len; i++) { - JSValue n = JS_GetPropertyUint32(ctx, newChild->childNodes, i); - auto* node = static_cast(JS_GetOpaque(n, JSValueGetClassId(n))); - self->internalInsertBefore(node, oldChild); - JS_FreeValue(ctx, n); - } - self->internalRemoveChild(oldChild); - // Clear fragment childNodes reference. - JS_SetPropertyStr(ctx, newChild->childNodes, "length", JS_NewUint32(ctx, 0)); - } else { - self->ensureDetached(newChild); - self->internalReplaceChild(newChild, oldChild); - } - return JS_DupValue(ctx, oldChild->jsObject); -} - -void Node::traverseCloneNode(JSContext* ctx, Node* baseNode, Node* 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, JSValueGetClassId(n))); - JSValue newNodeValue = copyNodeValue(ctx, node); - auto newNode = static_cast(JS_GetOpaque(newNodeValue, JSValueGetClassId(newNodeValue))); - targetNode->ensureDetached(newNode); - targetNode->internalAppendChild(newNode); - // element node needs recursive child nodes. - if (node->nodeType == NodeType::ELEMENT_NODE) { - traverseCloneNode(ctx, node, newNode); - } - JS_FreeValue(ctx, newNodeValue); - JS_FreeValue(ctx, n); - } -} - -JSValue Node::copyNodeValue(JSContext* ctx, Node* node) { - if (node->nodeType == NodeType::ELEMENT_NODE) { - auto* element = reinterpret_cast(node); - - /* createElement */ - std::string tagName = element->getRegisteredTagName(); - JSValue tagNameValue = JS_NewString(element->ctx(), tagName.c_str()); - JSValue arguments[] = {tagNameValue}; - JSValue newElementValue = - JS_CallConstructor(element->context()->ctx(), - element->context()->contextData()->constructorForType(&elementTypeInfo), 1, arguments); - JS_FreeValue(ctx, tagNameValue); - - auto* newElement = static_cast(JS_GetOpaque(newElementValue, JSValueGetClassId(newElementValue))); - - /* copy attributes */ - newElement->m_attributes->copyWith(element->m_attributes); - - /* copy style */ - newElement->m_style->copyWith(element->m_style); - - /* copy properties */ - EventTarget::copyNodeProperties(newElement, element); - - std::string newNodeEventTargetId = std::to_string(newElement->eventTargetId()); - std::unique_ptr args_01 = stringToNativeString(newNodeEventTargetId); - element->context()->uiCommandBuffer()->addCommand(element->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::constructor(textNode->context()), 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* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - return JS_NewBool(ctx, node->isConnected()); -} - -IMPL_PROPERTY_GETTER(Node, ownerDocument)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - return JS_DupValue(ctx, node->ownerDocument()->jsObject); -} - -IMPL_PROPERTY_GETTER(Node, firstChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - auto* instance = node->firstChild(); - return instance != nullptr ? instance->jsObject : JS_NULL; -} - -IMPL_PROPERTY_GETTER(Node, lastChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - auto* instance = node->lastChild(); - return instance != nullptr ? instance->jsObject : JS_NULL; +Node* Node::Create(ExecutingContext* context, ExceptionState& exception_state) { + exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor"); } -IMPL_PROPERTY_GETTER(Node, parentNode)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - return JS_DupValue(ctx, node->parentNode); -} - -IMPL_PROPERTY_GETTER(Node, previousSibling)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - auto* instance = node->previousSibling(); - return instance != nullptr ? instance->jsObject : JS_NULL; -} - -IMPL_PROPERTY_GETTER(Node, nextSibling)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - auto* instance = node->nextSibling(); - return instance != nullptr ? instance->jsObject : JS_NULL; -} - -IMPL_PROPERTY_GETTER(Node, nodeType)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - return JS_NewUint32(ctx, node->nodeType); -} - -IMPL_PROPERTY_GETTER(Node, textContent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - return node->internalGetTextContent(); -} -IMPL_PROPERTY_SETTER(Node, textContent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* node = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - node->internalSetTextContent(argv[0]); - return JS_NULL; -} - -bool Node::isConnected() { - bool _isConnected = this == ownerDocument(); - auto parent = static_cast(JS_GetOpaque(parentNode, JSValueGetClassId(parentNode))); - - while (parent != nullptr && !_isConnected) { - _isConnected = parent == ownerDocument(); - JSValue parentParentNode = parent->parentNode; - parent = static_cast(JS_GetOpaque(parentParentNode, JSValueGetClassId(parentParentNode))); - } - - return _isConnected; -} -Document* Node::ownerDocument() { - if (nodeType == NodeType::DOCUMENT_NODE) { - return nullptr; - } - - return context()->document(); -} -Node* Node::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, JSValueGetClassId(result))); -} -Node* Node::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, JSValueGetClassId(result))); -} -Node* Node::previousSibling() { - if (JS_IsNull(parentNode)) - return nullptr; - - auto* parent = static_cast(JS_GetOpaque(parentNode, JSValueGetClassId(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, JSValueGetClassId(result))); - } - - return nullptr; -} -Node* Node::nextSibling() { - if (JS_IsNull(parentNode)) - return nullptr; - auto* parent = static_cast(JS_GetOpaque(parentNode, JSValueGetClassId(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, JSValueGetClassId(result))); - } - - return nullptr; -} -void Node::internalAppendChild(Node* node) { - arrayPushValue(m_ctx, childNodes, node->jsObject); - node->setParentNode(this); - - node->_notifyNodeInsert(this); - - std::string nodeEventTargetId = std::to_string(node->eventTargetId()); - std::string position = std::string("beforeend"); - - std::unique_ptr args_01 = stringToNativeString(nodeEventTargetId); - std::unique_ptr args_02 = stringToNativeString(position); - - context()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::insertAdjacentNode, *args_01, *args_02, nullptr); -} -void Node::internalRemove() { - if (JS_IsNull(parentNode)) - return; - auto* parent = static_cast(JS_GetOpaque(parentNode, JSValueGetClassId(parentNode))); - parent->internalRemoveChild(this); -} -void Node::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, JSValueGetClassId(v))); - node->removeParentNode(); - node->_notifyNodeRemoved(this); - node->context()->uiCommandBuffer()->addCommand(node->eventTargetId(), UICommand::removeNode, nullptr); - JS_FreeValue(m_ctx, v); - } - - JS_SetPropertyStr(m_ctx, childNodes, "length", JS_NewUint32(m_ctx, 0)); -} -Node* Node::internalRemoveChild(Node* 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->context()->uiCommandBuffer()->addCommand(node->eventTargetId(), UICommand::removeNode, nullptr); - } - - return node; -} -JSValue Node::internalInsertBefore(Node* node, Node* 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, JSValueGetClassId(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->eventTargetId()); - std::string position = std::string("beforebegin"); - - std::unique_ptr args_01 = stringToNativeString(nodeEventTargetId); - std::unique_ptr args_02 = stringToNativeString(position); - - context()->uiCommandBuffer()->addCommand(referenceNode->eventTargetId(), UICommand::insertAdjacentNode, *args_01, - *args_02, nullptr); - } - } - - return JS_NULL; -} -JSValue Node::internalGetTextContent() { - return JS_NULL; -} -void Node::internalSetTextContent(JSValue content) {} -JSValue Node::internalReplaceChild(Node* newChild, Node* 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->eventTargetId()); - std::string position = std::string("afterend"); - - std::unique_ptr args_01 = stringToNativeString(newChildEventTargetId); - std::unique_ptr args_02 = stringToNativeString(position); - - context()->uiCommandBuffer()->addCommand(oldChild->eventTargetId(), UICommand::insertAdjacentNode, *args_01, *args_02, - nullptr); - - context()->uiCommandBuffer()->addCommand(oldChild->eventTargetId(), UICommand::removeNode, nullptr); - - return oldChild->jsObject; -} - -void Node::setParentNode(Node* parent) { - if (!JS_IsNull(parentNode)) { - JS_FreeValue(m_ctx, parentNode); - } - - parentNode = JS_DupValue(m_ctx, parent->jsObject); -} - -void Node::removeParentNode() { - if (!JS_IsNull(parentNode)) { - JS_FreeValue(m_ctx, parentNode); - } - - parentNode = JS_NULL; -} - -void Node::refer() { - JS_DupValue(m_ctx, jsObject); - list_add_tail(&nodeLink.link, &context()->node_job_list); -} -void Node::unrefer() { - list_del(&nodeLink.link); - JS_FreeValue(m_ctx, jsObject); -} -void Node::_notifyNodeRemoved(Node* node) {} -void Node::_notifyNodeInsert(Node* node) {} -void Node::ensureDetached(Node* node) { - auto* nodeParent = static_cast(JS_GetOpaque(node->parentNode, JSValueGetClassId(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 Node::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - EventTarget::trace(rt, val, mark_func); - - // Should check object is already inited before gc mark. - if (JS_IsObject(parentNode)) - JS_MarkValue(rt, parentNode, mark_func); -} - -void Node::dispose() const {} - } // namespace kraken diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts new file mode 100644 index 0000000000..11aac25ca3 --- /dev/null +++ b/bridge/core/dom/node.d.ts @@ -0,0 +1,127 @@ +import { EventTarget } from './events/event_target'; + +/** Node is an interface from which a number of DOM API object types inherit. It allows those types to be treated similarly; for example, inheriting the same set of methods, or being tested in the same way. */ +interface Node extends EventTarget { + /** + * Returns the children. + */ + readonly childNodes: Node[]; + /** + * Returns the first child. + */ + readonly firstChild: Node | null; + /** + * Returns true if node is connected and false otherwise. + */ + readonly isConnected: boolean; + /** + * Returns the last child. + */ + readonly lastChild: Node | null; + /** + * Returns the next sibling. + */ + readonly nextSibling: Node | null; + /** + * Returns a string appropriate for the type of node. + */ + readonly nodeName: string; + /** + * Returns the type of node. + */ + readonly nodeType: number; + nodeValue: string | null; + /** + * Returns the node document. Returns null for documents. + // */ + // readonly ownerDocument: Document | null; + // /** + // * Returns the parent element. + // */ + // readonly parentElement: HTMLElement | null; + // /** + // * Returns the parent. + // */ + // readonly parentNode: Node & ParentNode | 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; + replaceChild(newChild: Node, oldChild: Node): Node; + readonly ATTRIBUTE_NODE: number; + /** + * node is a CDATASection node. + */ + readonly CDATA_SECTION_NODE: number; + /** + * node is a Comment node. + */ + readonly COMMENT_NODE: number; + /** + * node is a DocumentFragment node. + */ + readonly DOCUMENT_FRAGMENT_NODE: number; + /** + * node is a document. + */ + readonly DOCUMENT_NODE: number; + /** + * Set when other is a descendant of node. + */ + readonly DOCUMENT_POSITION_CONTAINED_BY: number; + /** + * Set when other is an ancestor of node. + */ + readonly DOCUMENT_POSITION_CONTAINS: number; + /** + * Set when node and other are not in the same tree. + */ + readonly DOCUMENT_POSITION_DISCONNECTED: number; + /** + * Set when other is following node. + */ + readonly DOCUMENT_POSITION_FOLLOWING: number; + readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: number; + /** + * Set when other is preceding node. + */ + readonly DOCUMENT_POSITION_PRECEDING: number; + /** + * node is a doctype. + */ + readonly DOCUMENT_TYPE_NODE: number; + /** + * node is an element. + */ + readonly ELEMENT_NODE: number; + readonly ENTITY_NODE: number; + readonly ENTITY_REFERENCE_NODE: number; + readonly NOTATION_NODE: number; + /** + * node is a ProcessingInstruction node. + */ + readonly PROCESSING_INSTRUCTION_NODE: number; + /** + * node is a Text node. + */ + readonly TEXT_NODE: number; + + new(): Node; +} diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 796f5d414c..26111bf280 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -1,6 +1,5 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. + * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ #ifndef KRAKENBRIDGE_NODE_H @@ -9,113 +8,420 @@ #include #include +#include "foundation/macros.h" #include "events/event_target.h" +#include "node_list.h" namespace kraken { +const int kDOMNodeTypeShift = 2; +const int kElementNamespaceTypeShift = 4; +const int kNodeStyleChangeShift = 15; +const int kNodeCustomElementShift = 17; + class Element; class Document; class DocumentFragment; - -void bindNode(std::unique_ptr& context); - -enum NodeType { - ELEMENT_NODE = 1, - TEXT_NODE = 3, - COMMENT_NODE = 8, - DOCUMENT_NODE = 9, - DOCUMENT_TYPE_NODE = 10, - DOCUMENT_FRAGMENT_NODE = 11 -}; - -class Node; class TextNode; class Document; +class ContainerNode; -struct NodeJob { - Node* nodeInstance; - list_head link; +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(); public: - static JSClassID classId; - static JSValue constructor(ExecutionContext* context); - static JSValue prototype(ExecutionContext* context); - static Node* create(JSContext* ctx); - - DEFINE_FUNCTION(cloneNode); - DEFINE_FUNCTION(appendChild); - DEFINE_FUNCTION(remove); - DEFINE_FUNCTION(removeChild); - DEFINE_FUNCTION(insertBefore); - DEFINE_FUNCTION(replaceChild); - - 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); - - 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); } - - bool isConnected(); - Document* ownerDocument(); - Node* firstChild(); - Node* lastChild(); - Node* previousSibling(); - Node* nextSibling(); - - void setParentNode(Node* parent); - void removeParentNode(); - NodeType nodeType; - JSValue parentNode{JS_NULL}; - JSValue childNodes{JS_NewArray(m_ctx)}; - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - void dispose() const override; + enum NodeType { + kElementNode = 1, + kAttributeNode = 2, + kTextNode = 3, + kCommentNode = 8, + kDocumentNode = 9, + kDocumentTypeNode = 10, + kDocumentFragmentNode = 11, + }; - protected: - NodeJob nodeLink{this}; - - void refer(); - void unrefer(); - void internalAppendChild(Node* node); - void internalRemove(); - void internalClearChild(); - Node* internalRemoveChild(Node* node); - JSValue internalInsertBefore(Node* node, Node* referenceNode); - virtual JSValue internalGetTextContent(); - virtual void internalSetTextContent(JSValue content); - JSValue internalReplaceChild(Node* newChild, Node* oldChild); - - virtual void _notifyNodeRemoved(Node* node); - virtual void _notifyNodeInsert(Node* node); + using ImplType = Node*; + static Node* Create(ExecutingContext* context, ExceptionState& exception_state); + + // DOM methods & attributes for Node + bool HasTagName(const AtomicString&) const; + virtual AtomicString nodeName() const = 0; + virtual AtomicString nodeValue() const; + virtual void setNodeValue(const AtomicString&, ExceptionState&); + virtual NodeType getNodeType() const = 0; + ContainerNode* parentNode() const; + Element* parentElement() const; + ContainerNode* ParentElementOrShadowRoot() const; + ContainerNode* ParentElementOrDocumentFragment() const; + Node* previousSibling() const { return previous_; } + Node* nextSibling() const { return next_; } + NodeList* childNodes(); + Node* firstChild() const; + Node* lastChild() const; + + Node& TreeRoot() const; + Node& ShadowIncludingRoot() const; + + // TODO: support following APIs. +// void Prepend( +// const HeapVector>& nodes, +// ExceptionState& exception_state); +// void Append( +// const HeapVector>& nodes, +// ExceptionState& exception_state); +// void Before( +// const HeapVector>& nodes, +// ExceptionState& exception_state); +// void After( +// const HeapVector>& nodes, +// ExceptionState& exception_state); +// void ReplaceWith( +// const HeapVector>& nodes, +// ExceptionState& exception_state); +// void ReplaceChildren( +// const HeapVector>& nodes, +// ExceptionState& exception_state); + + void remove(ExceptionState&); + void remove(); + + Node* PseudoAwareNextSibling() const; + Node* PseudoAwarePreviousSibling() const; + Node* PseudoAwareFirstChild() const; + Node* PseudoAwareLastChild() const; + + Node* insertBefore(Node* new_child, Node* ref_child, ExceptionState&); + Node* insertBefore(Node* new_child, Node* ref_child); + Node* replaceChild(Node* new_child, Node* old_child, ExceptionState&); + Node* replaceChild(Node* new_child, Node* old_child); + Node* removeChild(Node* child, ExceptionState&); + Node* removeChild(Node* child); + Node* appendChild(Node* new_child, ExceptionState&); + Node* appendChild(Node* new_child); + + bool hasChildren() const { return firstChild(); } + Node* cloneNode(bool deep, ExceptionState&) const; + + // https://dom.spec.whatwg.org/#concept-node-clone + virtual Node* Clone(Document&, CloneChildrenFlag) const = 0; + + // This is not web-exposed. We should rename it or remove it. + Node* cloneNode(bool deep) const; + void normalize(); + + bool isEqualNode(Node*) const; + bool isSameNode(const Node* other) const { return this == other; } + + AtomicString textContent(bool convert_brs_to_newlines = false) const; + virtual void setTextContent(const AtomicString&); + + // Other methods (not part of DOM) + FORCE_INLINE bool IsTextNode() const { + return GetDOMNodeType() == DOMNodeType::kText; + } + FORCE_INLINE bool IsContainerNode() const { + return GetFlag(kIsContainerFlag); + } + FORCE_INLINE bool IsElementNode() const { + return GetDOMNodeType() == DOMNodeType::kElement; + } + FORCE_INLINE bool IsDocumentFragment() const { + return GetDOMNodeType() == DOMNodeType::kDocumentFragment; + } + + FORCE_INLINE bool IsHTMLElement() const { + return GetElementNamespaceType() == ElementNamespaceType::kHTML; + } + FORCE_INLINE bool IsMathMLElement() const { + return GetElementNamespaceType() == ElementNamespaceType::kMathML; + } + FORCE_INLINE bool IsSVGElement() const { + return GetElementNamespaceType() == ElementNamespaceType::kSVG; + } + + CustomElementState GetCustomElementState() const { + return static_cast(node_flags_ & + kCustomElementStateMask); + } + bool IsCustomElement() const { + return GetCustomElementState() != CustomElementState::kUncustomized; + } + void SetCustomElementState(CustomElementState); + + virtual bool IsMediaControlElement() const { return false; } + virtual bool IsMediaControls() const { return false; } + virtual bool IsMediaElement() const { return false; } + virtual bool IsTextTrackContainer() const { return false; } + virtual bool IsVTTElement() const { return false; } + virtual bool IsAttributeNode() const { return false; } + virtual bool IsCharacterDataNode() const { return false; } + virtual bool IsFrameOwnerElement() const { return false; } + virtual bool IsMediaRemotingInterstitial() const { return false; } + virtual bool IsPictureInPictureInterstitial() 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. + bool IsStyledElement() const { + return IsHTMLElement() || IsSVGElement() || IsMathMLElement(); + } + + bool IsDocumentNode() const; + + // Node's parent, shadow tree host. + ContainerNode* ParentOrShadowHostNode() const; + Element* ParentOrShadowHostElement() const; + void SetParentOrShadowHostNode(ContainerNode*); + + // Knows about all kinds of hosts. + ContainerNode* ParentOrShadowHostOrTemplateHostNode() const; + + // Returns the parent node, but nullptr if the parent node is a ShadowRoot. + 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; } + + bool HasEventTargetData() const { return GetFlag(kHasEventTargetDataFlag); } + void SetHasEventTargetData(bool flag) { + SetFlag(flag, kHasEventTargetDataFlag); + } + + unsigned NodeIndex() const; + + // Returns the DOM ownerDocument attribute. This method never returns null, + // except in the case of a Document node. + Document* ownerDocument() const; + + // Returns the document associated with this node. A Document node returns + // itself. + Document& GetDocument() const { } + + // Returns true if this node is connected to a document, false otherwise. + // See https://dom.spec.whatwg.org/#connected for the definition. + bool isConnected() const { return GetFlag(kIsConnectedFlag); } + + bool IsInDocumentTree() const { return isConnected(); } + + bool IsDocumentTypeNode() const { return getNodeType() == kDocumentTypeNode; } + virtual bool ChildTypeAllowed(NodeType) const { return false; } + unsigned CountChildren() const; + + bool IsDescendantOf(const Node*) const; + bool IsDescendantOrShadowDescendantOf(const Node*) const; + bool contains(const Node*) const; + // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor + bool IsShadowIncludingInclusiveAncestorOf(const Node&) const; + // https://dom.spec.whatwg.org/#concept-shadow-including-ancestor + bool IsShadowIncludingAncestorOf(const Node&) const; + bool ContainsIncludingHostElements(const Node&) const; + Node* CommonAncestor(const Node&, + ContainerNode* (*parent)(const Node&)) const; + + // Whether or not a selection can be started in this object + virtual bool CanStartSelection() const; + + void NotifyPriorityScrollAnchorStatusChanged(); + + // --------------------------------------------------------------------------- + // Notification of document structure changes (see container_node.h for more + // notification methods) + // + // At first, Blink notifies the node that it has been inserted into the + // document. This is called during document parsing, and also when a node is + // added through the DOM methods insertBefore(), appendChild() or + // replaceChild(). The call happens _after_ the node has been added to the + // tree. This is similar to the DOMNodeInsertedIntoDocument DOM event, but + // does not require the overhead of event dispatching. + // + // Blink notifies this callback regardless if the subtree of the node is a + // document tree or a floating subtree. Implementation can determine the type + // of subtree by seeing insertion_point->isConnected(). For performance + // reasons, notifications are delivered only to ContainerNode subclasses if + // the insertion_point is not in a document tree. + // + // There is another callback, DidNotifySubtreeInsertionsToDocument(), + // which is called after all the descendants are notified, if this node was + // inserted into the document tree. Only a few subclasses actually need + // this. To utilize this, the node should return + // kInsertionShouldCallDidNotifySubtreeInsertions from InsertedInto(). + // + // InsertedInto() implementations must not modify the DOM tree, and must not + // dispatch synchronous events. On the other hand, + // DidNotifySubtreeInsertionsToDocument() may modify the DOM tree, and may + // dispatch synchronous events. + enum InsertionNotificationRequest { + kInsertionDone, + kInsertionShouldCallDidNotifySubtreeInsertions + }; + + virtual InsertionNotificationRequest InsertedInto( + ContainerNode& insertion_point); + virtual void DidNotifySubtreeInsertionsToDocument() {} + + // Notifies the node that it is no longer part of the tree. + // + // This is a dual of InsertedInto(), and is similar to the + // DOMNodeRemovedFromDocument DOM event, 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); + + +// NodeListsNodeData* NodeLists(); +// void ClearNodeLists(); + + enum ShadowTreesTreatment { + kTreatShadowTreesAsDisconnected, + kTreatShadowTreesAsComposed + }; + + uint16_t compareDocumentPosition( + const Node*, + ShadowTreesTreatment = kTreatShadowTreesAsDisconnected) const; + + EventTargetData* GetEventTargetData() override; + EventTargetData& EnsureEventTargetData() override; + + bool IsFinishedParsingChildren() const { + return GetFlag(kIsFinishedParsingChildrenFlag); + } + + void SetHasDuplicateAttributes() { SetFlag(kHasDuplicateAttributes); } + bool HasDuplicateAttribute() const { + return GetFlag(kHasDuplicateAttributes); + } + + bool SelfOrAncestorHasDirAutoAttribute() const { + return GetFlag(kSelfOrAncestorHasDirAutoAttribute); + } + void SetSelfOrAncestorHasDirAutoAttribute() { + SetFlag(kSelfOrAncestorHasDirAutoAttribute); + } + void ClearSelfOrAncestorHasDirAutoAttribute() { + ClearFlag(kSelfOrAncestorHasDirAutoAttribute); + } + + void Trace(GCVisitor*) const override; private: - ObjectProperty m_childNodes{context(), jsObject, "childNodes", childNodes}; - void ensureDetached(Node* node); + enum NodeFlags : uint32_t { + // Node type flags. These never change once created. + kIsContainerFlag = 1 << 1, + kDOMNodeTypeMask = 0x3 << kDOMNodeTypeShift, + kElementNamespaceTypeMask = 0x3 << kElementNamespaceTypeShift, - static void traverseCloneNode(JSContext* ctx, Node* baseNode, Node* targetNode); - static JSValue copyNodeValue(JSContext* ctx, Node* node); -}; + // Tree state flags. These change when the element is added/removed + // from a DOM tree. + kIsConnectedFlag = 1 << 8, + + // Set by the parser when the children are done parsing. + kIsFinishedParsingChildrenFlag = 1 << 10, + + kCustomElementStateMask = 0x7 << kNodeCustomElementShift, + kHasNameOrIsEditingTextFlag = 1 << 20, + kHasEventTargetDataFlag = 1 << 21, + + kHasDuplicateAttributes = 1 << 24, + + kSelfOrAncestorHasDirAutoAttribute = 1 << 27, + kDefaultNodeFlags = kIsFinishedParsingChildrenFlag, -auto nodeCreator = - [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) - -> JSValue { return JS_ThrowTypeError(ctx, "Illegal constructor"); }; + // 2 bits remaining. + }; -const WrapperTypeInfo nodeTypeInfo = {"Node", &eventTargetTypeInfo, nodeCreator}; + 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, + }; + + FORCE_INLINE DOMNodeType GetDOMNodeType() const { + return static_cast(node_flags_ & kDOMNodeTypeMask); + } + + enum class ElementNamespaceType : uint32_t { + kHTML = 0, + kMathML = 1 << kElementNamespaceTypeShift, + kSVG = 2 << kElementNamespaceTypeShift, + kOther = 3 << kElementNamespaceTypeShift, + }; + FORCE_INLINE ElementNamespaceType GetElementNamespaceType() const { + return static_cast(node_flags_ & + kElementNamespaceTypeMask); + } + + protected: + enum ConstructionType { + kCreateOther = kDefaultNodeFlags | + static_cast(DOMNodeType::kOther) | + static_cast(ElementNamespaceType::kOther), + kCreateText = kDefaultNodeFlags | + static_cast(DOMNodeType::kText) | + static_cast(ElementNamespaceType::kOther), + kCreateContainer = kDefaultNodeFlags | kIsContainerFlag | + static_cast(DOMNodeType::kOther) | + static_cast(ElementNamespaceType::kOther), + kCreateElement = kDefaultNodeFlags | kIsContainerFlag | + static_cast(DOMNodeType::kElement) | + static_cast(ElementNamespaceType::kOther), + kCreateDocumentFragment = + kDefaultNodeFlags | kIsContainerFlag | + static_cast(DOMNodeType::kDocumentFragment) | + static_cast(ElementNamespaceType::kOther), + kCreateHTMLElement = kDefaultNodeFlags | kIsContainerFlag | + static_cast(DOMNodeType::kElement) | + static_cast(ElementNamespaceType::kHTML), + kCreateMathMLElement = + kDefaultNodeFlags | kIsContainerFlag | + static_cast(DOMNodeType::kElement) | + static_cast(ElementNamespaceType::kMathML), + kCreateSVGElement = kDefaultNodeFlags | kIsContainerFlag | + static_cast(DOMNodeType::kElement) | + static_cast(ElementNamespaceType::kSVG), + kCreateDocument = kCreateContainer | kIsConnectedFlag, + }; + + Node(ConstructionType); + + void SetIsFinishedParsingChildren(bool value) { + SetFlag(value, kIsFinishedParsingChildrenFlag); + } + + private: + uint32_t node_flags_; + Node* parent_or_shadow_host_node_; + Node* previous_; + Node* next_; +}; } // namespace kraken diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h new file mode 100644 index 0000000000..58247a0847 --- /dev/null +++ b/bridge/core/dom/node_list.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ +#define KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ + +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +class Node; + +class NodeList : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + public: + NodeList(JSContext* ctx): ScriptWrappable(ctx) {}; + ~NodeList() override = default; + + // DOM methods & attributes for NodeList + virtual unsigned length() const = 0; + virtual Node* item(unsigned index) const = 0; + + // Other methods (not part of DOM) + virtual bool IsEmptyNodeList() const { return false; } + virtual bool IsChildNodeList() const { return false; } + + virtual Node* VirtualOwnerNode() const { return nullptr; } + + protected: + NodeList() = default; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 6c68097711..a67fd2bd19 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -15,7 +15,7 @@ namespace kraken { class ExecutingContext; -enum UICommand { +enum class UICommand { createElement, createTextNode, createComment, diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index a2616faf76..2829a52d0e 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -172,7 +172,7 @@ function generateFunctionCallBody(blob: IDLBlob, declaration: FunctionDeclaratio call = `auto* self = toScriptWrappable<${getClassName(blob)}>(this_val); ${returnValueAssignment} self->${generateCallMethodName(declaration.name)}(${minimalRequiredArgc > 0 ? `${requiredArguments.join(',')}` : 'exception_state'});`; } else { - call = `${returnValueAssignment} ${getClassName(blob)}::${generateCallMethodName(declaration.name)}(context${minimalRequiredArgc > 0 ? `,${requiredArguments.join(',')}` : ''});`; + call = `${returnValueAssignment} ${getClassName(blob)}::${generateCallMethodName(declaration.name)}(context, ${requiredArguments.join(',')});`; } return `${requiredArgumentsInit.join('\n')} From 07e4aeee1597f3d0fc4fd1b03af483a9ac0f8107 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 7 Apr 2022 21:05:25 +0800 Subject: [PATCH 060/498] feat: add node and container node --- bridge/CMakeLists.txt | 18 +- bridge/bindings/qjs/atomic_string.cc | 8 + bridge/bindings/qjs/atomic_string.h | 5 + bridge/bindings/qjs/garbage_collected.h | 2 +- bridge/core/dom/child_node_list.cc | 2 +- bridge/core/dom/child_node_list.h | 2 +- bridge/core/dom/container_node.cc | 362 ++++++++ bridge/core/dom/container_node.h | 322 ++------ bridge/core/dom/document.cc | 652 --------------- bridge/core/dom/document.h | 142 ++-- bridge/core/dom/document_fragment.cc | 36 +- bridge/core/dom/document_fragment.d.ts | 5 + bridge/core/dom/document_fragment.h | 35 +- bridge/core/dom/element.cc | 912 +-------------------- bridge/core/dom/element.h | 172 +--- bridge/core/dom/element_data.cc | 5 + bridge/core/dom/element_data.h | 23 + bridge/core/dom/empty_node_list.cc | 18 + bridge/core/dom/empty_node_list.h | 31 + bridge/core/dom/events/event.cc | 2 +- bridge/core/dom/events/event.h | 6 +- bridge/core/dom/events/event_target.cc | 2 +- bridge/core/dom/node.cc | 34 +- bridge/core/dom/node.h | 249 ++---- bridge/core/dom/node_data.cc | 38 + bridge/core/dom/node_data.h | 41 + bridge/core/dom/node_list.h | 5 +- bridge/core/dom/space_split_string.cc | 180 ++++ bridge/core/dom/space_split_string.h | 91 ++ bridge/core/events/error_event.cc | 6 +- bridge/core/fileapi/blob.cc | 8 +- bridge/core/frame/dom_timer_coordinator.cc | 3 - bridge/core/frame/dom_timer_coordinator.h | 1 - bridge/core/html/html_collection.cc | 11 + bridge/core/html/html_collection.h | 21 + 35 files changed, 1174 insertions(+), 2276 deletions(-) create mode 100644 bridge/core/dom/document_fragment.d.ts create mode 100644 bridge/core/dom/element_data.cc create mode 100644 bridge/core/dom/element_data.h create mode 100644 bridge/core/dom/empty_node_list.cc create mode 100644 bridge/core/dom/empty_node_list.h create mode 100644 bridge/core/dom/node_data.cc create mode 100644 bridge/core/dom/node_data.h create mode 100644 bridge/core/dom/space_split_string.cc create mode 100644 bridge/core/dom/space_split_string.h create mode 100644 bridge/core/html/html_collection.cc create mode 100644 bridge/core/html/html_collection.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index cb26d51354..6afc71aeb1 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -285,16 +285,30 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/events/event_target_impl.h core/dom/node.cc core/dom/node.h + core/dom/element.cc + core/dom/element.h + core/dom/element_data.cc + core/dom/element_data.h + core/dom/space_split_string.cc + core/dom/space_split_string.h + core/dom/document.cc + core/dom/document.h + core/dom/node_data.cc + core/dom/node_data.h + core/dom/document_fragment.h + core/dom/document_fragment.cc core/dom/collection_index_cache.h core/dom/child_node_list.cc core/dom/child_node_list.h - core/dom/node_lists_node_data.cc - core/dom/node_lists_node_data.h + core/dom/empty_node_list.cc + core/dom/empty_node_list.h core/dom/node_list.h core/dom/container_node.cc core/dom/container_node.h core/events/error_event.cc core/events/error_event.h + core/html/html_collection.cc + core/html/html_collection.h # core/dom/character_data.cc # core/dom/character_data.h # core/dom/comment.cc diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index c3ba71aec0..c83dba9a50 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -19,6 +19,14 @@ AtomicString AtomicString::From(JSContext* ctx, NativeString* native_string) { return result; } +bool AtomicString::IsNull() const { + return atom_ == JS_ATOM_NULL; +} + +bool AtomicString::IsEmpty() const { + return *this == built_in_string::kempty_string; +} + std::string AtomicString::ToStdString() const { const char* buf = JS_AtomToCString(ctx_, atom_); std::string result = std::string(buf); diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 4f7595a0af..a8bb43dcdf 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -33,6 +33,11 @@ class AtomicString { // Return the undefined string value from atom key. JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); }; + bool IsNull() const; + bool IsEmpty() const; + + JSAtom Impl() const { return atom_; } + [[nodiscard]] std::string ToStdString() const; [[nodiscard]] std::unique_ptr ToNativeString() const; diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index c21412a1c4..1db1f4f12a 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -71,7 +71,7 @@ class MakeGarbageCollectedTrait { }; template -T* makeGarbageCollected(Args&&... args) { +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."); diff --git a/bridge/core/dom/child_node_list.cc b/bridge/core/dom/child_node_list.cc index c2a96c5b11..7d10de8d8f 100644 --- a/bridge/core/dom/child_node_list.cc +++ b/bridge/core/dom/child_node_list.cc @@ -6,7 +6,7 @@ namespace kraken { -ChildNodeList::ChildNodeList(JSContext* ctx, ContainerNode* parent) : parent_(parent), NodeList(ctx) {} +ChildNodeList::ChildNodeList(ContainerNode* parent) : parent_(parent), NodeList(parent->ctx()) {} ChildNodeList::~ChildNodeList() = default; Node* ChildNodeList::VirtualOwnerNode() const { diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h index 7175ba4cd0..d7ae0bb5f3 100644 --- a/bridge/core/dom/child_node_list.h +++ b/bridge/core/dom/child_node_list.h @@ -14,7 +14,7 @@ namespace kraken { class ChildNodeList : public NodeList { public: - explicit ChildNodeList(JSContext* ctx, ContainerNode* root_node); + explicit ChildNodeList(ContainerNode* root_node); ~ChildNodeList() override; // DOM API. diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index e96b330d00..a9bd66de8b 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -3,3 +3,365 @@ */ #include "container_node.h" +#include "document_fragment.h" +#include "bindings/qjs/garbage_collected.h" + +namespace kraken { + +HTMLCollection* ContainerNode::Children() {} + +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.getNodeType()); + + for (Node* node = child_fragment->firstChild(); node; + node = node->nextSibling()) { + if (!ChildTypeAllowed(node->getNodeType())) + 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; + post_insertion_notification_targets.reserve(kInitialNodeVectorSize); + 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); + } + } + DidInsertNodeVector(targets, next, 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; + } + + WillRemoveChild(*child); + + { + Node* prev = child->previousSibling(); + Node* next = child->nextSibling(); + { + RemoveBetween(prev, next, *child); + NotifyNodeRemoved(*child); + } + ChildrenChanged(ChildrenChange::ForRemoval(*child, prev, next, + ChildrenChangeSource::kAPI)); + } + 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); + } + DidInsertNodeVector(targets, nullptr, post_insertion_notification_targets); + return new_child; +} + +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()) { + DCHECK(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); + } + + // This should never happen, but also protect release builds from tree + // corruption. + DCHECK(!new_child.IsPseudoElement()); + if (new_child.IsPseudoElement()) { + exception_state.ThrowDOMException( + DOMExceptionCode::kHierarchyRequestError, + "The new child element is a pseudo-element."); + return false; + } + + 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.ThrowDOMException( + DOMExceptionCode::kHierarchyRequestError, + "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; + + // Do any prep work needed before actually starting to detach + // and remove... e.g. stop loading frames, fire unload events. + WillRemoveChildren(); + +// { +// // Removing a node from a selection can cause widget updates. +// GetDocument().NodeChildrenWillBeRemoved(*this); +// } + + std::vector removed_nodes; + const bool children_changed = ChildrenChangedAllChildrenRemovedNeedsList(); + { + { + while (Node* child = first_child_) { + RemoveBetween(nullptr, child->nextSibling(), *child); + NotifyNodeRemoved(*child); + if (children_changed) + removed_nodes.push_back(child); + } + } + + ChildrenChange change = {ChildrenChangeType::kAllChildrenRemoved, + ChildrenChangeSource::kAPI, + nullptr, + nullptr, + nullptr, + std::move(removed_nodes), + ""}; + ChildrenChanged(change); + } +} + +void ContainerNode::ParserAppendChild(Node* new_child) { + assert(new_child); + assert(!new_child->IsDocumentFragment()); + + if (!CheckParserAcceptChild(*new_child)) + return; + + // FIXME: parserRemoveChild can run script which could then insert the + // newChild back into the page. Loop until the child is actually removed. + // See: fast/parser/execute-script-during-adoption-agency-removal.html + while (ContainerNode* parent = new_child->parentNode()) + parent->ParserRemoveChild(*new_child); + + if (GetDocument() != new_child->GetDocument()) + GetDocument().adoptNode(new_child, ASSERT_NO_EXCEPTION); + + { + EventDispatchForbiddenScope assert_no_event_dispatch; + ScriptForbiddenScope forbid_script; + + AdoptAndAppendChild()(*this, *new_child, nullptr); + DCHECK_EQ(new_child->ConnectedSubframeCount(), 0u); + ChildListMutationScope(*this).ChildAdded(*new_child); + } + + NotifyNodeInserted(*new_child, ChildrenChangeSource::kParser); +} + +} // namespace kraken diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index 8ce80c3486..a517228dcc 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -5,12 +5,22 @@ #ifndef KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ #define KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ -#include "node.h" +#include #include "bindings/qjs/gc_visitor.h" +#include "node.h" namespace kraken { +class HTMLCollection; + +// This constant controls how much buffer is initially allocated +// for a Node Vector that is used to store child Nodes of a given Node. +// FIXME: Optimize the value. +const int kInitialNodeVectorSize = 11; +using NodeVector = std::vector; + class ContainerNode : public Node { + public: ~ContainerNode() override; Node* firstChild() const { return first_child_; } @@ -18,46 +28,23 @@ class ContainerNode : public Node { bool hasChildren() const { return first_child_; } bool HasChildren() const { return first_child_; } - bool HasOneChild() const { - return first_child_ && !first_child_->nextSibling(); - } - bool HasOneTextChild() const { - return HasOneChild() && first_child_->IsTextNode(); - } + bool HasOneChild() const { return first_child_ && !first_child_->nextSibling(); } + bool HasOneTextChild() const { return HasOneChild() && first_child_->IsTextNode(); } bool HasChildCount(unsigned) const; HTMLCollection* Children(); unsigned CountChildren() const; - Element* QuerySelector(const AtomicString& selectors, ExceptionState&); - Element* QuerySelector(const AtomicString& selectors); - StaticElementList* QuerySelectorAll(const AtomicString& selectors, - ExceptionState&); - StaticElementList* QuerySelectorAll(const AtomicString& selectors); - Node* InsertBefore(Node* new_child, Node* ref_child, ExceptionState&); - Node* InsertBefore(Node* new_child, Node* ref_child); Node* ReplaceChild(Node* new_child, Node* old_child, ExceptionState&); - Node* ReplaceChild(Node* new_child, Node* old_child); Node* RemoveChild(Node* child, ExceptionState&); - Node* RemoveChild(Node* child); 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; - Element* getElementById(const AtomicString& id) const; - HTMLCollection* getElementsByTagName(const AtomicString&); - HTMLCollection* getElementsByTagNameNS(const AtomicString& namespace_uri, - const AtomicString& local_name); - NodeList* getElementsByName(const AtomicString& element_name); - HTMLCollection* getElementsByClassName(const AtomicString& class_names); - RadioNodeList* GetRadioNodeList(const AtomicString&, - bool only_match_img_elements = false); - // These methods are only used during parsing. // They don't send DOM mutation events or accept DocumentFragments. void ParserAppendChild(Node*); @@ -65,155 +52,12 @@ class ContainerNode : public Node { void ParserInsertBefore(Node* new_child, Node& ref_child); void ParserTakeAllChildrenFrom(ContainerNode&); - void RemoveChildren( - SubtreeModificationAction = kDispatchSubtreeModifiedEvent); - - void CloneChildNodesFrom(const ContainerNode&, CloneChildrenFlag); - - void AttachLayoutTree(AttachContext&) override; - void DetachLayoutTree(bool performing_reattach = false) override; - PhysicalRect BoundingBox() const final; - void SetFocused(bool, mojom::blink::FocusType) override; - void SetHasFocusWithinUpToAncestor(bool, Node* ancestor); - void FocusStateChanged(); - void FocusVisibleStateChanged(); - void FocusWithinStateChanged(); - void SetDragged(bool) override; - void RemovedFrom(ContainerNode& insertion_point) override; - - bool ChildrenOrSiblingsAffectedByFocus() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocus); - } - void SetChildrenOrSiblingsAffectedByFocus() { - SetRestyleFlag(DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocus); - } - - bool ChildrenOrSiblingsAffectedByFocusVisible() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocusVisible); - } - void SetChildrenOrSiblingsAffectedByFocusVisible() { - SetRestyleFlag( - DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocusVisible); - } - - bool ChildrenOrSiblingsAffectedByFocusWithin() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocusWithin); - } - void SetChildrenOrSiblingsAffectedByFocusWithin() { - SetRestyleFlag( - DynamicRestyleFlags::kChildrenOrSiblingsAffectedByFocusWithin); - } - - bool ChildrenOrSiblingsAffectedByHover() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenOrSiblingsAffectedByHover); - } - void SetChildrenOrSiblingsAffectedByHover() { - SetRestyleFlag(DynamicRestyleFlags::kChildrenOrSiblingsAffectedByHover); - } - - bool ChildrenOrSiblingsAffectedByActive() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenOrSiblingsAffectedByActive); - } - void SetChildrenOrSiblingsAffectedByActive() { - SetRestyleFlag(DynamicRestyleFlags::kChildrenOrSiblingsAffectedByActive); - } - - bool ChildrenOrSiblingsAffectedByDrag() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenOrSiblingsAffectedByDrag); - } - void SetChildrenOrSiblingsAffectedByDrag() { - SetRestyleFlag(DynamicRestyleFlags::kChildrenOrSiblingsAffectedByDrag); - } - - bool ChildrenAffectedByFirstChildRules() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenAffectedByFirstChildRules); - } - void SetChildrenAffectedByFirstChildRules() { - SetRestyleFlag(DynamicRestyleFlags::kChildrenAffectedByFirstChildRules); - } - - bool ChildrenAffectedByLastChildRules() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenAffectedByLastChildRules); - } - void SetChildrenAffectedByLastChildRules() { - SetRestyleFlag(DynamicRestyleFlags::kChildrenAffectedByLastChildRules); - } - - bool ChildrenAffectedByDirectAdjacentRules() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenAffectedByDirectAdjacentRules); - } - void SetChildrenAffectedByDirectAdjacentRules() { - SetRestyleFlag(DynamicRestyleFlags::kChildrenAffectedByDirectAdjacentRules); - } - - bool ChildrenAffectedByIndirectAdjacentRules() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenAffectedByIndirectAdjacentRules); - } - void SetChildrenAffectedByIndirectAdjacentRules() { - SetRestyleFlag( - DynamicRestyleFlags::kChildrenAffectedByIndirectAdjacentRules); - } - - bool ChildrenAffectedByForwardPositionalRules() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenAffectedByForwardPositionalRules); - } - void SetChildrenAffectedByForwardPositionalRules() { - SetRestyleFlag( - DynamicRestyleFlags::kChildrenAffectedByForwardPositionalRules); - } - - bool ChildrenAffectedByBackwardPositionalRules() const { - return HasRestyleFlag( - DynamicRestyleFlags::kChildrenAffectedByBackwardPositionalRules); - } - void SetChildrenAffectedByBackwardPositionalRules() { - SetRestyleFlag( - DynamicRestyleFlags::kChildrenAffectedByBackwardPositionalRules); - } - - bool AffectedByFirstChildRules() const { - return HasRestyleFlag(DynamicRestyleFlags::kAffectedByFirstChildRules); - } - void SetAffectedByFirstChildRules() { - SetRestyleFlag(DynamicRestyleFlags::kAffectedByFirstChildRules); - } - - bool AffectedByLastChildRules() const { - return HasRestyleFlag(DynamicRestyleFlags::kAffectedByLastChildRules); - } - void SetAffectedByLastChildRules() { - SetRestyleFlag(DynamicRestyleFlags::kAffectedByLastChildRules); - } - - bool NeedsAdjacentStyleRecalc() const; + void RemoveChildren(); // FIXME: These methods should all be renamed to something better than // "check", since it's not clear that they alter the style bits of siblings // and children. - enum SiblingCheckType { - kFinishedParsingChildren, - kSiblingElementInserted, - kSiblingElementRemoved - }; - void CheckForSiblingStyleChanges(SiblingCheckType, - Element* changed_element, - Node* node_before_change, - Node* node_after_change); - void RecalcDescendantStyles(const StyleRecalcChange, - const StyleRecalcContext&); - void RebuildChildrenLayoutTrees(WhitespaceAttacher&); - void RebuildLayoutTreeForChild(Node* child, WhitespaceAttacher&); + enum SiblingCheckType { kFinishedParsingChildren, kSiblingElementInserted, kSiblingElementRemoved }; // ----------------------------------------------------------------------------- // Notification of document structure changes (see core/dom/node.h for more @@ -229,22 +73,19 @@ class ContainerNode : public Node { }; enum class ChildrenChangeSource : uint8_t { kAPI, kParser }; struct ChildrenChange { - STACK_ALLOCATED(); - public: static ChildrenChange ForInsertion(Node& node, Node* unchanged_previous, Node* unchanged_next, ChildrenChangeSource by_parser) { - ChildrenChange change = {node.IsElementNode() - ? ChildrenChangeType::kElementInserted - : ChildrenChangeType::kNonElementInserted, - by_parser, - &node, - unchanged_previous, - unchanged_next, - {}, - String()}; + ChildrenChange change = { + node.IsElementNode() ? ChildrenChangeType::kElementInserted : ChildrenChangeType::kNonElementInserted, + by_parser, + &node, + unchanged_previous, + unchanged_next, + {}, + ""}; return change; } @@ -252,29 +93,25 @@ class ContainerNode : public Node { Node* previous_sibling, Node* next_sibling, ChildrenChangeSource by_parser) { - ChildrenChange change = {node.IsElementNode() - ? ChildrenChangeType::kElementRemoved - : ChildrenChangeType::kNonElementRemoved, - by_parser, - &node, - previous_sibling, - next_sibling, - {}, - String()}; + ChildrenChange change = { + node.IsElementNode() ? ChildrenChangeType::kElementRemoved : ChildrenChangeType::kNonElementRemoved, + by_parser, + &node, + previous_sibling, + next_sibling, + {}, + ""}; return change; } bool IsChildInsertion() const { - return type == ChildrenChangeType::kElementInserted || - type == ChildrenChangeType::kNonElementInserted; + return type == ChildrenChangeType::kElementInserted || type == ChildrenChangeType::kNonElementInserted; } bool IsChildRemoval() const { - return type == ChildrenChangeType::kElementRemoved || - type == ChildrenChangeType::kNonElementRemoved; + return type == ChildrenChangeType::kElementRemoved || type == ChildrenChangeType::kNonElementRemoved; } bool IsChildElementChange() const { - return type == ChildrenChangeType::kElementInserted || - type == ChildrenChangeType::kElementRemoved; + return type == ChildrenChangeType::kElementInserted || type == ChildrenChangeType::kElementRemoved; } bool ByParser() const { return by_parser == ChildrenChangeSource::kParser; } @@ -296,9 +133,9 @@ class ContainerNode : public Node { // List of removed nodes for ChildrenChangeType::kAllChildrenRemoved. // Only populated if ChildrenChangedAllChildrenRemovedNeedsList() returns // true. - HeapVector> removed_nodes; + std::vector removed_nodes; // |old_text| is mostly empty, only used for text node changes. - const String& old_text; + const std::string& old_text; }; // Notifies the node that it's list of children have changed (either by adding @@ -314,45 +151,17 @@ class ContainerNode : public Node { virtual bool ChildrenCanHaveStyle() const { return true; } - void Trace(Visitor*) const override; + void Trace(GCVisitor* visitor) const override; protected: - ContainerNode(TreeScope*, ConstructionType = kCreateContainer); - - // |attr_name| and |owner_element| are only used for element attribute - // modifications. |ChildrenChange| is either nullptr or points to a - // ChildNode::ChildrenChange structure that describes the changes in the tree. - // If non-null, blink may preserve caches that aren't affected by the change. - void InvalidateNodeListCachesInAncestors(const QualifiedName* attr_name, - Element* attribute_owner_element, - const ChildrenChange*); - - void SetFirstChild(Node* child) { - first_child_ = child; - } - void SetLastChild(Node* child) { - last_child_ = child; - } + ContainerNode(ExecutingContext* context, ConstructionType = kCreateContainer); - // Utility functions for NodeListsNodeData API. - template - Collection* EnsureCachedCollection(CollectionType); - template - Collection* EnsureCachedCollection(CollectionType, const AtomicString& name); - template - Collection* EnsureCachedCollection(CollectionType, - const AtomicString& namespace_uri, - const AtomicString& local_name); - template - Collection* CachedCollection(CollectionType); + 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. - - NodeListsNodeData& EnsureNodeLists(); + 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. @@ -366,10 +175,12 @@ class ContainerNode : public Node { const NodeVector&, Node* next, const 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 WillRemoveChildren(); @@ -377,35 +188,38 @@ class ContainerNode : public Node { void RemoveDetachedChildrenInContainer(ContainerNode&); void AddChildNodesToDeletionQueue(Node*&, Node*&, ContainerNode&); - void NotifyNodeInserted(Node&, - ChildrenChangeSource = ChildrenChangeSource::kAPI); - void NotifyNodeInsertedInternal( - Node&, - NodeVector& post_insertion_notification_targets); void NotifyNodeRemoved(Node&); - bool HasRestyleFlag(DynamicRestyleFlags mask) const { - return HasRareData() && HasRestyleFlagInternal(mask); - } - bool HasRestyleFlags() const { - return HasRareData() && HasRestyleFlagsInternal(); - } - void SetRestyleFlag(DynamicRestyleFlags); - bool HasRestyleFlagInternal(DynamicRestyleFlags) const; - bool HasRestyleFlagsInternal() const; - - bool RecheckNodeInsertionStructuralPrereq(const NodeVector&, - const Node* next, - ExceptionState&); - inline bool CheckParserAcceptChild(const Node& new_child) const; - inline bool IsHostIncludingInclusiveAncestorOfThis(const Node&, - ExceptionState&) const; inline bool IsChildTypeAllowed(const Node& child) const; Node* first_child_; Node* 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_; + while (count && child) { + child = child->nextSibling(); + --count; + } + return !count && !child; } +} // namespace kraken + #endif // KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 3ec4c0aad1..327094bdbb 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -4,661 +4,9 @@ */ #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/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" - -#define DOCUMENT_TARGET_ID -2 namespace kraken { -void traverseNode(Node* node, TraverseHandler handler) { - bool shouldExit = handler(node); - if (shouldExit) - return; - - JSContext* ctx = node->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, JSValueGetClassId(n))); - traverseNode(nextNode, handler); - - JS_FreeValue(node->ctx(), n); - } - } -} - -void bindDocument(std::unique_ptr& context) { - JSValue classObject = Document::constructor(context.get()); - JSValue prototype = Document::prototype(context.get()); - - // Install methods on prototype. - INSTALL_FUNCTION(Document, prototype, createEvent, 1); - INSTALL_FUNCTION(Document, prototype, createElement, 1); - INSTALL_FUNCTION(Document, prototype, createDocumentFragment, 0); - INSTALL_FUNCTION(Document, prototype, createTextNode, 1); - INSTALL_FUNCTION(Document, prototype, createComment, 1); - INSTALL_FUNCTION(Document, prototype, getElementById, 1); - INSTALL_FUNCTION(Document, prototype, getElementsByTagName, 1); - INSTALL_FUNCTION(Document, prototype, getElementsByClassName, 1); - - // Install readonly properties on prototype. - INSTALL_READONLY_PROPERTY(Document, prototype, nodeName); - INSTALL_READONLY_PROPERTY(Document, prototype, all); - INSTALL_READONLY_PROPERTY(Document, prototype, documentElement); - INSTALL_READONLY_PROPERTY(Document, prototype, children); - INSTALL_READONLY_PROPERTY(Document, prototype, head); - - // Install properties on prototype. - INSTALL_PROPERTY(Document, prototype, cookie); - INSTALL_PROPERTY(Document, prototype, body); - - context->defineGlobalProperty("Document", classObject); -} - -JSClassID Document::classId{0}; - -Document* Document::create(JSContext* ctx) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - JSValue prototype = context->contextData()->prototypeForType(&eventTargetTypeInfo); - auto* document = makeGarbageCollected()->initialize(ctx, &Document::classId, nullptr); - - JS_SetPrototype(ctx, document->toQuickJS(), prototype); - - return document; -} - -JSValue Document::constructor(ExecutionContext* context) { - return context->contextData()->constructorForType(&documentTypeInfo); -} - -JSValue Document::prototype(ExecutionContext* context) { - return context->contextData()->prototypeForType(&documentTypeInfo); -} - -Document::Document() : Node() { - 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("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)); - // }); - } -} - -IMPL_FUNCTION(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)); - Event* event = Event::create(ctx, nativeEvent); - return event->toQuickJS(); - } else { - return JS_NULL; - } -} - -IMPL_FUNCTION(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->context(), - // tagName); - // - // JSValue element = JS_CallConstructor(ctx, constructor, argc, argv); - // return element; -} - -IMPL_FUNCTION(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::constructor(document->context()), argc, argv); - return textNode; -} - -IMPL_FUNCTION(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); -} - -IMPL_FUNCTION(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; -} - -IMPL_FUNCTION(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](Node* 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](Node* node) { - if (node->nodeType == NodeType::ELEMENT_NODE) { - auto element = reinterpret_cast(node); - if (element->classNames()->containsAll(className)) { - elements.emplace_back(element); - } - } - - return false; - }); - - JSValue array = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); - - for (auto& element : elements) { - JS_Call(ctx, pushMethod, array, 1, &element->jsObject); - } - - JS_FreeValue(ctx, pushMethod); - return array; -} - -void Document::defineElement(const std::string& tagName, Element* constructor) { - elementConstructorMap[tagName] = constructor; -} - -JSValue Document::getElementConstructor(ExecutionContext* context, const std::string& tagName) { - if (elementConstructorMap.count(tagName) > 0) - return elementConstructorMap[tagName]->jsObject; - return Element::instance(context)->jsObject; -} - -bool Document::isCustomElement(const std::string& tagName) { - return elementConstructorMap.count(tagName) > 0; -} - -IMPL_PROPERTY_GETTER(Document, location)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); - return JS_GetPropertyStr(ctx, document->m_context->global(), "location"); -} - -IMPL_PROPERTY_GETTER(Document, nodeName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NewString(ctx, "#document"); -} - -IMPL_PROPERTY_GETTER(Document, all)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* document = static_cast(JS_GetOpaque(this_val, Document::classId)); - auto all = new AllCollection(document->m_context); - - traverseNode(document, [&all](Node* 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)); - Element* 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)); - Element* 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)); - Element* 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)); - Element* 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; -} - -Document::Document(Document* document) : Node(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 -} - -Document::~Document() {} -void Document::removeElementById(JSAtom id, Element* 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 Document::addElementById(JSAtom id, Element* 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); - } -} - -Element* Document::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 Document::requestAnimationFrame(FrameCallback* frameCallback) { - return m_scriptAnimationController->registerFrameCallback(frameCallback); -} - -void Document::cancelAnimationFrame(uint32_t callbackId) { - m_scriptAnimationController->cancelFrameCallback(callbackId); -} - -void Document::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - Node::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->toQuickJS(), mark_func); - } - } -} - -void Document::dispose() const { - Node::dispose(); - // 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_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. - } -} } // namespace kraken diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index aab91fd9e4..96011e8853 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -6,86 +6,88 @@ #ifndef KRAKENBRIDGE_DOCUMENT_H #define KRAKENBRIDGE_DOCUMENT_H -#include "element.h" -#include "frame_request_callback_collection.h" -#include "node.h" -#include "scripted_animation_controller.h" +#include "container_node.h" namespace kraken { -void bindDocument(ExecutionContext* context); - -using TraverseHandler = std::function; - -void traverseNode(Node* node, TraverseHandler handler); - -class DocumentCookie { - public: - DocumentCookie() = default; - - std::string getCookie(); - void setCookie(std::string& str); - - private: - std::unordered_map cookiePairs; -}; - +// 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 Node { + DEFINE_WRAPPERTYPEINFO(); public: - static JSClassID classId; - static Document* create(JSContext* ctx); - static JSValue constructor(ExecutionContext* context); - static JSValue prototype(ExecutionContext* context); - explicit Document(); - - DEFINE_FUNCTION(createEvent); - DEFINE_FUNCTION(createElement); - DEFINE_FUNCTION(createTextNode); - DEFINE_FUNCTION(createDocumentFragment); - DEFINE_FUNCTION(createComment); - DEFINE_FUNCTION(getElementById); - DEFINE_FUNCTION(getElementsByTagName); - DEFINE_FUNCTION(getElementsByClassName); - - DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); - DEFINE_PROTOTYPE_READONLY_PROPERTY(all); - DEFINE_PROTOTYPE_READONLY_PROPERTY(documentElement); - DEFINE_PROTOTYPE_READONLY_PROPERTY(children); - DEFINE_PROTOTYPE_READONLY_PROPERTY(head); - - DEFINE_PROTOTYPE_PROPERTY(cookie); - DEFINE_PROTOTYPE_PROPERTY(body); - - JSValue getElementConstructor(ExecutionContext* context, const std::string& tagName); - bool isCustomElement(const std::string& tagName); - - int32_t requestAnimationFrame(FrameCallback* frameCallback); - void cancelAnimationFrame(uint32_t callbackId); - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - void dispose() const override; - private: - void removeElementById(JSAtom id, Element* element); - void addElementById(JSAtom id, Element* element); - Element* getDocumentElement(); - std::unordered_map> m_elementMapById; - Element* m_documentElement{nullptr}; - std::unique_ptr m_cookie; - - ScriptAnimationController* m_scriptAnimationController; - - void defineElement(const std::string& tagName, Element* constructor); - bool event_registered{false}; - bool document_registered{false}; - std::unordered_map elementConstructorMap; }; -auto documentCreator = - [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) - -> JSValue { return JS_ThrowTypeError(ctx, "Illegal constructor"); }; +//void bindDocument(ExecutionContext* context); +// +//using TraverseHandler = std::function; +// +//void traverseNode(Node* node, TraverseHandler handler); +// +//class DocumentCookie { +// public: +// DocumentCookie() = default; +// +// std::string getCookie(); +// void setCookie(std::string& str); +// +// private: +// std::unordered_map cookiePairs; +//}; + +//class Document : public Node { +// public: +// static JSClassID classId; +// static Document* create(JSContext* ctx); +// static JSValue constructor(ExecutionContext* context); +// static JSValue prototype(ExecutionContext* context); +// explicit Document(); +// +// DEFINE_FUNCTION(createEvent); +// DEFINE_FUNCTION(createElement); +// DEFINE_FUNCTION(createTextNode); +// DEFINE_FUNCTION(createDocumentFragment); +// DEFINE_FUNCTION(createComment); +// DEFINE_FUNCTION(getElementById); +// DEFINE_FUNCTION(getElementsByTagName); +// DEFINE_FUNCTION(getElementsByClassName); +// +// DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); +// DEFINE_PROTOTYPE_READONLY_PROPERTY(all); +// DEFINE_PROTOTYPE_READONLY_PROPERTY(documentElement); +// DEFINE_PROTOTYPE_READONLY_PROPERTY(children); +// DEFINE_PROTOTYPE_READONLY_PROPERTY(head); +// +// DEFINE_PROTOTYPE_PROPERTY(cookie); +// DEFINE_PROTOTYPE_PROPERTY(body); +// +// JSValue getElementConstructor(ExecutionContext* context, const std::string& tagName); +// bool isCustomElement(const std::string& tagName); +// +// int32_t requestAnimationFrame(FrameCallback* frameCallback); +// void cancelAnimationFrame(uint32_t callbackId); +// void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; +// void dispose() const override; +// +// private: +// void removeElementById(JSAtom id, Element* element); +// void addElementById(JSAtom id, Element* element); +// Element* getDocumentElement(); +// std::unordered_map> m_elementMapById; +// Element* m_documentElement{nullptr}; +// std::unique_ptr m_cookie; +// +// ScriptAnimationController* m_scriptAnimationController; +// +// void defineElement(const std::string& tagName, Element* constructor); +// +// bool event_registered{false}; +// bool document_registered{false}; +// std::unordered_map elementConstructorMap; +//}; -const WrapperTypeInfo documentTypeInfo = {"Document", &nodeTypeInfo, documentCreator}; } // namespace kraken diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index bc90a2c44c..5589cd4b15 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -4,33 +4,33 @@ */ #include "document_fragment.h" -#include "document.h" +#include "events/event_target.h" namespace kraken { -void bindDocumentFragment(std::unique_ptr& context) { - JSValue classObject = context->contextData()->constructorForType(&documentFragmentInfo); - context->defineGlobalProperty("DocumentFragment", classObject); +DocumentFragment* DocumentFragment::Create(ExecutingContext* context, ExceptionState& exception_state) { + return nullptr; } -JSValue DocumentFragment::constructor(ExecutionContext* context) { - return context->contextData()->constructorForType(&documentFragmentInfo); -} - -DocumentFragment* DocumentFragment::create(JSContext* ctx) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - JSValue prototype = context->contextData()->prototypeForType(&documentFragmentInfo); - auto* documentFragment = makeGarbageCollected()->initialize(ctx, &classId); +DocumentFragment::DocumentFragment(ExecutingContext* context): ContainerNode(context, ConstructionType::kCreateDocumentFragment) {} - // Let documentFragment instance inherit Document prototype methods. - JS_SetPrototype(ctx, documentFragment->toQuickJS(), prototype); +std::string DocumentFragment::nodeName() const { + return "#document-fragment"; +} - return documentFragment; +Node::NodeType DocumentFragment::getNodeType() const { + return NodeType::kDocumentFragmentNode; } -DocumentFragment::DocumentFragment() { - setNodeFlag(DocumentFragment::NodeFlag::IsDocumentFragment); - context()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::createDocumentFragment, nativeEventTarget); +bool DocumentFragment::ChildTypeAllowed(NodeType type) const { + switch (type) { + case kElementNode: + case kCommentNode: + case kTextNode: + return true; + default: + return false; + } } } // namespace kraken diff --git a/bridge/core/dom/document_fragment.d.ts b/bridge/core/dom/document_fragment.d.ts new file mode 100644 index 0000000000..83e1ec38a0 --- /dev/null +++ b/bridge/core/dom/document_fragment.d.ts @@ -0,0 +1,5 @@ +import { Node } from './node'; + +interface DocumentFragment extends Node { + new(): DocumentFragment; +} diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index b785667765..dd5367d7fc 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -6,32 +6,31 @@ #ifndef KRAKENBRIDGE_DOCUMENT_FRAGMENT_H #define KRAKENBRIDGE_DOCUMENT_FRAGMENT_H -#include "node.h" +#include "container_node.h" namespace kraken { -void bindDocumentFragment(ExecutionContext* context); - -class DocumentFragment : public Node { +class DocumentFragment : public ContainerNode { + DEFINE_WRAPPERTYPEINFO(); public: - static JSClassID classId; - // Return the constructor class object of DocumentFragment. - static JSValue constructor(ExecutionContext* context); - DocumentFragment* create(JSContext* ctx); - DocumentFragment(); + static DocumentFragment* Create(ExecutingContext* context, ExceptionState& exception_state); - private: - friend Node; -}; + DocumentFragment(ExecutingContext* context); + + virtual bool IsTemplateContent() const { return false; } -auto documentFragmentCreator = - [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) - -> JSValue { - auto* eventTarget = EventTarget::create(ctx); - return eventTarget->toQuickJS(); + // This will catch anyone doing an unnecessary check. + bool IsDocumentFragment() const = delete; + + protected: + std::string nodeName() const final; + + private: + NodeType getNodeType() const final; + Node* Clone(Document&, CloneChildrenFlag) const override; + bool ChildTypeAllowed(NodeType) const override; }; -const WrapperTypeInfo documentFragmentInfo = {"DocumentFragment", &nodeTypeInfo, documentFragmentCreator}; } // namespace kraken diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 0154066f45..0378578222 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -4,917 +4,25 @@ */ #include "element.h" -#include "bindings/qjs/bom/blob.h" -#include "bindings/qjs/html_parser.h" -#include "dart_methods.h" -#include "document.h" -#include "elements/template_element.h" -#include "text_node.h" #if UNIT_TEST #include "kraken_test_env.h" #endif -namespace kraken::binding::qjs { +namespace kraken { -std::once_flag kElementInitOnceFlag; +Element::Element(ExecutingContext* context, + const AtomicString& tag_name, + Document* document, + Node::ConstructionType construction_type) + : ContainerNode(context, construction_type) {} -void bindElement(ExecutionContext* context) { - auto* constructor = Element::instance(context); - // auto* domRectConstructor = BoundingClientRect - context->defineGlobalProperty("Element", constructor->jsObject); - context->defineGlobalProperty("HTMLElement", JS_DupValue(context->ctx(), constructor->jsObject)); -} - -bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance) { - if (JS_IsInstanceOf(context->ctx(), instance, Element::instance(context)->jsObject)) { - auto* elementInstance = static_cast(JS_GetOpaque(instance, Element::classId())); - std::string tagName = elementInstance->getRegisteredTagName(); - - // Special case for kraken official plugins. - if (tagName == "video" || tagName == "iframe") - return true; - - for (char i : tagName) { - if (i == '-') - return true; - } - } - - return false; -} - -JSClassID Element::kElementClassId{0}; - -Element::Element(ExecutionContext* context) : Node(context, "Element") { - std::call_once(kElementInitOnceFlag, []() { JS_NewClassID(&kElementClassId); }); - JS_SetPrototype(m_ctx, m_prototypeObject, Node::instance(m_context)->prototype()); -} - -JSClassID Element::classId() { - return kElementClassId; -} - -JSClassID ElementAttributes::classId{0}; -JSValue ElementAttributes::getAttribute(const std::string& name) { - bool numberIndex = isNumberIndex(name); - - if (numberIndex) { - return JS_NULL; - } - - return JS_DupValue(m_ctx, m_attributes[name]); -} - -JSValue ElementAttributes::setAttribute(const std::string& name, JSValue value) { - bool numberIndex = isNumberIndex(name); - - if (numberIndex) { - return JS_ThrowTypeError( - m_ctx, "Failed to execute 'setAttribute' on 'Element': '%s' is not a valid attribute name.", name.c_str()); - } - - if (name == "class") { - std::string classNameString = jsValueToStdString(m_ctx, value); - m_className->set(classNameString); - } - - // If attribute exists, should free the previous value. - if (m_attributes.count(name) > 0) { - JS_FreeValue(m_ctx, m_attributes[name]); - } - - m_attributes[name] = JS_DupValue(m_ctx, value); - - return JS_NULL; -} - -bool ElementAttributes::hasAttribute(std::string& name) { - bool numberIndex = isNumberIndex(name); - - if (numberIndex) { - return false; - } - - return m_attributes.count(name) > 0; -} - -void ElementAttributes::removeAttribute(std::string& name) { - JSValue value = m_attributes[name]; - JS_FreeValue(m_ctx, value); - m_attributes.erase(name); -} - -void ElementAttributes::copyWith(ElementAttributes* attributes) { - for (auto& attr : attributes->m_attributes) { - m_attributes[attr.first] = JS_DupValue(m_ctx, attr.second); - } -} - -std::shared_ptr ElementAttributes::className() { - return m_className; -} - -std::string ElementAttributes::toString() { - std::string s; - - for (auto& attr : m_attributes) { - s += attr.first + "="; - const char* pstr = JS_ToCString(m_ctx, attr.second); - s += "\"" + std::string(pstr) + "\""; - JS_FreeCString(m_ctx, pstr); - } - - return s; -} - -void ElementAttributes::dispose() const { - for (auto& attr : m_attributes) { - JS_FreeValueRT(m_runtime, attr.second); - } -} -void ElementAttributes::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - for (auto& attr : m_attributes) { - JS_MarkValue(rt, attr.second, mark_func); - } -} - -JSValue Element::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - if (argc == 0) - return JS_ThrowTypeError(ctx, "Illegal constructor"); - JSValue tagName = argv[0]; - - if (!JS_IsString(tagName)) { - return JS_ThrowTypeError(ctx, "Illegal constructor"); - } - - auto* context = static_cast(JS_GetContextOpaque(ctx)); - std::string name = jsValueToStdString(ctx, tagName); - - auto* Document = Document::instance(context); - if (Document->isCustomElement(name)) { - return JS_CallConstructor(ctx, Document->getElementConstructor(context, name), argc, argv); - } - - auto* element = new ElementInstance(this, name, true); - return element->jsObject; -} - -JSValue Element::getBoundingClientRect(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); - getDartMethod()->flushUICommand(); - return element->invokeBindingMethod("getBoundingClientRect", 0, nullptr); -} - -JSValue Element::hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError(ctx, - "Failed to execute 'hasAttribute' on 'Element': 1 argument required, but only 0 present"); - } - - JSValue nameValue = argv[0]; - - if (!JS_IsString(nameValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setAttribute' on 'Element': name attribute is not valid."); - } - - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - auto* attributes = element->m_attributes; - - const char* cname = JS_ToCString(ctx, nameValue); - std::string name = std::string(cname); - - JSValue result = JS_NewBool(ctx, attributes->hasAttribute(name)); - JS_FreeCString(ctx, cname); - - return result; -} - -JSValue Element::setAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc != 2) { - return JS_ThrowTypeError( - ctx, "Failed to execute 'setAttribute' on 'Element': 2 arguments required, but only %d present", argc); - } - - JSValue nameValue = argv[0]; - JSValue attributeValue = JS_ToString(ctx, argv[1]); - - if (!JS_IsString(nameValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setAttribute' on 'Element': name attribute is not valid."); - } - - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string name = jsValueToStdString(ctx, nameValue); - std::transform(name.begin(), name.end(), name.begin(), ::tolower); - - auto* attributes = element->m_attributes; - - if (attributes->hasAttribute(name)) { - JSValue oldAttribute = attributes->getAttribute(name); - JSValue exception = attributes->setAttribute(name, attributeValue); - if (JS_IsException(exception)) - return exception; - element->_didModifyAttribute(name, oldAttribute, attributeValue); - JS_FreeValue(ctx, oldAttribute); - } else { - JSValue exception = attributes->setAttribute(name, attributeValue); - if (JS_IsException(exception)) - return exception; - element->_didModifyAttribute(name, JS_NULL, attributeValue); - } - - std::unique_ptr args_01 = stringToNativeString(name); - std::unique_ptr args_02 = jsValueToNativeString(ctx, attributeValue); - - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::setAttribute, *args_01, - *args_02, nullptr); - - JS_FreeValue(ctx, attributeValue); - - return JS_NULL; -} - -JSValue Element::getAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc != 1) { - return JS_ThrowTypeError(ctx, - "Failed to execute 'getAttribute' on 'Element': 1 argument required, but only 0 present"); - } - - JSValue nameValue = argv[0]; - - if (!JS_IsString(nameValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'setAttribute' on 'Element': name attribute is not valid."); - } - - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string name = jsValueToStdString(ctx, nameValue); - - auto* attributes = element->m_attributes; - - if (attributes->hasAttribute(name)) { - return attributes->getAttribute(name); - } - - return JS_NULL; -} - -JSValue Element::removeAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc != 1) { - return JS_ThrowTypeError( - ctx, "Failed to execute 'removeAttribute' on 'Element': 1 argument required, but only 0 present"); - } - - JSValue nameValue = argv[0]; - - if (!JS_IsString(nameValue)) { - return JS_ThrowTypeError(ctx, "Failed to execute 'removeAttribute' on 'Element': name attribute is not valid."); - } - - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string name = jsValueToStdString(ctx, nameValue); - auto* attributes = element->m_attributes; - - if (attributes->hasAttribute(name)) { - JSValue targetValue = attributes->getAttribute(name); - element->m_attributes->removeAttribute(name); - element->_didModifyAttribute(name, targetValue, JS_NULL); - JS_FreeValue(ctx, targetValue); - - std::unique_ptr args_01 = stringToNativeString(name); - element->m_context->uiCommandBuffer()->addCommand(element->m_eventTargetId, UICommand::removeAttribute, *args_01, - nullptr); - } - - return JS_NULL; -} - -JSValue Element::toBlob(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - double devicePixelRatio = 1.0; - - if (argc > 0) { - JSValue devicePixelRatioValue = argv[0]; - - if (!JS_IsNumber(devicePixelRatioValue)) { - return JS_ThrowTypeError(ctx, "Failed to export blob: parameter 1 (devicePixelRatio) is not an number."); - } - - JS_ToFloat64(ctx, &devicePixelRatio, devicePixelRatioValue); - } - - if (getDartMethod()->toBlob == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to export blob: dart method (toBlob) is not registered."); - } - - auto* element = reinterpret_cast(JS_GetOpaque(this_val, Element::classId())); - getDartMethod()->flushUICommand(); - - auto blobCallback = [](void* callbackContext, int32_t contextId, const char* error, uint8_t* bytes, int32_t length) { - if (!isContextValid(contextId)) - return; - - auto promiseContext = static_cast(callbackContext); - JSContext* ctx = promiseContext->context->ctx(); - if (error == nullptr) { - std::vector vec(bytes, bytes + length); - JSValue arrayBuffer = JS_NewArrayBuffer(ctx, bytes, length, nullptr, nullptr, false); - Blob* constructor = Blob::instance(promiseContext->context); - JSValue argumentsArray = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, argumentsArray, "push"); - JS_Call(ctx, pushMethod, argumentsArray, 1, &arrayBuffer); - JSValue blobValue = JS_CallConstructor(ctx, constructor->jsObject, 1, &argumentsArray); - - if (JS_IsException(blobValue)) { - promiseContext->context->handleException(&blobValue); - } else { - JSValue ret = JS_Call(ctx, promiseContext->resolveFunc, promiseContext->promise, 1, &blobValue); - promiseContext->context->handleException(&ret); - promiseContext->context->drainPendingPromiseJobs(); - JS_FreeValue(ctx, ret); - } - - JS_FreeValue(ctx, pushMethod); - JS_FreeValue(ctx, blobValue); - JS_FreeValue(ctx, argumentsArray); - JS_FreeValue(ctx, arrayBuffer); - } else { - JS_ThrowInternalError(ctx, "%s", error); - JSValue errorObject = JS_GetException(ctx); - JSValue ret = JS_Call(ctx, promiseContext->rejectFunc, promiseContext->promise, 1, &errorObject); - promiseContext->context->handleException(&ret); - promiseContext->context->drainPendingPromiseJobs(); - JS_FreeValue(ctx, errorObject); - JS_FreeValue(ctx, ret); - } - - promiseContext->context->drainPendingPromiseJobs(); - - JS_FreeValue(ctx, promiseContext->resolveFunc); - JS_FreeValue(ctx, promiseContext->rejectFunc); - list_del(&promiseContext->link); - delete promiseContext; - }; - - JSValue resolving_funcs[2]; - JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); - - auto toBlobPromiseContext = new PromiseContext{ - nullptr, element->m_context, resolving_funcs[0], resolving_funcs[1], promise, - }; - - getDartMethod()->toBlob(static_cast(toBlobPromiseContext), element->m_context->getContextId(), blobCallback, - element->m_eventTargetId, devicePixelRatio); - list_add_tail(&toBlobPromiseContext->link, &element->m_context->promise_job_list); - - return promise; -} - -JSValue Element::click(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -#if FLUTTER_BACKEND - getDartMethod()->flushUICommand(); - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->invokeBindingMethod("click", 0, nullptr); -#elif UNIT_TEST - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); - TEST_dispatchEvent(element->m_contextId, element, "click"); - return JS_UNDEFINED; -#else - return JS_UNDEFINED; -#endif -} - -JSValue Element::scroll(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); - double arg0 = 0; - double arg1 = 0; - JS_ToFloat64(ctx, &arg0, argv[0]); - JS_ToFloat64(ctx, &arg1, argv[1]); - NativeValue arguments[] = {Native_NewFloat64(arg0), Native_NewFloat64(arg1)}; - return element->invokeBindingMethod("scroll", 2, arguments); -} - -JSValue Element::scrollBy(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto element = static_cast(JS_GetOpaque(this_val, Element::classId())); - double arg0 = 0; - double arg1 = 0; - JS_ToFloat64(ctx, &arg0, argv[0]); - JS_ToFloat64(ctx, &arg1, argv[1]); - NativeValue arguments[] = {Native_NewFloat64(arg0), Native_NewFloat64(arg1)}; - return element->invokeBindingMethod("scrollBy", 2, arguments); -} - -IMPL_PROPERTY_GETTER(Element, nodeName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string tagName = element->tagName(); - return JS_NewString(ctx, tagName.c_str()); -} - -IMPL_PROPERTY_GETTER(Element, tagName)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - std::string tagName = element->tagName(); - return JS_NewString(ctx, tagName.c_str()); -} - -IMPL_PROPERTY_GETTER(Element, className)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("className"); -} -IMPL_PROPERTY_SETTER(Element, className)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - JSValue value = argv[0]; - - // @TODO: Remove this line. - element->m_attributes->setAttribute("class", value); - - const char* string = JS_ToCString(ctx, value); - NativeValue nativeValue = Native_NewCString(string); - element->setBindingProperty("className", nativeValue); - JS_FreeCString(ctx, string); - return JS_DupValue(ctx, value); -} - -IMPL_PROPERTY_GETTER(Element, offsetLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("offsetLeft"); -} - -IMPL_PROPERTY_GETTER(Element, offsetTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("offsetTop"); -} - -IMPL_PROPERTY_GETTER(Element, offsetWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("offsetWidth"); -} - -IMPL_PROPERTY_GETTER(Element, offsetHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("offsetHeight"); -} - -IMPL_PROPERTY_GETTER(Element, clientWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("clientWidth"); -} - -IMPL_PROPERTY_GETTER(Element, clientHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("clientHeight"); -} - -IMPL_PROPERTY_GETTER(Element, clientTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("clientTop"); -} - -IMPL_PROPERTY_GETTER(Element, clientLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("clientLeft"); -} - -IMPL_PROPERTY_GETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("scrollTop"); -} -IMPL_PROPERTY_SETTER(Element, scrollTop)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - double floatValue = 0; - JSValue value = argv[0]; - JS_ToFloat64(ctx, &floatValue, value); - NativeValue nativeValue = Native_NewFloat64(floatValue); - element->setBindingProperty("scrollTop", nativeValue); - return JS_DupValue(ctx, value); -} - -IMPL_PROPERTY_GETTER(Element, scrollLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("scrollLeft"); -} -IMPL_PROPERTY_SETTER(Element, scrollLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - double floatValue = 0; - JSValue value = argv[0]; - JS_ToFloat64(ctx, &floatValue, value); - NativeValue nativeValue = Native_NewFloat64(floatValue); - element->setBindingProperty("scrollLeft", nativeValue); - return JS_DupValue(ctx, value); -} - -IMPL_PROPERTY_GETTER(Element, scrollHeight)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("scrollHeight"); -} - -IMPL_PROPERTY_GETTER(Element, scrollWidth)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return element->getBindingProperty("scrollWidth"); -} - -// Definition for firstElementChild -IMPL_PROPERTY_GETTER(Element, firstElementChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - int32_t len = arrayGetLength(ctx, element->childNodes); - - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(ctx, element->childNodes, i); - auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (instance->nodeType == NodeType::ELEMENT_NODE) { - return instance->jsObject; - } - JS_FreeValue(ctx, v); - } - - return JS_NULL; -} - -// Definition for lastElementChild -IMPL_PROPERTY_GETTER(Element, lastElementChild)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - int32_t len = arrayGetLength(ctx, element->childNodes); - - for (int i = len - 1; i >= 0; i--) { - JSValue v = JS_GetPropertyUint32(ctx, element->childNodes, i); - auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (instance->nodeType == NodeType::ELEMENT_NODE) { - return instance->jsObject; - } - JS_FreeValue(ctx, v); - } - - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Element, children)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - JSValue array = JS_NewArray(ctx); - JSValue pushMethod = JS_GetPropertyStr(ctx, array, "push"); - - int32_t len = arrayGetLength(ctx, element->childNodes); - - for (int i = 0; i < len; i++) { - JSValue v = JS_GetPropertyUint32(ctx, element->childNodes, i); - auto* instance = static_cast(JS_GetOpaque(v, Node::classId(v))); - if (instance->nodeType == NodeType::ELEMENT_NODE) { - JSValue arguments[] = {v}; - JS_Call(ctx, pushMethod, array, 1, arguments); - } - JS_FreeValue(ctx, v); - } - - JS_FreeValue(ctx, pushMethod); - - return array; -} - -IMPL_PROPERTY_GETTER(Element, attributes)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return JS_DupValue(ctx, element->m_attributes->toQuickJS()); -} - -IMPL_PROPERTY_GETTER(Element, innerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return JS_NewString(ctx, element->innerHTML().c_str()); -} -IMPL_PROPERTY_SETTER(Element, innerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - const char* chtml = JS_ToCString(ctx, argv[0]); - - if (element->hasNodeFlag(NodeInstance::NodeFlag::IsTemplateElement)) { - auto* templateElement = static_cast(element); - HTMLParser::parseHTMLFragment(chtml, strlen(chtml), templateElement->content()); - } else { - HTMLParser::parseHTMLFragment(chtml, strlen(chtml), element); - } - - JS_FreeCString(ctx, chtml); - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Element, outerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* element = static_cast(JS_GetOpaque(this_val, Element::classId())); - return JS_NewString(ctx, element->outerHTML().c_str()); -} -IMPL_PROPERTY_SETTER(Element, outerHTML)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_NULL; -} - -JSClassID ElementInstance::classID() { - return Element::classId(); -} - -ElementInstance::~ElementInstance() {} - -JSValue ElementInstance::internalGetTextContent() { - JSValue array = JS_NewArray(m_ctx); - JSValue pushMethod = JS_GetPropertyStr(m_ctx, array, "push"); - - int32_t len = arrayGetLength(m_ctx, childNodes); - - for (int i = 0; i < len; i++) { - JSValue n = JS_GetPropertyUint32(m_ctx, childNodes, i); - auto* node = static_cast(JS_GetOpaque(n, Node::classId(n))); - JSValue nodeText = node->internalGetTextContent(); - JS_Call(m_ctx, pushMethod, array, 1, &nodeText); - JS_FreeValue(m_ctx, nodeText); - JS_FreeValue(m_ctx, n); - } - - JSValue joinMethod = JS_GetPropertyStr(m_ctx, array, "join"); - JSValue emptyString = JS_NewString(m_ctx, ""); - JSValue joinArgs[] = {emptyString}; - JSValue returnValue = JS_Call(m_ctx, joinMethod, array, 1, joinArgs); - - JS_FreeValue(m_ctx, array); - JS_FreeValue(m_ctx, pushMethod); - JS_FreeValue(m_ctx, joinMethod); - JS_FreeValue(m_ctx, emptyString); - return returnValue; -} - -void ElementInstance::internalSetTextContent(JSValue content) { - internalClearChild(); - - JSValue textNodeValue = JS_CallConstructor(m_ctx, TextNode::instance(m_context)->jsObject, 1, &content); - auto* textNodeInstance = static_cast(JS_GetOpaque(textNodeValue, TextNode::classId())); - internalAppendChild(textNodeInstance); - JS_FreeValue(m_ctx, textNodeValue); -} - -std::shared_ptr ElementInstance::classNames() { - return m_attributes->className(); -} - -std::string SpaceSplitString::m_delimiter{" "}; - -void SpaceSplitString::set(std::string& string) { - size_t pos = 0; - std::string token; - std::string s = string; - while ((pos = s.find(m_delimiter)) != std::string::npos) { - token = s.substr(0, pos); - m_szData.push_back(token); - s.erase(0, pos + m_delimiter.length()); - } - m_szData.push_back(s); -} - -bool SpaceSplitString::contains(std::string& string) { - for (std::string& s : m_szData) { - if (s == string) { - return true; - } - } +bool Element::hasAttribute(const AtomicString&) const { 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); +const AtomicString& Element::getAttribute(const AtomicString&) const { + return <#initializer #>; } -} // namespace kraken::binding::qjs +} // namespace kraken diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 6a35d8ba6b..b671d178aa 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -6,18 +6,11 @@ #ifndef KRAKENBRIDGE_ELEMENT_H #define KRAKENBRIDGE_ELEMENT_H -#include #include "bindings/qjs/garbage_collected.h" -#include "node.h" +#include "container_node.h" namespace kraken { -void bindElement(ExecutionContext* context); - -class Element; - -using ElementCreator = Element* (*)(Element* element, std::string tagName); - struct NativeBoundingClientRect { double x; double y; @@ -29,167 +22,20 @@ struct NativeBoundingClientRect { 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); +//bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance); - private: - static std::string m_delimiter; - std::vector m_szData; -}; - -// TODO: refactor for better W3C standard support and higher performance. -// https://dom.spec.whatwg.org/#interface-namednodemap -class NamedNodeMap : public GarbageCollected { +class Element : public ContainerNode { + DEFINE_WRAPPERTYPEINFO(); public: - static JSClassID classId; - - FORCE_INLINE const char* getHumanReadableName() const override { return "NamedNodeMap"; } - - void dispose() const override; - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - - JSValue getNamedItem(const std::string& name); - JSValue setNamedItem(const std::string& name, JSValue value); - bool hasNamedItem(std::string& name); - void removeNamedItem(std::string& name); - void copyWith(NamedNodeMap* attributes); - std::string toString(); - - private: - std::unordered_map m_map; -}; - -bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance); - -class Element : public Node { - public: - static JSClassID classId; - static Element* create(JSContext* ctx); - static JSValue constructor(ExecutionContext* context); - static JSValue prototype(ExecutionContext* context); - - DEFINE_FUNCTION(getBoundingClientRect); - DEFINE_FUNCTION(hasAttribute); - DEFINE_FUNCTION(setAttribute); - DEFINE_FUNCTION(getAttribute); - DEFINE_FUNCTION(removeAttribute); - DEFINE_FUNCTION(toBlob); - DEFINE_FUNCTION(click); - DEFINE_FUNCTION(scroll); - DEFINE_FUNCTION(scrollBy); - - 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); + Element(ExecutingContext* context, const AtomicString& tag_name, + Document*, + ConstructionType = kCreateElement); - DEFINE_PROTOTYPE_PROPERTY(id); - DEFINE_PROTOTYPE_PROPERTY(className); - DEFINE_PROTOTYPE_PROPERTY(style); - DEFINE_PROTOTYPE_PROPERTY(innerHTML); - DEFINE_PROTOTYPE_PROPERTY(outerHTML); - DEFINE_PROTOTYPE_PROPERTY(scrollTop); - DEFINE_PROTOTYPE_PROPERTY(scrollLeft); + bool hasAttribute(const AtomicString&) const; + const AtomicString& getAttribute(const AtomicString&) const; - JSValue internalGetTextContent() override; - void internalSetTextContent(JSValue content) override; - - std::string className(); - std::shared_ptr classNames(); - std::string tagName(); - std::string getRegisteredTagName(); - std::string outerHTML(); - std::string innerHTML(); - StyleDeclarationInstance* style(); - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - void dispose() const override; - - protected: - StyleDeclarationInstance* m_style{nullptr}; - ElementAttributes* m_attributes{nullptr}; - std::string m_tagName; - std::string m_className; - - private: - void _notifyNodeRemoved(Node* node) override; - void _notifyChildRemoved(); - void _notifyNodeInsert(Node* insertNode) override; - void _notifyChildInsert(); - void _didModifyAttribute(std::string& name, JSValue oldId, JSValue newId); - void _beforeUpdateId(JSValue oldIdValue, JSValue newIdValue); - - static JSClassExoticMethods exoticMethods; - friend class Node; }; -auto elementCreator = - [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) - -> JSValue { - 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); - - Element* element = Element::create(ctx); - - auto* document = context->document(); - // auto* Document = Document::instance(context); - // if (Document->isCustomElement(name)) { - // return JS_CallConstructor(ctx, Document->getElementConstructor(context, name), argc, argv); - // } - // - // auto* element = new Element(this, name, true); - // return element->jsObject; -}; - -const WrapperTypeInfo elementTypeInfo = {"Element", &nodeTypeInfo, elementCreator}; - -class BoundingClientRect : public GarbageCollected { - public: - BoundingClientRect() = delete; - explicit BoundingClientRect(ExecutionContext* context, NativeBoundingClientRect* nativeBoundingClientRect) - : GarbageCollected(), m_nativeBoundingClientRect(nativeBoundingClientRect){}; - - const char* getHumanReadableName() const override { return "BoundingClientRect"; } - - private: - DEFINE_READONLY_PROPERTY(x); - DEFINE_READONLY_PROPERTY(y); - DEFINE_READONLY_PROPERTY(width); - DEFINE_READONLY_PROPERTY(height); - DEFINE_READONLY_PROPERTY(top); - DEFINE_READONLY_PROPERTY(right); - DEFINE_READONLY_PROPERTY(bottom); - DEFINE_READONLY_PROPERTY(left); - - NativeBoundingClientRect* m_nativeBoundingClientRect{nullptr}; -}; } // namespace kraken diff --git a/bridge/core/dom/element_data.cc b/bridge/core/dom/element_data.cc new file mode 100644 index 0000000000..5d839967b5 --- /dev/null +++ b/bridge/core/dom/element_data.cc @@ -0,0 +1,5 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "element_data.h" diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/element_data.h new file mode 100644 index 0000000000..086b7f7ad0 --- /dev/null +++ b/bridge/core/dom/element_data.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_H_ +#define KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace kraken { + +class ElementData { + public: + + private: + mutable Member inline_style_; + mutable SpaceSplitString class_names_; + mutable AtomicString id_for_style_resolution_; +}; + +} + +#endif // KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_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..ae7349ee64 --- /dev/null +++ b/bridge/core/dom/empty_node_list.cc @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "empty_node_list.h" +#include "node.h" + +namespace kraken { + +EmptyNodeList::EmptyNodeList(Node* root_node) : owner_(root_node), NodeList(root_node->ctx()) {} + +void EmptyNodeList::Trace(GCVisitor* visitor) const {} + +Node* EmptyNodeList::VirtualOwnerNode() const { + return &OwnerNode(); +} + +} // namespace kraken diff --git a/bridge/core/dom/empty_node_list.h b/bridge/core/dom/empty_node_list.h new file mode 100644 index 0000000000..d57fa2a07f --- /dev/null +++ b/bridge/core/dom/empty_node_list.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ +#define KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ + +#include "node_list.h" + +namespace kraken { + +class 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) const override { return nullptr; } + + bool IsEmptyNodeList() const override { return true; } + Node* VirtualOwnerNode() const override; + + Node* owner_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index a138281c01..a03d0160bf 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -13,7 +13,7 @@ Event* Event::From(ExecutingContext* context, NativeEvent* native_event) { AtomicString event_type = AtomicString::From(context->ctx(), native_event->type); auto* event = - makeGarbageCollected(context, event_type, native_event->bubbles == 0 ? Bubbles::kNo : Bubbles::kYes, + MakeGarbageCollected(context, event_type, native_event->bubbles == 0 ? Bubbles::kNo : Bubbles::kYes, native_event->cancelable == 0 ? Cancelable::kNo : Cancelable::kYes, ComposedMode::kComposed, native_event->timeStamp); event->SetTarget(static_cast(native_event->target)); diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 7715bde853..d39213e90d 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -90,15 +90,15 @@ class Event : public ScriptWrappable { enum PhaseType { kNone = 0, kCapturingPhase = 1, kAtTarget = 2, kBubblingPhase = 3 }; - static Event* Create(ExecutingContext* context) { return makeGarbageCollected(context); }; + static Event* Create(ExecutingContext* context) { return MakeGarbageCollected(context); }; static Event* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { - return makeGarbageCollected(context, type); + return MakeGarbageCollected(context, type); }; static Event* Create(ExecutingContext* context, const AtomicString& type, const std::shared_ptr& init, ExceptionState& exception_state) { - return makeGarbageCollected(context, type, init); + return MakeGarbageCollected(context, type, init); }; static Event* From(ExecutingContext* context, NativeEvent* native_event); diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index d21040ba35..871eef006a 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -37,7 +37,7 @@ void EventTargetData::Trace(GCVisitor* visitor) const { } EventTarget* EventTarget::Create(ExecutingContext* context, ExceptionState& exception_state) { - return makeGarbageCollected(context); + return MakeGarbageCollected(context); } EventTarget::EventTarget(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 778cc4a9be..9a2eb9e525 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -3,12 +3,10 @@ */ #include "node.h" -#include "bindings/qjs/qjs_engine_patch.h" -#include "comment.h" -#include "document.h" -#include "document_fragment.h" -#include "element.h" -#include "text_node.h" +#include "node_data.h" +#include "node_list.h" +#include "child_node_list.h" +#include "empty_node_list.h" namespace kraken { @@ -16,4 +14,28 @@ Node* Node::Create(ExecutingContext* context, ExceptionState& exception_state) { exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor"); } +ContainerNode* Node::parentNode() const { + return ParentOrShadowHostNode(); +} + +NodeList* Node::childNodes() { + auto* this_node = DynamicTo(this); + if (this_node) + return EnsureData().EnsureChildNodeList(*this_node); + return EnsureData().EnsureEmptyChildNodeList(*this); +} + +NodeData& Node::CreateData() { + data_ = std::make_unique(); + return *Data(); +} + +NodeData& Node::EnsureData() { + if (HasData()) + return *Data(); + return CreateData(); +} + +void Node::Trace(GCVisitor*) const {} + } // namespace kraken diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 26111bf280..104c235e7d 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -8,9 +8,8 @@ #include #include -#include "foundation/macros.h" #include "events/event_target.h" -#include "node_list.h" +#include "foundation/macros.h" namespace kraken { @@ -25,6 +24,8 @@ class DocumentFragment; class TextNode; class Document; class ContainerNode; +class NodeData; +class NodeList; enum class CustomElementState : uint32_t { // https://dom.spec.whatwg.org/#concept-element-custom-element-state @@ -42,6 +43,7 @@ enum class CloneChildrenFlag { kSkip, kClone, kCloneWithShadows }; // https://dom.spec.whatwg.org/#interface-node class Node : public EventTarget { DEFINE_WRAPPERTYPEINFO(); + public: enum NodeType { kElementNode = 1, @@ -58,14 +60,12 @@ class Node : public EventTarget { // DOM methods & attributes for Node bool HasTagName(const AtomicString&) const; - virtual AtomicString nodeName() const = 0; - virtual AtomicString nodeValue() const; - virtual void setNodeValue(const AtomicString&, ExceptionState&); + virtual std::string nodeName() const = 0; + virtual std::string nodeValue() const; + virtual void setNodeValue(const std::string&, ExceptionState&); virtual NodeType getNodeType() const = 0; ContainerNode* parentNode() const; Element* parentElement() const; - ContainerNode* ParentElementOrShadowRoot() const; - ContainerNode* ParentElementOrDocumentFragment() const; Node* previousSibling() const { return previous_; } Node* nextSibling() const { return next_; } NodeList* childNodes(); @@ -76,24 +76,24 @@ class Node : public EventTarget { Node& ShadowIncludingRoot() const; // TODO: support following APIs. -// void Prepend( -// const HeapVector>& nodes, -// ExceptionState& exception_state); -// void Append( -// const HeapVector>& nodes, -// ExceptionState& exception_state); -// void Before( -// const HeapVector>& nodes, -// ExceptionState& exception_state); -// void After( -// const HeapVector>& nodes, -// ExceptionState& exception_state); -// void ReplaceWith( -// const HeapVector>& nodes, -// ExceptionState& exception_state); -// void ReplaceChildren( -// const HeapVector>& nodes, -// ExceptionState& exception_state); + // void Prepend( + // const HeapVector>& nodes, + // ExceptionState& exception_state); + // void Append( + // const HeapVector>& nodes, + // ExceptionState& exception_state); + // void Before( + // const HeapVector>& nodes, + // ExceptionState& exception_state); + // void After( + // const HeapVector>& nodes, + // ExceptionState& exception_state); + // void ReplaceWith( + // const HeapVector>& nodes, + // ExceptionState& exception_state); + // void ReplaceChildren( + // const HeapVector>& nodes, + // ExceptionState& exception_state); void remove(ExceptionState&); void remove(); @@ -129,36 +129,19 @@ class Node : public EventTarget { virtual void setTextContent(const AtomicString&); // Other methods (not part of DOM) - FORCE_INLINE bool IsTextNode() const { - return GetDOMNodeType() == DOMNodeType::kText; - } - FORCE_INLINE bool IsContainerNode() const { - return GetFlag(kIsContainerFlag); - } - FORCE_INLINE bool IsElementNode() const { - return GetDOMNodeType() == DOMNodeType::kElement; - } - FORCE_INLINE bool IsDocumentFragment() const { - return GetDOMNodeType() == DOMNodeType::kDocumentFragment; - } + FORCE_INLINE bool IsTextNode() const { return GetDOMNodeType() == DOMNodeType::kText; } + FORCE_INLINE bool IsContainerNode() const { return GetFlag(kIsContainerFlag); } + FORCE_INLINE bool IsElementNode() const { return GetDOMNodeType() == DOMNodeType::kElement; } + FORCE_INLINE bool IsDocumentFragment() const { return GetDOMNodeType() == DOMNodeType::kDocumentFragment; } - FORCE_INLINE bool IsHTMLElement() const { - return GetElementNamespaceType() == ElementNamespaceType::kHTML; - } - FORCE_INLINE bool IsMathMLElement() const { - return GetElementNamespaceType() == ElementNamespaceType::kMathML; - } - FORCE_INLINE bool IsSVGElement() const { - return GetElementNamespaceType() == ElementNamespaceType::kSVG; - } + FORCE_INLINE bool IsHTMLElement() const { return GetElementNamespaceType() == ElementNamespaceType::kHTML; } + FORCE_INLINE bool IsMathMLElement() const { return GetElementNamespaceType() == ElementNamespaceType::kMathML; } + FORCE_INLINE bool IsSVGElement() const { return GetElementNamespaceType() == ElementNamespaceType::kSVG; } CustomElementState GetCustomElementState() const { - return static_cast(node_flags_ & - kCustomElementStateMask); - } - bool IsCustomElement() const { - return GetCustomElementState() != CustomElementState::kUncustomized; + return static_cast(node_flags_ & kCustomElementStateMask); } + bool IsCustomElement() const { return GetCustomElementState() != CustomElementState::kUncustomized; } void SetCustomElementState(CustomElementState); virtual bool IsMediaControlElement() const { return false; } @@ -176,9 +159,7 @@ class Node : public EventTarget { // 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. - bool IsStyledElement() const { - return IsHTMLElement() || IsSVGElement() || IsMathMLElement(); - } + bool IsStyledElement() const { return IsHTMLElement() || IsSVGElement() || IsMathMLElement(); } bool IsDocumentNode() const; @@ -199,9 +180,7 @@ class Node : public EventTarget { void SetNextSibling(Node* next) { next_ = next; } bool HasEventTargetData() const { return GetFlag(kHasEventTargetDataFlag); } - void SetHasEventTargetData(bool flag) { - SetFlag(flag, kHasEventTargetDataFlag); - } + void SetHasEventTargetData(bool flag) { SetFlag(flag, kHasEventTargetDataFlag); } unsigned NodeIndex() const; @@ -211,7 +190,7 @@ class Node : public EventTarget { // Returns the document associated with this node. A Document node returns // itself. - Document& GetDocument() const { } + Document& GetDocument() const {} // Returns true if this node is connected to a document, false otherwise. // See https://dom.spec.whatwg.org/#connected for the definition. @@ -231,99 +210,44 @@ class Node : public EventTarget { // https://dom.spec.whatwg.org/#concept-shadow-including-ancestor bool IsShadowIncludingAncestorOf(const Node&) const; bool ContainsIncludingHostElements(const Node&) const; - Node* CommonAncestor(const Node&, - ContainerNode* (*parent)(const Node&)) const; + Node* CommonAncestor(const Node&, ContainerNode* (*parent)(const Node&)) const; // Whether or not a selection can be started in this object virtual bool CanStartSelection() const; void NotifyPriorityScrollAnchorStatusChanged(); - // --------------------------------------------------------------------------- - // Notification of document structure changes (see container_node.h for more - // notification methods) - // - // At first, Blink notifies the node that it has been inserted into the - // document. This is called during document parsing, and also when a node is - // added through the DOM methods insertBefore(), appendChild() or - // replaceChild(). The call happens _after_ the node has been added to the - // tree. This is similar to the DOMNodeInsertedIntoDocument DOM event, but - // does not require the overhead of event dispatching. - // - // Blink notifies this callback regardless if the subtree of the node is a - // document tree or a floating subtree. Implementation can determine the type - // of subtree by seeing insertion_point->isConnected(). For performance - // reasons, notifications are delivered only to ContainerNode subclasses if - // the insertion_point is not in a document tree. - // - // There is another callback, DidNotifySubtreeInsertionsToDocument(), - // which is called after all the descendants are notified, if this node was - // inserted into the document tree. Only a few subclasses actually need - // this. To utilize this, the node should return - // kInsertionShouldCallDidNotifySubtreeInsertions from InsertedInto(). - // - // InsertedInto() implementations must not modify the DOM tree, and must not - // dispatch synchronous events. On the other hand, - // DidNotifySubtreeInsertionsToDocument() may modify the DOM tree, and may - // dispatch synchronous events. - enum InsertionNotificationRequest { - kInsertionDone, - kInsertionShouldCallDidNotifySubtreeInsertions - }; - - virtual InsertionNotificationRequest InsertedInto( - ContainerNode& insertion_point); - virtual void DidNotifySubtreeInsertionsToDocument() {} + // NodeListsNodeData* NodeLists(); + // void ClearNodeLists(); - // Notifies the node that it is no longer part of the tree. - // - // This is a dual of InsertedInto(), and is similar to the - // DOMNodeRemovedFromDocument DOM event, 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); + enum ShadowTreesTreatment { kTreatShadowTreesAsDisconnected, kTreatShadowTreesAsComposed }; - -// NodeListsNodeData* NodeLists(); -// void ClearNodeLists(); - - enum ShadowTreesTreatment { - kTreatShadowTreesAsDisconnected, - kTreatShadowTreesAsComposed - }; - - uint16_t compareDocumentPosition( - const Node*, - ShadowTreesTreatment = kTreatShadowTreesAsDisconnected) const; + uint16_t compareDocumentPosition(const Node*, ShadowTreesTreatment = kTreatShadowTreesAsDisconnected) const; EventTargetData* GetEventTargetData() override; EventTargetData& EnsureEventTargetData() override; - bool IsFinishedParsingChildren() const { - return GetFlag(kIsFinishedParsingChildrenFlag); - } + bool IsFinishedParsingChildren() const { return GetFlag(kIsFinishedParsingChildrenFlag); } void SetHasDuplicateAttributes() { SetFlag(kHasDuplicateAttributes); } - bool HasDuplicateAttribute() const { - return GetFlag(kHasDuplicateAttributes); - } + bool HasDuplicateAttribute() const { return GetFlag(kHasDuplicateAttributes); } - bool SelfOrAncestorHasDirAutoAttribute() const { - return GetFlag(kSelfOrAncestorHasDirAutoAttribute); - } - void SetSelfOrAncestorHasDirAutoAttribute() { - SetFlag(kSelfOrAncestorHasDirAutoAttribute); - } - void ClearSelfOrAncestorHasDirAutoAttribute() { - ClearFlag(kSelfOrAncestorHasDirAutoAttribute); - } + bool SelfOrAncestorHasDirAutoAttribute() const { return GetFlag(kSelfOrAncestorHasDirAutoAttribute); } + void SetSelfOrAncestorHasDirAutoAttribute() { SetFlag(kSelfOrAncestorHasDirAutoAttribute); } + void ClearSelfOrAncestorHasDirAutoAttribute() { ClearFlag(kSelfOrAncestorHasDirAutoAttribute); } + + NodeData& CreateData(); + bool HasData() const { return GetFlag(kHasDataFlag); } + // |RareData| cannot be replaced or removed once assigned. + NodeData* Data() const { return data_.get(); } + NodeData& EnsureData(); 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, @@ -348,12 +272,8 @@ class Node : public EventTarget { // 2 bits remaining. }; - 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); - } + 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; } @@ -364,9 +284,7 @@ class Node : public EventTarget { kOther = 3 << kDOMNodeTypeShift, }; - FORCE_INLINE DOMNodeType GetDOMNodeType() const { - return static_cast(node_flags_ & kDOMNodeTypeMask); - } + FORCE_INLINE DOMNodeType GetDOMNodeType() const { return static_cast(node_flags_ & kDOMNodeTypeMask); } enum class ElementNamespaceType : uint32_t { kHTML = 0, @@ -375,52 +293,41 @@ class Node : public EventTarget { kOther = 3 << kElementNamespaceTypeShift, }; FORCE_INLINE ElementNamespaceType GetElementNamespaceType() const { - return static_cast(node_flags_ & - kElementNamespaceTypeMask); + return static_cast(node_flags_ & kElementNamespaceTypeMask); } protected: enum ConstructionType { - kCreateOther = kDefaultNodeFlags | - static_cast(DOMNodeType::kOther) | - static_cast(ElementNamespaceType::kOther), - kCreateText = kDefaultNodeFlags | - static_cast(DOMNodeType::kText) | - static_cast(ElementNamespaceType::kOther), - kCreateContainer = kDefaultNodeFlags | kIsContainerFlag | - static_cast(DOMNodeType::kOther) | - static_cast(ElementNamespaceType::kOther), - kCreateElement = kDefaultNodeFlags | kIsContainerFlag | - static_cast(DOMNodeType::kElement) | - static_cast(ElementNamespaceType::kOther), - kCreateDocumentFragment = - kDefaultNodeFlags | kIsContainerFlag | - static_cast(DOMNodeType::kDocumentFragment) | - static_cast(ElementNamespaceType::kOther), - kCreateHTMLElement = kDefaultNodeFlags | kIsContainerFlag | - static_cast(DOMNodeType::kElement) | - static_cast(ElementNamespaceType::kHTML), - kCreateMathMLElement = - kDefaultNodeFlags | kIsContainerFlag | - static_cast(DOMNodeType::kElement) | - static_cast(ElementNamespaceType::kMathML), - kCreateSVGElement = kDefaultNodeFlags | kIsContainerFlag | - static_cast(DOMNodeType::kElement) | - static_cast(ElementNamespaceType::kSVG), + kCreateOther = kDefaultNodeFlags | static_cast(DOMNodeType::kOther) | + static_cast(ElementNamespaceType::kOther), + kCreateText = kDefaultNodeFlags | static_cast(DOMNodeType::kText) | + static_cast(ElementNamespaceType::kOther), + kCreateContainer = kDefaultNodeFlags | kIsContainerFlag | static_cast(DOMNodeType::kOther) | + static_cast(ElementNamespaceType::kOther), + kCreateElement = kDefaultNodeFlags | kIsContainerFlag | static_cast(DOMNodeType::kElement) | + static_cast(ElementNamespaceType::kOther), + kCreateDocumentFragment = kDefaultNodeFlags | kIsContainerFlag | + static_cast(DOMNodeType::kDocumentFragment) | + static_cast(ElementNamespaceType::kOther), + kCreateHTMLElement = kDefaultNodeFlags | kIsContainerFlag | static_cast(DOMNodeType::kElement) | + static_cast(ElementNamespaceType::kHTML), + kCreateMathMLElement = kDefaultNodeFlags | kIsContainerFlag | static_cast(DOMNodeType::kElement) | + static_cast(ElementNamespaceType::kMathML), + kCreateSVGElement = kDefaultNodeFlags | kIsContainerFlag | static_cast(DOMNodeType::kElement) | + static_cast(ElementNamespaceType::kSVG), kCreateDocument = kCreateContainer | kIsConnectedFlag, }; Node(ConstructionType); - void SetIsFinishedParsingChildren(bool value) { - SetFlag(value, kIsFinishedParsingChildrenFlag); - } + void SetIsFinishedParsingChildren(bool value) { SetFlag(value, kIsFinishedParsingChildrenFlag); } private: uint32_t node_flags_; Node* parent_or_shadow_host_node_; Node* previous_; Node* next_; + std::unique_ptr data_; }; } // namespace kraken diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc new file mode 100644 index 0000000000..2447030446 --- /dev/null +++ b/bridge/core/dom/node_data.cc @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "node_data.h" +#include "bindings/qjs/garbage_collected.h" +#include "child_node_list.h" +#include "empty_node_list.h" +#include "container_node.h" + +namespace kraken { + +ChildNodeList* NodeData::GetChildNodeList(ContainerNode& node) { + assert(!child_node_list_ || &node == child_node_list_->VirtualOwnerNode()); + return To(child_node_list_); +} + +ChildNodeList* NodeData::EnsureChildNodeList(ContainerNode& node) { + if (child_node_list_) + return To(child_node_list_); + auto* list = MakeGarbageCollected(&node); + child_node_list_ = list; + return list; +} + +EmptyNodeList* NodeData::EnsureEmptyChildNodeList(Node& node) { + if (child_node_list_) + return To(child_node_list_); + auto* list = MakeGarbageCollected(&node); + child_node_list_ = list; + return list; +} + +void NodeData::Trace(GCVisitor* visitor) const { + child_node_list_->Trace(visitor); +} + +} // namespace kraken diff --git a/bridge/core/dom/node_data.h b/bridge/core/dom/node_data.h new file mode 100644 index 0000000000..d9b28d3c71 --- /dev/null +++ b/bridge/core/dom/node_data.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ +#define KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ + +#include +#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/gc_visitor.h" + +namespace kraken { + +class ChildNodeList; +class EmptyNodeList; +class ContainerNode; +class NodeList; +class Node; + +class NodeData { + public: + enum class ClassType : uint8_t { + kNodeRareData, + kElementRareData, + }; + + ChildNodeList* GetChildNodeList(ContainerNode& node); + + ChildNodeList* EnsureChildNodeList(ContainerNode& node); + + EmptyNodeList* EnsureEmptyChildNodeList(Node& node); + + void Trace(GCVisitor* visitor) const; + + private: + NodeList* child_node_list_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h index 58247a0847..c17be2db97 100644 --- a/bridge/core/dom/node_list.h +++ b/bridge/core/dom/node_list.h @@ -13,8 +13,9 @@ class Node; class NodeList : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); + public: - NodeList(JSContext* ctx): ScriptWrappable(ctx) {}; + NodeList(JSContext* ctx) : ScriptWrappable(ctx){}; ~NodeList() override = default; // DOM methods & attributes for NodeList @@ -25,6 +26,8 @@ class NodeList : public ScriptWrappable { virtual bool IsEmptyNodeList() const { return false; } virtual bool IsChildNodeList() const { return false; } + const char* GetHumanReadableName() const override { return "NodeList"; }; + virtual Node* VirtualOwnerNode() const { return nullptr; } protected: diff --git a/bridge/core/dom/space_split_string.cc b/bridge/core/dom/space_split_string.cc new file mode 100644 index 0000000000..a06fa3694b --- /dev/null +++ b/bridge/core/dom/space_split_string.cc @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "space_split_string.h" + +namespace kraken { + +// https://dom.spec.whatwg.org/#concept-ordered-set-parser +template +inline void SpaceSplitString::Data::CreateVector(const AtomicString& source, + const CharacterType* characters, + unsigned length) { + DCHECK_EQ(0u, vector_.size()); + HashSet token_set; + unsigned start = 0; + while (true) { + while (start < length && IsHTMLSpace(characters[start])) + ++start; + if (start >= length) + break; + unsigned end = start + 1; + while (end < length && IsNotHTMLSpace(characters[end])) + ++end; + + if (start == 0 && end == length) { + vector_.push_back(source); + return; + } + + AtomicString token(characters + start, end - start); + // We skip adding |token| to |token_set| for the first token to reduce the + // cost of HashSet<>::insert(), and adjust |token_set| when the second + // unique token is found. + if (vector_.size() == 0) { + vector_.push_back(std::move(token)); + } else if (vector_.size() == 1) { + if (vector_[0] != token) { + token_set.insert(vector_[0].Impl()); + token_set.insert(token.Impl()); + vector_.push_back(std::move(token)); + } + } else if (token_set.insert(token.Impl()).is_new_entry) { + vector_.push_back(std::move(token)); + } + + start = end + 1; + } +} + +void SpaceSplitString::Data::CreateVector(const AtomicString& string) { + unsigned length = string.length(); + + if (string.Is8Bit()) { + CreateVector(string, string.Characters8(), length); + return; + } + + CreateVector(string, string.Characters16(), length); +} + +bool SpaceSplitString::Data::ContainsAll(Data& other) { + if (this == &other) + return true; + + wtf_size_t this_size = vector_.size(); + wtf_size_t other_size = other.vector_.size(); + for (wtf_size_t i = 0; i < other_size; ++i) { + const AtomicString& name = other.vector_[i]; + wtf_size_t j; + for (j = 0; j < this_size; ++j) { + if (vector_[j] == name) + break; + } + if (j == this_size) + return false; + } + return true; +} + +void SpaceSplitString::Data::Add(const AtomicString& string) { + DCHECK(HasOneRef()); + DCHECK(!Contains(string)); + vector_.push_back(string); +} + +void SpaceSplitString::Data::Remove(unsigned index) { + DCHECK(HasOneRef()); + vector_.EraseAt(index); +} + +void SpaceSplitString::Add(const AtomicString& string) { + if (Contains(string)) + return; + EnsureUnique(); + if (data_) + data_->Add(string); + else + data_ = Data::Create(string); +} + +bool SpaceSplitString::Remove(const AtomicString& string) { + if (!data_) + return false; + unsigned i = 0; + bool changed = false; + while (i < data_->size()) { + if ((*data_)[i] == string) { + if (!changed) + EnsureUnique(); + data_->Remove(i); + changed = true; + continue; + } + ++i; + } + return changed; +} + +void SpaceSplitString::Remove(wtf_size_t index) { + DCHECK_LT(index, size()); + EnsureUnique(); + data_->Remove(index); +} + +void SpaceSplitString::ReplaceAt(wtf_size_t index, const AtomicString& token) { + DCHECK_LT(index, data_->size()); + EnsureUnique(); + (*data_)[index] = token; +} + +AtomicString SpaceSplitString::SerializeToString() const { + size_t size = this->size(); + if (size == 0) + return g_empty_atom; + if (size == 1) + return (*data_)[0]; + StringBuilder builder; + builder.Append((*data_)[0]); + for (wtf_size_t i = 1; i < size; ++i) { + builder.Append(' '); + builder.Append((*data_)[i]); + } + return builder.ToAtomicString(); +} + +void SpaceSplitString::Set(const AtomicString& input_string) { + if (input_string.IsNull()) { + Clear(); + return; + } + data_ = Data::Create(input_string); +} + +SpaceSplitString::Data::~Data() {} + +std::shared_ptr SpaceSplitString::Data::Create(const AtomicString& string) { + Data*& data = SharedDataMap().insert({string.Impl(), nullptr}).stored_value->value; + if (!data) { + data = new Data(string); + return base::AdoptRef(data); + } + return data; +} + +std::unique_ptr SpaceSplitString::Data::CreateUnique(const Data& other) { + return std::make_unique(other); +} + +SpaceSplitString::Data::Data(const AtomicString& string) : key_string_(string) { + DCHECK(!string.IsNull()); + CreateVector(string); +} + +SpaceSplitString::Data::Data(const SpaceSplitString::Data& other) : RefCounted(), vector_(other.vector_) { + // Note that we don't copy key_string_ to indicate to the destructor that + // there's nothing to be removed from the SharedDataMap(). +} + +} // namespace kraken diff --git a/bridge/core/dom/space_split_string.h b/bridge/core/dom/space_split_string.h new file mode 100644 index 0000000000..23cd4473f0 --- /dev/null +++ b/bridge/core/dom/space_split_string.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_SPACE_SPLIT_STRING_H_ +#define KRAKENBRIDGE_CORE_DOM_SPACE_SPLIT_STRING_H_ + +#include +#include +#include +#include "bindings/qjs/atomic_string.h" + +namespace kraken { + +class SpaceSplitString { + public: + SpaceSplitString() = default; + explicit SpaceSplitString(const AtomicString& string) { Set(string); } + + bool operator!=(const SpaceSplitString& other) const { return data_ != other.data_; } + + void Set(const AtomicString&); + void Clear() { data_ = nullptr; } + + bool Contains(const AtomicString& string) const { return data_ && data_->Contains(string); } + bool ContainsAll(const SpaceSplitString& names) const { + return !names.data_ || (data_ && data_->ContainsAll(*names.data_)); + } + void Add(const AtomicString&); + bool Remove(const AtomicString&); + void Remove(size_t index); + void ReplaceAt(size_t index, const AtomicString&); + + // https://dom.spec.whatwg.org/#concept-ordered-set-serializer + // The ordered set serializer takes a set and returns the concatenation of the + // strings in set, separated from each other by U+0020, if set is non-empty, + // and the empty string otherwise. + AtomicString SerializeToString() const; + + size_t size() const { return data_ ? data_->size() : 0; } + bool IsNull() const { return !data_; } + const AtomicString& operator[](size_t i) const { return (*data_)[i]; } + + private: + class Data { + public: + static std::shared_ptr Create(const AtomicString&); + static std::unique_ptr CreateUnique(const Data&); + + ~Data(); + + bool Contains(const AtomicString& string) const { return std::find(vector_.begin(), vector_.end(), string) != vector_.end(); } + + bool ContainsAll(Data&); + + void Add(const AtomicString&); + void Remove(unsigned index); + + bool IsUnique() const { return key_string_.IsNull(); } + size_t size() const { return vector_.size(); } + const AtomicString& operator[](size_t i) const { return vector_[i]; } + AtomicString& operator[](size_t i) { return vector_[i]; } + + explicit Data(const Data&); + private: + explicit Data(const AtomicString&); + + void CreateVector(const AtomicString&); + template + inline void CreateVector(const AtomicString&, const CharacterType*, unsigned); + + AtomicString key_string_; + std::vector vector_; + }; + + // We can use a non-ref-counted StringImpl* as the key because the associated + // Data object will keep it alive via the key_string_ member. + typedef std::unordered_map DataMap; + static DataMap& SharedDataMap(); + + void EnsureUnique() { + if (data_ && !data_->IsUnique()) + data_ = Data::CreateUnique(*data_); + } + + std::shared_ptr data_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_SPACE_SPLIT_STRING_H_ diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc index d62f381e0f..f15d37f2c9 100644 --- a/bridge/core/events/error_event.cc +++ b/bridge/core/events/error_event.cc @@ -8,16 +8,16 @@ namespace kraken { ErrorEvent* ErrorEvent::Create(ExecutingContext* context, const std::string& message) { - return makeGarbageCollected(context, message); + return MakeGarbageCollected(context, message); } ErrorEvent* ErrorEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { - return makeGarbageCollected(context, type, exception_state); + return MakeGarbageCollected(context, type, exception_state); } ErrorEvent* ErrorEvent::Create(ExecutingContext* context, const AtomicString& type, const std::shared_ptr& initializer, ExceptionState& exception_state) { - return makeGarbageCollected(context, type, initializer, exception_state); + return MakeGarbageCollected(context, type, initializer, exception_state); } ErrorEvent::ErrorEvent(ExecutingContext* context, const std::string& message) diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 3493fbeec0..6d46958b93 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -51,20 +51,20 @@ void BlobReaderClient::DidFinishLoading() { } Blob* Blob::Create(ExecutingContext* context) { - return makeGarbageCollected(context->ctx()); + return MakeGarbageCollected(context->ctx()); } Blob* Blob::Create(ExecutingContext* context, std::vector>& data, ExceptionState& exception_state) { - return makeGarbageCollected(context->ctx(), data); + return MakeGarbageCollected(context->ctx(), data); } Blob* Blob::Create(ExecutingContext* context, std::vector>& data, std::shared_ptr property, ExceptionState& exception_state) { - return makeGarbageCollected(context->ctx(), data, property); + return MakeGarbageCollected(context->ctx(), data, property); } int32_t Blob::size() { @@ -90,7 +90,7 @@ Blob* Blob::slice(int64_t start, int64_t end, ExceptionState& exception_state) { return slice(start, end, AtomicString::Empty(ctx()), exception_state); } Blob* Blob::slice(int64_t start, int64_t end, const AtomicString& content_type, ExceptionState& exception_state) { - auto* newBlob = makeGarbageCollected(ctx()); + auto* newBlob = MakeGarbageCollected(ctx()); std::vector newData; newData.reserve(_data.size() - (end - start)); newData.insert(newData.begin(), _data.begin() + start, _data.end() - (_data.size() - end)); diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 75d9aee533..18c402c7fa 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -51,9 +51,6 @@ void* DOMTimerCoordinator::removeTimeoutById(int32_t timerId) { return nullptr; auto 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; } diff --git a/bridge/core/frame/dom_timer_coordinator.h b/bridge/core/frame/dom_timer_coordinator.h index 51226b495a..99621f08ef 100644 --- a/bridge/core/frame/dom_timer_coordinator.h +++ b/bridge/core/frame/dom_timer_coordinator.h @@ -33,7 +33,6 @@ class DOMTimerCoordinator { private: std::unordered_map> m_activeTimers; - std::vector> m_abandonedTimers; }; } // namespace kraken diff --git a/bridge/core/html/html_collection.cc b/bridge/core/html/html_collection.cc new file mode 100644 index 0000000000..d084c41ef3 --- /dev/null +++ b/bridge/core/html/html_collection.cc @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "html_collection.h" + +namespace kraken { + + + +} diff --git a/bridge/core/html/html_collection.h b/bridge/core/html/html_collection.h new file mode 100644 index 0000000000..24efb53ff4 --- /dev/null +++ b/bridge/core/html/html_collection.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ + +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +class HTMLCollection : public ScriptWrappable { + public: + + private: + +}; + +} + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ From a94980449d731154d8b9155c605c4a3a600f5190 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 8 Apr 2022 06:00:06 +0800 Subject: [PATCH 061/498] feat: add node and attr methods --- bridge/CMakeLists.txt | 2 + bridge/core/dom/attr.cc | 6 ++ bridge/core/dom/attr.h | 65 +++++++++++++ bridge/core/dom/container_node.cc | 27 +----- bridge/core/dom/container_node.h | 12 +-- bridge/core/dom/document_fragment.cc | 11 ++- bridge/core/dom/document_fragment.h | 4 +- bridge/core/dom/node.cc | 132 ++++++++++++++++++++++++++- bridge/core/dom/node.h | 25 +---- 9 files changed, 219 insertions(+), 65 deletions(-) create mode 100644 bridge/core/dom/attr.cc create mode 100644 bridge/core/dom/attr.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 6afc71aeb1..5981b5c7f0 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -285,6 +285,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/events/event_target_impl.h core/dom/node.cc core/dom/node.h + core/dom/attr.cc + core/dom/attr.h core/dom/element.cc core/dom/element.h core/dom/element_data.cc diff --git a/bridge/core/dom/attr.cc b/bridge/core/dom/attr.cc new file mode 100644 index 0000000000..8a7b1c785c --- /dev/null +++ b/bridge/core/dom/attr.cc @@ -0,0 +1,6 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + + +#include "attr.h" diff --git a/bridge/core/dom/attr.h b/bridge/core/dom/attr.h new file mode 100644 index 0000000000..e2947fd5a9 --- /dev/null +++ b/bridge/core/dom/attr.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_ATTR_H_ +#define KRAKENBRIDGE_CORE_DOM_ATTR_H_ + +#include "bindings/qjs/atomic_string.h" +#include "node.h" + +namespace kraken { + +class Element; +class Document; + +class Attr : public Node { + DEFINE_WRAPPERTYPEINFO(); + public: + Attr(Element& element, const AtomicString& name); + Attr(Document& document, const AtomicString& name, const AtomicString& value); + + ~Attr() override; + + std::string name() const { return name_.ToStdString(); } + bool specified() const { return true; } + Element* ownerElement() const { return element_; } + + const AtomicString& value() const; + void setValue(const AtomicString&, ExceptionState&); + + const QualifiedName GetQualifiedName() const; + + void AttachToElement(Element*, const AtomicString&); + void DetachFromElementWithValue(const AtomicString&); + + const AtomicString& localName() const { return name_.LocalName(); } + const AtomicString& namespaceURI() const { return name_.NamespaceURI(); } + const AtomicString& prefix() const { return name_.Prefix(); } + + void Trace(Visitor*) const override; + + const AtomicString& localName() const { return name_; } + + + private: + bool IsElementNode() const = delete; // This will catch anyone doing an unnecessary check. + + std::string nodeName() const override { return name(); } + NodeType getNodeType() const override { return kAttributeNode; } + + std::string nodeValue() const override { return value().ToStdString(); } + void setNodeValue(const std::string& node_value, ExceptionState& exception_state) override; + void setTextContentForBinding(const V8UnionStringOrTrustedScript* value, + ExceptionState& exception_state) override; + Node* Clone(Document&, CloneChildrenFlag) const override; + + bool IsAttributeNode() const override { return true; } + + Element* element_; + AtomicString name_; +}; + +} + +#endif // KRAKENBRIDGE_CORE_DOM_ATTR_H_ diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index a9bd66de8b..03b0b0e168 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -336,32 +336,7 @@ void ContainerNode::RemoveChildren() { } } -void ContainerNode::ParserAppendChild(Node* new_child) { - assert(new_child); - assert(!new_child->IsDocumentFragment()); - - if (!CheckParserAcceptChild(*new_child)) - return; - - // FIXME: parserRemoveChild can run script which could then insert the - // newChild back into the page. Loop until the child is actually removed. - // See: fast/parser/execute-script-during-adoption-agency-removal.html - while (ContainerNode* parent = new_child->parentNode()) - parent->ParserRemoveChild(*new_child); - - if (GetDocument() != new_child->GetDocument()) - GetDocument().adoptNode(new_child, ASSERT_NO_EXCEPTION); +ContainerNode::ContainerNode(Document* document, ConstructionType type) : Node(document, type) {} - { - EventDispatchForbiddenScope assert_no_event_dispatch; - ScriptForbiddenScope forbid_script; - - AdoptAndAppendChild()(*this, *new_child, nullptr); - DCHECK_EQ(new_child->ConnectedSubframeCount(), 0u); - ChildListMutationScope(*this).ChildAdded(*new_child); - } - - NotifyNodeInserted(*new_child, ChildrenChangeSource::kParser); -} } // namespace kraken diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index a517228dcc..0700dab23b 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -154,7 +154,7 @@ class ContainerNode : public Node { void Trace(GCVisitor* visitor) const override; protected: - ContainerNode(ExecutingContext* context, ConstructionType = kCreateContainer); + ContainerNode(Document* document, ConstructionType = kCreateContainer); void SetFirstChild(Node* child) { first_child_ = child; } void SetLastChild(Node* child) { last_child_ = child; } @@ -167,14 +167,8 @@ class ContainerNode : public Node { // |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); - void DidInsertNodeVector( - const NodeVector&, - Node* next, - const NodeVector& post_insertion_notification_targets); + void InsertNodeVector(const NodeVector&, Node* next, const Functor&, NodeVector* post_insertion_notification_targets); + void DidInsertNodeVector(const NodeVector&, Node* next, const NodeVector& post_insertion_notification_targets); class AdoptAndInsertBefore; class AdoptAndAppendChild; diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index 5589cd4b15..3516a82ca7 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -8,11 +8,11 @@ namespace kraken { -DocumentFragment* DocumentFragment::Create(ExecutingContext* context, ExceptionState& exception_state) { +DocumentFragment* DocumentFragment::Create(ExecutingContext* context, Document* document, ExceptionState& exception_state) { return nullptr; } -DocumentFragment::DocumentFragment(ExecutingContext* context): ContainerNode(context, ConstructionType::kCreateDocumentFragment) {} +DocumentFragment::DocumentFragment(ExecutingContext* context, Document* document): ContainerNode(context, ConstructionType::kCreateDocumentFragment) {} std::string DocumentFragment::nodeName() const { return "#document-fragment"; @@ -22,6 +22,13 @@ Node::NodeType DocumentFragment::getNodeType() const { return NodeType::kDocumentFragmentNode; } +Node* DocumentFragment::Clone(Document& factory, CloneChildrenFlag flag) const { + DocumentFragment* clone = Create(factory); + if (flag != CloneChildrenFlag::kSkip) + clone->CloneChildNodesFrom(*this, flag); + return clone; +} + bool DocumentFragment::ChildTypeAllowed(NodeType type) const { switch (type) { case kElementNode: diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index dd5367d7fc..f446d4d634 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -13,9 +13,9 @@ namespace kraken { class DocumentFragment : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); public: - static DocumentFragment* Create(ExecutingContext* context, ExceptionState& exception_state); + static DocumentFragment* Create(ExecutingContext* context, Document* document, ExceptionState& exception_state); - DocumentFragment(ExecutingContext* context); + DocumentFragment(ExecutingContext* context, Document* document); virtual bool IsTemplateContent() const { return false; } diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 9a2eb9e525..d1cf9f86fb 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -3,10 +3,13 @@ */ #include "node.h" -#include "node_data.h" -#include "node_list.h" #include "child_node_list.h" +#include "document.h" +#include "document_fragment.h" #include "empty_node_list.h" +#include "node_data.h" +#include "node_list.h" +#include "attr.h" namespace kraken { @@ -36,6 +39,131 @@ NodeData& Node::EnsureData() { return CreateData(); } +Node& Node::TreeRoot() const { + const Node* node = this; + while (node->parentNode()) + node = node->parentNode(); + return const_cast(*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(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(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(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(this); + if (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(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(this); + bool clone_shadows_flag = fragment && fragment->IsTemplateContent(); + return Clone(GetDocument(), + deep ? (clone_shadows_flag ? CloneChildrenFlag::kCloneWithShadows : CloneChildrenFlag::kClone) + : CloneChildrenFlag::kSkip); +} + +bool Node::isEqualNode(Node* other) const { + if (!other) + return false; + + NodeType node_type = getNodeType(); + if (node_type != other->getNodeType()) + return false; + + if (nodeValue() != other->nodeValue()) + return false; + + if (auto* this_attr = DynamicTo(this)) { + auto* other_attr = To(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(this)) { + auto* other_element = DynamicTo(other); + if (this_element->TagQName() != other_element->TagQName()) + 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; + + if (const auto* document_type_this = DynamicTo(this)) { + const auto* document_type_other = To(other); + + if (document_type_this->publicId() != document_type_other->publicId()) + return false; + + if (document_type_this->systemId() != document_type_other->systemId()) + return false; + } + + return true; +} + +Node::Node(Document* document, ConstructionType type) + : EventTarget(document->GetExecutingContext()), + node_flags_(type), + parent_or_shadow_host_node_(nullptr), + previous_(nullptr), + next_(nullptr) { +} + void Node::Trace(GCVisitor*) const {} } // namespace kraken diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 104c235e7d..660bd2d29f 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -22,7 +22,6 @@ class Element; class Document; class DocumentFragment; class TextNode; -class Document; class ContainerNode; class NodeData; class NodeList; @@ -73,7 +72,6 @@ class Node : public EventTarget { Node* lastChild() const; Node& TreeRoot() const; - Node& ShadowIncludingRoot() const; // TODO: support following APIs. // void Prepend( @@ -96,21 +94,11 @@ class Node : public EventTarget { // ExceptionState& exception_state); void remove(ExceptionState&); - void remove(); - - Node* PseudoAwareNextSibling() const; - Node* PseudoAwarePreviousSibling() const; - Node* PseudoAwareFirstChild() const; - Node* PseudoAwareLastChild() const; Node* insertBefore(Node* new_child, Node* ref_child, ExceptionState&); - Node* insertBefore(Node* new_child, Node* ref_child); Node* replaceChild(Node* new_child, Node* old_child, ExceptionState&); - Node* replaceChild(Node* new_child, Node* old_child); Node* removeChild(Node* child, ExceptionState&); - Node* removeChild(Node* child); Node* appendChild(Node* new_child, ExceptionState&); - Node* appendChild(Node* new_child); bool hasChildren() const { return firstChild(); } Node* cloneNode(bool deep, ExceptionState&) const; @@ -118,10 +106,6 @@ class Node : public EventTarget { // https://dom.spec.whatwg.org/#concept-node-clone virtual Node* Clone(Document&, CloneChildrenFlag) const = 0; - // This is not web-exposed. We should rename it or remove it. - Node* cloneNode(bool deep) const; - void normalize(); - bool isEqualNode(Node*) const; bool isSameNode(const Node* other) const { return this == other; } @@ -144,16 +128,9 @@ class Node : public EventTarget { bool IsCustomElement() const { return GetCustomElementState() != CustomElementState::kUncustomized; } void SetCustomElementState(CustomElementState); - virtual bool IsMediaControlElement() const { return false; } - virtual bool IsMediaControls() const { return false; } virtual bool IsMediaElement() const { return false; } - virtual bool IsTextTrackContainer() const { return false; } - virtual bool IsVTTElement() const { return false; } virtual bool IsAttributeNode() const { return false; } virtual bool IsCharacterDataNode() const { return false; } - virtual bool IsFrameOwnerElement() const { return false; } - virtual bool IsMediaRemotingInterstitial() const { return false; } - virtual bool IsPictureInPictureInterstitial() const { return false; } // StyledElements allow inline style (style="border: 1px"), presentational // attributes (ex. color), class names (ex. class="foo bar") and other @@ -318,7 +295,7 @@ class Node : public EventTarget { kCreateDocument = kCreateContainer | kIsConnectedFlag, }; - Node(ConstructionType); + Node(Document*, ConstructionType); void SetIsFinishedParsingChildren(bool value) { SetFlag(value, kIsFinishedParsingChildrenFlag); } From b2e809d151d184790be804fd3565bd4324f6d5d0 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 8 Apr 2022 07:39:46 +0800 Subject: [PATCH 062/498] feat: add text --- bridge/CMakeLists.txt | 4 ++++ bridge/core/dom/character_data.cc | 12 ++++++++--- bridge/core/dom/character_data.h | 32 +++++++++++++++++++++++++---- bridge/core/dom/node.cc | 34 +++++++++++++++++++++++-------- bridge/core/dom/node.h | 3 ++- bridge/core/dom/text.cc | 25 +++++++++++++++++++++++ bridge/core/dom/text.h | 30 +++++++++++++++++++++++++++ 7 files changed, 123 insertions(+), 17 deletions(-) create mode 100644 bridge/core/dom/text.cc create mode 100644 bridge/core/dom/text.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 5981b5c7f0..e17dacee9b 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -287,6 +287,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/node.h core/dom/attr.cc core/dom/attr.h + core/dom/character_data.cc + core/dom/character_data.h + core/dom/text.cc + core/dom/text.h core/dom/element.cc core/dom/element.h core/dom/element_data.cc diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index 9786018ea0..19c547962c 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -1,6 +1,12 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2021-present The Kraken authors. All rights reserved. +*/ #include "character_data.h" + +namespace kraken { + +void CharacterData::setData(const std::string& data) { + data_ = data; +} +} diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index f67ac8c03b..bc7c43ce21 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -1,11 +1,35 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2021-present The Kraken authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CHARACTER_DATA_H #define KRAKENBRIDGE_CHARACTER_DATA_H -class character_data {}; +#include "node.h" + +namespace kraken { + +class CharacterData : public Node { + DEFINE_WRAPPERTYPEINFO(); + public: + const AtomicString& data() const { + return data_; + } + void setData(const std::string& data); + + protected: + CharacterData(Document& tree_scope, + const AtomicString& text, + ConstructionType type) + : Node(&tree_scope, type), + data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { + assert(type == kCreateOther || type == kCreateText); + } + + private: + AtomicString data_; +}; + +} #endif // KRAKENBRIDGE_CHARACTER_DATA_H diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index d1cf9f86fb..9570d34602 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -10,6 +10,7 @@ #include "node_data.h" #include "node_list.h" #include "attr.h" +#include "character_data.h" namespace kraken { @@ -143,17 +144,32 @@ bool Node::isEqualNode(Node* other) const { if (other_child) return false; - if (const auto* document_type_this = DynamicTo(this)) { - const auto* document_type_other = To(other); - - if (document_type_this->publicId() != document_type_other->publicId()) - return false; + return true; +} - if (document_type_this->systemId() != document_type_other->systemId()) - return false; +std::string 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(this)) + return character_data->data(); + + // Attribute nodes have their attribute values as textContent. + if (auto* attr = DynamicTo(this)) + return attr->value(); + + // Documents and non-container nodes (that are not CharacterData) + // have null textContent. + if (IsDocumentNode() || !IsContainerNode()) + return ""; + + std::string content; + for (const Node& node : NodeTraversal::InclusiveDescendantsOf(*this)) { + if (auto* text_node = DynamicTo(node)) { + content += (text_node->data()); + } } - - return true; + return content.ReleaseString(); } Node::Node(Document* document, ConstructionType type) diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 660bd2d29f..c655566592 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -63,6 +63,7 @@ class Node : public EventTarget { virtual std::string nodeValue() const; virtual void setNodeValue(const std::string&, ExceptionState&); virtual NodeType getNodeType() const = 0; + ContainerNode* parentNode() const; Element* parentElement() const; Node* previousSibling() const { return previous_; } @@ -109,7 +110,7 @@ class Node : public EventTarget { bool isEqualNode(Node*) const; bool isSameNode(const Node* other) const { return this == other; } - AtomicString textContent(bool convert_brs_to_newlines = false) const; + std::string textContent(bool convert_brs_to_newlines = false) const; virtual void setTextContent(const AtomicString&); // Other methods (not part of DOM) diff --git a/bridge/core/dom/text.cc b/bridge/core/dom/text.cc new file mode 100644 index 0000000000..bafc9b0317 --- /dev/null +++ b/bridge/core/dom/text.cc @@ -0,0 +1,25 @@ +/* +* Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "text.h" + +namespace kraken { + +Text* Text::Create(Document& document, const AtomicString& value) { + return MakeGarbageCollected(document, value, ConstructionType::kCreateText); +} + +Node::NodeType Text::getNodeType() const { + return Node::kTextNode; +} + +std::string Text::nodeName() const { + return "#text"; +} + +Node* Text::Clone(Document& document, CloneChildrenFlag flag) const { + return Create(document, data()); +} + +} diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h new file mode 100644 index 0000000000..6aa06b3cd1 --- /dev/null +++ b/bridge/core/dom/text.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_TEXT_H_ +#define KRAKENBRIDGE_CORE_DOM_TEXT_H_ + +#include "character_data.h" + +namespace kraken { + +class Text : public CharacterData { + DEFINE_WRAPPERTYPEINFO(); + public: + static const unsigned kDefaultLengthLimit = 1 << 16; + + static Text* Create(Document&, const AtomicString&); + + Text(Document& document, const AtomicString& data, ConstructionType type) : CharacterData(document, data, type) {} + + NodeType getNodeType() const override; + + private: + std::string nodeName() const override; + Node* Clone(Document&, CloneChildrenFlag) const override; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_TEXT_H_ From f181600016e4791be6e84841d1c52e8f2d6ca85d Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Thu, 7 Apr 2022 23:40:40 +0000 Subject: [PATCH 063/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 4 +- bridge/bindings/qjs/converter_impl.h | 2 +- bridge/bindings/qjs/js_event_handler.cc | 4 +- bridge/bindings/qjs/js_event_handler.h | 1 - bridge/bindings/qjs/js_event_listener.cc | 7 +- bridge/bindings/qjs/qjs_function.h | 4 +- bridge/bindings/qjs/script_value.cc | 2 - bridge/bindings/qjs/script_value.h | 2 +- bridge/bindings/qjs/script_value_test.cc | 9 +-- bridge/core/dom/attr.cc | 1 - bridge/core/dom/attr.h | 7 +- bridge/core/dom/character_data.cc | 6 +- bridge/core/dom/character_data.h | 18 ++--- bridge/core/dom/child_node_list.cc | 17 ++-- bridge/core/dom/child_node_list.h | 16 ++-- bridge/core/dom/collection_index_cache.h | 2 +- bridge/core/dom/container_node.cc | 83 +++++++------------- bridge/core/dom/document.cc | 6 +- bridge/core/dom/document.h | 13 ++- bridge/core/dom/document_fragment.cc | 7 +- bridge/core/dom/document_fragment.h | 2 +- bridge/core/dom/element.h | 9 +-- bridge/core/dom/element_data.h | 3 +- bridge/core/dom/events/event.cc | 10 ++- bridge/core/dom/events/event_listener_map.cc | 4 +- bridge/core/dom/node.cc | 7 +- bridge/core/dom/node_data.cc | 2 +- bridge/core/dom/space_split_string.h | 11 ++- bridge/core/dom/text.cc | 4 +- bridge/core/dom/text.h | 1 + bridge/core/html/html_collection.cc | 6 +- bridge/core/html/html_collection.h | 4 +- 32 files changed, 108 insertions(+), 166 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index f28bc56614..c77ac6303a 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -7,10 +7,10 @@ #include "core/executing_context.h" #include "qjs_console.h" +#include "qjs_event.h" +#include "qjs_event_target.h" #include "qjs_module_manager.h" #include "qjs_window.h" -#include "qjs_event_target.h" -#include "qjs_event.h" namespace kraken { diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index b40657f065..bbd8faabed 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -424,7 +424,7 @@ struct Converter : public ConverterBase +template <> struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index 8cab5ae583..235749cef7 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -100,8 +100,6 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // TODO: special handling for beforeunload event and onerror event. } -void JSEventHandler::Trace(GCVisitor* visitor) const { - -} +void JSEventHandler::Trace(GCVisitor* visitor) const {} } // namespace kraken diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index 5dc852ba93..429d2248d4 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -55,7 +55,6 @@ class JSEventHandler : public JSBasedEventListener { void Trace(GCVisitor* visitor) const override; - private: // JSBasedEventListener override: // Performs "The event handler processing algorithm" diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index d81e5d4a25..0db61c2e43 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -16,12 +16,9 @@ JSValue JSEventListener::GetEffectiveFunction(EventTarget&) { return event_listener_->ToQuickJS(); } void JSEventListener::InvokeInternal(EventTarget& event_target, Event& event, ExceptionState& exception_state) { - ScriptValue arguments[] = { - event.ToValue() - }; + ScriptValue arguments[] = {event.ToValue()}; - ScriptValue result = - event_listener_->Invoke(event.ctx(), event_target.ToValue(), 1, arguments); + ScriptValue result = event_listener_->Invoke(event.ctx(), event_target.ToValue(), 1, arguments); if (result.IsException()) { exception_state.ThrowException(event.ctx(), result.QJSValue()); return; diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 9429eba96c..556c357dca 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -20,9 +20,7 @@ class QJSFunction { } explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), function_(JS_DupValue(ctx, function)){}; // This safe to free function_ at GC stage. - ~QJSFunction() { - JS_FreeValue(ctx_, function_); - } + ~QJSFunction() { JS_FreeValue(ctx_, function_); } bool IsFunction(JSContext* ctx); diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 3a8671a7b4..1be5317acd 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -58,7 +58,6 @@ ScriptValue& ScriptValue::operator=(ScriptValue&& value) noexcept { return *this; } - JSValue ScriptValue::QJSValue() const { return value_; } @@ -92,7 +91,6 @@ bool ScriptValue::IsObject() { return JS_IsObject(value_); } - bool ScriptValue::IsString() { return JS_IsString(value_); } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index ebfcde380c..5a27f086bf 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -9,11 +9,11 @@ #include #include +#include "atomic_string.h" #include "exception_state.h" #include "foundation/macros.h" #include "foundation/native_string.h" #include "gc_visitor.h" -#include "atomic_string.h" namespace kraken { diff --git a/bridge/bindings/qjs/script_value_test.cc b/bridge/bindings/qjs/script_value_test.cc index 2f4dcb1388..505d2efd63 100644 --- a/bridge/bindings/qjs/script_value_test.cc +++ b/bridge/bindings/qjs/script_value_test.cc @@ -2,10 +2,10 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ -#include "atomic_string.h" +#include "script_value.h" #include #include -#include "script_value.h" +#include "atomic_string.h" #include "gtest/gtest.h" using namespace kraken; @@ -22,7 +22,6 @@ void TestScriptValue(TestCallback callback) { JS_FreeRuntime(runtime); } - TEST(ScriptValue, createErrorObject) { TestScriptValue([](JSContext* ctx) { ScriptValue value = ScriptValue::CreateErrorObject(ctx, "error"); @@ -63,7 +62,7 @@ TEST(ScriptValue, CopyAssignment) { }; P p; p.value = json; - EXPECT_STREQ(p.value.ToJSONStringify(nullptr).ToString().ToStdString().c_str(), code.c_str()); + EXPECT_STREQ(p.value.ToJSONStringify(nullptr).ToString().ToStdString().c_str(), code.c_str()); }); } @@ -75,6 +74,6 @@ TEST(ScriptValue, MoveAssignment) { other = ScriptValue::CreateJsonObject(ctx, code.c_str(), code.size()); } - EXPECT_STREQ(other.ToJSONStringify(nullptr).ToString().ToStdString().c_str(), "{\"name\":1}"); + EXPECT_STREQ(other.ToJSONStringify(nullptr).ToString().ToStdString().c_str(), "{\"name\":1}"); }); } diff --git a/bridge/core/dom/attr.cc b/bridge/core/dom/attr.cc index 8a7b1c785c..d9864f8c72 100644 --- a/bridge/core/dom/attr.cc +++ b/bridge/core/dom/attr.cc @@ -2,5 +2,4 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ - #include "attr.h" diff --git a/bridge/core/dom/attr.h b/bridge/core/dom/attr.h index e2947fd5a9..002c2f1b25 100644 --- a/bridge/core/dom/attr.h +++ b/bridge/core/dom/attr.h @@ -15,6 +15,7 @@ class Document; class Attr : public Node { DEFINE_WRAPPERTYPEINFO(); + public: Attr(Element& element, const AtomicString& name); Attr(Document& document, const AtomicString& name, const AtomicString& value); @@ -41,7 +42,6 @@ class Attr : public Node { const AtomicString& localName() const { return name_; } - private: bool IsElementNode() const = delete; // This will catch anyone doing an unnecessary check. @@ -50,8 +50,7 @@ class Attr : public Node { std::string nodeValue() const override { return value().ToStdString(); } void setNodeValue(const std::string& node_value, ExceptionState& exception_state) override; - void setTextContentForBinding(const V8UnionStringOrTrustedScript* value, - ExceptionState& exception_state) override; + void setTextContentForBinding(const V8UnionStringOrTrustedScript* value, ExceptionState& exception_state) override; Node* Clone(Document&, CloneChildrenFlag) const override; bool IsAttributeNode() const override { return true; } @@ -60,6 +59,6 @@ class Attr : public Node { AtomicString name_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_ATTR_H_ diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index 19c547962c..2425d064f3 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2021-present The Kraken authors. All rights reserved. -*/ + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ #include "character_data.h" @@ -9,4 +9,4 @@ namespace kraken { void CharacterData::setData(const std::string& data) { data_ = data; } -} +} // namespace kraken diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index bc7c43ce21..c011fe6848 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2021-present The Kraken authors. All rights reserved. -*/ + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ #ifndef KRAKENBRIDGE_CHARACTER_DATA_H #define KRAKENBRIDGE_CHARACTER_DATA_H @@ -11,18 +11,14 @@ namespace kraken { class CharacterData : public Node { DEFINE_WRAPPERTYPEINFO(); + public: - const AtomicString& data() const { - return data_; - } + const AtomicString& data() const { return data_; } void setData(const std::string& data); protected: - CharacterData(Document& tree_scope, - const AtomicString& text, - ConstructionType type) - : Node(&tree_scope, type), - data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { + CharacterData(Document& tree_scope, const AtomicString& text, ConstructionType type) + : Node(&tree_scope, type), data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { assert(type == kCreateOther || type == kCreateText); } @@ -30,6 +26,6 @@ class CharacterData : public Node { AtomicString data_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CHARACTER_DATA_H diff --git a/bridge/core/dom/child_node_list.cc b/bridge/core/dom/child_node_list.cc index 7d10de8d8f..70e044e7d8 100644 --- a/bridge/core/dom/child_node_list.cc +++ b/bridge/core/dom/child_node_list.cc @@ -13,33 +13,26 @@ Node* ChildNodeList::VirtualOwnerNode() const { return &OwnerNode(); } - Node* ChildNodeList::item(unsigned index) const { return collection_index_cache_.NodeAt(*this, index); } -Node* ChildNodeList::TraverseForwardToOffset(unsigned offset, - Node& current_node, - unsigned& current_offset) const { +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()) { + 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 { +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()) { + for (Node* previous = current_node.previousSibling(); previous; previous = previous->previousSibling()) { if (--current_offset == offset) return previous; } @@ -52,4 +45,4 @@ void ChildNodeList::Trace(GCVisitor* visitor) const { NodeList::Trace(visitor); } -} +} // namespace kraken diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h index d7ae0bb5f3..abbae27720 100644 --- a/bridge/core/dom/child_node_list.h +++ b/bridge/core/dom/child_node_list.h @@ -6,9 +6,9 @@ #define KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ #include "bindings/qjs/gc_visitor.h" -#include "node_list.h" #include "collection_index_cache.h" #include "container_node.h" +#include "node_list.h" namespace kraken { @@ -18,9 +18,7 @@ class ChildNodeList : public NodeList { ~ChildNodeList() override; // DOM API. - unsigned length() const override { - return collection_index_cache_.NodeCount(*this); - } + unsigned length() const override { return collection_index_cache_.NodeCount(*this); } Node* item(unsigned index) const override; @@ -34,12 +32,8 @@ class ChildNodeList : public NodeList { 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; + 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; @@ -51,6 +45,6 @@ class ChildNodeList : public NodeList { mutable CollectionIndexCache collection_index_cache_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ diff --git a/bridge/core/dom/collection_index_cache.h b/bridge/core/dom/collection_index_cache.h index bd75a1222d..e185cadd53 100644 --- a/bridge/core/dom/collection_index_cache.h +++ b/bridge/core/dom/collection_index_cache.h @@ -6,8 +6,8 @@ #define KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ #include -#include "foundation/macros.h" #include "bindings/qjs/gc_visitor.h" +#include "foundation/macros.h" namespace kraken { diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 03b0b0e168..7b2295b51a 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -3,8 +3,8 @@ */ #include "container_node.h" -#include "document_fragment.h" #include "bindings/qjs/garbage_collected.h" +#include "document_fragment.h" namespace kraken { @@ -23,12 +23,9 @@ inline void GetChildNodes(ContainerNode& node, NodeVector& nodes) { nodes.push_back(child); } - class ContainerNode::AdoptAndInsertBefore { public: - inline void operator()(ContainerNode& container, - Node& child, - Node* next) const { + inline void operator()(ContainerNode& container, Node& child, Node* next) const { assert(next); assert(next->parentNode() == &container); container.InsertBeforeCommon(*next, child); @@ -37,9 +34,7 @@ class ContainerNode::AdoptAndInsertBefore { class ContainerNode::AdoptAndAppendChild { public: - inline void operator()(ContainerNode& container, Node& child, Node*) const { - container.AppendChildCommon(child); - } + inline void operator()(ContainerNode& container, Node& child, Node*) const { container.AppendChildCommon(child); } }; bool ContainerNode::IsChildTypeAllowed(const Node& child) const { @@ -47,8 +42,7 @@ bool ContainerNode::IsChildTypeAllowed(const Node& child) const { if (!child_fragment) return ChildTypeAllowed(child.getNodeType()); - for (Node* node = child_fragment->firstChild(); node; - node = node->nextSibling()) { + for (Node* node = child_fragment->firstChild(); node; node = node->nextSibling()) { if (!ChildTypeAllowed(node->getNodeType())) return false; } @@ -58,10 +52,9 @@ bool ContainerNode::IsChildTypeAllowed(const Node& child) const { // 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) { +static inline bool CollectChildrenAndRemoveFromOldParent(Node& node, + NodeVector& nodes, + ExceptionState& exception_state) { if (auto* fragment = DynamicTo(node)) { GetChildNodes(*fragment, nodes); fragment->RemoveChildren(); @@ -82,8 +75,7 @@ Node* ContainerNode::InsertBefore(Node* new_child, Node* ref_child, ExceptionSta 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)) + if (!EnsurePreInsertionValidity(*new_child, ref_child, nullptr, exception_state)) return new_child; // 2. Let reference child be child. @@ -97,8 +89,7 @@ Node* ContainerNode::InsertBefore(Node* new_child, Node* ref_child, ExceptionSta // 4. Adopt node into parent’s node document. NodeVector targets; targets.reserve(kInitialNodeVectorSize); - if (!CollectChildrenAndRemoveFromOldParent(*new_child, targets, - exception_state)) + if (!CollectChildrenAndRemoveFromOldParent(*new_child, targets, exception_state)) return new_child; // 5. Insert node into parent before reference child. @@ -117,8 +108,7 @@ Node* ContainerNode::ReplaceChild(Node* new_child, Node* old_child, ExceptionSta } // Step 2 to 6. - if (!EnsurePreInsertionValidity(*new_child, nullptr, old_child, - exception_state)) + if (!EnsurePreInsertionValidity(*new_child, nullptr, old_child, exception_state)) return old_child; // 7. Let reference child be child’s next sibling. @@ -159,18 +149,15 @@ Node* ContainerNode::ReplaceChild(Node* new_child, Node* old_child, ExceptionSta // 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)) + 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); + InsertNodeVector(targets, next, AdoptAndInsertBefore(), &post_insertion_notification_targets); } else { - InsertNodeVector(targets, nullptr, AdoptAndAppendChild(), - &post_insertion_notification_targets); + InsertNodeVector(targets, nullptr, AdoptAndAppendChild(), &post_insertion_notification_targets); } } DidInsertNodeVector(targets, next, post_insertion_notification_targets); @@ -191,9 +178,10 @@ Node* ContainerNode::RemoveChild(Node* old_child, ExceptionState& exception_stat // 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?"); + 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; } @@ -206,8 +194,7 @@ Node* ContainerNode::RemoveChild(Node* old_child, ExceptionState& exception_stat RemoveBetween(prev, next, *child); NotifyNodeRemoved(*child); } - ChildrenChanged(ChildrenChange::ForRemoval(*child, prev, next, - ChildrenChangeSource::kAPI)); + ChildrenChanged(ChildrenChange::ForRemoval(*child, prev, next, ChildrenChangeSource::kAPI)); } return child; } @@ -215,22 +202,17 @@ Node* ContainerNode::RemoveChild(Node* old_child, ExceptionState& exception_stat 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)) + if (!EnsurePreInsertionValidity(*new_child, nullptr, nullptr, exception_state)) return new_child; NodeVector targets; targets.reserve(kInitialNodeVectorSize); - if (!CollectChildrenAndRemoveFromOldParent(*new_child, targets, - exception_state)) + 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); - } + { InsertNodeVector(targets, nullptr, AdoptAndAppendChild(), &post_insertion_notification_targets); } DidInsertNodeVector(targets, nullptr, post_insertion_notification_targets); return new_child; } @@ -242,8 +224,7 @@ bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, assert(!(next && old_child)); // Use common case fast path if possible. - if ((new_child.IsElementNode() || new_child.IsTextNode()) && - IsElementNode()) { + if ((new_child.IsElementNode() || new_child.IsTextNode()) && IsElementNode()) { DCHECK(IsChildTypeAllowed(new_child)); // 2. If node is a host-including inclusive ancestor of parent, throw a // HierarchyRequestError. @@ -258,9 +239,8 @@ bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, // corruption. DCHECK(!new_child.IsPseudoElement()); if (new_child.IsPseudoElement()) { - exception_state.ThrowDOMException( - DOMExceptionCode::kHierarchyRequestError, - "The new child element is a pseudo-element."); + exception_state.ThrowDOMException(DOMExceptionCode::kHierarchyRequestError, + "The new child element is a pseudo-element."); return false; } @@ -270,8 +250,7 @@ bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, if (!CheckReferenceChildParent(*this, next, old_child, exception_state)) return false; // Step 4-6. - return document->CanAcceptChild(new_child, next, old_child, - exception_state); + return document->CanAcceptChild(new_child, next, old_child, exception_state); } // 2. If node is a host-including inclusive ancestor of parent, throw a @@ -291,8 +270,7 @@ bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, if (!IsChildTypeAllowed(new_child)) { exception_state.ThrowDOMException( DOMExceptionCode::kHierarchyRequestError, - "Nodes of type '" + new_child.nodeName() + - "' may not be inserted inside nodes of type '" + nodeName() + "'."); + "Nodes of type '" + new_child.nodeName() + "' may not be inserted inside nodes of type '" + nodeName() + "'."); return false; } @@ -308,10 +286,10 @@ void ContainerNode::RemoveChildren() { // and remove... e.g. stop loading frames, fire unload events. WillRemoveChildren(); -// { -// // Removing a node from a selection can cause widget updates. -// GetDocument().NodeChildrenWillBeRemoved(*this); -// } + // { + // // Removing a node from a selection can cause widget updates. + // GetDocument().NodeChildrenWillBeRemoved(*this); + // } std::vector removed_nodes; const bool children_changed = ChildrenChangedAllChildrenRemovedNeedsList(); @@ -338,5 +316,4 @@ void ContainerNode::RemoveChildren() { ContainerNode::ContainerNode(Document* document, ConstructionType type) : Node(document, type) {} - } // namespace kraken diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 327094bdbb..52b2529ded 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -5,8 +5,4 @@ #include "document.h" -namespace kraken { - - - -} // namespace kraken +namespace kraken {} // namespace kraken diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 96011e8853..63b60894bc 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -15,18 +15,18 @@ namespace kraken { // (typically, HTML) resource. class Document : public Node { DEFINE_WRAPPERTYPEINFO(); + public: private: - }; -//void bindDocument(ExecutionContext* context); +// void bindDocument(ExecutionContext* context); // -//using TraverseHandler = std::function; +// using TraverseHandler = std::function; // -//void traverseNode(Node* node, TraverseHandler handler); +// void traverseNode(Node* node, TraverseHandler handler); // -//class DocumentCookie { +// class DocumentCookie { // public: // DocumentCookie() = default; // @@ -37,7 +37,7 @@ class Document : public Node { // std::unordered_map cookiePairs; //}; -//class Document : public Node { +// class Document : public Node { // public: // static JSClassID classId; // static Document* create(JSContext* ctx); @@ -88,7 +88,6 @@ class Document : public Node { // std::unordered_map elementConstructorMap; //}; - } // namespace kraken #endif // KRAKENBRIDGE_DOCUMENT_H diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index 3516a82ca7..1d5705b973 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -8,11 +8,14 @@ namespace kraken { -DocumentFragment* DocumentFragment::Create(ExecutingContext* context, Document* document, ExceptionState& exception_state) { +DocumentFragment* DocumentFragment::Create(ExecutingContext* context, + Document* document, + ExceptionState& exception_state) { return nullptr; } -DocumentFragment::DocumentFragment(ExecutingContext* context, Document* document): ContainerNode(context, ConstructionType::kCreateDocumentFragment) {} +DocumentFragment::DocumentFragment(ExecutingContext* context, Document* document) + : ContainerNode(context, ConstructionType::kCreateDocumentFragment) {} std::string DocumentFragment::nodeName() const { return "#document-fragment"; diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index f446d4d634..9d827930a6 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -12,6 +12,7 @@ namespace kraken { class DocumentFragment : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); + public: static DocumentFragment* Create(ExecutingContext* context, Document* document, ExceptionState& exception_state); @@ -31,7 +32,6 @@ class DocumentFragment : public ContainerNode { bool ChildTypeAllowed(NodeType) const override; }; - } // namespace kraken #endif // KRAKENBRIDGE_DOCUMENT_FRAGMENT_H diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index b671d178aa..65eae4e074 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -22,21 +22,18 @@ struct NativeBoundingClientRect { double left; }; -//bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance); +// bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance); class Element : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); + public: - Element(ExecutingContext* context, const AtomicString& tag_name, - Document*, - ConstructionType = kCreateElement); + Element(ExecutingContext* context, const AtomicString& tag_name, Document*, ConstructionType = kCreateElement); bool hasAttribute(const AtomicString&) const; const AtomicString& getAttribute(const AtomicString&) const; - }; - } // namespace kraken #endif // KRAKENBRIDGE_ELEMENT_H diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/element_data.h index 086b7f7ad0..e030728cdc 100644 --- a/bridge/core/dom/element_data.h +++ b/bridge/core/dom/element_data.h @@ -11,13 +11,12 @@ namespace kraken { class ElementData { public: - private: mutable Member inline_style_; mutable SpaceSplitString class_names_; mutable AtomicString id_for_style_resolution_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_H_ diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index a03d0160bf..a55e1902e9 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -25,7 +25,12 @@ Event* Event::From(ExecutingContext* context, NativeEvent* native_event) { Event::Event(ExecutingContext* context) : Event(context, AtomicString::Empty(context->ctx())) {} Event::Event(ExecutingContext* context, const AtomicString& event_type) - : Event(context, event_type, Bubbles::kNo, Cancelable::kNo, ComposedMode::kComposed, std::chrono::system_clock::now().time_since_epoch().count()) {} + : Event(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& init) : ScriptWrappable(context->ctx()), @@ -114,7 +119,6 @@ void Event::SetHandlingPassive(PassiveMode mode) { handling_passive_ = mode; } -void Event::Trace(GCVisitor* visitor) const { -} +void Event::Trace(GCVisitor* visitor) const {} } // namespace kraken diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index 5347b7f3aa..ad65dd90fd 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -115,8 +115,8 @@ EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) { } void EventListenerMap::Trace(GCVisitor* visitor) const { - for(const auto& entry: entries_) { - for(auto& listener : *entry.second) { + for (const auto& entry : entries_) { + for (auto& listener : *entry.second) { listener.Trace(visitor); } } diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 9570d34602..d6966d1089 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -3,14 +3,14 @@ */ #include "node.h" +#include "attr.h" +#include "character_data.h" #include "child_node_list.h" #include "document.h" #include "document_fragment.h" #include "empty_node_list.h" #include "node_data.h" #include "node_list.h" -#include "attr.h" -#include "character_data.h" namespace kraken { @@ -177,8 +177,7 @@ Node::Node(Document* document, ConstructionType type) node_flags_(type), parent_or_shadow_host_node_(nullptr), previous_(nullptr), - next_(nullptr) { -} + next_(nullptr) {} void Node::Trace(GCVisitor*) const {} diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index 2447030446..8aa14299f1 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -5,8 +5,8 @@ #include "node_data.h" #include "bindings/qjs/garbage_collected.h" #include "child_node_list.h" -#include "empty_node_list.h" #include "container_node.h" +#include "empty_node_list.h" namespace kraken { diff --git a/bridge/core/dom/space_split_string.h b/bridge/core/dom/space_split_string.h index 23cd4473f0..06b3aa6bfb 100644 --- a/bridge/core/dom/space_split_string.h +++ b/bridge/core/dom/space_split_string.h @@ -6,8 +6,8 @@ #define KRAKENBRIDGE_CORE_DOM_SPACE_SPLIT_STRING_H_ #include -#include #include +#include #include "bindings/qjs/atomic_string.h" namespace kraken { @@ -49,7 +49,9 @@ class SpaceSplitString { ~Data(); - bool Contains(const AtomicString& string) const { return std::find(vector_.begin(), vector_.end(), string) != vector_.end(); } + bool Contains(const AtomicString& string) const { + return std::find(vector_.begin(), vector_.end(), string) != vector_.end(); + } bool ContainsAll(Data&); @@ -62,6 +64,7 @@ class SpaceSplitString { AtomicString& operator[](size_t i) { return vector_[i]; } explicit Data(const Data&); + private: explicit Data(const AtomicString&); @@ -75,8 +78,8 @@ class SpaceSplitString { // We can use a non-ref-counted StringImpl* as the key because the associated // Data object will keep it alive via the key_string_ member. - typedef std::unordered_map DataMap; - static DataMap& SharedDataMap(); + typedef std::unordered_map DataMap; + static DataMap& SharedDataMap(); void EnsureUnique() { if (data_ && !data_->IsUnique()) diff --git a/bridge/core/dom/text.cc b/bridge/core/dom/text.cc index bafc9b0317..439a0578c1 100644 --- a/bridge/core/dom/text.cc +++ b/bridge/core/dom/text.cc @@ -1,5 +1,5 @@ /* -* Copyright (C) 2021-present The Kraken authors. All rights reserved. + * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ #include "text.h" @@ -22,4 +22,4 @@ Node* Text::Clone(Document& document, CloneChildrenFlag flag) const { return Create(document, data()); } -} +} // namespace kraken diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h index 6aa06b3cd1..3c69f2feec 100644 --- a/bridge/core/dom/text.h +++ b/bridge/core/dom/text.h @@ -11,6 +11,7 @@ namespace kraken { class Text : public CharacterData { DEFINE_WRAPPERTYPEINFO(); + public: static const unsigned kDefaultLengthLimit = 1 << 16; diff --git a/bridge/core/html/html_collection.cc b/bridge/core/html/html_collection.cc index d084c41ef3..c4e421a942 100644 --- a/bridge/core/html/html_collection.cc +++ b/bridge/core/html/html_collection.cc @@ -4,8 +4,4 @@ #include "html_collection.h" -namespace kraken { - - - -} +namespace kraken {} diff --git a/bridge/core/html/html_collection.h b/bridge/core/html/html_collection.h index 24efb53ff4..638f65c2b4 100644 --- a/bridge/core/html/html_collection.h +++ b/bridge/core/html/html_collection.h @@ -11,11 +11,9 @@ namespace kraken { class HTMLCollection : public ScriptWrappable { public: - private: - }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ From 50ff106ef6172f8859589ca8f9cf5b33e95a88ce Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 8 Apr 2022 15:43:26 +0800 Subject: [PATCH 064/498] feat: add full node and container impl. --- bridge/CMakeLists.txt | 5 + bridge/bindings/qjs/converter_impl.h | 57 ++++- bridge/bindings/qjs/exception_state.cc | 12 +- bridge/bindings/qjs/exception_state.h | 3 +- bridge/core/dom/attr.h | 2 +- bridge/core/dom/container_node.cc | 165 ++++++++++---- bridge/core/dom/container_node.h | 111 +--------- bridge/core/dom/document.h | 3 +- bridge/core/dom/document_fragment.cc | 8 +- bridge/core/dom/document_fragment.h | 4 +- bridge/core/dom/node.cc | 209 +++++++++++++++++- bridge/core/dom/node.d.ts | 77 +------ bridge/core/dom/node.h | 66 ++---- bridge/core/dom/node_list.h | 2 +- bridge/core/dom/node_traversal.cc | 120 ++++++++++ bridge/core/dom/node_traversal.h | 175 +++++++++++++++ .../dom/template_content_document_fragment.h | 34 +++ bridge/core/dom/text.cc | 2 +- bridge/core/dom/text.h | 2 +- bridge/core/dom/traversal_range.h | 140 ++++++++++++ bridge/core/html/html_element.cc | 11 + bridge/core/html/html_element.h | 21 ++ 22 files changed, 923 insertions(+), 306 deletions(-) create mode 100644 bridge/core/dom/node_traversal.cc create mode 100644 bridge/core/dom/node_traversal.h create mode 100644 bridge/core/dom/template_content_document_fragment.h create mode 100644 bridge/core/dom/traversal_range.h create mode 100644 bridge/core/html/html_element.cc create mode 100644 bridge/core/html/html_element.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index e17dacee9b..cc6221f948 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -287,6 +287,9 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/node.h core/dom/attr.cc core/dom/attr.h + core/dom/node_traversal.cc + core/dom/node_traversal.h + core/dom/template_content_document_fragment.h core/dom/character_data.cc core/dom/character_data.h core/dom/text.cc @@ -315,6 +318,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/events/error_event.h core/html/html_collection.cc core/html/html_collection.h + core/html/html_element.cc + core/html/html_element.h # core/dom/character_data.cc # core/dom/character_data.h # core/dom/comment.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index bbd8faabed..b995e6cdc4 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -9,10 +9,13 @@ #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/html/html_element.h" #include "idl_type.h" #include "js_event_listener.h" #include "native_string_utils.h" @@ -43,6 +46,10 @@ struct Converter, std::enable_if_t::ImplType value) { + if (value == nullptr) { + return JS_UNDEFINED; + } + return Converter::ToValue(ctx, value); } }; @@ -61,6 +68,10 @@ struct Converter, std::enable_if_t::ImplType value) { + if (value == nullptr) { + return JS_NULL; + } + return Converter::ToValue(ctx, value); } }; @@ -78,6 +89,10 @@ struct Converter, std::enable_if_t::ImplType value) { + if (value == nullptr) { + return JS_UNDEFINED; + } + return Converter::ToValue(ctx, value); } }; @@ -126,6 +141,10 @@ struct Converter> : public ConverterBase 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); } @@ -235,6 +254,9 @@ struct Converter> : public ConverterBase return AtomicString::Empty(ctx); return Converter::FromValue(ctx, value, exception_state); } + + static JSValue ToValue(JSContext* ctx, const std::string& value) { return AtomicString(ctx, value).ToQuickJS(ctx); } + static JSValue ToValue(JSContext* ctx, const AtomicString& value) { return value.ToQuickJS(ctx); } }; template @@ -357,11 +379,19 @@ struct Converter : public ConverterBase { 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) { return Converter::ToValue(ctx, value); } + static JSValue ToValue(JSContext* ctx, ImplType value) { + if (value == nullptr) + return JS_NULL; + return Converter::ToValue(ctx, value); + } }; template <> @@ -387,6 +417,10 @@ struct Converter : public ConverterBase { 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); } @@ -424,12 +458,23 @@ struct Converter : public ConverterBase \ + struct Converter : public ConverterBase { \ + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { \ + assert(!JS_IsException(value)); \ + return toScriptWrappable(value); \ + } \ + static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } \ + }; + +DEFINE_SCRIPT_WRAPPABLE_CONVERTER(Node); +DEFINE_SCRIPT_WRAPPABLE_CONVERTER(Document); +DEFINE_SCRIPT_WRAPPABLE_CONVERTER(HTMLElement); + template <> -struct Converter : public ConverterBase { - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - assert(!JS_IsException(value)); - return toScriptWrappable(value); - } +struct Converter : public ConverterBase { + static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } }; } // namespace kraken diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index abd8059a32..c62c523e18 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -7,22 +7,22 @@ namespace kraken { -void ExceptionState::ThrowException(JSContext* ctx, ErrorType type, const char* message) { +void ExceptionState::ThrowException(JSContext* ctx, ErrorType type, const std::string& message) { switch (type) { case ErrorType::TypeError: - exception_ = JS_ThrowTypeError(ctx, "%s", message); + exception_ = JS_ThrowTypeError(ctx, "%s", message.c_str()); break; case InternalError: - exception_ = JS_ThrowInternalError(ctx, "%s", message); + exception_ = JS_ThrowInternalError(ctx, "%s", message.c_str()); break; case RangeError: - exception_ = JS_ThrowRangeError(ctx, "%s", message); + exception_ = JS_ThrowRangeError(ctx, "%s", message.c_str()); break; case ReferenceError: - exception_ = JS_ThrowReferenceError(ctx, "%s", message); + exception_ = JS_ThrowReferenceError(ctx, "%s", message.c_str()); break; case SyntaxError: - exception_ = JS_ThrowSyntaxError(ctx, "%s", message); + exception_ = JS_ThrowSyntaxError(ctx, "%s", message.c_str()); break; } } diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index fa6ef48230..4de5a6ed9f 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -7,6 +7,7 @@ #define KRAKENBRIDGE_EXCEPTION_STATE_H #include +#include #include "foundation/macros.h" namespace kraken { @@ -19,7 +20,7 @@ class ExceptionState { KRAKEN_DISALLOW_NEW(); public: - void ThrowException(JSContext* ctx, ErrorType type, const char* message); + void ThrowException(JSContext* ctx, ErrorType type, const std::string& message); void ThrowException(JSContext* ctx, JSValue exception); bool HasException(); JSValue ToQuickJS(); diff --git a/bridge/core/dom/attr.h b/bridge/core/dom/attr.h index 002c2f1b25..aefc96d47a 100644 --- a/bridge/core/dom/attr.h +++ b/bridge/core/dom/attr.h @@ -46,7 +46,7 @@ class Attr : public Node { bool IsElementNode() const = delete; // This will catch anyone doing an unnecessary check. std::string nodeName() const override { return name(); } - NodeType getNodeType() const override { return kAttributeNode; } + NodeType nodeType() const override { return kAttributeNode; } std::string nodeValue() const override { return value().ToStdString(); } void setNodeValue(const std::string& node_value, ExceptionState& exception_state) override; diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 7b2295b51a..7f1723d281 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -40,15 +40,59 @@ class ContainerNode::AdoptAndAppendChild { bool ContainerNode::IsChildTypeAllowed(const Node& child) const { auto* child_fragment = DynamicTo(child); if (!child_fragment) - return ChildTypeAllowed(child.getNodeType()); + return ChildTypeAllowed(child.nodeType()); for (Node* node = child_fragment->firstChild(); node; node = node->nextSibling()) { - if (!ChildTypeAllowed(node->getNodeType())) + 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; + if (GetDocument().IsTemplateDocument()) { + child_contains_parent = new_child.ContainsIncludingHostElements(*this); + } else { + 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); + } + } + 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. @@ -160,7 +204,6 @@ Node* ContainerNode::ReplaceChild(Node* new_child, Node* old_child, ExceptionSta InsertNodeVector(targets, nullptr, AdoptAndAppendChild(), &post_insertion_notification_targets); } } - DidInsertNodeVector(targets, next, post_insertion_notification_targets); // 16. Return child. return old_child; @@ -185,16 +228,12 @@ Node* ContainerNode::RemoveChild(Node* old_child, ExceptionState& exception_stat return nullptr; } - WillRemoveChild(*child); - { Node* prev = child->previousSibling(); Node* next = child->nextSibling(); { RemoveBetween(prev, next, *child); - NotifyNodeRemoved(*child); } - ChildrenChanged(ChildrenChange::ForRemoval(*child, prev, next, ChildrenChangeSource::kAPI)); } return child; } @@ -213,7 +252,6 @@ Node* ContainerNode::AppendChild(Node* new_child, ExceptionState& exception_stat NodeVector post_insertion_notification_targets; post_insertion_notification_targets.reserve(kInitialNodeVectorSize); { InsertNodeVector(targets, nullptr, AdoptAndAppendChild(), &post_insertion_notification_targets); } - DidInsertNodeVector(targets, nullptr, post_insertion_notification_targets); return new_child; } @@ -225,7 +263,7 @@ bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, // Use common case fast path if possible. if ((new_child.IsElementNode() || new_child.IsTextNode()) && IsElementNode()) { - DCHECK(IsChildTypeAllowed(new_child)); + assert(IsChildTypeAllowed(new_child)); // 2. If node is a host-including inclusive ancestor of parent, throw a // HierarchyRequestError. if (IsHostIncludingInclusiveAncestorOfThis(new_child, exception_state)) @@ -235,15 +273,6 @@ bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, return CheckReferenceChildParent(*this, next, old_child, exception_state); } - // This should never happen, but also protect release builds from tree - // corruption. - DCHECK(!new_child.IsPseudoElement()); - if (new_child.IsPseudoElement()) { - exception_state.ThrowDOMException(DOMExceptionCode::kHierarchyRequestError, - "The new child element is a pseudo-element."); - return false; - } - if (auto* document = DynamicTo(this)) { // Step 2 is unnecessary. No one can have a Document child. // Step 3: @@ -268,9 +297,7 @@ bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, // 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.ThrowDOMException( - DOMExceptionCode::kHierarchyRequestError, - "Nodes of type '" + new_child.nodeName() + "' may not be inserted inside nodes of type '" + nodeName() + "'."); + exception_state.ThrowException(ctx(), ErrorType::TypeError, "Nodes of type '" + new_child.nodeName() + "' may not be inserted inside nodes of type '" + nodeName() + "'."); return false; } @@ -282,38 +309,84 @@ void ContainerNode::RemoveChildren() { if (!first_child_) return; - // Do any prep work needed before actually starting to detach - // and remove... e.g. stop loading frames, fire unload events. - WillRemoveChildren(); + while (Node* child = first_child_) { + RemoveBetween(nullptr, child->nextSibling(), *child); + } +} + +ContainerNode::ContainerNode(Document* document, ConstructionType type) : Node(document, type) {} + +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); +} - // { - // // Removing a node from a selection can cause widget updates. - // GetDocument().NodeChildrenWillBeRemoved(*this); - // } - std::vector removed_nodes; - const bool children_changed = ChildrenChangedAllChildrenRemovedNeedsList(); +template +void ContainerNode::InsertNodeVector( + const NodeVector& targets, + Node* next, + const Functor& mutator, + NodeVector* post_insertion_notification_targets) { + assert(post_insertion_notification_targets); { - { - while (Node* child = first_child_) { - RemoveBetween(nullptr, child->nextSibling(), *child); - NotifyNodeRemoved(*child); - if (children_changed) - removed_nodes.push_back(child); - } + for (const auto& target_node : targets) { + assert(target_node); + assert(!target_node->parentNode()); + Node& child = *target_node; + mutator(*this, child, next); } + } +} - ChildrenChange change = {ChildrenChangeType::kAllChildrenRemoved, - ChildrenChangeSource::kAPI, - nullptr, - nullptr, - nullptr, - std::move(removed_nodes), - ""}; - ChildrenChanged(change); +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); } -ContainerNode::ContainerNode(Document* document, ConstructionType type) : Node(document, type) {} +void ContainerNode::AppendChildCommon(Node& child) { + child.SetParentOrShadowHostNode(this); + if (last_child_) { + child.SetPreviousSibling(last_child_); + last_child_->SetNextSibling(&child); + } else { + SetFirstChild(&child); + } + SetLastChild(&child); +} + +void ContainerNode::Trace(GCVisitor* visitor) const { + Node::Trace(visitor); +} } // namespace kraken diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index 0700dab23b..f23997b44b 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -15,7 +15,6 @@ class HTMLCollection; // This constant controls how much buffer is initially allocated // for a Node Vector that is used to store child Nodes of a given Node. -// FIXME: Optimize the value. const int kInitialNodeVectorSize = 11; using NodeVector = std::vector; @@ -45,110 +44,8 @@ class ContainerNode : public Node { const Node* old_child, ExceptionState&) const; - // These methods are only used during parsing. - // They don't send DOM mutation events or accept DocumentFragments. - void ParserAppendChild(Node*); - void ParserRemoveChild(Node&); - void ParserInsertBefore(Node* new_child, Node& ref_child); - void ParserTakeAllChildrenFrom(ContainerNode&); - void RemoveChildren(); - // FIXME: These methods should all be renamed to something better than - // "check", since it's not clear that they alter the style bits of siblings - // and children. - enum SiblingCheckType { kFinishedParsingChildren, kSiblingElementInserted, kSiblingElementRemoved }; - - // ----------------------------------------------------------------------------- - // Notification of document structure changes (see core/dom/node.h for more - // notification methods) - - enum class ChildrenChangeType : uint8_t { - kElementInserted, - kNonElementInserted, - kElementRemoved, - kNonElementRemoved, - kAllChildrenRemoved, - kTextChanged - }; - enum class ChildrenChangeSource : uint8_t { kAPI, kParser }; - struct ChildrenChange { - public: - static ChildrenChange ForInsertion(Node& node, - Node* unchanged_previous, - Node* unchanged_next, - ChildrenChangeSource by_parser) { - ChildrenChange change = { - node.IsElementNode() ? ChildrenChangeType::kElementInserted : ChildrenChangeType::kNonElementInserted, - by_parser, - &node, - unchanged_previous, - unchanged_next, - {}, - ""}; - return change; - } - - static ChildrenChange ForRemoval(Node& node, - Node* previous_sibling, - Node* next_sibling, - ChildrenChangeSource by_parser) { - ChildrenChange change = { - node.IsElementNode() ? ChildrenChangeType::kElementRemoved : ChildrenChangeType::kNonElementRemoved, - by_parser, - &node, - previous_sibling, - next_sibling, - {}, - ""}; - return change; - } - - bool IsChildInsertion() const { - return type == ChildrenChangeType::kElementInserted || type == ChildrenChangeType::kNonElementInserted; - } - bool IsChildRemoval() const { - return type == ChildrenChangeType::kElementRemoved || type == ChildrenChangeType::kNonElementRemoved; - } - bool IsChildElementChange() const { - return type == ChildrenChangeType::kElementInserted || type == ChildrenChangeType::kElementRemoved; - } - - bool ByParser() const { return by_parser == ChildrenChangeSource::kParser; } - - ChildrenChangeType type; - ChildrenChangeSource by_parser; - Node* sibling_changed = nullptr; - // |siblingBeforeChange| is - // - siblingChanged.previousSibling before node removal - // - siblingChanged.previousSibling after single node insertion - // - previousSibling of the first inserted node after multiple node - // insertion - Node* sibling_before_change = nullptr; - // |siblingAfterChange| is - // - siblingChanged.nextSibling before node removal - // - siblingChanged.nextSibling after single node insertion - // - nextSibling of the last inserted node after multiple node insertion. - Node* sibling_after_change = nullptr; - // List of removed nodes for ChildrenChangeType::kAllChildrenRemoved. - // Only populated if ChildrenChangedAllChildrenRemovedNeedsList() returns - // true. - std::vector removed_nodes; - // |old_text| is mostly empty, only used for text node changes. - const std::string& old_text; - }; - - // Notifies the node that it's list of children have changed (either by adding - // or removing child nodes), or a child node that is of the type - // kCdataSectionNode, kTextNode or kCommentNode has changed its value. - // - // ChildrenChanged() implementations may modify the DOM tree, and may dispatch - // synchronous events. - virtual void ChildrenChanged(const ChildrenChange&); - - // Provides ChildrenChange::removed_nodes for kAllChildrenRemoved. - virtual bool ChildrenChangedAllChildrenRemovedNeedsList() const; - virtual bool ChildrenCanHaveStyle() const { return true; } void Trace(GCVisitor* visitor) const override; @@ -168,7 +65,6 @@ class ContainerNode : public Node { // |post_insertion_notification_targets| must not be nullptr. template void InsertNodeVector(const NodeVector&, Node* next, const Functor&, NodeVector* post_insertion_notification_targets); - void DidInsertNodeVector(const NodeVector&, Node* next, const NodeVector& post_insertion_notification_targets); class AdoptAndInsertBefore; class AdoptAndAppendChild; @@ -177,14 +73,9 @@ class ContainerNode : public Node { void InsertBeforeCommon(Node& next_child, Node& new_child); void AppendChildCommon(Node& child); - void WillRemoveChildren(); - void WillRemoveChild(Node& child); - void RemoveDetachedChildrenInContainer(ContainerNode&); - void AddChildNodesToDeletionQueue(Node*&, Node*&, ContainerNode&); - - void NotifyNodeRemoved(Node&); inline bool IsChildTypeAllowed(const Node& child) const; + inline bool IsHostIncludingInclusiveAncestorOfThis(const Node&, ExceptionState&) const; Node* first_child_; Node* last_child_; diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 63b60894bc..a3281c18aa 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -15,8 +15,9 @@ namespace kraken { // (typically, HTML) resource. class Document : public Node { DEFINE_WRAPPERTYPEINFO(); - public: + using ImplType = Document*; + private: }; diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index 1d5705b973..be130dd1ed 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -11,17 +11,17 @@ namespace kraken { DocumentFragment* DocumentFragment::Create(ExecutingContext* context, Document* document, ExceptionState& exception_state) { - return nullptr; + return MakeGarbageCollected(document, ConstructionType::kCreateDocumentFragment); } -DocumentFragment::DocumentFragment(ExecutingContext* context, Document* document) - : ContainerNode(context, ConstructionType::kCreateDocumentFragment) {} +DocumentFragment::DocumentFragment(Document* document, ConstructionType type) + : ContainerNode(document, type) {} std::string DocumentFragment::nodeName() const { return "#document-fragment"; } -Node::NodeType DocumentFragment::getNodeType() const { +Node::NodeType DocumentFragment::nodeType() const { return NodeType::kDocumentFragmentNode; } diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index 9d827930a6..7e13ba97b3 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -16,7 +16,7 @@ class DocumentFragment : public ContainerNode { public: static DocumentFragment* Create(ExecutingContext* context, Document* document, ExceptionState& exception_state); - DocumentFragment(ExecutingContext* context, Document* document); + DocumentFragment(Document* document, ConstructionType type); virtual bool IsTemplateContent() const { return false; } @@ -27,7 +27,7 @@ class DocumentFragment : public ContainerNode { std::string nodeName() const final; private: - NodeType getNodeType() const final; + NodeType nodeType() const final; Node* Clone(Document&, CloneChildrenFlag) const override; bool ChildTypeAllowed(NodeType) const override; }; diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index d6966d1089..439b887551 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -11,6 +11,9 @@ #include "empty_node_list.h" #include "node_data.h" #include "node_list.h" +#include "node_traversal.h" +#include "template_content_document_fragment.h" +#include "text.h" namespace kraken { @@ -18,6 +21,10 @@ Node* Node::Create(ExecutingContext* context, ExceptionState& exception_state) { exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor"); } +void Node::setNodeValue(const AtomicString& value) { + // By default, setting nodeValue has no effect. +} + ContainerNode* Node::parentNode() const { return ParentOrShadowHostNode(); } @@ -88,6 +95,10 @@ Node* Node::appendChild(Node* new_child, ExceptionState& exception_state) { 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 @@ -101,12 +112,12 @@ Node* Node::cloneNode(bool deep, ExceptionState&) const { : CloneChildrenFlag::kSkip); } -bool Node::isEqualNode(Node* other) const { +bool Node::isEqualNode(Node* other, ExceptionState& exception_state) const { if (!other) return false; - NodeType node_type = getNodeType(); - if (node_type != other->getNodeType()) + NodeType node_type = nodeType(); + if (node_type != other->nodeType()) return false; if (nodeValue() != other->nodeValue()) @@ -147,7 +158,7 @@ bool Node::isEqualNode(Node* other) const { return true; } -std::string Node::textContent(bool convert_brs_to_newlines) const { +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. @@ -161,15 +172,188 @@ std::string Node::textContent(bool convert_brs_to_newlines) const { // Documents and non-container nodes (that are not CharacterData) // have null textContent. if (IsDocumentNode() || !IsContainerNode()) - return ""; + return AtomicString::Empty(ctx()); + + // TODO: Implement text content. + // std::string content; + // for (const Node& node : NodeTraversal::InclusiveDescendantsOf(*this)) { + // if (auto* text_node = DynamicTo(node)) { + // content += (text_node->data()); + // } + // } + // return content.ReleaseString(); +} - std::string content; - for (const Node& node : NodeTraversal::InclusiveDescendantsOf(*this)) { - if (auto* text_node = DynamicTo(node)) { - content += (text_node->data()); +void Node::setTextContent(const AtomicString& text) { + switch (nodeType()) { + case kAttributeNode: + case kTextNode: + case kCommentNode: + setNodeValue(text); + return; + case kElementNode: + case kDocumentFragmentNode: { + // FIXME: Merge this logic into replaceChildrenWithText. + auto* container = To(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(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), ExceptionState()); + } + 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(this); + node_flags_ = (node_flags_ & ~kCustomElementStateMask) | static_cast(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(parent); +} + +ContainerNode* Node::ParentOrShadowHostOrTemplateHostNode() const { + auto* this_fragment = DynamicTo(this); + if (this_fragment && this_fragment->IsTemplateContent()) + return static_cast(this)->Host(); + return ParentOrShadowHostNode(); +} + +ContainerNode* Node::NonShadowBoundaryParentNode() const { + return parentNode(); +} + +unsigned int Node::NodeIndex() const { + const Node* temp_node = previousSibling(); + unsigned count = 0; + for (count = 0; temp_node; count++) + temp_node = temp_node->previousSibling(); + return count; +} + +Document* Node::ownerDocument() const { + Document* doc = &GetDocument(); + return doc == this ? nullptr : doc; +} + +bool Node::IsDescendantOf(const Node* other) const { + // Return true if other is an ancestor of this, otherwise false + if (!other || isConnected() != other->isConnected()) + return false; + if (&other->GetDocument() != &GetDocument()) + return false; + for (const ContainerNode* n = parentNode(); n; n = n->parentNode()) { + if (n == other) + return true; + } + return false; +} + +bool Node::contains(const Node* node, ExceptionState& exception_state) const { + if (!node) + return false; + return this == node || node->IsDescendantOf(this); +} + +bool Node::ContainsIncludingHostElements(const Node& node) const { + const Node* current = &node; + do { + if (current == this) + return true; + auto* curr_fragment = DynamicTo(current); + if (curr_fragment && curr_fragment->IsTemplateContent()) + current = static_cast(current)->Host(); + else + current = current->ParentOrShadowHostNode(); + } while (current); + return false; +} + +Node* Node::CommonAncestor(const Node& other, ContainerNode* (*parent)(const Node&)) const { + if (this == &other) + return const_cast(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); + this_depth++; } - return content.ReleaseString(); + int other_depth = 0; + for (const Node* node = &other; node; node = parent(*node)) { + if (node == this) + return const_cast(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(this_iterator); + this_iterator = parent(*this_iterator); + other_iterator = parent(*other_iterator); + } + assert(!other_iterator); + return nullptr; } Node::Node(Document* document, ConstructionType type) @@ -177,8 +361,11 @@ Node::Node(Document* document, ConstructionType type) node_flags_(type), parent_or_shadow_host_node_(nullptr), previous_(nullptr), + document_(document), next_(nullptr) {} -void Node::Trace(GCVisitor*) const {} +void Node::Trace(GCVisitor*) const { + +} } // namespace kraken diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts index 11aac25ca3..a90ea8f2c7 100644 --- a/bridge/core/dom/node.d.ts +++ b/bridge/core/dom/node.d.ts @@ -5,7 +5,7 @@ interface Node extends EventTarget { /** * Returns the children. */ - readonly childNodes: Node[]; + readonly childNodes: NodeList; /** * Returns the first child. */ @@ -34,15 +34,15 @@ interface Node extends EventTarget { /** * Returns the node document. Returns null for documents. // */ - // readonly ownerDocument: Document | null; - // /** - // * Returns the parent element. - // */ - // readonly parentElement: HTMLElement | null; - // /** - // * Returns the parent. - // */ - // readonly parentNode: Node & ParentNode | null; + readonly ownerDocument: Document | null; + /** + * Returns the parent element. + */ + readonly parentElement: HTMLElement | null; + /** + * Returns the parent. + */ + readonly parentNode: Node | null; /** * Returns the previous sibling. */ @@ -65,63 +65,6 @@ interface Node extends EventTarget { isSameNode(otherNode: Node | null): boolean; removeChild(oldChild: Node): Node; replaceChild(newChild: Node, oldChild: Node): Node; - readonly ATTRIBUTE_NODE: number; - /** - * node is a CDATASection node. - */ - readonly CDATA_SECTION_NODE: number; - /** - * node is a Comment node. - */ - readonly COMMENT_NODE: number; - /** - * node is a DocumentFragment node. - */ - readonly DOCUMENT_FRAGMENT_NODE: number; - /** - * node is a document. - */ - readonly DOCUMENT_NODE: number; - /** - * Set when other is a descendant of node. - */ - readonly DOCUMENT_POSITION_CONTAINED_BY: number; - /** - * Set when other is an ancestor of node. - */ - readonly DOCUMENT_POSITION_CONTAINS: number; - /** - * Set when node and other are not in the same tree. - */ - readonly DOCUMENT_POSITION_DISCONNECTED: number; - /** - * Set when other is following node. - */ - readonly DOCUMENT_POSITION_FOLLOWING: number; - readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: number; - /** - * Set when other is preceding node. - */ - readonly DOCUMENT_POSITION_PRECEDING: number; - /** - * node is a doctype. - */ - readonly DOCUMENT_TYPE_NODE: number; - /** - * node is an element. - */ - readonly ELEMENT_NODE: number; - readonly ENTITY_NODE: number; - readonly ENTITY_REFERENCE_NODE: number; - readonly NOTATION_NODE: number; - /** - * node is a ProcessingInstruction node. - */ - readonly PROCESSING_INSTRUCTION_NODE: number; - /** - * node is a Text node. - */ - readonly TEXT_NODE: number; new(): Node; } diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index c655566592..e1583c43f7 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -61,8 +61,8 @@ class Node : public EventTarget { bool HasTagName(const AtomicString&) const; virtual std::string nodeName() const = 0; virtual std::string nodeValue() const; - virtual void setNodeValue(const std::string&, ExceptionState&); - virtual NodeType getNodeType() const = 0; + virtual void setNodeValue(const AtomicString&); + virtual NodeType nodeType() const = 0; ContainerNode* parentNode() const; Element* parentElement() const; @@ -71,29 +71,7 @@ class Node : public EventTarget { NodeList* childNodes(); Node* firstChild() const; Node* lastChild() const; - Node& TreeRoot() const; - - // TODO: support following APIs. - // void Prepend( - // const HeapVector>& nodes, - // ExceptionState& exception_state); - // void Append( - // const HeapVector>& nodes, - // ExceptionState& exception_state); - // void Before( - // const HeapVector>& nodes, - // ExceptionState& exception_state); - // void After( - // const HeapVector>& nodes, - // ExceptionState& exception_state); - // void ReplaceWith( - // const HeapVector>& nodes, - // ExceptionState& exception_state); - // void ReplaceChildren( - // const HeapVector>& nodes, - // ExceptionState& exception_state); - void remove(ExceptionState&); Node* insertBefore(Node* new_child, Node* ref_child, ExceptionState&); @@ -103,14 +81,15 @@ class Node : public EventTarget { 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*) const; - bool isSameNode(const Node* other) const { return this == other; } + bool isEqualNode(Node*, ExceptionState& exception_state) const; + bool isSameNode(const Node* other, ExceptionState& exception_state) const { return this == other; } - std::string textContent(bool convert_brs_to_newlines = false) const; + AtomicString textContent(bool convert_brs_to_newlines = false) const; virtual void setTextContent(const AtomicString&); // Other methods (not part of DOM) @@ -168,7 +147,7 @@ class Node : public EventTarget { // Returns the document associated with this node. A Document node returns // itself. - Document& GetDocument() const {} + Document& GetDocument() const { return *document_; } // Returns true if this node is connected to a document, false otherwise. // See https://dom.spec.whatwg.org/#connected for the definition. @@ -176,32 +155,17 @@ class Node : public EventTarget { bool IsInDocumentTree() const { return isConnected(); } - bool IsDocumentTypeNode() const { return getNodeType() == kDocumentTypeNode; } + bool IsDocumentTypeNode() const { return nodeType() == kDocumentTypeNode; } virtual bool ChildTypeAllowed(NodeType) const { return false; } unsigned CountChildren() const; bool IsDescendantOf(const Node*) const; - bool IsDescendantOrShadowDescendantOf(const Node*) const; - bool contains(const Node*) const; - // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor - bool IsShadowIncludingInclusiveAncestorOf(const Node&) const; - // https://dom.spec.whatwg.org/#concept-shadow-including-ancestor - bool IsShadowIncludingAncestorOf(const Node&) const; + bool contains(const Node*, ExceptionState&) const; bool ContainsIncludingHostElements(const Node&) const; Node* CommonAncestor(const Node&, ContainerNode* (*parent)(const Node&)) const; - // Whether or not a selection can be started in this object - virtual bool CanStartSelection() const; - - void NotifyPriorityScrollAnchorStatusChanged(); - - // NodeListsNodeData* NodeLists(); - // void ClearNodeLists(); - enum ShadowTreesTreatment { kTreatShadowTreesAsDisconnected, kTreatShadowTreesAsComposed }; - uint16_t compareDocumentPosition(const Node*, ShadowTreesTreatment = kTreatShadowTreesAsDisconnected) const; - EventTargetData* GetEventTargetData() override; EventTargetData& EnsureEventTargetData() override; @@ -246,7 +210,6 @@ class Node : public EventTarget { kSelfOrAncestorHasDirAutoAttribute = 1 << 27, kDefaultNodeFlags = kIsFinishedParsingChildrenFlag, - // 2 bits remaining. }; @@ -298,16 +261,23 @@ class Node : public EventTarget { Node(Document*, ConstructionType); - void SetIsFinishedParsingChildren(bool value) { SetFlag(value, kIsFinishedParsingChildrenFlag); } - private: uint32_t node_flags_; Node* parent_or_shadow_host_node_; Node* previous_; Node* next_; + Document* document_; std::unique_ptr data_; }; +inline ContainerNode* Node::ParentOrShadowHostNode() const { + return reinterpret_cast(parent_or_shadow_host_node_); +} + +inline void Node::SetParentOrShadowHostNode(ContainerNode* parent) { + parent_or_shadow_host_node_ = reinterpret_cast(parent); +} + } // namespace kraken #endif // KRAKENBRIDGE_NODE_H diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h index c17be2db97..fc4b7cb963 100644 --- a/bridge/core/dom/node_list.h +++ b/bridge/core/dom/node_list.h @@ -13,8 +13,8 @@ class Node; class NodeList : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); - public: + using ImplType = NodeList*; NodeList(JSContext* ctx) : ScriptWrappable(ctx){}; ~NodeList() override = default; diff --git a/bridge/core/dom/node_traversal.cc b/bridge/core/dom/node_traversal.cc new file mode 100644 index 0000000000..96c8be8313 --- /dev/null +++ b/bridge/core/dom/node_traversal.cc @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "node_traversal.h" + +namespace kraken { + +Node* NodeTraversal::NextAncestorSibling(const Node& current) { + assert(!current.nextSibling()); + for (Node& parent : AncestorsOf(current)) { + if (parent.nextSibling()) + return parent.nextSibling(); + } + return nullptr; +} + +Node* NodeTraversal::NextAncestorSibling(const Node& current, + const Node* stay_within) { + DCHECK(!current.nextSibling()); + DCHECK_NE(current, 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(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 (current == 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::PreviousAbsoluteSiblingIncludingPseudo( + const Node& current, + const Node* stay_within) { + for (Node& iter : InclusiveAncestorsOf(current)) { + if (iter == stay_within) + return nullptr; + if (Node* result = iter.PseudoAwarePreviousSibling()) + return result; + } + return nullptr; +} + +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 (current == 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) { + DCHECK(!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 (current == stay_within) + return nullptr; + if (current.previousSibling()) + return current.previousSibling(); + return PreviousAncestorSiblingPostOrder(current, stay_within); +} + +Node* NodeTraversal::CommonAncestor(const Node& node_a, const Node& node_b) { + return Range::commonAncestorContainer(&node_a, &node_b); +} + + +} diff --git a/bridge/core/dom/node_traversal.h b/bridge/core/dom/node_traversal.h new file mode 100644 index 0000000000..92bc8d066f --- /dev/null +++ b/bridge/core/dom/node_traversal.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ +#define KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ + +#include "foundation/macros.h" +#include "node.h" +#include "traversal_range.h" + +namespace kraken { + +class NodeTraversal { + KRAKEN_STATIC_ONLY(NodeTraversal); + + public: + using TraversalNodeType = Node; + + // Does a pre-order traversal of the tree to find the next node after this + // one. This uses the same order that tags appear in the source file. If the + // stayWithin argument is non-null, the traversal will stop once the specified + // node is reached. This can be used to restrict traversal to a particular + // sub-tree. + static Node* Next(const Node& current) { return TraverseNextTemplate(current); } + static Node* Next(const ContainerNode& current) { return TraverseNextTemplate(current); } + static Node* Next(const Node& current, const Node* stay_within) { return TraverseNextTemplate(current, stay_within); } + static Node* Next(const ContainerNode& current, const Node* stay_within) { + return TraverseNextTemplate(current, stay_within); + } + + // Like next, but skips children and starts with the next sibling. + static Node* NextSkippingChildren(const Node&); + static Node* NextSkippingChildren(const Node&, const Node* stay_within); + + static Node* FirstWithin(const Node& current) { return current.firstChild(); } + + static Node* LastWithin(const ContainerNode&); + static Node& LastWithinOrSelf(Node&); + + // Does a reverse pre-order traversal to find the node that comes before the + // current one in document order + static Node* Previous(const Node&, const Node* stay_within = nullptr); + + // Returns the previous direct sibling of the node, if there is one. If not, + // it will traverse up the ancestor chain until it finds an ancestor + // that has a previous sibling, returning that sibling. Or nullptr if none. + // See comment for |FlatTreeTraversal::PreviousAbsoluteSibling| for details. + static Node* PreviousAbsoluteSibling(const Node&, const Node* stay_within = nullptr); + + // Like next, but visits parents after their children. + static Node* NextPostOrder(const Node&, const Node* stay_within = nullptr); + + // Like previous, but visits parents before their children. + static Node* PreviousPostOrder(const Node&, const Node* stay_within = nullptr); + + static Node* NextAncestorSibling(const Node&); + static Node* NextAncestorSibling(const Node&, const Node* stay_within); + static Node& HighestAncestorOrSelf(const Node&); + + // Children traversal. + static Node* ChildAt(const Node& parent, unsigned index) { return ChildAtTemplate(parent, index); } + static Node* ChildAt(const ContainerNode& parent, unsigned index) { return ChildAtTemplate(parent, index); } + + // These functions are provided for matching with |FlatTreeTraversal|. + static bool HasChildren(const Node& parent) { return FirstChild(parent); } + static bool IsDescendantOf(const Node& node, const Node& other) { return node.IsDescendantOf(&other); } + static Node* FirstChild(const Node& parent) { return parent.firstChild(); } + static Node* LastChild(const Node& parent) { return parent.lastChild(); } + static Node* NextSibling(const Node& node) { return node.nextSibling(); } + static Node* PreviousSibling(const Node& node) { return node.previousSibling(); } + static ContainerNode* Parent(const Node& node) { return node.parentNode(); } + static Node* CommonAncestor(const Node& node_a, const Node& node_b); + 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 AncestorsOf(const Node&); + static TraversalAncestorRange InclusiveAncestorsOf(const Node&); + static TraversalSiblingRange ChildrenOf(const Node&); + static TraversalDescendantRange DescendantsOf(const Node&); + static TraversalInclusiveDescendantRange InclusiveDescendantsOf(const Node&); + static TraversalNextRange StartsAt(const Node&); + static TraversalNextRange StartsAfter(const Node&); + + private: + template + static Node* TraverseNextTemplate(NodeType&); + template + static Node* TraverseNextTemplate(NodeType&, const Node* stay_within); + template + static Node* ChildAtTemplate(NodeType&, unsigned); + static Node* PreviousAncestorSiblingPostOrder(const Node& current, const Node* stay_within); +}; + +inline TraversalAncestorRange NodeTraversal::AncestorsOf(const Node& node) { + return TraversalAncestorRange(NodeTraversal::Parent(node)); +} + +inline TraversalAncestorRange NodeTraversal::InclusiveAncestorsOf(const Node& node) { + return TraversalAncestorRange(&node); +} + +inline TraversalSiblingRange NodeTraversal::ChildrenOf(const Node& parent) { + return TraversalSiblingRange(NodeTraversal::FirstChild(parent)); +} + +inline TraversalDescendantRange NodeTraversal::DescendantsOf(const Node& root) { + return TraversalDescendantRange(&root); +} + +inline TraversalInclusiveDescendantRange NodeTraversal::InclusiveDescendantsOf(const Node& root) { + return TraversalInclusiveDescendantRange(&root); +} + +inline TraversalNextRange NodeTraversal::StartsAt(const Node& start) { + return TraversalNextRange(&start); +} + +inline TraversalNextRange NodeTraversal::StartsAfter(const Node& start) { + return TraversalNextRange(NodeTraversal::Next(start)); +} + +template +inline Node* NodeTraversal::TraverseNextTemplate(NodeType& current) { + if (current.hasChildren()) + return current.firstChild(); + if (current.nextSibling()) + return current.nextSibling(); + return NextAncestorSibling(current); +} + +template +inline Node* NodeTraversal::TraverseNextTemplate(NodeType& current, const Node* stay_within) { + if (current.hasChildren()) + return current.firstChild(); + if (current == 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 (current == 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(¤t); + while (highest->parentNode()) + highest = highest->parentNode(); + return *highest; +} + +template +inline Node* NodeTraversal::ChildAtTemplate(NodeType& parent, unsigned index) { + Node* child = parent.firstChild(); + while (child && index--) + child = child->nextSibling(); + return child; +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ diff --git a/bridge/core/dom/template_content_document_fragment.h b/bridge/core/dom/template_content_document_fragment.h new file mode 100644 index 0000000000..a629e3f482 --- /dev/null +++ b/bridge/core/dom/template_content_document_fragment.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ +#define KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ + +#include "document_fragment.h" +#include "bindings/qjs/gc_visitor.h" +#include "element.h" + +namespace kraken { + +class TemplateContentDocumentFragment final : public DocumentFragment { + public: + TemplateContentDocumentFragment(Document& document, Element* host) + : DocumentFragment(&document, kCreateDocumentFragment), host_(host) {} + + Element* Host() const { return host_; } + + void Trace(GCVisitor* visitor) const override { + visitor->Trace(host_); + DocumentFragment::Trace(visitor); + } + + private: + bool IsTemplateContent() const override { return true; } + Element* host_; +}; + + +} + +#endif // KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ diff --git a/bridge/core/dom/text.cc b/bridge/core/dom/text.cc index 439a0578c1..95d4cdab31 100644 --- a/bridge/core/dom/text.cc +++ b/bridge/core/dom/text.cc @@ -10,7 +10,7 @@ Text* Text::Create(Document& document, const AtomicString& value) { return MakeGarbageCollected(document, value, ConstructionType::kCreateText); } -Node::NodeType Text::getNodeType() const { +Node::NodeType Text::nodeType() const { return Node::kTextNode; } diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h index 3c69f2feec..473f96ce13 100644 --- a/bridge/core/dom/text.h +++ b/bridge/core/dom/text.h @@ -19,7 +19,7 @@ class Text : public CharacterData { Text(Document& document, const AtomicString& data, ConstructionType type) : CharacterData(document, data, type) {} - NodeType getNodeType() const override; + NodeType nodeType() const override; private: std::string nodeName() const override; diff --git a/bridge/core/dom/traversal_range.h b/bridge/core/dom/traversal_range.h new file mode 100644 index 0000000000..97e404d890 --- /dev/null +++ b/bridge/core/dom/traversal_range.h @@ -0,0 +1,140 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TRAVERSAL_RANGE_H_ +#define THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TRAVERSAL_RANGE_H_ + +#include "foundation/macros.h" + +namespace kraken { + +class Node; + +template +class TraversalRange { + KRAKEN_STATIC_ONLY(TraversalRange); + + public: + using StartNodeType = typename Iterator::StartNodeType; + explicit TraversalRange(const StartNodeType* start) : start_(start) {} + Iterator begin() { return Iterator(start_); } + Iterator end() { return Iterator::End(); } + + private: + const StartNodeType* start_; +}; + +template +class TraversalIteratorBase { + KRAKEN_STATIC_ONLY(TraversalIteratorBase); + + public: + using NodeType = typename Traversal::TraversalNodeType; + NodeType& operator*() { return *current_; } + bool operator!=(const TraversalIteratorBase& rval) const { + return current_ != rval.current_; + } + + protected: + explicit TraversalIteratorBase(NodeType* current) : current_(current) {} + + NodeType* current_; +}; + +template +class TraversalIterator : public TraversalIteratorBase { + public: + using StartNodeType = typename Traversal::TraversalNodeType; + using TraversalIteratorBase::current_; + + explicit TraversalIterator(const StartNodeType* start) + : TraversalIteratorBase(const_cast(start)) {} + + void operator++() { current_ = Traversal::Next(*current_); } + + static TraversalIterator End() { return TraversalIterator(); } + + private: + TraversalIterator() : TraversalIteratorBase(nullptr) {} +}; + +template +class TraversalDescendantIterator : public TraversalIteratorBase { + public: + using StartNodeType = Node; + using TraversalIteratorBase::current_; + + explicit TraversalDescendantIterator(const StartNodeType* start) + : TraversalIteratorBase(start ? Traversal::FirstWithin(*start) + : nullptr), + root_(start) {} + + void operator++() { current_ = Traversal::Next(*current_, root_); } + static TraversalDescendantIterator End() { + return TraversalDescendantIterator(); + } + + private: + TraversalDescendantIterator() : TraversalIteratorBase(nullptr) {} + const StartNodeType* root_ = nullptr; +}; + +template +class TraversalInclusiveDescendantIterator + : public TraversalIteratorBase { + public: + using StartNodeType = typename Traversal::TraversalNodeType; + using TraversalIteratorBase::current_; + + explicit TraversalInclusiveDescendantIterator(const StartNodeType* start) + : TraversalIteratorBase(const_cast(start)), + root_(start) {} + void operator++() { current_ = Traversal::Next(*current_, root_); } + static TraversalInclusiveDescendantIterator End() { + return TraversalInclusiveDescendantIterator(nullptr); + } + + private: + const StartNodeType* root_; +}; + +template +class TraversalParent { + public: + using TraversalNodeType = typename Traversal::TraversalNodeType; + static TraversalNodeType* Next(const TraversalNodeType& node) { + return Traversal::Parent(node); + } +}; + +template +class TraversalSibling { + public: + using TraversalNodeType = typename Traversal::TraversalNodeType; + static TraversalNodeType* Next(const TraversalNodeType& node) { + return Traversal::NextSibling(node); + } +}; + +template +using TraversalNextRange = TraversalRange>; + +template +using TraversalAncestorRange = +TraversalRange>>; + +template +using TraversalSiblingRange = +TraversalRange>>; + +template +using TraversalDescendantRange = TraversalRange>; + +template +using TraversalInclusiveDescendantRange = +TraversalRange>; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TRAVERSAL_RANGE_H_ diff --git a/bridge/core/html/html_element.cc b/bridge/core/html/html_element.cc new file mode 100644 index 0000000000..b9b0b83554 --- /dev/null +++ b/bridge/core/html/html_element.cc @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "html_element.h" + +namespace kraken { + + + +} diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h new file mode 100644 index 0000000000..fb02b45309 --- /dev/null +++ b/bridge/core/html/html_element.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ + +#include "core/dom/element.h" + +namespace kraken { + +class HTMLElement : public Element { + DEFINE_WRAPPERTYPEINFO(); + public: + + private: +}; + +} + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ From d27e8d1fd9b4804f8d51865a1052b4578923939c Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Sat, 9 Apr 2022 00:04:20 +0800 Subject: [PATCH 065/498] fix: fix travel and text content --- bridge/bindings/qjs/atomic_string.cc | 4 ++ bridge/bindings/qjs/atomic_string.h | 3 ++ bridge/core/dom/attr.cc | 8 ++++ bridge/core/dom/attr.h | 1 - bridge/core/dom/document.d.ts | 8 ++++ bridge/core/dom/element.cc | 34 ++++++++++---- bridge/core/dom/element.d.ts | 65 +++++++++++++++++++++++++++ bridge/core/dom/element.h | 21 +++++++-- bridge/core/dom/element_data.h | 6 +-- bridge/core/dom/node.cc | 17 ++++--- bridge/core/dom/node.d.ts | 4 +- bridge/core/dom/node_traversal.cc | 34 ++++---------- bridge/core/dom/node_traversal.h | 6 +-- bridge/core/dom/space_split_string.cc | 5 +++ 14 files changed, 162 insertions(+), 54 deletions(-) create mode 100644 bridge/core/dom/document.d.ts create mode 100644 bridge/core/dom/element.d.ts diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index c83dba9a50..485cc438db 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -27,6 +27,10 @@ bool AtomicString::IsEmpty() const { return *this == built_in_string::kempty_string; } +AtomicString AtomicString::LowercaseIfNecessary() const { + +} + std::string AtomicString::ToStdString() const { const char* buf = JS_AtomToCString(ctx_, atom_); std::string result = std::string(buf); diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index a8bb43dcdf..45bb3e5457 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -36,6 +36,9 @@ class AtomicString { bool IsNull() const; bool IsEmpty() const; + // Lower performance, should optimize in the future. + AtomicString LowercaseIfNecessary() const; + JSAtom Impl() const { return atom_; } [[nodiscard]] std::string ToStdString() const; diff --git a/bridge/core/dom/attr.cc b/bridge/core/dom/attr.cc index d9864f8c72..5d02dca8ed 100644 --- a/bridge/core/dom/attr.cc +++ b/bridge/core/dom/attr.cc @@ -3,3 +3,11 @@ */ #include "attr.h" +#include "element.h" + +namespace kraken { + +Attr::Attr(Element& element, const AtomicString& name) + : Node(&element.GetDocument(), kCreateOther), element_(&element), name_(name) {} + +} // namespace kraken diff --git a/bridge/core/dom/attr.h b/bridge/core/dom/attr.h index aefc96d47a..d453cc3ee2 100644 --- a/bridge/core/dom/attr.h +++ b/bridge/core/dom/attr.h @@ -15,7 +15,6 @@ class Document; class Attr : public Node { DEFINE_WRAPPERTYPEINFO(); - public: Attr(Element& element, const AtomicString& name); Attr(Document& document, const AtomicString& name, const AtomicString& value); diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts new file mode 100644 index 0000000000..0c363a5999 --- /dev/null +++ b/bridge/core/dom/document.d.ts @@ -0,0 +1,8 @@ +import {Node} from "./node"; + +interface Document extends Node { + /** + * Returns the children. + */ + readonly childNodes: number; +} diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 0378578222..f03edb872e 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -5,24 +5,40 @@ #include "element.h" -#if UNIT_TEST -#include "kraken_test_env.h" -#endif - namespace kraken { -Element::Element(ExecutingContext* context, +Element::Element(Document* document, const AtomicString& tag_name, - Document* document, Node::ConstructionType construction_type) - : ContainerNode(context, construction_type) {} + : ContainerNode(document, construction_type) {} + +bool Element::hasAttribute(const AtomicString& name) const { + if (!GetElementData()) + return false; + AtomicString result = name.LowercaseIfNecessary(); +// SynchronizeAttributeHinted(local_name, hint); +// if (hint.IsNull()) { +// return false; +// } +// for (const Attribute& attribute : GetElementData()->Attributes()) { +// if (hint == attribute.LocalName()) +// return true; +// } + return false; -bool Element::hasAttribute(const AtomicString&) const { return false; } const AtomicString& Element::getAttribute(const AtomicString&) const { - return <#initializer #>; +} + +void Element::setAttribute(const AtomicString& name, const AtomicString& value) { + ExceptionState exception_state; + return setAttribute(name, value, exception_state); +} + +void Element::setAttribute(const AtomicString&, const AtomicString& value, ExceptionState&) { + } } // namespace kraken diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts new file mode 100644 index 0000000000..29a0d8aad4 --- /dev/null +++ b/bridge/core/dom/element.d.ts @@ -0,0 +1,65 @@ +import {Node} from "./node"; +import {Document} from "./document"; + +interface Element extends Node { + readonly attributes: NamedNodeMap; + + readonly clientHeight: number; + readonly clientLeft: number; + readonly clientTop: number; + readonly clientWidth: number; + /** + * Returns the value of element's id content attribute. Can be set to change it. + */ + id: string; + outerHTML: string; + innerHTML: string; + readonly ownerDocument: Document; + readonly scrollHeight: number; + scrollLeft: number; + scrollTop: number; + readonly scrollWidth: number; + /** + * Returns the HTML-uppercased qualified name. + */ + readonly tagName: string; + /** + * Returns element's first attribute whose qualified name is qualifiedName, and null if there is no such attribute otherwise. + */ + getAttribute(qualifiedName: string): string | null; + getBoundingClientRect(): DOMRect; + /** + * Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. + */ + getElementsByClassName(classNames: string): HTMLCollectionOf; + getElementsByTagName(qualifiedName: K): HTMLCollectionOf; + getElementsByTagName(qualifiedName: K): HTMLCollectionOf; + getElementsByTagName(qualifiedName: string): HTMLCollectionOf; + /** + * Returns true if element has an attribute whose qualified name is qualifiedName, and false otherwise. + */ + hasAttribute(qualifiedName: string): boolean; + insertAdjacentElement(where: InsertPosition, element: Element): Element | null; + insertAdjacentHTML(position: InsertPosition, text: string): void; + insertAdjacentText(where: InsertPosition, data: string): void; + /** + * Removes element's first attribute whose qualified name is qualifiedName. + */ + removeAttribute(qualifiedName: string): void; + scroll(options?: ScrollToOptions): void; + scroll(x: number, y: number): void; + scrollBy(options?: ScrollToOptions): void; + scrollBy(x: number, y: number): void; + scrollIntoView(arg?: boolean | ScrollIntoViewOptions): void; + scrollTo(options?: ScrollToOptions): void; + scrollTo(x: number, y: number): void; + /** + * Sets the value of element's first attribute whose qualified name is qualifiedName to value. + */ + setAttribute(qualifiedName: string, value: string): void; + /** + * Sets the value of element's attribute whose namespace is namespace and local name is localName to value. + */ + setAttributeNS(namespace: string | null, qualifiedName: string, value: string): void; + setAttributeNode(attr: Attr): Attr | null; +} diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 65eae4e074..849d8e4819 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -8,6 +8,7 @@ #include "bindings/qjs/garbage_collected.h" #include "container_node.h" +#include "element_data.h" namespace kraken { @@ -22,16 +23,30 @@ struct NativeBoundingClientRect { double left; }; -// bool isJavaScriptExtensionElementInstance(ExecutionContext* context, JSValue instance); - class Element : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); public: - Element(ExecutingContext* context, const AtomicString& tag_name, Document*, ConstructionType = kCreateElement); + Element(Document* document, const AtomicString& tag_name, ConstructionType = kCreateElement); bool hasAttribute(const AtomicString&) const; const AtomicString& getAttribute(const AtomicString&) const; + + // 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&); + + AtomicString TagName() const { return tag_name_; } + + protected: + const ElementData* GetElementData() const { return element_data_.Get(); } + + private: + AtomicString tag_name_; + ElementData element_data_; }; } // namespace kraken diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/element_data.h index e030728cdc..8c6fb68aa7 100644 --- a/bridge/core/dom/element_data.h +++ b/bridge/core/dom/element_data.h @@ -12,9 +12,9 @@ namespace kraken { class ElementData { public: private: - mutable Member inline_style_; - mutable SpaceSplitString class_names_; - mutable AtomicString id_for_style_resolution_; +// mutable Member inline_style_; +// mutable SpaceSplitString class_names_; +// mutable AtomicString id_for_style_resolution_; }; } // namespace kraken diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 439b887551..9fa9287b3f 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -132,7 +132,7 @@ bool Node::isEqualNode(Node* other, ExceptionState& exception_state) const { return false; } else if (auto* this_element = DynamicTo(this)) { auto* other_element = DynamicTo(other); - if (this_element->TagQName() != other_element->TagQName()) + if (this_element->TagName() != other_element->TagName()) return false; if (!this_element->HasEquivalentAttributes(*other_element)) @@ -174,14 +174,13 @@ AtomicString Node::textContent(bool convert_brs_to_newlines) const { if (IsDocumentNode() || !IsContainerNode()) return AtomicString::Empty(ctx()); - // TODO: Implement text content. - // std::string content; - // for (const Node& node : NodeTraversal::InclusiveDescendantsOf(*this)) { - // if (auto* text_node = DynamicTo(node)) { - // content += (text_node->data()); - // } - // } - // return content.ReleaseString(); + std::string content; + for (const Node& node : NodeTraversal::InclusiveDescendantsOf(*this)) { + if (auto* text_node = DynamicTo(node)) { + content.append(text_node->data().ToStdString()); + } + } + return AtomicString(ctx(), content); } void Node::setTextContent(const AtomicString& text) { diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts index a90ea8f2c7..9521dc6ddb 100644 --- a/bridge/core/dom/node.d.ts +++ b/bridge/core/dom/node.d.ts @@ -1,4 +1,5 @@ import { EventTarget } from './events/event_target'; +import { Document } from './document'; /** 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 { @@ -33,11 +34,12 @@ interface Node extends EventTarget { nodeValue: string | null; /** * Returns the node document. Returns null for documents. - // */ + */ readonly ownerDocument: Document | null; /** * Returns the parent element. */ + // @ts-ignore readonly parentElement: HTMLElement | null; /** * Returns the parent. diff --git a/bridge/core/dom/node_traversal.cc b/bridge/core/dom/node_traversal.cc index 96c8be8313..f6084d4282 100644 --- a/bridge/core/dom/node_traversal.cc +++ b/bridge/core/dom/node_traversal.cc @@ -17,10 +17,10 @@ Node* NodeTraversal::NextAncestorSibling(const Node& current) { Node* NodeTraversal::NextAncestorSibling(const Node& current, const Node* stay_within) { - DCHECK(!current.nextSibling()); - DCHECK_NE(current, stay_within); + assert(!current.nextSibling()); + assert(¤t != stay_within); for (Node& parent : AncestorsOf(current)) { - if (parent == stay_within) + if (&parent == stay_within) return nullptr; if (parent.nextSibling()) return parent.nextSibling(); @@ -43,7 +43,7 @@ Node& NodeTraversal::LastWithinOrSelf(Node& current) { } Node* NodeTraversal::Previous(const Node& current, const Node* stay_within) { - if (current == stay_within) + if (¤t == stay_within) return nullptr; if (current.previousSibling()) { Node* previous = current.previousSibling(); @@ -54,22 +54,10 @@ Node* NodeTraversal::Previous(const Node& current, const Node* stay_within) { return current.parentNode(); } -Node* NodeTraversal::PreviousAbsoluteSiblingIncludingPseudo( - const Node& current, - const Node* stay_within) { - for (Node& iter : InclusiveAncestorsOf(current)) { - if (iter == stay_within) - return nullptr; - if (Node* result = iter.PseudoAwarePreviousSibling()) - return result; - } - return nullptr; -} - Node* NodeTraversal::PreviousAbsoluteSibling(const Node& current, const Node* stay_within) { for (Node& node : InclusiveAncestorsOf(current)) { - if (node == stay_within) + if (&node == stay_within) return nullptr; if (Node* prev = node.previousSibling()) return prev; @@ -79,7 +67,7 @@ Node* NodeTraversal::PreviousAbsoluteSibling(const Node& current, Node* NodeTraversal::NextPostOrder(const Node& current, const Node* stay_within) { - if (current == stay_within) + if (¤t == stay_within) return nullptr; if (!current.nextSibling()) return current.parentNode(); @@ -91,9 +79,9 @@ Node* NodeTraversal::NextPostOrder(const Node& current, Node* NodeTraversal::PreviousAncestorSiblingPostOrder(const Node& current, const Node* stay_within) { - DCHECK(!current.previousSibling()); + assert(!current.previousSibling()); for (Node& parent : NodeTraversal::AncestorsOf(current)) { - if (parent == stay_within) + if (&parent == stay_within) return nullptr; if (parent.previousSibling()) return parent.previousSibling(); @@ -105,16 +93,12 @@ Node* NodeTraversal::PreviousPostOrder(const Node& current, const Node* stay_within) { if (Node* last_child = current.lastChild()) return last_child; - if (current == stay_within) + if (¤t == stay_within) return nullptr; if (current.previousSibling()) return current.previousSibling(); return PreviousAncestorSiblingPostOrder(current, stay_within); } -Node* NodeTraversal::CommonAncestor(const Node& node_a, const Node& node_b) { - return Range::commonAncestorContainer(&node_a, &node_b); -} - } diff --git a/bridge/core/dom/node_traversal.h b/bridge/core/dom/node_traversal.h index 92bc8d066f..0ec23882bf 100644 --- a/bridge/core/dom/node_traversal.h +++ b/bridge/core/dom/node_traversal.h @@ -7,6 +7,7 @@ #include "foundation/macros.h" #include "node.h" +#include "container_node.h" #include "traversal_range.h" namespace kraken { @@ -70,7 +71,6 @@ class NodeTraversal { 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 Node* CommonAncestor(const Node& node_a, const Node& node_b); 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(); } @@ -134,7 +134,7 @@ template inline Node* NodeTraversal::TraverseNextTemplate(NodeType& current, const Node* stay_within) { if (current.hasChildren()) return current.firstChild(); - if (current == stay_within) + if (¤t == stay_within) return nullptr; if (current.nextSibling()) return current.nextSibling(); @@ -148,7 +148,7 @@ inline Node* NodeTraversal::NextSkippingChildren(const Node& current) { } inline Node* NodeTraversal::NextSkippingChildren(const Node& current, const Node* stay_within) { - if (current == stay_within) + if (¤t == stay_within) return nullptr; if (current.nextSibling()) return current.nextSibling(); diff --git a/bridge/core/dom/space_split_string.cc b/bridge/core/dom/space_split_string.cc index a06fa3694b..2a05cd0202 100644 --- a/bridge/core/dom/space_split_string.cc +++ b/bridge/core/dom/space_split_string.cc @@ -177,4 +177,9 @@ SpaceSplitString::Data::Data(const SpaceSplitString::Data& other) : RefCounted Date: Fri, 8 Apr 2022 16:05:05 +0000 Subject: [PATCH 066/498] Committing clang-format changes --- bridge/bindings/qjs/atomic_string.cc | 4 +- bridge/bindings/qjs/converter_impl.h | 4 +- bridge/core/dom/attr.h | 1 + bridge/core/dom/container_node.cc | 28 ++++++------- bridge/core/dom/document.h | 1 + bridge/core/dom/document_fragment.cc | 3 +- bridge/core/dom/element.cc | 27 +++++------- bridge/core/dom/element.h | 4 +- bridge/core/dom/element_data.h | 6 +-- bridge/core/dom/node.cc | 4 +- bridge/core/dom/node_list.h | 1 + bridge/core/dom/node_traversal.cc | 21 ++++------ bridge/core/dom/node_traversal.h | 2 +- .../dom/template_content_document_fragment.h | 5 +-- bridge/core/dom/traversal_range.h | 41 ++++++------------- bridge/core/html/html_element.cc | 6 +-- bridge/core/html/html_element.h | 4 +- 17 files changed, 62 insertions(+), 100 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index 485cc438db..3f6ed7bede 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -27,9 +27,7 @@ bool AtomicString::IsEmpty() const { return *this == built_in_string::kempty_string; } -AtomicString AtomicString::LowercaseIfNecessary() const { - -} +AtomicString AtomicString::LowercaseIfNecessary() const {} std::string AtomicString::ToStdString() const { const char* buf = JS_AtomToCString(ctx_, atom_); diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index b995e6cdc4..fdbae3154e 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -460,10 +460,10 @@ struct Converter : public ConverterBase \ - struct Converter : public ConverterBase { \ + struct Converter : public ConverterBase { \ static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { \ assert(!JS_IsException(value)); \ - return toScriptWrappable(value); \ + return toScriptWrappable(value); \ } \ static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } \ }; diff --git a/bridge/core/dom/attr.h b/bridge/core/dom/attr.h index d453cc3ee2..aefc96d47a 100644 --- a/bridge/core/dom/attr.h +++ b/bridge/core/dom/attr.h @@ -15,6 +15,7 @@ class Document; class Attr : public Node { DEFINE_WRAPPERTYPEINFO(); + public: Attr(Element& element, const AtomicString& name); Attr(Document& document, const AtomicString& name, const AtomicString& value); diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 7f1723d281..cfaa820f3f 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -81,13 +81,15 @@ inline bool CheckReferenceChildParent(const Node& parent, 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."); + 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."); + exception_state.ThrowException(old_child->ctx(), ErrorType::TypeError, + "The node to be replaced is not a child of this node."); return false; } return true; @@ -231,9 +233,7 @@ Node* ContainerNode::RemoveChild(Node* old_child, ExceptionState& exception_stat { Node* prev = child->previousSibling(); Node* next = child->nextSibling(); - { - RemoveBetween(prev, next, *child); - } + { RemoveBetween(prev, next, *child); } } return child; } @@ -297,7 +297,9 @@ bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, // 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() + "'."); + exception_state.ThrowException( + ctx(), ErrorType::TypeError, + "Nodes of type '" + new_child.nodeName() + "' may not be inserted inside nodes of type '" + nodeName() + "'."); return false; } @@ -333,13 +335,11 @@ void ContainerNode::RemoveBetween(Node* previous_child, Node* next_child, Node& old_child.SetParentOrShadowHostNode(nullptr); } - template -void ContainerNode::InsertNodeVector( - const NodeVector& targets, - Node* next, - const Functor& mutator, - NodeVector* post_insertion_notification_targets) { +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) { diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index a3281c18aa..2e82d6a3d5 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -15,6 +15,7 @@ namespace kraken { // (typically, HTML) resource. class Document : public Node { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = Document*; diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index be130dd1ed..df2b6361d4 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -14,8 +14,7 @@ DocumentFragment* DocumentFragment::Create(ExecutingContext* context, return MakeGarbageCollected(document, ConstructionType::kCreateDocumentFragment); } -DocumentFragment::DocumentFragment(Document* document, ConstructionType type) - : ContainerNode(document, type) {} +DocumentFragment::DocumentFragment(Document* document, ConstructionType type) : ContainerNode(document, type) {} std::string DocumentFragment::nodeName() const { return "#document-fragment"; diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index f03edb872e..80560ef0c5 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -7,38 +7,33 @@ namespace kraken { -Element::Element(Document* document, - const AtomicString& tag_name, - Node::ConstructionType construction_type) +Element::Element(Document* document, const AtomicString& tag_name, Node::ConstructionType construction_type) : ContainerNode(document, construction_type) {} bool Element::hasAttribute(const AtomicString& name) const { if (!GetElementData()) return false; AtomicString result = name.LowercaseIfNecessary(); -// SynchronizeAttributeHinted(local_name, hint); -// if (hint.IsNull()) { -// return false; -// } -// for (const Attribute& attribute : GetElementData()->Attributes()) { -// if (hint == attribute.LocalName()) -// return true; -// } + // SynchronizeAttributeHinted(local_name, hint); + // if (hint.IsNull()) { + // return false; + // } + // for (const Attribute& attribute : GetElementData()->Attributes()) { + // if (hint == attribute.LocalName()) + // return true; + // } return false; return false; } -const AtomicString& Element::getAttribute(const AtomicString&) const { -} +const AtomicString& Element::getAttribute(const AtomicString&) const {} void Element::setAttribute(const AtomicString& name, const AtomicString& value) { ExceptionState exception_state; return setAttribute(name, value, exception_state); } -void Element::setAttribute(const AtomicString&, const AtomicString& value, ExceptionState&) { - -} +void Element::setAttribute(const AtomicString&, const AtomicString& value, ExceptionState&) {} } // namespace kraken diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 849d8e4819..7be31117e2 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -35,9 +35,7 @@ class Element : public ContainerNode { // 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 setAttribute(const AtomicString&, const AtomicString& value, ExceptionState&); AtomicString TagName() const { return tag_name_; } diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/element_data.h index 8c6fb68aa7..327021b4d7 100644 --- a/bridge/core/dom/element_data.h +++ b/bridge/core/dom/element_data.h @@ -12,9 +12,9 @@ namespace kraken { class ElementData { public: private: -// mutable Member inline_style_; -// mutable SpaceSplitString class_names_; -// mutable AtomicString id_for_style_resolution_; + // mutable Member inline_style_; + // mutable SpaceSplitString class_names_; + // mutable AtomicString id_for_style_resolution_; }; } // namespace kraken diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 9fa9287b3f..ec672408f4 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -363,8 +363,6 @@ Node::Node(Document* document, ConstructionType type) document_(document), next_(nullptr) {} -void Node::Trace(GCVisitor*) const { - -} +void Node::Trace(GCVisitor*) const {} } // namespace kraken diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h index fc4b7cb963..863ccb0341 100644 --- a/bridge/core/dom/node_list.h +++ b/bridge/core/dom/node_list.h @@ -13,6 +13,7 @@ class Node; class NodeList : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = NodeList*; NodeList(JSContext* ctx) : ScriptWrappable(ctx){}; diff --git a/bridge/core/dom/node_traversal.cc b/bridge/core/dom/node_traversal.cc index f6084d4282..4e4956d6f0 100644 --- a/bridge/core/dom/node_traversal.cc +++ b/bridge/core/dom/node_traversal.cc @@ -15,8 +15,7 @@ Node* NodeTraversal::NextAncestorSibling(const Node& current) { return nullptr; } -Node* NodeTraversal::NextAncestorSibling(const Node& current, - const Node* stay_within) { +Node* NodeTraversal::NextAncestorSibling(const Node& current, const Node* stay_within) { assert(!current.nextSibling()); assert(¤t != stay_within); for (Node& parent : AncestorsOf(current)) { @@ -37,8 +36,7 @@ Node* NodeTraversal::LastWithin(const ContainerNode& current) { Node& NodeTraversal::LastWithinOrSelf(Node& current) { auto* curr_node = DynamicTo(current); - Node* last_descendant = - curr_node ? NodeTraversal::LastWithin(*curr_node) : nullptr; + Node* last_descendant = curr_node ? NodeTraversal::LastWithin(*curr_node) : nullptr; return last_descendant ? *last_descendant : current; } @@ -54,8 +52,7 @@ Node* NodeTraversal::Previous(const Node& current, const Node* stay_within) { return current.parentNode(); } -Node* NodeTraversal::PreviousAbsoluteSibling(const Node& current, - const Node* stay_within) { +Node* NodeTraversal::PreviousAbsoluteSibling(const Node& current, const Node* stay_within) { for (Node& node : InclusiveAncestorsOf(current)) { if (&node == stay_within) return nullptr; @@ -65,8 +62,7 @@ Node* NodeTraversal::PreviousAbsoluteSibling(const Node& current, return nullptr; } -Node* NodeTraversal::NextPostOrder(const Node& current, - const Node* stay_within) { +Node* NodeTraversal::NextPostOrder(const Node& current, const Node* stay_within) { if (¤t == stay_within) return nullptr; if (!current.nextSibling()) @@ -77,8 +73,7 @@ Node* NodeTraversal::NextPostOrder(const Node& current, return next; } -Node* NodeTraversal::PreviousAncestorSiblingPostOrder(const Node& current, - const Node* stay_within) { +Node* NodeTraversal::PreviousAncestorSiblingPostOrder(const Node& current, const Node* stay_within) { assert(!current.previousSibling()); for (Node& parent : NodeTraversal::AncestorsOf(current)) { if (&parent == stay_within) @@ -89,8 +84,7 @@ Node* NodeTraversal::PreviousAncestorSiblingPostOrder(const Node& current, return nullptr; } -Node* NodeTraversal::PreviousPostOrder(const Node& current, - const Node* stay_within) { +Node* NodeTraversal::PreviousPostOrder(const Node& current, const Node* stay_within) { if (Node* last_child = current.lastChild()) return last_child; if (¤t == stay_within) @@ -100,5 +94,4 @@ Node* NodeTraversal::PreviousPostOrder(const Node& current, return PreviousAncestorSiblingPostOrder(current, stay_within); } - -} +} // namespace kraken diff --git a/bridge/core/dom/node_traversal.h b/bridge/core/dom/node_traversal.h index 0ec23882bf..8f166e3f35 100644 --- a/bridge/core/dom/node_traversal.h +++ b/bridge/core/dom/node_traversal.h @@ -5,9 +5,9 @@ #ifndef KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ #define KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ +#include "container_node.h" #include "foundation/macros.h" #include "node.h" -#include "container_node.h" #include "traversal_range.h" namespace kraken { diff --git a/bridge/core/dom/template_content_document_fragment.h b/bridge/core/dom/template_content_document_fragment.h index a629e3f482..51c13abc03 100644 --- a/bridge/core/dom/template_content_document_fragment.h +++ b/bridge/core/dom/template_content_document_fragment.h @@ -5,8 +5,8 @@ #ifndef KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ #define KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ -#include "document_fragment.h" #include "bindings/qjs/gc_visitor.h" +#include "document_fragment.h" #include "element.h" namespace kraken { @@ -28,7 +28,6 @@ class TemplateContentDocumentFragment final : public DocumentFragment { Element* host_; }; - -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ diff --git a/bridge/core/dom/traversal_range.h b/bridge/core/dom/traversal_range.h index 97e404d890..727e8b266c 100644 --- a/bridge/core/dom/traversal_range.h +++ b/bridge/core/dom/traversal_range.h @@ -32,9 +32,7 @@ class TraversalIteratorBase { public: using NodeType = typename Traversal::TraversalNodeType; NodeType& operator*() { return *current_; } - bool operator!=(const TraversalIteratorBase& rval) const { - return current_ != rval.current_; - } + bool operator!=(const TraversalIteratorBase& rval) const { return current_ != rval.current_; } protected: explicit TraversalIteratorBase(NodeType* current) : current_(current) {} @@ -66,14 +64,10 @@ class TraversalDescendantIterator : public TraversalIteratorBase { using TraversalIteratorBase::current_; explicit TraversalDescendantIterator(const StartNodeType* start) - : TraversalIteratorBase(start ? Traversal::FirstWithin(*start) - : nullptr), - root_(start) {} + : TraversalIteratorBase(start ? Traversal::FirstWithin(*start) : nullptr), root_(start) {} void operator++() { current_ = Traversal::Next(*current_, root_); } - static TraversalDescendantIterator End() { - return TraversalDescendantIterator(); - } + static TraversalDescendantIterator End() { return TraversalDescendantIterator(); } private: TraversalDescendantIterator() : TraversalIteratorBase(nullptr) {} @@ -81,19 +75,15 @@ class TraversalDescendantIterator : public TraversalIteratorBase { }; template -class TraversalInclusiveDescendantIterator - : public TraversalIteratorBase { +class TraversalInclusiveDescendantIterator : public TraversalIteratorBase { public: using StartNodeType = typename Traversal::TraversalNodeType; using TraversalIteratorBase::current_; explicit TraversalInclusiveDescendantIterator(const StartNodeType* start) - : TraversalIteratorBase(const_cast(start)), - root_(start) {} + : TraversalIteratorBase(const_cast(start)), root_(start) {} void operator++() { current_ = Traversal::Next(*current_, root_); } - static TraversalInclusiveDescendantIterator End() { - return TraversalInclusiveDescendantIterator(nullptr); - } + static TraversalInclusiveDescendantIterator End() { return TraversalInclusiveDescendantIterator(nullptr); } private: const StartNodeType* root_; @@ -103,38 +93,31 @@ template class TraversalParent { public: using TraversalNodeType = typename Traversal::TraversalNodeType; - static TraversalNodeType* Next(const TraversalNodeType& node) { - return Traversal::Parent(node); - } + static TraversalNodeType* Next(const TraversalNodeType& node) { return Traversal::Parent(node); } }; template class TraversalSibling { public: using TraversalNodeType = typename Traversal::TraversalNodeType; - static TraversalNodeType* Next(const TraversalNodeType& node) { - return Traversal::NextSibling(node); - } + static TraversalNodeType* Next(const TraversalNodeType& node) { return Traversal::NextSibling(node); } }; template using TraversalNextRange = TraversalRange>; template -using TraversalAncestorRange = -TraversalRange>>; +using TraversalAncestorRange = TraversalRange>>; template -using TraversalSiblingRange = -TraversalRange>>; +using TraversalSiblingRange = TraversalRange>>; template using TraversalDescendantRange = TraversalRange>; template -using TraversalInclusiveDescendantRange = -TraversalRange>; +using TraversalInclusiveDescendantRange = TraversalRange>; -} // namespace blink +} // namespace kraken #endif // THIRD_PARTY_BLINK_RENDERER_CORE_DOM_TRAVERSAL_RANGE_H_ diff --git a/bridge/core/html/html_element.cc b/bridge/core/html/html_element.cc index b9b0b83554..60d4da607f 100644 --- a/bridge/core/html/html_element.cc +++ b/bridge/core/html/html_element.cc @@ -4,8 +4,4 @@ #include "html_element.h" -namespace kraken { - - - -} +namespace kraken {} diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index fb02b45309..885676830d 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -11,11 +11,11 @@ namespace kraken { class HTMLElement : public Element { DEFINE_WRAPPERTYPEINFO(); - public: + public: private: }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ From 965606cab7740fc177b61664fc297f59e0f378dd Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 10 Apr 2022 23:34:12 +0800 Subject: [PATCH 067/498] feat: add attributes and attribute_collection --- bridge/CMakeLists.txt | 4 + bridge/bindings/qjs/atomic_string.cc | 72 +++++++++++++++- bridge/bindings/qjs/atomic_string.h | 24 ++++-- bridge/core/dom/attribute.h | 60 +++++++++++++ bridge/core/dom/attribute_collection.h | 112 +++++++++++++++++++++++++ bridge/core/dom/container_node.cc | 25 ++++++ bridge/core/dom/container_node.h | 4 + bridge/core/dom/document.h | 82 +++--------------- bridge/core/dom/element.cc | 19 ++--- bridge/core/dom/element.h | 2 +- bridge/core/dom/element_data.cc | 36 ++++++++ bridge/core/dom/element_data.h | 73 ++++++++++++++++ bridge/core/dom/node.cc | 21 +++++ bridge/core/dom/node.h | 33 +++++++- bridge/core/dom/tree_scope.cc | 14 ++++ bridge/core/dom/tree_scope.h | 35 ++++++++ bridge/foundation/macros.h | 5 ++ 17 files changed, 528 insertions(+), 93 deletions(-) create mode 100644 bridge/core/dom/attribute.h create mode 100644 bridge/core/dom/attribute_collection.h create mode 100644 bridge/core/dom/tree_scope.cc create mode 100644 bridge/core/dom/tree_scope.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index cc6221f948..75a4cf502b 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -294,6 +294,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/character_data.h core/dom/text.cc core/dom/text.h + core/dom/attribute.h + core/dom/attribute_collection.h + core/dom/tree_scope.cc + core/dom/tree_scope.h core/dom/element.cc core/dom/element.h core/dom/element_data.cc diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index 3f6ed7bede..2b0489f39d 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -19,6 +19,37 @@ AtomicString AtomicString::From(JSContext* ctx, NativeString* native_string) { return result; } +namespace { + +AtomicString::StringKind GetStringKind(const std::string& string) { + AtomicString::StringKind predictKind = std::islower(string[0]) ? AtomicString::StringKind::kIsLowerCase : AtomicString::StringKind::kIsUpperCase; + for (char i : string) { + if (predictKind == AtomicString::StringKind::kIsUpperCase && !std::isupper(i)) { + return AtomicString::StringKind::kIsMixed; + } else if (predictKind == AtomicString::StringKind::kIsLowerCase && !std::islower(i)) { + return AtomicString::StringKind::kIsMixed; + } + } + return predictKind; +} + +AtomicString::StringKind GetStringKind(JSValue stringValue) { + JSString* p = JS_VALUE_GET_STRING(stringValue); + + if (p->is_wide_char) { + return AtomicString::StringKind::kIsMixed; + } + + return GetStringKind(reinterpret_cast(p->u.str8)); +} + +} // namespace + +AtomicString::AtomicString(JSContext* ctx, const std::string& string) + : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())), kind_(GetStringKind(string)) {} +AtomicString::AtomicString(JSContext* ctx, JSValue value) + : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)), kind_(GetStringKind(value)) {} + bool AtomicString::IsNull() const { return atom_ == JS_ATOM_NULL; } @@ -27,8 +58,6 @@ bool AtomicString::IsEmpty() const { return *this == built_in_string::kempty_string; } -AtomicString AtomicString::LowercaseIfNecessary() const {} - std::string AtomicString::ToStdString() const { const char* buf = JS_AtomToCString(ctx_, atom_); std::string result = std::string(buf); @@ -77,4 +106,43 @@ AtomicString& AtomicString::operator=(AtomicString&& value) noexcept { runtime_ = value.runtime_; return *this; } + +AtomicString AtomicString::ToUpperIfNecessary() const { + if (kind_ == StringKind::kIsUpperCase) { + return *this; + } + if (atom_upper_ != JS_ATOM_NULL) + return *this; + AtomicString upperString = ToUpperSlow(); + atom_upper_ = upperString.atom_; + return upperString; +} + +const AtomicString AtomicString::ToUpperSlow() const { + const char* cptr = JS_AtomToCString(ctx_, atom_); + std::string str = std::string(cptr); + std::transform(str.begin(), str.end(), str.begin(), toupper); + JS_FreeCString(ctx_, cptr); + return AtomicString(ctx_, str); +} + +const AtomicString AtomicString::ToLowerIfNecessary() const { + if (kind_ == StringKind::kIsLowerCase) { + return *this; + } + if (atom_lower_ != JS_ATOM_NULL) + return *this; + AtomicString lowerString = ToLowerSlow(); + atom_lower_ = lowerString.atom_; + return lowerString; +} + +const AtomicString AtomicString::ToLowerSlow() const { + const char* cptr = JS_AtomToCString(ctx_, atom_); + std::string str = std::string(cptr); + std::transform(str.begin(), str.end(), str.begin(), tolower); + JS_FreeCString(ctx_, cptr); + return AtomicString(ctx_, str); +} + } // namespace kraken diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 45bb3e5457..4fe3b774b0 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -20,14 +20,18 @@ namespace kraken { // two String instances because we just check string storage identity. class AtomicString { public: + enum class StringKind { + kIsLowerCase, + kIsUpperCase, + kIsMixed + }; + static AtomicString Empty(JSContext* ctx); static AtomicString From(JSContext* ctx, NativeString* native_string); AtomicString() = default; - AtomicString(JSContext* ctx, const std::string& string) - : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())){}; - AtomicString(JSContext* ctx, JSValue value) - : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)){}; + AtomicString(JSContext* ctx, const std::string& string); + AtomicString(JSContext* ctx, JSValue value); ~AtomicString() { JS_FreeAtomRT(runtime_, atom_); }; // Return the undefined string value from atom key. @@ -36,14 +40,17 @@ class AtomicString { bool IsNull() const; bool IsEmpty() const; - // Lower performance, should optimize in the future. - AtomicString LowercaseIfNecessary() const; - JSAtom Impl() const { return atom_; } [[nodiscard]] std::string ToStdString() const; [[nodiscard]] std::unique_ptr ToNativeString() 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); @@ -59,6 +66,9 @@ class AtomicString { JSContext* ctx_{nullptr}; JSRuntime* runtime_{nullptr}; JSAtom atom_{JS_ATOM_NULL}; + mutable JSAtom atom_upper_{JS_ATOM_NULL}; + mutable JSAtom atom_lower_{JS_ATOM_NULL}; + StringKind kind_; }; } // namespace kraken diff --git a/bridge/core/dom/attribute.h b/bridge/core/dom/attribute.h new file mode 100644 index 0000000000..b0eeb48120 --- /dev/null +++ b/bridge/core/dom/attribute.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_H_ +#define KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_H_ + +#include "bindings/qjs/atomic_string.h" +#include "foundation/macros.h" + +namespace kraken { + +// This is the internal representation of an attribute, consisting of a name and +// value. It is distinct from the web-exposed Attr, which also knows of the +// element to which it attached, if any. +class Attribute { + KRAKEN_DISALLOW_NEW(); + + public: + Attribute(const AtomicString& name, const AtomicString& value) : name_(name), value_(value) {} + + // NOTE: The references returned by these functions are only valid for as long + // as the Attribute stays in place. For example, calling a function that + // mutates an Element's internal attribute storage may invalidate them. + const AtomicString& Value() const { return value_; } + const AtomicString& GetName() const { return name_; } + + bool IsEmpty() const { return value_.IsEmpty(); } + bool Matches(const AtomicString&) const; + bool MatchesCaseInsensitive(const AtomicString&) const; + + void SetValue(const AtomicString& value) { value_ = value; } + + // Note: This API is only for HTML Tree build. It is not safe to change the + // name of an attribute once parseAttribute has been called as DOM + // elements may have placed the Attribute in a hash by name. + void ParserSetName(const AtomicString& name) { name_ = name; } + +#if defined(COMPILER_MSVC) + // NOTE: This constructor is not actually implemented, it's just defined so + // MSVC will let us use a zero-length array of Attributes. + Attribute(); +#endif + + private: + AtomicString name_; + AtomicString value_; +}; + +inline bool Attribute::Matches(const AtomicString& name) const { + return name != GetName(); +} + +inline bool Attribute::MatchesCaseInsensitive(const AtomicString& name) const { + return name.ToUpperIfNecessary() == name_.ToUpperIfNecessary(); +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_H_ diff --git a/bridge/core/dom/attribute_collection.h b/bridge/core/dom/attribute_collection.h new file mode 100644 index 0000000000..eed14da8ac --- /dev/null +++ b/bridge/core/dom/attribute_collection.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_COLLECTION_H_ +#define KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_COLLECTION_H_ + +#include +#include "attribute.h" +#include "foundation/macros.h" + +namespace kraken { + +const size_t kNotFound = UINT_MAX; + +template +class AttributeCollectionGeneric { + KRAKEN_STACK_ALLOCATED(); + + public: + using value_type = typename Container::value_type; + using iterator = value_type*; + + AttributeCollectionGeneric(Container& attributes) : attributes_(attributes) {} + + value_type& operator[](unsigned index) const { return at(index); } + value_type& at(unsigned index) const { + CHECK_LT(index, size()); + return begin()[index]; + } + + value_type* data() { return attributes_.data(); } + const value_type* data() const { return attributes_.data(); } + + iterator begin() const { return attributes_.data(); } + iterator end() const { return begin() + size(); } + + unsigned size() const { return attributes_.size(); } + bool IsEmpty() const { return !size(); } + + // Find() returns nullptr if the specified name is not found. + iterator Find(const AtomicString& name) const; + size_t FindIndex(const AtomicString& name) const; + + protected: + ContainerMemberType attributes_; +}; + +class AttributeArray { + KRAKEN_DISALLOW_NEW(); + + public: + using value_type = const Attribute; + + AttributeArray(const Attribute* array, unsigned size) : array_(array), size_(size) {} + + const Attribute* data() const { return array_; } + unsigned size() const { return size_; } + + private: + const Attribute* array_; + unsigned size_; +}; + +class AttributeCollection : public AttributeCollectionGeneric { + public: + AttributeCollection() : AttributeCollectionGeneric(AttributeArray(nullptr, 0)) {} + + AttributeCollection(const Attribute* array, unsigned size) + : AttributeCollectionGeneric(AttributeArray(array, size)) {} +}; + +using AttributeVector = std::vector; +class MutableAttributeCollection : public AttributeCollectionGeneric { + public: + explicit MutableAttributeCollection(AttributeVector& attributes) + : AttributeCollectionGeneric(attributes) {} + + // These functions do no error/duplicate checking. + void Append(const AtomicString&, const AtomicString& value); + void Remove(unsigned index); +}; + +inline void MutableAttributeCollection::Append(const AtomicString& name, const AtomicString& value) { + attributes_.emplace_back(name, value); +} + +inline void MutableAttributeCollection::Remove(unsigned index) { + attributes_.erase(attributes_.begin() + index); +} + +template +inline typename AttributeCollectionGeneric::iterator +AttributeCollectionGeneric::Find(const AtomicString& name) const { + size_t index = FindIndex(name); + return index != kNotFound ? &at(index) : nullptr; +} + +template +inline size_t AttributeCollectionGeneric::FindIndex(const AtomicString& name) const { + iterator end = this->end(); + size_t index = 0; + for (iterator it = begin(); it != end; ++it, ++index) { + if (it->GetName().Matches(name)) + return index; + } + return kNotFound; +} + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_COLLECTION_H_ diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index cfaa820f3f..61e52f8abd 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -5,6 +5,7 @@ #include "container_node.h" #include "bindings/qjs/garbage_collected.h" #include "document_fragment.h" +#include "node_traversal.h" namespace kraken { @@ -385,6 +386,30 @@ void ContainerNode::AppendChildCommon(Node& child) { SetLastChild(&child); } +void ContainerNode::NotifyNodeInserted(Node& root) { + NotifyNodeInsertedInternal(root); +} + +void ContainerNode::NotifyNodeInsertedInternal(Node& root) { + for (Node& node : NodeTraversal::InclusiveDescendantsOf(root)) { + // As an optimization we don't notify leaf nodes when when inserting + // into detached subtrees that are not in a shadow tree. + if (!isConnected() && !node.IsContainerNode()) + continue; + } +} + +void ContainerNode::NotifyNodeRemoved(Node& root) { + for (Node& node : NodeTraversal::InclusiveDescendantsOf(root)) { + // As an optimization we skip notifying Text nodes and other leaf nodes + // of removal when they're not in the Document tree and not in a shadow root + // since the virtual call to removedFrom is not needed. + if (!node.IsContainerNode() && !node.IsInTreeScope()) + continue; + node.RemovedFrom(*this); + } +} + void ContainerNode::Trace(GCVisitor* visitor) const { Node::Trace(visitor); } diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index f23997b44b..ca82bed511 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -74,6 +74,10 @@ class ContainerNode : public Node { void InsertBeforeCommon(Node& next_child, Node& new_child); void AppendChildCommon(Node& child); + void NotifyNodeInserted(Node&); + void NotifyNodeInsertedInternal(Node&); + void NotifyNodeRemoved(Node&); + inline bool IsChildTypeAllowed(const Node& child) const; inline bool IsHostIncludingInclusiveAncestorOfThis(const Node&, ExceptionState&) const; diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 2e82d6a3d5..eb430a1c8e 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -7,89 +7,31 @@ #define KRAKENBRIDGE_DOCUMENT_H #include "container_node.h" +#include "tree_scope.h" namespace kraken { // 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 Node { +class Document : public Node, TreeScope { DEFINE_WRAPPERTYPEINFO(); - public: using ImplType = Document*; + void IncrementNodeCount() { + node_count_++; + } + void DecrementNodeCount() { + assert(node_count_ > 0); + node_count_--; + } + int NodeCount() const { return node_count_; } + private: + int node_count_; }; -// void bindDocument(ExecutionContext* context); -// -// using TraverseHandler = std::function; -// -// void traverseNode(Node* node, TraverseHandler handler); -// -// class DocumentCookie { -// public: -// DocumentCookie() = default; -// -// std::string getCookie(); -// void setCookie(std::string& str); -// -// private: -// std::unordered_map cookiePairs; -//}; - -// class Document : public Node { -// public: -// static JSClassID classId; -// static Document* create(JSContext* ctx); -// static JSValue constructor(ExecutionContext* context); -// static JSValue prototype(ExecutionContext* context); -// explicit Document(); -// -// DEFINE_FUNCTION(createEvent); -// DEFINE_FUNCTION(createElement); -// DEFINE_FUNCTION(createTextNode); -// DEFINE_FUNCTION(createDocumentFragment); -// DEFINE_FUNCTION(createComment); -// DEFINE_FUNCTION(getElementById); -// DEFINE_FUNCTION(getElementsByTagName); -// DEFINE_FUNCTION(getElementsByClassName); -// -// DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); -// DEFINE_PROTOTYPE_READONLY_PROPERTY(all); -// DEFINE_PROTOTYPE_READONLY_PROPERTY(documentElement); -// DEFINE_PROTOTYPE_READONLY_PROPERTY(children); -// DEFINE_PROTOTYPE_READONLY_PROPERTY(head); -// -// DEFINE_PROTOTYPE_PROPERTY(cookie); -// DEFINE_PROTOTYPE_PROPERTY(body); -// -// JSValue getElementConstructor(ExecutionContext* context, const std::string& tagName); -// bool isCustomElement(const std::string& tagName); -// -// int32_t requestAnimationFrame(FrameCallback* frameCallback); -// void cancelAnimationFrame(uint32_t callbackId); -// void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; -// void dispose() const override; -// -// private: -// void removeElementById(JSAtom id, Element* element); -// void addElementById(JSAtom id, Element* element); -// Element* getDocumentElement(); -// std::unordered_map> m_elementMapById; -// Element* m_documentElement{nullptr}; -// std::unique_ptr m_cookie; -// -// ScriptAnimationController* m_scriptAnimationController; -// -// void defineElement(const std::string& tagName, Element* constructor); -// -// bool event_registered{false}; -// bool document_registered{false}; -// std::unordered_map elementConstructorMap; -//}; - } // namespace kraken #endif // KRAKENBRIDGE_DOCUMENT_H diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 80560ef0c5..8ee608bce7 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -4,6 +4,7 @@ */ #include "element.h" +#include "attribute.h" namespace kraken { @@ -13,21 +14,19 @@ Element::Element(Document* document, const AtomicString& tag_name, Node::Constru bool Element::hasAttribute(const AtomicString& name) const { if (!GetElementData()) return false; - AtomicString result = name.LowercaseIfNecessary(); - // SynchronizeAttributeHinted(local_name, hint); - // if (hint.IsNull()) { - // return false; - // } - // for (const Attribute& attribute : GetElementData()->Attributes()) { - // if (hint == attribute.LocalName()) - // return true; - // } + AtomicString result = name.ToLowerIfNecessary(); + for (const Attribute& attribute : GetElementData()->Attributes()) { + if (hint == attribute.LocalName()) + return true; + } return false; return false; } -const AtomicString& Element::getAttribute(const AtomicString&) const {} +const AtomicString& Element::getAttribute(const AtomicString&) const { +// GetElementData()-> +} void Element::setAttribute(const AtomicString& name, const AtomicString& value) { ExceptionState exception_state; diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 7be31117e2..241fd88b7c 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -40,7 +40,7 @@ class Element : public ContainerNode { AtomicString TagName() const { return tag_name_; } protected: - const ElementData* GetElementData() const { return element_data_.Get(); } + const ElementData* GetElementData() const { return &element_data_; } private: AtomicString tag_name_; diff --git a/bridge/core/dom/element_data.cc b/bridge/core/dom/element_data.cc index 5d839967b5..0a7de92931 100644 --- a/bridge/core/dom/element_data.cc +++ b/bridge/core/dom/element_data.cc @@ -3,3 +3,39 @@ */ #include "element_data.h" + +namespace kraken { + +ElementData::~ElementData() { + if (auto* unique_element_data = DynamicTo(this)) + unique_element_data->~UniqueElementData(); + else + To(this)->~ShareableElementData(); +} + +std::shared_ptr ElementData::MakeUniqueCopy() const { + if (auto* unique_element_data = DynamicTo(this)) + return std::make_shared(*unique_element_data); + return std::make_shared( + To(*this)); +} + +bool ElementData::IsEquivalent(const ElementData* other) const { + AttributeCollection attributes = Attributes(); + if (!other) + return attributes.IsEmpty(); + + AttributeCollection other_attributes = other->Attributes(); + if (attributes.size() != other_attributes.size()) + return false; + + for (const Attribute& attribute : attributes) { + const Attribute* other_attr = other_attributes.Find(attribute.GetName()); + if (!other_attr || attribute.Value() != other_attr->Value()) + return false; + } + return true; +} + + +} diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/element_data.h index 327021b4d7..b28f396940 100644 --- a/bridge/core/dom/element_data.h +++ b/bridge/core/dom/element_data.h @@ -6,17 +6,90 @@ #define KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_H_ #include "bindings/qjs/atomic_string.h" +#include "attribute_collection.h" +#include "foundation/casting.h" namespace kraken { +class UniqueElementData; + +// ElementData represents very common, but not necessarily unique to an element, +// data such as attributes, inline style, and parsed class names and ids. class ElementData { public: + AttributeCollection Attributes() const; + + ~ElementData(); + + bool IsEquivalent(const ElementData* other) const; + + protected: + uint32_t array_size; + private: + std::shared_ptr MakeUniqueCopy() const; + // mutable Member inline_style_; // mutable SpaceSplitString class_names_; // mutable AtomicString id_for_style_resolution_; }; +// SharableElementData is managed by ElementDataCache and is produced by +// the parser during page load for elements that have identical attributes. This +// is a memory optimization since it's very common for many elements to have +// duplicate sets of attributes (ex. the same classes). +class ShareableElementData final : public ElementData { + public: + static ShareableElementData* CreateWithAttributes(const std::vector&); + + explicit ShareableElementData(const std::vector&); + explicit ShareableElementData(const UniqueElementData&); + ~ShareableElementData(); + + AttributeCollection Attributes() const; + + Attribute attribute_array_[0]; +}; + +// UniqueElementData is created when an element needs to mutate its attributes +// or gains presentation attribute style (ex. width="10"). It does not need to +// be created to fill in values in the ElementData that are derived from +// attributes. For example populating the inline_style_ from the style attribute +// doesn't require a UniqueElementData as all elements with the same style +// attribute will have the same inline style. +class UniqueElementData final : public ElementData { + public: + ShareableElementData* MakeShareableCopy() const; + + MutableAttributeCollection Attributes(); + AttributeCollection Attributes() const; + + UniqueElementData(); + explicit UniqueElementData(const ShareableElementData&); + explicit UniqueElementData(const UniqueElementData&); + + AttributeVector attribute_vector_; +}; + +inline AttributeCollection ElementData::Attributes() const { + if (auto* unique_element_data = DynamicTo(this)) + return unique_element_data->Attributes(); + return To(this)->Attributes(); +} + +inline AttributeCollection ShareableElementData::Attributes() const { + return AttributeCollection(attribute_array_, array_size); +} + +inline AttributeCollection UniqueElementData::Attributes() const { + return AttributeCollection(attribute_vector_.data(), + attribute_vector_.size()); +} + +inline MutableAttributeCollection UniqueElementData::Attributes() { + return MutableAttributeCollection(attribute_vector_); +} + } // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_H_ diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index ec672408f4..0d33b38a12 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -158,6 +158,11 @@ bool Node::isEqualNode(Node* other, ExceptionState& exception_state) const { 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 @@ -262,6 +267,22 @@ Element* Node::ParentOrShadowHostElement() const { return DynamicTo(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::ParentOrShadowHostOrTemplateHostNode() const { auto* this_fragment = DynamicTo(this); if (this_fragment && this_fragment->IsTemplateContent()) diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index e1583c43f7..5dfcb0150b 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -10,6 +10,7 @@ #include "events/event_target.h" #include "foundation/macros.h" +#include "tree_scope.h" namespace kraken { @@ -21,7 +22,6 @@ const int kNodeCustomElementShift = 17; class Element; class Document; class DocumentFragment; -class TextNode; class ContainerNode; class NodeData; class NodeList; @@ -42,6 +42,7 @@ enum class CloneChildrenFlag { kSkip, kClone, kCloneWithShadows }; // https://dom.spec.whatwg.org/#interface-node class Node : public EventTarget { DEFINE_WRAPPERTYPEINFO(); + friend class TreeScope; public: enum NodeType { @@ -87,6 +88,7 @@ class Node : public EventTarget { 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; } AtomicString textContent(bool convert_brs_to_newlines = false) const; @@ -125,6 +127,23 @@ class Node : public EventTarget { 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); + // Knows about all kinds of hosts. ContainerNode* ParentOrShadowHostOrTemplateHostNode() const; @@ -147,13 +166,19 @@ class Node : public EventTarget { // Returns the document associated with this node. A Document node returns // itself. - Document& GetDocument() const { return *document_; } + Document& GetDocument() const { return GetTreeScope().GetDocument(); } + + 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. bool isConnected() const { return GetFlag(kIsConnectedFlag); } bool IsInDocumentTree() const { return isConnected(); } + bool IsInTreeScope() const { return GetFlag(static_cast(kIsConnectedFlag)); } bool IsDocumentTypeNode() const { return nodeType() == kDocumentTypeNode; } virtual bool ChildTypeAllowed(NodeType) const { return false; } @@ -259,6 +284,8 @@ class Node : public EventTarget { kCreateDocument = kCreateContainer | kIsConnectedFlag, }; + void SetTreeScope(TreeScope* scope) { tree_scope_ = scope; } + Node(Document*, ConstructionType); private: @@ -266,7 +293,7 @@ class Node : public EventTarget { Node* parent_or_shadow_host_node_; Node* previous_; Node* next_; - Document* document_; + TreeScope* tree_scope_; std::unique_ptr data_; }; diff --git a/bridge/core/dom/tree_scope.cc b/bridge/core/dom/tree_scope.cc new file mode 100644 index 0000000000..76ba007173 --- /dev/null +++ b/bridge/core/dom/tree_scope.cc @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "tree_scope.h" +#include "container_node.h" + +namespace kraken { + +TreeScope::TreeScope(ContainerNode& root_node, Document& document) : root_node_(&root_node), document_(&document) { + root_node.SetTreeScope(this); +} + +} // namespace kraken diff --git a/bridge/core/dom/tree_scope.h b/bridge/core/dom/tree_scope.h new file mode 100644 index 0000000000..03227212e1 --- /dev/null +++ b/bridge/core/dom/tree_scope.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ +#define KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ + +#include + +namespace kraken { + +class ContainerNode; +class Document; + +class TreeScope { + friend class Node; + + public: + Document& GetDocument() const { + assert(document_); + return *document_; + } + + protected: + explicit TreeScope(ContainerNode&, Document&); + + private: + ContainerNode* root_node_; + Document* document_; + TreeScope* parent_tree_scope_; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h index 2fd9abd9be..ec4ab6dbac 100644 --- a/bridge/foundation/macros.h +++ b/bridge/foundation/macros.h @@ -35,6 +35,11 @@ void* operator new(size_t) = delete; \ void* operator new(size_t, void*) = delete +#define KRAKEN_STACK_ALLOCATED() \ + private: \ + void* operator new(size_t) = delete; \ + void* operator new(size_t, void*) = delete + // KRAKEN_DISALLOW_NEW(): Cannot be allocated with new operators but can be a // part of object, a value object in collections or stack allocated. If it has // Members you need a trace method and the containing object needs to call that From a18bc0c4e497b7052b9ee0097a6704719881fa14 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Sun, 10 Apr 2022 15:34:59 +0000 Subject: [PATCH 068/498] Committing clang-format changes --- bridge/bindings/qjs/atomic_string.cc | 3 ++- bridge/bindings/qjs/atomic_string.h | 6 +----- bridge/core/dom/document.h | 5 ++--- bridge/core/dom/element.cc | 2 +- bridge/core/dom/element_data.cc | 6 ++---- bridge/core/dom/element_data.h | 5 ++--- bridge/core/dom/node.cc | 2 +- bridge/foundation/macros.h | 6 +++--- 8 files changed, 14 insertions(+), 21 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index 2b0489f39d..7245aca238 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -22,7 +22,8 @@ AtomicString AtomicString::From(JSContext* ctx, NativeString* native_string) { namespace { AtomicString::StringKind GetStringKind(const std::string& string) { - AtomicString::StringKind predictKind = std::islower(string[0]) ? AtomicString::StringKind::kIsLowerCase : AtomicString::StringKind::kIsUpperCase; + 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; diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 4fe3b774b0..605437bfc3 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -20,11 +20,7 @@ namespace kraken { // two String instances because we just check string storage identity. class AtomicString { public: - enum class StringKind { - kIsLowerCase, - kIsUpperCase, - kIsMixed - }; + enum class StringKind { kIsLowerCase, kIsUpperCase, kIsMixed }; static AtomicString Empty(JSContext* ctx); static AtomicString From(JSContext* ctx, NativeString* native_string); diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index eb430a1c8e..80c1963bbf 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -16,12 +16,11 @@ namespace kraken { // (typically, HTML) resource. class Document : public Node, TreeScope { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = Document*; - void IncrementNodeCount() { - node_count_++; - } + void IncrementNodeCount() { node_count_++; } void DecrementNodeCount() { assert(node_count_ > 0); node_count_--; diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 8ee608bce7..1260ece19e 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -25,7 +25,7 @@ bool Element::hasAttribute(const AtomicString& name) const { } const AtomicString& Element::getAttribute(const AtomicString&) const { -// GetElementData()-> + // GetElementData()-> } void Element::setAttribute(const AtomicString& name, const AtomicString& value) { diff --git a/bridge/core/dom/element_data.cc b/bridge/core/dom/element_data.cc index 0a7de92931..d53084e8fa 100644 --- a/bridge/core/dom/element_data.cc +++ b/bridge/core/dom/element_data.cc @@ -16,8 +16,7 @@ ElementData::~ElementData() { std::shared_ptr ElementData::MakeUniqueCopy() const { if (auto* unique_element_data = DynamicTo(this)) return std::make_shared(*unique_element_data); - return std::make_shared( - To(*this)); + return std::make_shared(To(*this)); } bool ElementData::IsEquivalent(const ElementData* other) const { @@ -37,5 +36,4 @@ bool ElementData::IsEquivalent(const ElementData* other) const { return true; } - -} +} // namespace kraken diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/element_data.h index b28f396940..46464fde26 100644 --- a/bridge/core/dom/element_data.h +++ b/bridge/core/dom/element_data.h @@ -5,8 +5,8 @@ #ifndef KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_H_ #define KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_H_ -#include "bindings/qjs/atomic_string.h" #include "attribute_collection.h" +#include "bindings/qjs/atomic_string.h" #include "foundation/casting.h" namespace kraken { @@ -82,8 +82,7 @@ inline AttributeCollection ShareableElementData::Attributes() const { } inline AttributeCollection UniqueElementData::Attributes() const { - return AttributeCollection(attribute_vector_.data(), - attribute_vector_.size()); + return AttributeCollection(attribute_vector_.data(), attribute_vector_.size()); } inline MutableAttributeCollection UniqueElementData::Attributes() { diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 0d33b38a12..554e9e5c29 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -158,7 +158,7 @@ bool Node::isEqualNode(Node* other, ExceptionState& exception_state) const { return true; } -bool Node::isEqualNode(Node* other) const{ +bool Node::isEqualNode(Node* other) const { ExceptionState exception_state; return isEqualNode(other, exception_state); } diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h index ec4ab6dbac..35baeddaeb 100644 --- a/bridge/foundation/macros.h +++ b/bridge/foundation/macros.h @@ -35,9 +35,9 @@ void* operator new(size_t) = delete; \ void* operator new(size_t, void*) = delete -#define KRAKEN_STACK_ALLOCATED() \ - private: \ - void* operator new(size_t) = delete; \ +#define KRAKEN_STACK_ALLOCATED() \ + private: \ + void* operator new(size_t) = delete; \ void* operator new(size_t, void*) = delete // KRAKEN_DISALLOW_NEW(): Cannot be allocated with new operators but can be a From edd4cf62216cb9ae668832525997fa029b85ad60 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Mon, 11 Apr 2022 21:37:02 +0800 Subject: [PATCH 069/498] feat: add legacy element implts. feat: add native value converter traits refactor: refactor binding object. --- bridge/CMakeLists.txt | 18 ++- bridge/bindings/qjs/atomic_string.h | 9 ++ bridge/bindings/qjs/qjs_interface_bridge.cc | 10 ++ bridge/bindings/qjs/qjs_interface_bridge.h | 5 - bridge/bindings/qjs/script_value.cc | 41 ++++++ bridge/bindings/qjs/script_value.h | 2 + bridge/bindings/qjs/script_wrappable.h | 1 + bridge/core/dom/binding_object.cc | 28 ++++ bridge/core/dom/binding_object.h | 57 ++++++++ bridge/core/dom/comment.h | 10 -- bridge/core/dom/element.cc | 49 +++++-- bridge/core/dom/element.h | 28 +++- bridge/core/dom/events/event_target.cc | 16 +- bridge/core/dom/events/event_target.h | 8 +- .../core/dom/legacy/bounding_client_rect.cc | 28 ++++ .../core/dom/legacy/bounding_client_rect.d.ts | 10 ++ bridge/core/dom/legacy/bounding_client_rect.h | 48 ++++++ bridge/core/dom/legacy/element_attributes.cc | 87 +++++++++++ bridge/core/dom/legacy/element_attributes.h | 44 ++++++ bridge/core/dom/legacy/space_split_string.cc | 59 ++++++++ bridge/core/dom/legacy/space_split_string.h | 30 ++++ bridge/core/dom/{ => ng}/attribute.h | 0 .../core/dom/{ => ng}/attribute_collection.h | 0 bridge/core/dom/{ => ng}/element_data.cc | 0 bridge/core/dom/{ => ng}/element_data.h | 0 .../core/dom/{ => ng}/space_split_string.cc | 0 bridge/core/dom/{ => ng}/space_split_string.h | 0 bridge/foundation/native_string.cc | 5 + bridge/foundation/native_string.h | 3 + bridge/foundation/native_type.h | 55 +++++++ bridge/foundation/native_value.cc | 137 ++--------------- bridge/foundation/native_value.h | 9 +- bridge/foundation/native_value_converter.cc | 138 ++++++++++++++++++ bridge/foundation/native_value_converter.h | 133 +++++++++++++++++ bridge/foundation/ui_command_buffer.cc | 10 +- bridge/foundation/ui_command_buffer.h | 20 +-- 36 files changed, 902 insertions(+), 196 deletions(-) create mode 100644 bridge/core/dom/binding_object.cc create mode 100644 bridge/core/dom/binding_object.h create mode 100644 bridge/core/dom/legacy/bounding_client_rect.cc create mode 100644 bridge/core/dom/legacy/bounding_client_rect.d.ts create mode 100644 bridge/core/dom/legacy/bounding_client_rect.h create mode 100644 bridge/core/dom/legacy/element_attributes.cc create mode 100644 bridge/core/dom/legacy/element_attributes.h create mode 100644 bridge/core/dom/legacy/space_split_string.cc create mode 100644 bridge/core/dom/legacy/space_split_string.h rename bridge/core/dom/{ => ng}/attribute.h (100%) rename bridge/core/dom/{ => ng}/attribute_collection.h (100%) rename bridge/core/dom/{ => ng}/element_data.cc (100%) rename bridge/core/dom/{ => ng}/element_data.h (100%) rename bridge/core/dom/{ => ng}/space_split_string.cc (100%) rename bridge/core/dom/{ => ng}/space_split_string.h (100%) create mode 100644 bridge/foundation/native_type.h create mode 100644 bridge/foundation/native_value_converter.cc create mode 100644 bridge/foundation/native_value_converter.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 75a4cf502b..f352d4b65c 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -96,6 +96,8 @@ list(APPEND BRIDGE_SOURCE foundation/task_queue.h foundation/native_value.cc foundation/native_value.h + foundation/native_type.h + foundation/native_value_converter.h foundation/casting.h foundation/ui_command_buffer.cc foundation/ui_command_buffer.h @@ -283,6 +285,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/events/event_listener_map.cc core/dom/events/event_target_impl.cc core/dom/events/event_target_impl.h + core/dom/binding_object.h + core/dom/binding_object.cc core/dom/node.cc core/dom/node.h core/dom/attr.cc @@ -294,16 +298,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/character_data.h core/dom/text.cc core/dom/text.h - core/dom/attribute.h - core/dom/attribute_collection.h core/dom/tree_scope.cc core/dom/tree_scope.h core/dom/element.cc core/dom/element.h - core/dom/element_data.cc - core/dom/element_data.h - core/dom/space_split_string.cc - core/dom/space_split_string.h core/dom/document.cc core/dom/document.h core/dom/node_data.cc @@ -324,6 +322,14 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/html/html_collection.h core/html/html_element.cc core/html/html_element.h + # Legacy implements, should remove them in the future. + core/dom/legacy/space_split_string.cc + core/dom/legacy/space_split_string.h + core/dom/legacy/element_attributes.cc + core/dom/legacy/element_attributes.h + core/dom/legacy/bounding_client_rect.cc + core/dom/legacy/bounding_client_rect.h + # core/dom/character_data.cc # core/dom/character_data.h # core/dom/comment.cc diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 605437bfc3..6c78a22abd 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -7,6 +7,7 @@ #include #include +#include #include "foundation/macros.h" #include "foundation/native_string.h" #include "native_string_utils.h" @@ -22,6 +23,14 @@ class AtomicString { public: enum class StringKind { kIsLowerCase, kIsUpperCase, kIsMixed }; + struct KeyHasher + { + std::size_t operator()(const AtomicString& k) const + { + return k.atom_; + } + }; + static AtomicString Empty(JSContext* ctx); static AtomicString From(JSContext* ctx, NativeString* native_string); diff --git a/bridge/bindings/qjs/qjs_interface_bridge.cc b/bridge/bindings/qjs/qjs_interface_bridge.cc index caa7296332..eccaa227b1 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.cc +++ b/bridge/bindings/qjs/qjs_interface_bridge.cc @@ -4,3 +4,13 @@ */ #include "qjs_interface_bridge.h" +#include "core/executing_context.h" + +namespace kraken { + +template +bool QJSInterfaceBridge::HasInstance(ExecutingContext* context, JSValue value) { + return JS_IsInstanceOf(context->ctx(), value, context->contextData()->prototypeForType(QJST::GetWrapperTypeInfo())); +} + +} diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index c61c7fdf62..c032ab1f63 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -20,11 +20,6 @@ class QJSInterfaceBridge { static bool HasInstance(ExecutingContext* context, JSValue value); }; -template -bool QJSInterfaceBridge::HasInstance(ExecutingContext* context, JSValue value) { - return false; -} - } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 1be5317acd..daeac112e3 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -5,9 +5,11 @@ #include "script_value.h" #include +#include "foundation/native_value_converter.h" #include "core/executing_context.h" #include "native_string_utils.h" #include "qjs_engine_patch.h" +#include "qjs_bounding_client_rect.h" namespace kraken { @@ -79,6 +81,45 @@ AtomicString ScriptValue::ToString() const { return AtomicString(ctx_, value_); } +NativeValue ScriptValue::ToNative() const { + if (JS_IsNull(value_) || JS_IsUndefined(value_)) { + return Native_NewNull(); + } else if (JS_IsBool(value_)) { + return Native_NewBool(JS_ToBool(ctx_, value_)); + } else if (JS_IsNumber(value_)) { + uint32_t tag = JS_VALUE_GET_TAG(value_); + if (JS_TAG_IS_FLOAT64(tag)) { + double v; + JS_ToFloat64(ctx_, &v, value_); + return Native_NewFloat64(v); + } else { + int32_t v; + JS_ToInt32(ctx_, &v, value_); + return Native_NewInt64(v); + } + } else if (JS_IsString(value_)) { + // NativeString owned by NativeValue will be freed by users. + NativeString* string = this->ToString().ToNativeString().release(); + return NativeValueConverter::ToNativeValue(string); + } else if (JS_IsFunction(ctx_, value_)) { + auto* context = static_cast(JS_GetContextOpaque(ctx_)); + auto* functionContext = new NativeFunctionContext{context, value_}; + return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); + } else if (JS_IsObject(value_)) { + +// auto* context = static_cast(JS_GetContextOpaque(ctx_)); + // auto* context = static_cast(JS_GetContextOpaque(ctx)); + // if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { + // auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); + // return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); + // } + + // return Native_NewJSON(context, value); + } + + return Native_NewNull(); +} + bool ScriptValue::IsException() { return JS_IsException(value_); } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 5a27f086bf..6a456079e3 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -19,6 +19,7 @@ namespace kraken { class ExecutingContext; class WrapperTypeInfo; +class NativeValue; // ScriptValue is a stack allocate only QuickJS JSValue wrapper ScriptValuewhich hold all information to hide out // QuickJS running details. @@ -53,6 +54,7 @@ class ScriptValue final { // Create a new ScriptValue from call JSON.stringify to current value. ScriptValue ToJSONStringify(ExceptionState* exception) const; AtomicString ToString() const; + NativeValue ToNative() const; bool IsException(); bool IsEmpty(); diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 80f3373600..cbfa2545fd 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -49,6 +49,7 @@ class ScriptWrappable : public GarbageCollected { return static_cast(JS_GetContextOpaque(ctx_)); }; FORCE_INLINE JSContext* ctx() const { return ctx_; } + FORCE_INLINE JSRuntime* runtime() const { return runtime_; } private: bool wrapped_{false}; diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc new file mode 100644 index 0000000000..e7bca01ca3 --- /dev/null +++ b/bridge/core/dom/binding_object.cc @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "binding_object.h" + +namespace kraken { + +void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, NativeValue* argv) { + NativeValue result = binding_object->binding_target_->HandleCallFromDartSide(method, argc, argv); + if (return_value != nullptr) *return_value = result; +} + +BindingObject::BindingObject(ExecutingContext* context) { + +} + +NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* args) const {} + +NativeValue BindingObject::GetBindingProperty(const AtomicString& prop) const { + return NativeValue(); +} + +NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, NativeValue value) const { + return NativeValue(); +} + +} // namespace kraken diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h new file mode 100644 index 0000000000..dc8fd22225 --- /dev/null +++ b/bridge/core/dom/binding_object.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ +#define KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ + +#include +#include "bindings/qjs/atomic_string.h" +#include "foundation/native_value.h" + +namespace kraken { + +class BindingObject; +class NativeBindingObject; + +using InvokeBindingsMethodsFromNative = + void (*)(NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, NativeValue* argv); + +using InvokeBindingMethodsFromDart = + void (*)(NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, NativeValue* argv); + +struct NativeBindingObject { + NativeBindingObject() = delete; + explicit NativeBindingObject(BindingObject* target) + : binding_target_(target), invoke_binding_methods_from_dart(HandleCallFromDartSide) {}; + + static void HandleCallFromDartSide(NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, NativeValue* argv); + + BindingObject* binding_target_{nullptr}; +#if UNIT_TEST + InvokeBindingMethod invokeBindingMethod{reinterpret_cast(TEST_invokeBindingMethod)}; +#else + InvokeBindingMethodsFromDart invoke_binding_methods_from_dart{nullptr}; + InvokeBindingsMethodsFromNative invoke_bindings_methods_from_native{nullptr}; +}; + +class BindingObject { + public: + BindingObject() = delete; + explicit BindingObject(ExecutingContext* context); + + // Handle call from dart side. + virtual NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const = 0; + // Invoke methods which implemented at dart side. + NativeValue InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* args) const; + NativeValue GetBindingProperty(const AtomicString& prop) const; + NativeValue SetBindingProperty(const AtomicString& prop, NativeValue value) const; + + private: + NativeBindingObject binding_object_{this}; +}; + + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ diff --git a/bridge/core/dom/comment.h b/bridge/core/dom/comment.h index 0cc079ae75..0a7e88eddf 100644 --- a/bridge/core/dom/comment.h +++ b/bridge/core/dom/comment.h @@ -42,16 +42,6 @@ auto commentCreator = const WrapperTypeInfo commentTypeInfo = {"Comment", &nodeTypeInfo, commentCreator}; -// -// class CommentInstance : public NodeInstance { -// public: -// CommentInstance() = delete; -// explicit CommentInstance(Comment* comment); -// -// private: -// friend Comment; -//}; - } // namespace kraken #endif // KRAKENBRIDGE_COMMENT_H diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 1260ece19e..4b69c3fde6 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -4,28 +4,18 @@ */ #include "element.h" -#include "attribute.h" namespace kraken { Element::Element(Document* document, const AtomicString& tag_name, Node::ConstructionType construction_type) - : ContainerNode(document, construction_type) {} - -bool Element::hasAttribute(const AtomicString& name) const { - if (!GetElementData()) - return false; - AtomicString result = name.ToLowerIfNecessary(); - for (const Attribute& attribute : GetElementData()->Attributes()) { - if (hint == attribute.LocalName()) - return true; - } - return false; + : ContainerNode(document, construction_type), attributes_(MakeGarbageCollected(this)) {} - return false; +bool Element::hasAttribute(const AtomicString& name, ExceptionState& exception_state) const { + return attributes_->HasAttribute(name); } -const AtomicString& Element::getAttribute(const AtomicString&) const { - // GetElementData()-> +AtomicString Element::getAttribute(const AtomicString& name, ExceptionState& exception_state) const { + return attributes_->GetAttribute(name); } void Element::setAttribute(const AtomicString& name, const AtomicString& value) { @@ -33,6 +23,33 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value) return setAttribute(name, value, exception_state); } -void Element::setAttribute(const AtomicString&, const AtomicString& value, ExceptionState&) {} +void Element::setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { + if (attributes_->HasAttribute(name)) { + AtomicString&& oldAttribute = attributes_->GetAttribute(name); + if (!attributes_->SetAttribute(name, value, exception_state)) { + return; + }; + _didModifyAttribute(name, oldAttribute, value); + } else { + if (!attributes_->SetAttribute(name, value, exception_state)) { + return; + }; + _didModifyAttribute(name, AtomicString::Empty(ctx()), value); + } + + std::unique_ptr args_01 = name.ToNativeString(); + std::unique_ptr args_02 = value.ToNativeString(); + + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), static_cast(UICommand::setAttribute), + args_01, args_02, nullptr); +} + +void Element::removeAttribute(const AtomicString& name, ExceptionState& exception_state) { + attributes_->RemoveAttribute(name); +} + +BoundingClientRect* Element::getBoundingClientRect() { + return nullptr; +} } // namespace kraken diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 241fd88b7c..35c4d7908c 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -8,7 +8,8 @@ #include "bindings/qjs/garbage_collected.h" #include "container_node.h" -#include "element_data.h" +#include "legacy/element_attributes.h" +#include "legacy/bounding_client_rect.h" namespace kraken { @@ -29,22 +30,37 @@ class Element : public ContainerNode { public: Element(Document* document, const AtomicString& tag_name, ConstructionType = kCreateElement); - bool hasAttribute(const AtomicString&) const; - const AtomicString& getAttribute(const AtomicString&) const; + bool hasAttribute(const AtomicString&, ExceptionState& exception_state) const; + AtomicString getAttribute(const AtomicString&, ExceptionState& exception_state) const; // 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(); + +// static JSValue getBoundingClientRect(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); + AtomicString TagName() const { return tag_name_; } protected: - const ElementData* GetElementData() const { return &element_data_; } - private: + 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); + + ElementAttributes* attributes_{nullptr}; AtomicString tag_name_; - ElementData element_data_; }; } // namespace kraken diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 871eef006a..1f0d0fdc8c 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -17,6 +17,8 @@ namespace kraken { +static std::atomic global_event_target_id{0}; + Event::PassiveMode EventPassiveMode(const RegisteredEventListener& event_listener) { if (!event_listener.Passive()) { return Event::PassiveMode::kNotPassiveDefault; @@ -27,12 +29,9 @@ Event::PassiveMode EventPassiveMode(const RegisteredEventListener& event_listene // EventTargetData EventTargetData::EventTargetData() {} -EventTargetData::~EventTargetData() { - KRAKEN_LOG(VERBOSE) << "DISPOSE"; -} +EventTargetData::~EventTargetData() {} void EventTargetData::Trace(GCVisitor* visitor) const { - KRAKEN_LOG(VERBOSE) << "TRACE"; event_listener_map.Trace(visitor); } @@ -40,7 +39,10 @@ EventTarget* EventTarget::Create(ExecutingContext* context, ExceptionState& exce return MakeGarbageCollected(context); } -EventTarget::EventTarget(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} +EventTarget::EventTarget(ExecutingContext* context) + : BindingObject(context), + ScriptWrappable(context->ctx()), + event_target_id_(global_event_target_id.fetch_add(std::memory_order_relaxed)) {} bool EventTarget::addEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, @@ -190,6 +192,10 @@ DispatchEventResult EventTarget::DispatchEventInternal(Event& event, ExceptionSt return dispatch_result; } +NativeValue EventTarget::HandleCallFromDartSide(const NativeString* method, int32_t argc, const NativeValue* argv) { + +} + const char* EventTarget::GetHumanReadableName() const { return "EventTarget"; } diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 40363c778c..1fdd2e7610 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -11,6 +11,7 @@ #include "bindings/qjs/script_wrappable.h" #include "event_listener_map.h" #include "foundation/native_string.h" +#include "core/dom/binding_object.h" #if UNIT_TEST void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); @@ -77,7 +78,7 @@ class EventTargetData final { // 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 { +class EventTarget : public ScriptWrappable, BindingObject { DEFINE_WRAPPERTYPEINFO(); public: @@ -112,6 +113,8 @@ class EventTarget : public ScriptWrappable { static DispatchEventResult GetDispatchEventResult(const Event&); + int32_t eventTargetId() const { return event_target_id_; } + virtual bool IsWindowOrWorkerGlobalScope() const { return false; } protected: @@ -129,9 +132,12 @@ class EventTarget : public ScriptWrappable { virtual EventTargetData* GetEventTargetData() = 0; virtual EventTargetData& EnsureEventTargetData() = 0; + NativeValue HandleCallFromDartSide(const NativeString* method, int32_t argc, const NativeValue *argv) override; + const char* GetHumanReadableName() const override; private: + int32_t event_target_id_; bool FireEventListeners(Event&, EventTargetData*, EventListenerVector&, ExceptionState&); }; 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..846c307bd8 --- /dev/null +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "bounding_client_rect.h" +#include "core/executing_context.h" + +namespace kraken { + +BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, + NativeBoundingClientRect* native_bounding_client_rect) { + return MakeGarbageCollected(context, native_bounding_client_rect); +} + +BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBoundingClientRect* nativeBoundingClientRect) + : ScriptWrappable(context->ctx()), + x_(nativeBoundingClientRect->x), + y_(nativeBoundingClientRect->y), + width_(nativeBoundingClientRect->width), + height_(nativeBoundingClientRect->height), + top_(nativeBoundingClientRect->top), + right_(nativeBoundingClientRect->right), + left_(nativeBoundingClientRect->left), + bottom_(nativeBoundingClientRect->bottom) {} + +void BoundingClientRect::Trace(GCVisitor* visitor) const {} + +} // namespace kraken diff --git a/bridge/core/dom/legacy/bounding_client_rect.d.ts b/bridge/core/dom/legacy/bounding_client_rect.d.ts new file mode 100644 index 0000000000..8262309a13 --- /dev/null +++ b/bridge/core/dom/legacy/bounding_client_rect.d.ts @@ -0,0 +1,10 @@ +interface BoundingClientRect { + readonly x: double; + readonly y: double; + readonly width: double; + readonly height: double; + readonly top: double; + readonly right: double; + readonly bottom: double; + readonly left: double; +} 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..eaa006be37 --- /dev/null +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ +#define KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ + +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +class ExecutingContext; + +struct NativeBoundingClientRect { + double x; + double y; + double width; + double height; + double top; + double right; + double bottom; + double left; +}; + +class BoundingClientRect : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + public: + BoundingClientRect() = delete; + static BoundingClientRect* Create(ExecutingContext* context, NativeBoundingClientRect* native_bounding_client_rect); + explicit BoundingClientRect(ExecutingContext* context, NativeBoundingClientRect* nativeBoundingClientRect); + + FORCE_INLINE const char* GetHumanReadableName() const override { return "BoundingClientRect"; } + void Trace(GCVisitor* visitor) const override; + + private: + double x_; + double y_; + double width_; + double height_; + double top_; + double right_; + double bottom_; + double left_; +}; + +} + +#endif // KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc new file mode 100644 index 0000000000..6b1ce1d68f --- /dev/null +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "bindings/qjs/exception_state.h" +#include "element_attributes.h" +#include "built_in_string.h" +#include "core/dom/element.h" + +namespace kraken { + +static inline bool IsNumberIndex(const std::string& name) { + if (name.empty()) + return false; + char f = name[0]; + return f >= '0' && f <= '9'; +} + +ElementAttributes::ElementAttributes(Element* element): ScriptWrappable(element->ctx()) {} + +AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { + bool numberIndex = IsNumberIndex(name.ToStdString()); + + if (numberIndex) { + AtomicString::Empty(ctx()); + } + + return attributes_[name]; +} + +bool ElementAttributes::SetAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { + bool numberIndex = IsNumberIndex(name.ToStdString()); + + if (numberIndex) { + exception_state.ThrowException(ctx(), ErrorType::TypeError, "Failed to execute 'setAttribute' on 'Element': '" + name.ToStdString() + "' is not a valid attribute name."); + return false; + } + + if (name == built_in_string::kclass) { + std::string v = value.ToStdString(); + class_name_->set(v); + } + + attributes_[name] = value; + + return true; +} + +bool ElementAttributes::HasAttribute(const AtomicString& name) { + bool numberIndex = IsNumberIndex(name.ToStdString()); + + if (numberIndex) { + return false; + } + + return attributes_.count(name) > 0; +} + +void ElementAttributes::RemoveAttribute(const AtomicString& name) { + attributes_.erase(name); +} + +void ElementAttributes::CopyWith(ElementAttributes* attributes) { + for (auto& attr : attributes->attributes_) { + attributes_[attr.first] = attr.second; + } +} + +std::shared_ptr ElementAttributes::ClassName() { + return class_name_; +} + +std::string ElementAttributes::ToString() { + std::string s; + + for (auto& attr : attributes_) { + s += attr.first.ToStdString() + "="; + s += "\"" + attr.second.ToStdString() + "\""; + } + + return s; +} + +void ElementAttributes::Trace(GCVisitor* visitor) const { +} + +} diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h new file mode 100644 index 0000000000..4cc7f4d1b3 --- /dev/null +++ b/bridge/core/dom/legacy/element_attributes.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ +#define KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ + +#include +#include "space_split_string.h" +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/script_wrappable.h" + +namespace kraken { + +class ExceptionState; +class Element; + +// TODO: refactor for better W3C standard support and higher performance. +class ElementAttributes : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + public: + + ElementAttributes(Element) = delete; + ElementAttributes(Element* element); + FORCE_INLINE const char* GetHumanReadableName() const override { return "ElementAttributes"; } + void Trace(GCVisitor* visitor) const override; + + AtomicString GetAttribute(const AtomicString& name); + bool SetAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state); + bool HasAttribute(const AtomicString& name); + void RemoveAttribute(const AtomicString& name); + void CopyWith(ElementAttributes* attributes); + std::shared_ptr ClassName(); + std::string ToString(); + + private: + std::unordered_map attributes_; + std::shared_ptr class_name_{std::make_shared("")}; +}; + + +} + +#endif // KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ diff --git a/bridge/core/dom/legacy/space_split_string.cc b/bridge/core/dom/legacy/space_split_string.cc new file mode 100644 index 0000000000..7bbd504731 --- /dev/null +++ b/bridge/core/dom/legacy/space_split_string.cc @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "space_split_string.h" + +namespace kraken { + + +void SpaceSplitString::set(std::string& string) { + size_t pos = 0; + std::string token; + std::string s = string; + while ((pos = s.find(delimiter_)) != std::string::npos) { + token = s.substr(0, pos); + sz_data_.push_back(token); + s.erase(0, pos + delimiter_.length()); + } + sz_data_.push_back(s); +} + +bool SpaceSplitString::contains(std::string& string) { + for (std::string& s : sz_data_) { + 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(delimiter_)) != std::string::npos) { + token = s.substr(0, pos); + szData.push_back(token); + s.erase(0, pos + delimiter_.length()); + } + szData.push_back(s); + + bool flag = true; + for (std::string& str : szData) { + bool isContains = false; + for (std::string& data : sz_data_) { + if (data == str) { + isContains = true; + break; + } + } + flag &= isContains; + } + + return flag; +} + + +} 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..17ec653dd5 --- /dev/null +++ b/bridge/core/dom/legacy/space_split_string.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ +#define KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ + +#include +#include + +namespace kraken { + +class SpaceSplitString { + public: + SpaceSplitString() = default; + explicit SpaceSplitString(std::string string) { set(string); } + + void set(std::string& string); + bool contains(std::string& string); + bool containsAll(std::string s); + + private: + static std::string delimiter_; + std::vector sz_data_; +}; + + +} + +#endif // KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ diff --git a/bridge/core/dom/attribute.h b/bridge/core/dom/ng/attribute.h similarity index 100% rename from bridge/core/dom/attribute.h rename to bridge/core/dom/ng/attribute.h diff --git a/bridge/core/dom/attribute_collection.h b/bridge/core/dom/ng/attribute_collection.h similarity index 100% rename from bridge/core/dom/attribute_collection.h rename to bridge/core/dom/ng/attribute_collection.h diff --git a/bridge/core/dom/element_data.cc b/bridge/core/dom/ng/element_data.cc similarity index 100% rename from bridge/core/dom/element_data.cc rename to bridge/core/dom/ng/element_data.cc diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/ng/element_data.h similarity index 100% rename from bridge/core/dom/element_data.h rename to bridge/core/dom/ng/element_data.h diff --git a/bridge/core/dom/space_split_string.cc b/bridge/core/dom/ng/space_split_string.cc similarity index 100% rename from bridge/core/dom/space_split_string.cc rename to bridge/core/dom/ng/space_split_string.cc diff --git a/bridge/core/dom/space_split_string.h b/bridge/core/dom/ng/space_split_string.h similarity index 100% rename from bridge/core/dom/space_split_string.h rename to bridge/core/dom/ng/space_split_string.h diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc index e503fb59d8..e89cf89300 100644 --- a/bridge/foundation/native_string.cc +++ b/bridge/foundation/native_string.cc @@ -13,6 +13,11 @@ NativeString::NativeString(const uint16_t* string, uint32_t length) : length_(le memcpy((void*)string_, string, length * sizeof(uint16_t)); } +NativeString::NativeString(const NativeString* source): length_(source->length()) { + string_ = static_cast(malloc(source->length() * sizeof(uint16_t))); + memcpy((void*)string_, source->string_, source->length() * sizeof(u_int16_t)); +} + NativeString::~NativeString() { delete[] string_; } diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h index be5f24c5bb..024a15c05b 100644 --- a/bridge/foundation/native_string.h +++ b/bridge/foundation/native_string.h @@ -10,10 +10,13 @@ #include #include +#include "foundation/macros.h" + namespace kraken { struct NativeString { NativeString(const uint16_t* string, uint32_t length); + NativeString(const NativeString* source); ~NativeString(); inline const uint16_t* string() const { return string_; } diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h new file mode 100644 index 0000000000..edcd3a6624 --- /dev/null +++ b/bridge/foundation/native_type.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ +#define KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ + +#include +#include "foundation/native_string.h" +#include "bindings/qjs/script_value.h" +#include "bindings/qjs/qjs_function.h" + +namespace kraken { + +struct NativeTypeBase { + using ImplType = void; +}; + +template +struct NativeTypeBaseHelper { + using ImplType = T; +}; + +// Null +struct NativeTypeNull final : public NativeTypeBaseHelper {}; + +// Bool +struct NativeTypeBool final : public NativeTypeBaseHelper {}; + +// String +struct NativeTypeString final : public NativeTypeBaseHelper {}; + +// Int64 +struct NativeTypeInt64 final : public NativeTypeBaseHelper {}; + +// Double +struct NativeTypeDouble final : public NativeTypeBaseHelper {}; + +// JSON +struct NativeTypeJSON final : public NativeTypeBaseHelper {}; + +// Pointer +template +struct NativeTypePointer final : public NativeTypeBaseHelper {}; + +// Sync function +struct NativeTypeFunction final : public NativeTypeBaseHelper> {}; + +// Async function +struct NativeTypeAsyncFunction final : public NativeTypeBaseHelper> {}; + +} + +#endif // KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 91df84d767..b8d2296fac 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -5,13 +5,11 @@ #include "native_value.h" #include "bindings/qjs/qjs_engine_patch.h" +#include "bindings/qjs/script_value.h" #include "core/executing_context.h" namespace kraken { -#define AnonymousFunctionCallPreFix "_anonymous_fn_" -#define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" - NativeValue Native_NewNull() { return (NativeValue){0, .u = {.int64 = 0}, NativeTag::TAG_NULL}; } @@ -50,7 +48,7 @@ NativeValue Native_NewBool(bool value) { }; } -NativeValue Native_NewInt32(int32_t value) { +NativeValue Native_NewInt64(int64_t value) { return (NativeValue){ 0, .u = {.int64 = value}, @@ -58,46 +56,23 @@ NativeValue Native_NewInt32(int32_t value) { }; } -NativeValue Native_NewJSON(ExecutingContext* context, JSValue& value) { - JSValue stringifiedValue = JS_JSONStringify(context->ctx(), value, JS_UNDEFINED, JS_UNDEFINED); - if (JS_IsException(stringifiedValue)) +NativeValue Native_NewJSON(const ScriptValue& value) { + ExceptionState exception_state; + ScriptValue json = value.ToJSONStringify(&exception_state); + if (exception_state.HasException()) { return Native_NewNull(); + } - // NativeString owned by NativeValue will be freed by users. - NativeString* string = jsValueToNativeString(context->ctx(), stringifiedValue).release(); + AtomicString str = json.ToString(); + auto native_string = str.ToNativeString(); NativeValue result = (NativeValue){ 0, - .u = {.ptr = static_cast(string)}, + .u = {.ptr = static_cast(native_string.release())}, 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(); @@ -112,7 +87,7 @@ NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { } else { int32_t v; JS_ToInt32(ctx, &v, value); - return Native_NewInt32(v); + return Native_NewInt64(v); } } else if (JS_IsString(value)) { // NativeString owned by NativeValue will be freed by users. @@ -128,7 +103,6 @@ NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { // auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); // return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); // } - // return Native_NewJSON(context, value); } @@ -146,92 +120,6 @@ NativeFunctionContext::~NativeFunctionContext() { 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->callNativeMethods(call_params.c_str(), argc, arguments); - // delete[] arguments; - // return returnValue; - return JS_NULL; -} - -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->contextId() != contextId) - // return; - // - // auto* context = promiseContext->context; - // - // if (nativeValue != nullptr) { - // JSValue value = nativeValueToJSValue(promiseContext->context, *nativeValue); - // JSValue returnValue = JS_Call(context->ctx(), promiseContext->resolveFunc, context->Global(), 1, &value); - // context->DrainPendingPromiseJobs(); - // context->HandleException(&returnValue); - // JS_FreeValue(context->ctx(), value); - // JS_FreeValue(context->ctx(), returnValue); - // } else if (errmsg != nullptr) { - // JSValue error = JS_NewError(context->ctx()); - // JS_DefinePropertyValueStr(context->ctx(), error, "message", JS_NewString(context->ctx(), errmsg), - // JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue returnValue = JS_Call(context->ctx(), - // promiseContext->rejectFunc, context->Global(), 1, &error); context->DrainPendingPromiseJobs(); - // context->HandleException(&returnValue); - // JS_FreeValue(context->ctx(), error); - // JS_FreeValue(context->ctx(), returnValue); - // } - // - // JS_FreeValue(context->ctx(), promiseContext->resolveFunc); - // JS_FreeValue(context->ctx(), promiseContext->rejectFunc); - // list_del(&promiseContext->link); -} - -static JSValue anonymousAsyncFunction(JSContext* ctx, - JSValueConst this_val, - int argc, - JSValueConst* argv, - int magic, - JSValue* func_data) { - // JSValue resolving_funcs[2]; - // JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); - // - // auto id = magic; - // auto* eventTarget = static_cast(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->callNativeMethods(call_params.c_str(), argc + 3, arguments); - // delete[] arguments; - // - // return promise; - return JS_NULL; -} JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { switch (value.tag) { @@ -245,9 +133,6 @@ JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { case NativeTag::TAG_INT: { return JS_NewUint32(context->ctx(), value.u.int64); } - case NativeTag::TAG_BOOL: { - return JS_NewBool(context->ctx(), value.u.int64 == 1); - } case NativeTag::TAG_FLOAT64: { return JS_NewFloat64(context->ctx(), value.float64); } diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index a62f9317d1..0dc453c7c5 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -35,6 +35,7 @@ enum class JSPointerType { }; class ExecutingContext; +class ScriptValue; // Exchange data struct between dart and C++ struct NativeValue { @@ -73,13 +74,9 @@ NativeValue Native_NewString(NativeString* string); NativeValue Native_NewCString(std::string string); NativeValue Native_NewFloat64(double value); NativeValue Native_NewBool(bool value); -NativeValue Native_NewInt32(int32_t value); +NativeValue Native_NewInt64(int64_t value); NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr); -NativeValue Native_NewJSON(ExecutingContext* context, JSValue& value); -NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value); -JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value); - -std::string nativeStringToStdString(NativeString* nativeString); +NativeValue Native_NewJSON(const ScriptValue& value); } // namespace kraken diff --git a/bridge/foundation/native_value_converter.cc b/bridge/foundation/native_value_converter.cc new file mode 100644 index 0000000000..85592e1687 --- /dev/null +++ b/bridge/foundation/native_value_converter.cc @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "native_value_converter.h" + +namespace kraken { + +#define AnonymousFunctionCallPreFix "_anonymous_fn_" +#define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" + + +void call_native_function(NativeFunctionContext* functionContext, + int32_t argc, + NativeValue* argv, + NativeValue* returnValue) { +// auto* context = functionContext->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; +} + +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->callNativeMethods(call_params.c_str(), argc, arguments); +// delete[] arguments; +// return returnValue; +} + + +void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int32_t contextId, const char* errmsg) { + // auto* promiseContext = static_cast(callbackContext); + // if (!promiseContext->context->IsValid()) + // return; + // if (promiseContext->context->contextId() != contextId) + // return; + // + // auto* context = promiseContext->context; + // + // if (nativeValue != nullptr) { + // JSValue value = nativeValueToJSValue(promiseContext->context, *nativeValue); + // JSValue returnValue = JS_Call(context->ctx(), promiseContext->resolveFunc, context->Global(), 1, &value); + // context->DrainPendingPromiseJobs(); + // context->HandleException(&returnValue); + // JS_FreeValue(context->ctx(), value); + // JS_FreeValue(context->ctx(), returnValue); + // } else if (errmsg != nullptr) { + // JSValue error = JS_NewError(context->ctx()); + // JS_DefinePropertyValueStr(context->ctx(), error, "message", JS_NewString(context->ctx(), errmsg), + // JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue returnValue = JS_Call(context->ctx(), + // promiseContext->rejectFunc, context->Global(), 1, &error); context->DrainPendingPromiseJobs(); + // context->HandleException(&returnValue); + // JS_FreeValue(context->ctx(), error); + // JS_FreeValue(context->ctx(), returnValue); + // } + // + // JS_FreeValue(context->ctx(), promiseContext->resolveFunc); + // JS_FreeValue(context->ctx(), promiseContext->rejectFunc); + // list_del(&promiseContext->link); +} + +static JSValue anonymousAsyncFunction(JSContext* ctx, + JSValueConst this_val, + int argc, + JSValueConst* argv, + int magic, + JSValue* func_data) { + // JSValue resolving_funcs[2]; + // JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); + // + // auto id = magic; + // auto* eventTarget = static_cast(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->callNativeMethods(call_params.c_str(), argc + 3, arguments); + // delete[] arguments; + // + // return promise; + return JS_NULL; +} + +std::shared_ptr CreateSyncCallback(JSContext* ctx, int function_id) { + JSValue callback = JS_NewCFunctionData(ctx, anonymousFunction, 4, function_id, 0, nullptr); + auto result = QJSFunction::Create(ctx, callback); + JS_FreeValue(ctx, callback); + return result; +} + +std::shared_ptr CreateAsyncCallback(JSContext* ctx, int function_id) { + JSValue callback = JS_NewCFunctionData(ctx, anonymousAsyncFunction, 4, function_id, 0, nullptr); + auto result = QJSFunction::Create(ctx, callback); + JS_FreeValue(ctx, callback); + return result; +} + +} // namespace kraken diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h new file mode 100644 index 0000000000..58c2080f76 --- /dev/null +++ b/bridge/foundation/native_value_converter.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ +#define KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ + +#include "native_type.h" +#include "native_value.h" + +namespace kraken { + +// NativeValueConverter converts types back and forth from C++ types to NativeValue. The template +// parameter |T| determines what kind of type conversion to perform. +template +struct NativeValueConverter { + using ImplType = T; +}; + +template +struct NativeValueConverterBase { + using ImplType = typename T::ImplType; +}; + +template <> +struct NativeValueConverter : public NativeValueConverterBase { + static NativeValue ToNativeValue() { return Native_NewNull(); } + + static ImplType FromNativeValue(JSContext* ctx) { return ScriptValue::Empty(ctx); } +}; + +template <> +struct NativeValueConverter : public NativeValueConverterBase { + static NativeValue ToNativeValue(ImplType value) { return Native_NewString(value); } + + static ImplType FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } +}; + +template <> +struct NativeValueConverter : public NativeValueConverterBase { + static NativeValue ToNativeValue(ImplType value) { return Native_NewBool(value); } + + static ImplType FromNativeValue(NativeValue value) { return value.u.int64 == 1; } +}; + +template <> +struct NativeValueConverter : public NativeValueConverterBase { + static NativeValue ToNativeValue(ImplType value) { return Native_NewInt64(value); } + + static ImplType FromNativeValue(NativeValue value) { return value.u.int64; } +}; + +template <> +struct NativeValueConverter : public NativeValueConverterBase { + static NativeValue ToNativeValue(ImplType value) { return Native_NewFloat64(value); } + + static ImplType FromNativeValue(NativeValue value) { return value.float64; } +}; + +template <> +struct NativeValueConverter : public NativeValueConverterBase { + static NativeValue ToNativeValue(ImplType value) { return Native_NewJSON(value); } + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + auto* str = static_cast(value.u.ptr); + return ScriptValue::CreateJsonObject(ctx, str, strlen(str)); + } +}; + +class NativeBoundingClientRect; +class NativeEventTarget; +class NativeCanvasRenderingContext2D; + +template<> +struct NativeValueConverter> : public NativeValueConverterBase> { + static NativeValue ToNativeValue(ImplType value) { + return Native_NewPtr(JSPointerType::NativeBoundingClientRect, value); + } + static ImplType FromNativeValue(NativeValue value) { + return static_cast(value.u.ptr); + } +}; + +template<> +struct NativeValueConverter> : public NativeValueConverterBase> { + static NativeValue ToNativeValue(ImplType value) { + return Native_NewPtr(JSPointerType::NativeEventTarget, value); + } + static ImplType FromNativeValue(NativeValue value) { + return static_cast(value.u.ptr); + } +}; + +template<> +struct NativeValueConverter> : public NativeValueConverterBase> { + static NativeValue ToNativeValue(ImplType value) { + return Native_NewPtr(JSPointerType::NativeCanvasRenderingContext2D, value); + } + static ImplType FromNativeValue(NativeValue value) { + return static_cast(value.u.ptr); + } +}; + +std::shared_ptr CreateSyncCallback(JSContext* ctx, int function_id); +std::shared_ptr CreateAsyncCallback(JSContext* ctx, int function_id); + +template<> +struct NativeValueConverter : public NativeValueConverterBase { + static NativeValue ToNativeValue(ImplType value) { + // Not supported. + assert(false); + } + + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + return CreateSyncCallback(ctx, value.u.int64); + }; +}; + +template<> +struct NativeValueConverter : public NativeValueConverterBase { + static NativeValue ToNativeValue(ImplType value) { + // Not supported. + assert(false); + } + + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + return CreateAsyncCallback(ctx, value.u.int64); + } +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index f732491207..458a4f934b 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -33,7 +33,7 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { queue.emplace_back(item); } -void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01, void* nativePtr) { +void UICommandBuffer::addCommand(int32_t id, int32_t type, const std::unique_ptr& args_01, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); @@ -41,14 +41,14 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString& args_01 #endif } - UICommandItem item{id, type, args_01, nativePtr}; + UICommandItem item{id, type, args_01.get(), nativePtr}; queue.emplace_back(item); } void UICommandBuffer::addCommand(int32_t id, int32_t type, - NativeString& args_01, - NativeString& args_02, + const std::unique_ptr& args_01, + const std::unique_ptr& args_02, void* nativePtr) { #if FLUTTER_BACKEND if (!update_batched) { @@ -56,7 +56,7 @@ void UICommandBuffer::addCommand(int32_t id, update_batched = true; } #endif - UICommandItem item{id, type, args_01, args_02, nativePtr}; + UICommandItem item{id, type, args_01.get(), args_02.get(), nativePtr}; queue.emplace_back(item); } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index a67fd2bd19..c3ca2db522 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -32,18 +32,18 @@ enum class UICommand { }; struct UICommandItem { - UICommandItem(int32_t id, int32_t type, NativeString args_01, NativeString args_02, void* nativePtr) + UICommandItem(int32_t id, int32_t type, const NativeString* args_01, const NativeString* args_02, void* nativePtr) : type(type), - string_01(reinterpret_cast(args_01.string())), - args_01_length(args_01.length()), - string_02(reinterpret_cast(args_02.string())), - args_02_length(args_02.length()), + string_01(reinterpret_cast(new NativeString(args_01))), + args_01_length(args_01->length()), + string_02(reinterpret_cast(new NativeString(args_02))), + args_02_length(args_02->length()), id(id), nativePtr(reinterpret_cast(nativePtr)){}; - UICommandItem(int32_t id, int32_t type, NativeString args_01, void* nativePtr) + UICommandItem(int32_t id, int32_t type, const NativeString* args_01, void* nativePtr) : type(type), - string_01(reinterpret_cast(args_01.string())), - args_01_length(args_01.length()), + string_01(reinterpret_cast(new NativeString(args_01))), + args_01_length(args_01->length()), id(id), nativePtr(reinterpret_cast(nativePtr)){}; UICommandItem(int32_t id, int32_t type, void* nativePtr) @@ -63,8 +63,8 @@ class UICommandBuffer { explicit UICommandBuffer(ExecutingContext* context); 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); + void addCommand(int32_t id, int32_t type, const std::unique_ptr& args_01, const std::unique_ptr& args_02, void* nativePtr); + void addCommand(int32_t id, int32_t type, const std::unique_ptr& args_01, void* nativePtr); UICommandItem* data(); int64_t size(); void clear(); From 6bdaba60be65f5c1904db6b62311d1337f80f4a2 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Mon, 11 Apr 2022 21:37:31 +0800 Subject: [PATCH 070/498] refactor: refactor binding object and remove dispatchEvent. --- kraken/lib/src/bridge/binding.dart | 53 +++++++++++++++---------- kraken/lib/src/bridge/native_types.dart | 25 +++++------- kraken/lib/src/bridge/native_value.dart | 11 +++++ kraken/lib/src/bridge/to_native.dart | 28 ++++++------- kraken/lib/src/foundation/binding.dart | 4 +- 5 files changed, 70 insertions(+), 51 deletions(-) diff --git a/kraken/lib/src/bridge/binding.dart b/kraken/lib/src/bridge/binding.dart index 4c2989218d..640adde07c 100644 --- a/kraken/lib/src/bridge/binding.dart +++ b/kraken/lib/src/bridge/binding.dart @@ -25,7 +25,7 @@ typedef NativeAsyncAnonymousFunctionCallback = Void Function( typedef DartAsyncAnonymousFunctionCallback = void Function(Pointer callbackContext, Pointer nativeValue, int contextId, Pointer errmsg); // This function receive calling from binding side. -void _invokeBindingMethod(Pointer nativeBindingObject, Pointer returnValue, Pointer nativeMethod, int argc, Pointer argv) { +void _invokeBindingMethodFromNativeImpl(Pointer nativeBindingObject, Pointer returnValue, Pointer nativeMethod, int argc, Pointer argv) { String method = nativeStringToString(nativeMethod); List values = List.generate(argc, (i) { Pointer nativeValue = argv.elementAt(i); @@ -64,7 +64,7 @@ void _invokeBindingMethod(Pointer nativeBindingObject, Pointer()); + BindingObject bindingObject = BindingBridge.getBindingObject(nativeBindingObject); var result; try { if (method == GetPropertyMagic && argc == 1) { @@ -83,18 +83,37 @@ void _invokeBindingMethod(Pointer nativeBindingObject, Pointer rawEvent = event.toRaw().cast(); + bool isCustomEvent = event is CustomEvent; + return [event.type, rawEvent, isCustomEvent ? 1 : 0]; +} + // Dispatch the event to the binding side. -void _dispatchBindingEvent(Event event) { +void _dispatchEventToNative(Event event) { Pointer? pointer = event.currentTarget?.pointer; int? contextId = event.target?.contextId; if (contextId != null && pointer != null) { - emitUIEvent(contextId, pointer, event); + // Call methods implements at C++ side. + DartInvokeBindingMethodsFromDart f = pointer.ref.invokeBindingMethodFromDart.asFunction(); + + List dispatchEventArguments = prepareDispatchEventArguments(event); + Pointer method = stringToNativeString('dispatchEvent'); + Pointer allocatedNativeArguments = makeNativeValueArguments(dispatchEventArguments); + + f(pointer, nullptr, method, dispatchEventArguments.length, allocatedNativeArguments); + + // Free the allocated arguments. + malloc.free(method); + malloc.free(allocatedNativeArguments); + + } } abstract class BindingBridge { - static final Pointer> _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(); @@ -107,28 +126,18 @@ abstract class BindingBridge { } static void _bindObject(BindingObject object) { - Pointer? nativeBindingObject = castToType(object.pointer); + Pointer? nativeBindingObject = object.pointer; if (nativeBindingObject != null) { _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; } } @@ -143,10 +152,10 @@ abstract class BindingBridge { } static void listenEvent(EventTarget target, String type) { - target.addEventListener(type, _dispatchBindingEvent); + target.addEventListener(type, _dispatchEventToNative); } static void unlistenEvent(EventTarget target, String type) { - target.removeEventListener(type, _dispatchBindingEvent); + target.removeEventListener(type, _dispatchEventToNative); } } diff --git a/kraken/lib/src/bridge/native_types.dart b/kraken/lib/src/bridge/native_types.dart index 7ce55a471c..2aaa78b529 100644 --- a/kraken/lib/src/bridge/native_types.dart +++ b/kraken/lib/src/bridge/native_types.dart @@ -86,6 +86,7 @@ class RawNativeMessageEvent extends Struct { @Int64() external int length; } + // class RawNativeCustomEvent extends Struct { // Raw bytes represent the following fields. @@ -271,25 +272,21 @@ class NativeBoundingClientRect extends Struct { external double left; } +// using InvokeNativeBindingMethod = +// void (*)(NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, NativeValue* argv); -typedef NativeDispatchEvent = Int32 Function( - Int32 contextId, - Pointer nativeBindingObject, - Pointer eventType, - Pointer nativeEvent, - Int32 isCustomEvent); -typedef NativeInvokeBindingMethod = Void Function( - Pointer nativePtr, - Pointer returnValue, - Pointer method, - Int32 argc, - Pointer argv); +typedef InvokeBindingsMethodsFromNative = Void Function(Pointer binding_object, + Pointer return_value, 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 { 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 { diff --git a/kraken/lib/src/bridge/native_value.dart b/kraken/lib/src/bridge/native_value.dart index 43b0998270..4b7e1b7329 100644 --- a/kraken/lib/src/bridge/native_value.dart +++ b/kraken/lib/src/bridge/native_value.dart @@ -146,3 +146,14 @@ void toNativeValue(Pointer target, value) { target.ref.u = str.toNativeUtf8().address; } } + +Pointer makeNativeValueArguments(List args) { + Pointer> buffer = malloc.allocate(sizeOf() * args.length).cast>(); + + for(int i = 0; i < args.length; i ++) { + buffer[i] = malloc.allocate(sizeOf()); + toNativeValue(buffer[i], args[i]); + } + + return buffer.cast(); +} diff --git a/kraken/lib/src/bridge/to_native.dart b/kraken/lib/src/bridge/to_native.dart index 499330ca15..b417ad1a0e 100644 --- a/kraken/lib/src/bridge/to_native.dart +++ b/kraken/lib/src/bridge/to_native.dart @@ -109,20 +109,20 @@ typedef DartDispatchEvent = int Function( int isCustomEvent ); -void emitUIEvent( - int contextId, Pointer nativeBindingObject, Event event) { - if (KrakenController.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 inhert 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 emitUIEvent( +// int contextId, Pointer nativeBindingObject, Event event) { +// if (KrakenController.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 inhert 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) { diff --git a/kraken/lib/src/foundation/binding.dart b/kraken/lib/src/foundation/binding.dart index 8cc9ff4029..b879cdc189 100644 --- a/kraken/lib/src/foundation/binding.dart +++ b/kraken/lib/src/foundation/binding.dart @@ -3,12 +3,14 @@ * Author: Kraken Team. */ import 'package:flutter/foundation.dart'; +import 'package:kraken/bridge.dart'; +import 'dart:ffi'; typedef BindingObjectOperation = void Function(BindingObject bindingObject); class BindingContext { final int contextId; - final pointer; + final Pointer pointer; const BindingContext(this.contextId, this.pointer); } From 37969e21e3bd32921ab0c2b3da8e79aa7523c4e4 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Mon, 11 Apr 2022 13:52:27 +0000 Subject: [PATCH 071/498] Committing clang-format changes --- bridge/bindings/qjs/atomic_string.h | 10 +-- bridge/bindings/qjs/qjs_interface_bridge.cc | 2 +- bridge/bindings/qjs/script_value.cc | 7 +-- bridge/core/dom/binding_object.cc | 17 +++-- bridge/core/dom/binding_object.h | 23 ++++--- bridge/core/dom/element.h | 15 +++-- bridge/core/dom/events/event_target.cc | 4 +- bridge/core/dom/events/event_target.h | 4 +- bridge/core/dom/legacy/bounding_client_rect.h | 3 +- bridge/core/dom/legacy/element_attributes.cc | 17 ++--- bridge/core/dom/legacy/element_attributes.h | 7 +-- bridge/core/dom/legacy/space_split_string.cc | 4 +- bridge/core/dom/legacy/space_split_string.h | 3 +- bridge/foundation/native_string.cc | 2 +- bridge/foundation/native_type.h | 8 +-- bridge/foundation/native_value.cc | 1 - bridge/foundation/native_value_converter.cc | 62 +++++++++---------- bridge/foundation/native_value_converter.h | 43 +++++-------- bridge/foundation/ui_command_buffer.cc | 5 +- bridge/foundation/ui_command_buffer.h | 6 +- 20 files changed, 122 insertions(+), 121 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 6c78a22abd..c738657597 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -6,8 +6,8 @@ #define KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ #include -#include #include +#include #include "foundation/macros.h" #include "foundation/native_string.h" #include "native_string_utils.h" @@ -23,12 +23,8 @@ class AtomicString { public: enum class StringKind { kIsLowerCase, kIsUpperCase, kIsMixed }; - struct KeyHasher - { - std::size_t operator()(const AtomicString& k) const - { - return k.atom_; - } + struct KeyHasher { + std::size_t operator()(const AtomicString& k) const { return k.atom_; } }; static AtomicString Empty(JSContext* ctx); diff --git a/bridge/bindings/qjs/qjs_interface_bridge.cc b/bridge/bindings/qjs/qjs_interface_bridge.cc index eccaa227b1..6e153f5394 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.cc +++ b/bridge/bindings/qjs/qjs_interface_bridge.cc @@ -13,4 +13,4 @@ bool QJSInterfaceBridge::HasInstance(ExecutingContext* context, JSValue return JS_IsInstanceOf(context->ctx(), value, context->contextData()->prototypeForType(QJST::GetWrapperTypeInfo())); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index daeac112e3..65df25b0cd 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -5,11 +5,11 @@ #include "script_value.h" #include -#include "foundation/native_value_converter.h" #include "core/executing_context.h" +#include "foundation/native_value_converter.h" #include "native_string_utils.h" -#include "qjs_engine_patch.h" #include "qjs_bounding_client_rect.h" +#include "qjs_engine_patch.h" namespace kraken { @@ -106,8 +106,7 @@ NativeValue ScriptValue::ToNative() const { auto* functionContext = new NativeFunctionContext{context, value_}; return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); } else if (JS_IsObject(value_)) { - -// auto* context = static_cast(JS_GetContextOpaque(ctx_)); + // auto* context = static_cast(JS_GetContextOpaque(ctx_)); // 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())); diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc index e7bca01ca3..b48dd74d1c 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/dom/binding_object.cc @@ -6,16 +6,21 @@ namespace kraken { -void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, NativeValue* argv) { +void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_object, + NativeValue* return_value, + NativeString* method, + int32_t argc, + NativeValue* argv) { NativeValue result = binding_object->binding_target_->HandleCallFromDartSide(method, argc, argv); - if (return_value != nullptr) *return_value = result; + if (return_value != nullptr) + *return_value = result; } -BindingObject::BindingObject(ExecutingContext* context) { +BindingObject::BindingObject(ExecutingContext* context) {} -} - -NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* args) const {} +NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, + int32_t argc, + const NativeValue* args) const {} NativeValue BindingObject::GetBindingProperty(const AtomicString& prop) const { return NativeValue(); diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h index dc8fd22225..9e05b56263 100644 --- a/bridge/core/dom/binding_object.h +++ b/bridge/core/dom/binding_object.h @@ -14,18 +14,28 @@ namespace kraken { class BindingObject; class NativeBindingObject; -using InvokeBindingsMethodsFromNative = - void (*)(NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, NativeValue* argv); +using InvokeBindingsMethodsFromNative = void (*)(NativeBindingObject* binding_object, + NativeValue* return_value, + NativeString* method, + int32_t argc, + NativeValue* argv); -using InvokeBindingMethodsFromDart = - void (*)(NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, NativeValue* argv); +using InvokeBindingMethodsFromDart = void (*)(NativeBindingObject* binding_object, + NativeValue* return_value, + NativeString* method, + int32_t argc, + NativeValue* argv); struct NativeBindingObject { NativeBindingObject() = delete; explicit NativeBindingObject(BindingObject* target) - : binding_target_(target), invoke_binding_methods_from_dart(HandleCallFromDartSide) {}; + : binding_target_(target), invoke_binding_methods_from_dart(HandleCallFromDartSide){}; - static void HandleCallFromDartSide(NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, NativeValue* argv); + static void HandleCallFromDartSide(NativeBindingObject* binding_object, + NativeValue* return_value, + NativeString* method, + int32_t argc, + NativeValue* argv); BindingObject* binding_target_{nullptr}; #if UNIT_TEST @@ -51,7 +61,6 @@ class BindingObject { NativeBindingObject binding_object_{this}; }; - } // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 35c4d7908c..dc3fbb8980 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -8,8 +8,8 @@ #include "bindings/qjs/garbage_collected.h" #include "container_node.h" -#include "legacy/element_attributes.h" #include "legacy/bounding_client_rect.h" +#include "legacy/element_attributes.h" namespace kraken { @@ -40,13 +40,12 @@ class Element : public ContainerNode { void removeAttribute(const AtomicString&, ExceptionState& exception_state); BoundingClientRect* getBoundingClientRect(); -// static JSValue getBoundingClientRect(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); - + // static JSValue getBoundingClientRect(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); AtomicString TagName() const { return tag_name_; } diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 1f0d0fdc8c..1b78f5d390 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -192,9 +192,7 @@ DispatchEventResult EventTarget::DispatchEventInternal(Event& event, ExceptionSt return dispatch_result; } -NativeValue EventTarget::HandleCallFromDartSide(const NativeString* method, int32_t argc, const NativeValue* argv) { - -} +NativeValue EventTarget::HandleCallFromDartSide(const NativeString* method, int32_t argc, const NativeValue* argv) {} const char* EventTarget::GetHumanReadableName() const { return "EventTarget"; diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 1fdd2e7610..1e06703273 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -9,9 +9,9 @@ #include "bindings/qjs/js_event_listener.h" #include "bindings/qjs/qjs_function.h" #include "bindings/qjs/script_wrappable.h" +#include "core/dom/binding_object.h" #include "event_listener_map.h" #include "foundation/native_string.h" -#include "core/dom/binding_object.h" #if UNIT_TEST void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv); @@ -132,7 +132,7 @@ class EventTarget : public ScriptWrappable, BindingObject { virtual EventTargetData* GetEventTargetData() = 0; virtual EventTargetData& EnsureEventTargetData() = 0; - NativeValue HandleCallFromDartSide(const NativeString* method, int32_t argc, const NativeValue *argv) override; + NativeValue HandleCallFromDartSide(const NativeString* method, int32_t argc, const NativeValue* argv) override; const char* GetHumanReadableName() const override; diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index eaa006be37..16b0abd1c8 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -24,6 +24,7 @@ struct NativeBoundingClientRect { class BoundingClientRect : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); + public: BoundingClientRect() = delete; static BoundingClientRect* Create(ExecutingContext* context, NativeBoundingClientRect* native_bounding_client_rect); @@ -43,6 +44,6 @@ class BoundingClientRect : public ScriptWrappable { double left_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 6b1ce1d68f..e8d24ea9e6 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -2,8 +2,8 @@ * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ -#include "bindings/qjs/exception_state.h" #include "element_attributes.h" +#include "bindings/qjs/exception_state.h" #include "built_in_string.h" #include "core/dom/element.h" @@ -16,7 +16,7 @@ static inline bool IsNumberIndex(const std::string& name) { return f >= '0' && f <= '9'; } -ElementAttributes::ElementAttributes(Element* element): ScriptWrappable(element->ctx()) {} +ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()) {} AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { bool numberIndex = IsNumberIndex(name.ToStdString()); @@ -28,11 +28,15 @@ AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { return attributes_[name]; } -bool ElementAttributes::SetAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { +bool ElementAttributes::SetAttribute(const AtomicString& name, + const AtomicString& value, + ExceptionState& exception_state) { bool numberIndex = IsNumberIndex(name.ToStdString()); if (numberIndex) { - exception_state.ThrowException(ctx(), ErrorType::TypeError, "Failed to execute 'setAttribute' on 'Element': '" + name.ToStdString() + "' is not a valid attribute name."); + exception_state.ThrowException( + ctx(), ErrorType::TypeError, + "Failed to execute 'setAttribute' on 'Element': '" + name.ToStdString() + "' is not a valid attribute name."); return false; } @@ -81,7 +85,6 @@ std::string ElementAttributes::ToString() { return s; } -void ElementAttributes::Trace(GCVisitor* visitor) const { -} +void ElementAttributes::Trace(GCVisitor* visitor) const {} -} +} // namespace kraken diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index 4cc7f4d1b3..9404ee43db 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -6,9 +6,9 @@ #define KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ #include -#include "space_split_string.h" #include "bindings/qjs/atomic_string.h" #include "bindings/qjs/script_wrappable.h" +#include "space_split_string.h" namespace kraken { @@ -18,8 +18,8 @@ class Element; // TODO: refactor for better W3C standard support and higher performance. class ElementAttributes : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); - public: + public: ElementAttributes(Element) = delete; ElementAttributes(Element* element); FORCE_INLINE const char* GetHumanReadableName() const override { return "ElementAttributes"; } @@ -38,7 +38,6 @@ class ElementAttributes : public ScriptWrappable { std::shared_ptr class_name_{std::make_shared("")}; }; - -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ diff --git a/bridge/core/dom/legacy/space_split_string.cc b/bridge/core/dom/legacy/space_split_string.cc index 7bbd504731..0226161e71 100644 --- a/bridge/core/dom/legacy/space_split_string.cc +++ b/bridge/core/dom/legacy/space_split_string.cc @@ -6,7 +6,6 @@ namespace kraken { - void SpaceSplitString::set(std::string& string) { size_t pos = 0; std::string token; @@ -55,5 +54,4 @@ bool SpaceSplitString::containsAll(std::string s) { return flag; } - -} +} // namespace kraken diff --git a/bridge/core/dom/legacy/space_split_string.h b/bridge/core/dom/legacy/space_split_string.h index 17ec653dd5..e2881deaa2 100644 --- a/bridge/core/dom/legacy/space_split_string.h +++ b/bridge/core/dom/legacy/space_split_string.h @@ -24,7 +24,6 @@ class SpaceSplitString { std::vector sz_data_; }; - -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc index e89cf89300..af7d8975c0 100644 --- a/bridge/foundation/native_string.cc +++ b/bridge/foundation/native_string.cc @@ -13,7 +13,7 @@ NativeString::NativeString(const uint16_t* string, uint32_t length) : length_(le memcpy((void*)string_, string, length * sizeof(uint16_t)); } -NativeString::NativeString(const NativeString* source): length_(source->length()) { +NativeString::NativeString(const NativeString* source) : length_(source->length()) { string_ = static_cast(malloc(source->length() * sizeof(uint16_t))); memcpy((void*)string_, source->string_, source->length() * sizeof(u_int16_t)); } diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h index edcd3a6624..49ae7c4542 100644 --- a/bridge/foundation/native_type.h +++ b/bridge/foundation/native_type.h @@ -7,9 +7,9 @@ #define KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ #include -#include "foundation/native_string.h" -#include "bindings/qjs/script_value.h" #include "bindings/qjs/qjs_function.h" +#include "bindings/qjs/script_value.h" +#include "foundation/native_string.h" namespace kraken { @@ -41,7 +41,7 @@ struct NativeTypeDouble final : public NativeTypeBaseHelper {}; struct NativeTypeJSON final : public NativeTypeBaseHelper {}; // Pointer -template +template struct NativeTypePointer final : public NativeTypeBaseHelper {}; // Sync function @@ -50,6 +50,6 @@ struct NativeTypeFunction final : public NativeTypeBaseHelper> {}; -} +} // namespace kraken #endif // KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index b8d2296fac..48e95f7329 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -120,7 +120,6 @@ NativeFunctionContext::~NativeFunctionContext() { JS_FreeValue(m_ctx, m_callback); } - JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { switch (value.tag) { case NativeTag::TAG_STRING: { diff --git a/bridge/foundation/native_value_converter.cc b/bridge/foundation/native_value_converter.cc index 85592e1687..cb0a2c6aa5 100644 --- a/bridge/foundation/native_value_converter.cc +++ b/bridge/foundation/native_value_converter.cc @@ -9,29 +9,28 @@ namespace kraken { #define AnonymousFunctionCallPreFix "_anonymous_fn_" #define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" - void call_native_function(NativeFunctionContext* functionContext, int32_t argc, NativeValue* argv, NativeValue* returnValue) { -// auto* context = functionContext->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; + // 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; } static JSValue anonymousFunction(JSContext* ctx, @@ -41,21 +40,20 @@ static JSValue anonymousFunction(JSContext* ctx, 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->callNativeMethods(call_params.c_str(), argc, arguments); -// delete[] arguments; -// return returnValue; + // 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->callNativeMethods(call_params.c_str(), argc, arguments); + // delete[] arguments; + // return returnValue; } - void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int32_t contextId, const char* errmsg) { // auto* promiseContext = static_cast(callbackContext); // if (!promiseContext->context->IsValid()) diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 58c2080f76..f0c3dff4bd 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -71,61 +71,52 @@ class NativeBoundingClientRect; class NativeEventTarget; class NativeCanvasRenderingContext2D; -template<> -struct NativeValueConverter> : public NativeValueConverterBase> { +template <> +struct NativeValueConverter> + : public NativeValueConverterBase> { static NativeValue ToNativeValue(ImplType value) { return Native_NewPtr(JSPointerType::NativeBoundingClientRect, value); } - static ImplType FromNativeValue(NativeValue value) { - return static_cast(value.u.ptr); - } + static ImplType FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } }; -template<> -struct NativeValueConverter> : public NativeValueConverterBase> { - static NativeValue ToNativeValue(ImplType value) { - return Native_NewPtr(JSPointerType::NativeEventTarget, value); - } - static ImplType FromNativeValue(NativeValue value) { - return static_cast(value.u.ptr); - } +template <> +struct NativeValueConverter> + : public NativeValueConverterBase> { + static NativeValue ToNativeValue(ImplType value) { return Native_NewPtr(JSPointerType::NativeEventTarget, value); } + static ImplType FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } }; -template<> -struct NativeValueConverter> : public NativeValueConverterBase> { +template <> +struct NativeValueConverter> + : public NativeValueConverterBase> { static NativeValue ToNativeValue(ImplType value) { return Native_NewPtr(JSPointerType::NativeCanvasRenderingContext2D, value); } - static ImplType FromNativeValue(NativeValue value) { - return static_cast(value.u.ptr); - } + static ImplType FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } }; std::shared_ptr CreateSyncCallback(JSContext* ctx, int function_id); std::shared_ptr CreateAsyncCallback(JSContext* ctx, int function_id); -template<> +template <> struct NativeValueConverter : public NativeValueConverterBase { static NativeValue ToNativeValue(ImplType value) { // Not supported. assert(false); } - static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { - return CreateSyncCallback(ctx, value.u.int64); - }; + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { return CreateSyncCallback(ctx, value.u.int64); }; }; -template<> +template <> struct NativeValueConverter : public NativeValueConverterBase { static NativeValue ToNativeValue(ImplType value) { // Not supported. assert(false); } - static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { - return CreateAsyncCallback(ctx, value.u.int64); - } + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { return CreateAsyncCallback(ctx, value.u.int64); } }; } // namespace kraken diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 458a4f934b..9790a0a336 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -33,7 +33,10 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { queue.emplace_back(item); } -void UICommandBuffer::addCommand(int32_t id, int32_t type, const std::unique_ptr& args_01, void* nativePtr) { +void UICommandBuffer::addCommand(int32_t id, + int32_t type, + const std::unique_ptr& args_01, + void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index c3ca2db522..b60dbfcba8 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -63,7 +63,11 @@ class UICommandBuffer { explicit UICommandBuffer(ExecutingContext* context); 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, const std::unique_ptr& args_01, const std::unique_ptr& args_02, void* nativePtr); + void addCommand(int32_t id, + int32_t type, + const std::unique_ptr& args_01, + const std::unique_ptr& args_02, + void* nativePtr); void addCommand(int32_t id, int32_t type, const std::unique_ptr& args_01, void* nativePtr); UICommandItem* data(); int64_t size(); From c36ad469d8c6007f6ea0b38e9ae73ae10258e480 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 11 Apr 2022 23:59:19 +0800 Subject: [PATCH 072/498] feat: add binding object impl --- bridge/bindings/qjs/exception_state.h | 1 - bridge/core/dom/binding_call_methods.json | 11 ++++++++ bridge/core/dom/binding_object.cc | 34 +++++++++++++++++++---- bridge/core/dom/binding_object.h | 15 +++++----- bridge/core/dom/element.cc | 16 +++++++++-- bridge/core/dom/element.d.ts | 2 +- bridge/core/dom/element.h | 16 ++--------- bridge/core/dom/events/event_target.cc | 4 ++- bridge/core/dom/events/event_target.h | 6 ++-- bridge/core/dom/node.cc | 10 +++---- 10 files changed, 74 insertions(+), 41 deletions(-) create mode 100644 bridge/core/dom/binding_call_methods.json diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index 4de5a6ed9f..f618de8113 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -27,7 +27,6 @@ class ExceptionState { private: JSValue exception_{JS_NULL}; - JSContext* ctx_; }; } // namespace kraken diff --git a/bridge/core/dom/binding_call_methods.json b/bridge/core/dom/binding_call_methods.json new file mode 100644 index 0000000000..7504dcbe3a --- /dev/null +++ b/bridge/core/dom/binding_call_methods.json @@ -0,0 +1,11 @@ +{ + "metadata": { + "templates": ["make_names"] + }, + "data": [ + "click", + "getBoundingClientRect", + ["getPropertyMagic", "%g"], + ["setPropertyMagic", "%s"] + ] +} diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc index b48dd74d1c..ff571e2da7 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/dom/binding_object.cc @@ -3,6 +3,9 @@ */ #include "binding_object.h" +#include "bindings/qjs/exception_state.h" +#include "core/executing_context.h" +#include "binding_call_methods.h" namespace kraken { @@ -16,18 +19,37 @@ void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_ob *return_value = result; } -BindingObject::BindingObject(ExecutingContext* context) {} +BindingObject::BindingObject(ExecutingContext* context): context_(context) {} NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, int32_t argc, - const NativeValue* args) const {} + const NativeValue* argv, + ExceptionState& exception_state) const { + if (binding_object_.invoke_bindings_methods_from_native == nullptr) { + exception_state.ThrowException(context_->ctx(), RangeError, "Failed to call dart method: invokeBindingMethod not initialized."); + return Native_NewNull(); + } + + NativeValue return_value = Native_NewNull(); + binding_object_.invoke_bindings_methods_from_native(&binding_object_, &return_value, method.ToNativeString().release(), argc, argv); + return return_value; +} -NativeValue BindingObject::GetBindingProperty(const AtomicString& prop) const { - return NativeValue(); +NativeValue BindingObject::GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const { + context_->dartMethodPtr()->flushUICommand(); + const NativeValue argv[] = { + Native_NewString(prop.ToNativeString().release()) + }; + return InvokeBindingMethod(binding_call_methods::kgetPropertyMagic, 1, argv, exception_state); } -NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, NativeValue value) const { - return NativeValue(); +NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, NativeValue value, ExceptionState& exception_state) const { + context_->dartMethodPtr()->flushUICommand(); + const NativeValue argv[] = { + Native_NewString(prop.ToNativeString().release()), + value + }; + return InvokeBindingMethod(binding_call_methods::ksetPropertyMagic, 2, argv, exception_state); } } // namespace kraken diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h index 9e05b56263..557ff5e2b8 100644 --- a/bridge/core/dom/binding_object.h +++ b/bridge/core/dom/binding_object.h @@ -13,12 +13,13 @@ namespace kraken { class BindingObject; class NativeBindingObject; +class ExceptionState; -using InvokeBindingsMethodsFromNative = void (*)(NativeBindingObject* binding_object, +using InvokeBindingsMethodsFromNative = void (*)(const NativeBindingObject* binding_object, NativeValue* return_value, NativeString* method, int32_t argc, - NativeValue* argv); + const NativeValue* argv); using InvokeBindingMethodsFromDart = void (*)(NativeBindingObject* binding_object, NativeValue* return_value, @@ -38,9 +39,6 @@ struct NativeBindingObject { NativeValue* argv); BindingObject* binding_target_{nullptr}; -#if UNIT_TEST - InvokeBindingMethod invokeBindingMethod{reinterpret_cast(TEST_invokeBindingMethod)}; -#else InvokeBindingMethodsFromDart invoke_binding_methods_from_dart{nullptr}; InvokeBindingsMethodsFromNative invoke_bindings_methods_from_native{nullptr}; }; @@ -53,11 +51,12 @@ class BindingObject { // Handle call from dart side. virtual NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const = 0; // Invoke methods which implemented at dart side. - NativeValue InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* args) const; - NativeValue GetBindingProperty(const AtomicString& prop) const; - NativeValue SetBindingProperty(const AtomicString& prop, NativeValue value) const; + 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; private: + ExecutingContext* context_{nullptr}; NativeBindingObject binding_object_{this}; }; diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 4b69c3fde6..ba67b44181 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -4,6 +4,9 @@ */ #include "element.h" +#include "binding_call_methods.h" +#include "bindings/qjs/exception_state.h" +#include "foundation/native_value_converter.h" namespace kraken { @@ -48,8 +51,17 @@ void Element::removeAttribute(const AtomicString& name, ExceptionState& exceptio attributes_->RemoveAttribute(name); } -BoundingClientRect* Element::getBoundingClientRect() { - return nullptr; +BoundingClientRect* Element::getBoundingClientRect(ExceptionState& exception_state) { + GetExecutingContext()->dartMethodPtr()->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()->dartMethodPtr()->flushUICommand(); + InvokeBindingMethod(binding_call_methods::kclick, 0, nullptr, exception_state); } } // namespace kraken diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index 29a0d8aad4..7bc0f423bb 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -27,7 +27,7 @@ interface Element extends Node { * Returns element's first attribute whose qualified name is qualifiedName, and null if there is no such attribute otherwise. */ getAttribute(qualifiedName: string): string | null; - getBoundingClientRect(): DOMRect; + getBoundingClientRect(): BoundingClientRect; /** * Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. */ diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index dc3fbb8980..20458f2fc7 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -13,17 +13,6 @@ namespace kraken { -struct NativeBoundingClientRect { - double x; - double y; - double width; - double height; - double top; - double right; - double bottom; - double left; -}; - class Element : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); @@ -38,10 +27,9 @@ class Element : public ContainerNode { void setAttribute(const AtomicString&, const AtomicString& value); void setAttribute(const AtomicString&, const AtomicString& value, ExceptionState&); void removeAttribute(const AtomicString&, ExceptionState& exception_state); - BoundingClientRect* getBoundingClientRect(); + BoundingClientRect* getBoundingClientRect(ExceptionState& exception_state); + void click(ExceptionState& exception_state); - // static JSValue getBoundingClientRect(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); diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 1b78f5d390..6380887d75 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -192,7 +192,9 @@ DispatchEventResult EventTarget::DispatchEventInternal(Event& event, ExceptionSt return dispatch_result; } -NativeValue EventTarget::HandleCallFromDartSide(const NativeString* method, int32_t argc, const NativeValue* argv) {} +NativeValue EventTarget::HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const { + +} const char* EventTarget::GetHumanReadableName() const { return "EventTarget"; diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 1e06703273..72cd480bb2 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -78,7 +78,7 @@ class EventTargetData final { // 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, BindingObject { +class EventTarget : public ScriptWrappable, public BindingObject { DEFINE_WRAPPERTYPEINFO(); public: @@ -127,13 +127,13 @@ class EventTarget : public ScriptWrappable, BindingObject { DispatchEventResult DispatchEventInternal(Event& event, ExceptionState& exception_state); + NativeValue HandleCallFromDartSide(NativeString *method, int32_t argc, const NativeValue *argv) const override; + // Subclasses should likely not override these themselves; instead, they // should subclass EventTargetWithInlineData. virtual EventTargetData* GetEventTargetData() = 0; virtual EventTargetData& EnsureEventTargetData() = 0; - NativeValue HandleCallFromDartSide(const NativeString* method, int32_t argc, const NativeValue* argv) override; - const char* GetHumanReadableName() const override; private: diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 554e9e5c29..3e1b1a04b0 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -18,7 +18,7 @@ namespace kraken { Node* Node::Create(ExecutingContext* context, ExceptionState& exception_state) { - exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor"); + exception_state.ThrowException(ErrorType::TypeError, "Illegal constructor"); } void Node::setNodeValue(const AtomicString& value) { @@ -64,7 +64,7 @@ Node* Node::insertBefore(Node* new_child, Node* ref_child, ExceptionState& excep 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."); + exception_state.ThrowException(ErrorType::TypeError, "This node type does not support this method."); return nullptr; } @@ -73,7 +73,7 @@ Node* Node::replaceChild(Node* new_child, Node* old_child, ExceptionState& excep 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."); + exception_state.ThrowException(ErrorType::TypeError, "This node type does not support this method."); return nullptr; } @@ -82,7 +82,7 @@ Node* Node::removeChild(Node* old_child, ExceptionState& exception_state) { 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."); + exception_state.ThrowException(ErrorType::TypeError, "This node type does not support this method."); return nullptr; } @@ -91,7 +91,7 @@ Node* Node::appendChild(Node* new_child, ExceptionState& exception_state) { if (this_node) return this_node->AppendChild(new_child, exception_state); - exception_state.ThrowException(ctx(), ErrorType::TypeError, "This node type does not support this method."); + exception_state.ThrowException(ErrorType::TypeError, "This node type does not support this method."); return nullptr; } From 40500946a1f008b09622c071d1edb4afa06da780 Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 12 Apr 2022 08:13:12 +0800 Subject: [PATCH 073/498] add scroll options --- bridge/CMakeLists.txt | 4 +++ bridge/bindings/qjs/converter_impl.h | 9 ++++++ bridge/bindings/qjs/wrapper_type_info.h | 5 +++- bridge/core/dom/element.cc | 4 +++ bridge/core/dom/element.d.ts | 30 ++++--------------- bridge/core/dom/element.h | 6 ++++ bridge/core/dom/legacy/bounding_client_rect.h | 9 ++++++ bridge/core/dom/legacy/element_attributes.cc | 11 +++++++ bridge/core/dom/legacy/element_attributes.h | 7 +++-- bridge/core/dom/node.cc | 6 ++-- bridge/core/dom/scroll_options.d.ts | 7 +++++ bridge/core/dom/scroll_to_options.d.ts | 11 +++++++ .../code_generator/src/idl/generateSource.ts | 5 +++- 13 files changed, 83 insertions(+), 31 deletions(-) create mode 100644 bridge/core/dom/scroll_options.d.ts create mode 100644 bridge/core/dom/scroll_to_options.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index f352d4b65c..f850cefbad 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -374,6 +374,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/event_type_names.cc out/built_in_string.cc out/built_in_string.h + out/qjs_scroll_options.cc + out/qjs_scroll_options.h + out/qjs_scroll_to_options.cc + out/qjs_scroll_to_options.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index fdbae3154e..87458fe44c 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -24,6 +24,7 @@ #include "qjs_event_init.h" #include "qjs_event_listener_options.h" #include "qjs_node.h" +#include "qjs_scroll_to_options.h" namespace kraken { @@ -477,6 +478,14 @@ struct Converter : public ConverterBase { static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } }; +template<> +struct Converter : ConverterBase { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + assert(!JS_IsException(value)); + return ScrollToOptions::Create(ctx, value, exception_state); + } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 9b3f4cf461..71921458ea 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -18,7 +18,10 @@ enum { JS_CLASS_EVENT, JS_CLASS_ERROREVENT, JS_CLASS_EVENTTARGET, - JS_CLASS_NODE + JS_CLASS_NODE, + JS_CLASS_ELEMENT, + JS_CLASS_DOCUMENT, + JS_CLASS_BOUNDINGCLIENTRECT }; // This struct provides a way to store a bunch of information that is helpful diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index ba67b44181..0c578ddda2 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -64,4 +64,8 @@ void Element::click(ExceptionState& exception_state) { InvokeBindingMethod(binding_call_methods::kclick, 0, nullptr, exception_state); } +bool Element::HasEquivalentAttributes(const Element& other) const { + return other.attributes_->IsEquivalent(*attributes_); +} + } // namespace kraken diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index 7bc0f423bb..40c5a7be60 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -1,8 +1,9 @@ import {Node} from "./node"; import {Document} from "./document"; +import {ScrollToOptions} from "./scroll_to_options"; interface Element extends Node { - readonly attributes: NamedNodeMap; + readonly attributes: ElementAttributes; readonly clientHeight: number; readonly clientLeft: number; @@ -27,39 +28,20 @@ interface Element extends Node { * Returns element's first attribute whose qualified name is qualifiedName, and null if there is no such attribute otherwise. */ getAttribute(qualifiedName: string): string | null; - getBoundingClientRect(): BoundingClientRect; - /** - * Returns a HTMLCollection of the elements in the object on which the method was invoked (a document or an element) that have all the classes given by classNames. The classNames argument is interpreted as a space-separated list of classes. - */ - getElementsByClassName(classNames: string): HTMLCollectionOf; - getElementsByTagName(qualifiedName: K): HTMLCollectionOf; - getElementsByTagName(qualifiedName: K): HTMLCollectionOf; - getElementsByTagName(qualifiedName: string): HTMLCollectionOf; /** - * Returns true if element has an attribute whose qualified name is qualifiedName, and false otherwise. + * Sets the value of element's first attribute whose qualified name is qualifiedName to value. */ - hasAttribute(qualifiedName: string): boolean; - insertAdjacentElement(where: InsertPosition, element: Element): Element | null; - insertAdjacentHTML(position: InsertPosition, text: string): void; - insertAdjacentText(where: InsertPosition, data: string): void; + setAttribute(qualifiedName: string, value: string): void; /** * Removes element's first attribute whose qualified name is qualifiedName. */ removeAttribute(qualifiedName: string): void; + getBoundingClientRect(): BoundingClientRect; + scroll(options?: ScrollToOptions): void; scroll(x: number, y: number): void; scrollBy(options?: ScrollToOptions): void; scrollBy(x: number, y: number): void; - scrollIntoView(arg?: boolean | ScrollIntoViewOptions): void; scrollTo(options?: ScrollToOptions): void; scrollTo(x: number, y: number): void; - /** - * Sets the value of element's first attribute whose qualified name is qualifiedName to value. - */ - setAttribute(qualifiedName: string, value: string): void; - /** - * Sets the value of element's attribute whose namespace is namespace and local name is localName to value. - */ - setAttributeNS(namespace: string | null, qualifiedName: string, value: string): void; - setAttributeNode(attr: Attr): Attr | null; } diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 20458f2fc7..ec8e85f79c 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -10,6 +10,7 @@ #include "container_node.h" #include "legacy/bounding_client_rect.h" #include "legacy/element_attributes.h" +#include "qjs_scroll_to_options.h" namespace kraken { @@ -29,6 +30,9 @@ class Element : public ContainerNode { 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); // static JSValue toBlob(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); // static JSValue click(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); @@ -37,6 +41,8 @@ class Element : public ContainerNode { AtomicString TagName() const { return tag_name_; } + bool HasEquivalentAttributes(const Element& other) const; + protected: private: void _notifyNodeRemoved(Node* node); diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index 16b0abd1c8..f566f82295 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -33,6 +33,15 @@ class BoundingClientRect : public ScriptWrappable { FORCE_INLINE const char* GetHumanReadableName() const override { return "BoundingClientRect"; } void Trace(GCVisitor* visitor) const override; + double x() const { return x_; } + double y() const { return y_; } + double width() const { return width_; } + double height() const { return height_; } + double top() const { return top_; } + double right() const { return right_; } + double bottom() const { return bottom_; } + double left() const { return left_; } + private: double x_; double y_; diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index e8d24ea9e6..426355baee 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -85,6 +85,17 @@ std::string ElementAttributes::ToString() { return s; } +bool ElementAttributes::IsEquivalent(const ElementAttributes& other) const { + if (attributes_.size() != other.attributes_.size()) return false; + for(auto& entry: attributes_) { + auto it = other.attributes_.find(entry.first); + if (it == other.attributes_.end()) { + return false; + } + } + return true; +} + void ElementAttributes::Trace(GCVisitor* visitor) const {} } // namespace kraken diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index 9404ee43db..ae63f154a3 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -22,8 +22,6 @@ class ElementAttributes : public ScriptWrappable { public: ElementAttributes(Element) = delete; ElementAttributes(Element* element); - FORCE_INLINE const char* GetHumanReadableName() const override { return "ElementAttributes"; } - void Trace(GCVisitor* visitor) const override; AtomicString GetAttribute(const AtomicString& name); bool SetAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state); @@ -33,6 +31,11 @@ class ElementAttributes : public ScriptWrappable { std::shared_ptr ClassName(); std::string ToString(); + bool IsEquivalent(const ElementAttributes& other) const; + + FORCE_INLINE const char* GetHumanReadableName() const override { return "ElementAttributes"; } + void Trace(GCVisitor* visitor) const override; + private: std::unordered_map attributes_; std::shared_ptr class_name_{std::make_shared("")}; diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 3e1b1a04b0..5a3589d99d 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -64,7 +64,7 @@ Node* Node::insertBefore(Node* new_child, Node* ref_child, ExceptionState& excep if (this_node) return this_node->InsertBefore(new_child, ref_child, exception_state); - exception_state.ThrowException(ErrorType::TypeError, "This node type does not support this method."); + exception_state.ThrowException(ctx(), ErrorType::TypeError, "This node type does not support this method."); return nullptr; } @@ -73,7 +73,7 @@ Node* Node::replaceChild(Node* new_child, Node* old_child, ExceptionState& excep if (this_node) return this_node->ReplaceChild(new_child, old_child, exception_state); - exception_state.ThrowException(ErrorType::TypeError, "This node type does not support this method."); + exception_state.ThrowException(ctx(), ErrorType::TypeError, "This node type does not support this method."); return nullptr; } @@ -82,7 +82,7 @@ Node* Node::removeChild(Node* old_child, ExceptionState& exception_state) { if (this_node) return this_node->RemoveChild(old_child, exception_state); - exception_state.ThrowException(ErrorType::TypeError, "This node type does not support this method."); + exception_state.ThrowException(ctx(), ErrorType::TypeError, "This node type does not support this method."); return nullptr; } 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/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 2829a52d0e..c88c748689 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -45,6 +45,9 @@ export function generateTypeValue(type: ParameterType[]): string { case FunctionArgumentType.void: { return 'void'; } + case FunctionArgumentType.double: { + return 'double'; + } case FunctionArgumentType.boolean: { return 'bool'; } @@ -267,7 +270,7 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { options.constructorInstallList.push(`{"${getClassName(blob)}", nullptr, nullptr, constructor}`) } options.wrapperTypeInfoInit = ` -const WrapperTypeInfo QJS${getClassName(blob)}::wrapper_type_info_ {JS_CLASS_${getClassName(blob).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, QJS${getClassName(blob)}::ConstructorCallback}; +const WrapperTypeInfo QJS${getClassName(blob)}::wrapper_type_info_ {JS_CLASS_${getClassName(blob).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ${object.construct ? `QJS${getClassName(blob)}::ConstructorCallback` : 'nullptr'}}; const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::wrapper_type_info_;`; return _.template(readTemplate('interface'))({ className: getClassName(blob), From e7985d73b6e218cc8990073a75bac14e81f70ecd Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 12 Apr 2022 08:27:20 +0800 Subject: [PATCH 074/498] fix: fix ui command duplicated copy --- bridge/core/dom/element.cc | 2 +- bridge/core/dom/legacy/element_attribute.d.ts | 3 +++ bridge/foundation/ui_command_buffer.cc | 10 +++++----- bridge/foundation/ui_command_buffer.h | 10 +++++----- 4 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 bridge/core/dom/legacy/element_attribute.d.ts diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 0c578ddda2..12c5d2cef4 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -44,7 +44,7 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value, std::unique_ptr args_02 = value.ToNativeString(); GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), static_cast(UICommand::setAttribute), - args_01, args_02, nullptr); + args_01.release(), args_02.release(), nullptr); } void Element::removeAttribute(const AtomicString& name, ExceptionState& exception_state) { diff --git a/bridge/core/dom/legacy/element_attribute.d.ts b/bridge/core/dom/legacy/element_attribute.d.ts new file mode 100644 index 0000000000..625d52fa77 --- /dev/null +++ b/bridge/core/dom/legacy/element_attribute.d.ts @@ -0,0 +1,3 @@ +interface ElementAttribute { + +} diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 9790a0a336..17f031273f 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -35,7 +35,7 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { void UICommandBuffer::addCommand(int32_t id, int32_t type, - const std::unique_ptr& args_01, + NativeString* args_01, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND @@ -44,14 +44,14 @@ void UICommandBuffer::addCommand(int32_t id, #endif } - UICommandItem item{id, type, args_01.get(), nativePtr}; + UICommandItem item{id, type, args_01, nativePtr}; queue.emplace_back(item); } void UICommandBuffer::addCommand(int32_t id, int32_t type, - const std::unique_ptr& args_01, - const std::unique_ptr& args_02, + NativeString* args_01, + NativeString* args_02, void* nativePtr) { #if FLUTTER_BACKEND if (!update_batched) { @@ -59,7 +59,7 @@ void UICommandBuffer::addCommand(int32_t id, update_batched = true; } #endif - UICommandItem item{id, type, args_01.get(), args_02.get(), nativePtr}; + UICommandItem item{id, type, args_01, args_02, nativePtr}; queue.emplace_back(item); } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index b60dbfcba8..731b9bd6d4 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -32,7 +32,7 @@ enum class UICommand { }; struct UICommandItem { - UICommandItem(int32_t id, int32_t type, const NativeString* args_01, const NativeString* args_02, void* nativePtr) + UICommandItem(int32_t id, int32_t type, NativeString* args_01, NativeString* args_02, void* nativePtr) : type(type), string_01(reinterpret_cast(new NativeString(args_01))), args_01_length(args_01->length()), @@ -40,7 +40,7 @@ struct UICommandItem { args_02_length(args_02->length()), id(id), nativePtr(reinterpret_cast(nativePtr)){}; - UICommandItem(int32_t id, int32_t type, const NativeString* args_01, void* nativePtr) + UICommandItem(int32_t id, int32_t type, NativeString* args_01, void* nativePtr) : type(type), string_01(reinterpret_cast(new NativeString(args_01))), args_01_length(args_01->length()), @@ -65,10 +65,10 @@ class UICommandBuffer { void addCommand(int32_t id, int32_t type, void* nativePtr); void addCommand(int32_t id, int32_t type, - const std::unique_ptr& args_01, - const std::unique_ptr& args_02, + NativeString* args_01, + NativeString* args_02, void* nativePtr); - void addCommand(int32_t id, int32_t type, const std::unique_ptr& args_01, void* nativePtr); + void addCommand(int32_t id, int32_t type, NativeString* args_01, void* nativePtr); UICommandItem* data(); int64_t size(); void clear(); From a48d85a84ada5615eb41ff54c6a73e12e4f42d23 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Tue, 12 Apr 2022 00:28:08 +0000 Subject: [PATCH 075/498] Committing clang-format changes --- bridge/bindings/qjs/converter_impl.h | 2 +- bridge/core/dom/binding_object.cc | 23 ++++++++++---------- bridge/core/dom/binding_object.h | 5 ++++- bridge/core/dom/element.h | 2 +- bridge/core/dom/events/event_target.cc | 4 +--- bridge/core/dom/events/event_target.h | 2 +- bridge/core/dom/legacy/element_attributes.cc | 5 +++-- bridge/foundation/ui_command_buffer.cc | 5 +---- bridge/foundation/ui_command_buffer.h | 6 +---- 9 files changed, 24 insertions(+), 30 deletions(-) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 87458fe44c..c8a621471a 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -478,7 +478,7 @@ struct Converter : public ConverterBase { static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } }; -template<> +template <> struct Converter : ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc index ff571e2da7..253a90ea81 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/dom/binding_object.cc @@ -3,9 +3,9 @@ */ #include "binding_object.h" +#include "binding_call_methods.h" #include "bindings/qjs/exception_state.h" #include "core/executing_context.h" -#include "binding_call_methods.h" namespace kraken { @@ -19,36 +19,35 @@ void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_ob *return_value = result; } -BindingObject::BindingObject(ExecutingContext* context): context_(context) {} +BindingObject::BindingObject(ExecutingContext* context) : context_(context) {} NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* argv, ExceptionState& exception_state) const { if (binding_object_.invoke_bindings_methods_from_native == nullptr) { - exception_state.ThrowException(context_->ctx(), RangeError, "Failed to call dart method: invokeBindingMethod not initialized."); + exception_state.ThrowException(context_->ctx(), RangeError, + "Failed to call dart method: invokeBindingMethod not initialized."); return Native_NewNull(); } NativeValue return_value = Native_NewNull(); - binding_object_.invoke_bindings_methods_from_native(&binding_object_, &return_value, method.ToNativeString().release(), argc, argv); + binding_object_.invoke_bindings_methods_from_native(&binding_object_, &return_value, + method.ToNativeString().release(), argc, argv); return return_value; } NativeValue BindingObject::GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const { context_->dartMethodPtr()->flushUICommand(); - const NativeValue argv[] = { - Native_NewString(prop.ToNativeString().release()) - }; + const NativeValue argv[] = {Native_NewString(prop.ToNativeString().release())}; return InvokeBindingMethod(binding_call_methods::kgetPropertyMagic, 1, argv, exception_state); } -NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, NativeValue value, ExceptionState& exception_state) const { +NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, + NativeValue value, + ExceptionState& exception_state) const { context_->dartMethodPtr()->flushUICommand(); - const NativeValue argv[] = { - Native_NewString(prop.ToNativeString().release()), - value - }; + const NativeValue argv[] = {Native_NewString(prop.ToNativeString().release()), value}; return InvokeBindingMethod(binding_call_methods::ksetPropertyMagic, 2, argv, exception_state); } diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h index 557ff5e2b8..f83db5d5ff 100644 --- a/bridge/core/dom/binding_object.h +++ b/bridge/core/dom/binding_object.h @@ -51,7 +51,10 @@ class BindingObject { // Handle call from dart side. virtual NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const = 0; // Invoke methods which implemented at dart side. - NativeValue InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* args, ExceptionState& exception_state) const; + NativeValue 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; diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index ec8e85f79c..6497ac0725 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -31,7 +31,7 @@ class Element : public ContainerNode { 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(const std::shared_ptr& options, ExceptionState& exception_state); void scroll(double x, double y, ExceptionState& exception_state); // static JSValue toBlob(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 6380887d75..5e653d15a6 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -192,9 +192,7 @@ DispatchEventResult EventTarget::DispatchEventInternal(Event& event, ExceptionSt return dispatch_result; } -NativeValue EventTarget::HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const { - -} +NativeValue EventTarget::HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const {} const char* EventTarget::GetHumanReadableName() const { return "EventTarget"; diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 72cd480bb2..3405d47d45 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -127,7 +127,7 @@ class EventTarget : public ScriptWrappable, public BindingObject { DispatchEventResult DispatchEventInternal(Event& event, ExceptionState& exception_state); - NativeValue HandleCallFromDartSide(NativeString *method, int32_t argc, const NativeValue *argv) const override; + NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const override; // Subclasses should likely not override these themselves; instead, they // should subclass EventTargetWithInlineData. diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 426355baee..48fb4f3e92 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -86,8 +86,9 @@ std::string ElementAttributes::ToString() { } bool ElementAttributes::IsEquivalent(const ElementAttributes& other) const { - if (attributes_.size() != other.attributes_.size()) return false; - for(auto& entry: attributes_) { + 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; diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 17f031273f..8abf6f4f57 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -33,10 +33,7 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { queue.emplace_back(item); } -void UICommandBuffer::addCommand(int32_t id, - int32_t type, - NativeString* args_01, - void* nativePtr) { +void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString* args_01, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 731b9bd6d4..7bc4064e9e 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -63,11 +63,7 @@ class UICommandBuffer { explicit UICommandBuffer(ExecutingContext* context); 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, NativeString* args_02, void* nativePtr); void addCommand(int32_t id, int32_t type, NativeString* args_01, void* nativePtr); UICommandItem* data(); int64_t size(); From df475017fb87ab4442338e2801d998a8f25ffacd Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Tue, 12 Apr 2022 13:34:34 +0800 Subject: [PATCH 076/498] feat: add idl methods overload. --- .../code_generator/src/idl/generateSource.ts | 31 ++++++++++++++++++- .../static/idl_templates/interface.cc.tpl | 20 ++++++++++-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index c88c748689..43754a3e44 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -188,6 +188,25 @@ ${optionalArgumentsInit.join('\n')} `; } +type OverLoadMethods = { + [name: string]: FunctionDeclaration[]; +}; + +function generateOverLoadSwitchBody(overloadMethods: FunctionDeclaration[]) { + let callBodyList = overloadMethods.map((overload, index) => { + return `if (${overload.args.length} == argc) { + return ${overload.name}_overload_${index}(ctx, this_val, argc, argv); +} + `; + }); + + return ` +${callBodyList.join('\n')} + +return ${overloadMethods[0].name}_overload_${0}(ctx, this_val, argc, argv) +`; +} + function generateReturnValueInit(blob: IDLBlob, type: ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { if (type[0] == FunctionArgumentType.void) return ''; @@ -263,12 +282,20 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { object.props.forEach(prop => { options.classMethodsInstallList.push(`{"${prop.name}", ${prop.name}AttributeGetCallback, ${prop.readonly ? 'nullptr' : `${prop.name}AttributeSetCallback`}}`) }); + + let overloadMethods = {}; object.methods.forEach(method => { - options.classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) + if (overloadMethods.hasOwnProperty(method.name)) { + overloadMethods[method.name].push(method); + } else { + overloadMethods[method.name] = [method]; + options.classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) + } }); if (object.construct) { options.constructorInstallList.push(`{"${getClassName(blob)}", nullptr, nullptr, constructor}`) } + options.wrapperTypeInfoInit = ` const WrapperTypeInfo QJS${getClassName(blob)}::wrapper_type_info_ {JS_CLASS_${getClassName(blob).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ${object.construct ? `QJS${getClassName(blob)}::ConstructorCallback` : 'nullptr'}}; const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::wrapper_type_info_;`; @@ -277,6 +304,8 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass blob: blob, object: object, generateFunctionBody, + generateOverLoadSwitchBody, + overloadMethods, generateTypeConverter }); } diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index fb57fb6a47..d19e753fd6 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -5,9 +5,23 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob <% } %> <% _.forEach(object.methods, function(method, index) { %> -static JSValue <%= method.name %>(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - <%= generateFunctionBody(blob, method, {isInstanceMethod: true}) %> -} + + <% 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) { %> From 860b3eab0495653ed438469dc55b19cdd8f56496 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Tue, 12 Apr 2022 16:31:02 +0800 Subject: [PATCH 077/498] feat: add element attributes impl. --- bridge/CMakeLists.txt | 6 + bridge/bindings/qjs/converter_impl.h | 10 +- .../bindings/qjs/script_promise_resolver.cc | 7 + bridge/bindings/qjs/to_quickjs.h | 3 + bridge/core/dom/binding_call_methods.json | 8 + bridge/core/dom/binding_object.cc | 2 +- bridge/core/dom/element.cc | 224 +++++++++++++++++- bridge/core/dom/element.d.ts | 10 +- bridge/core/dom/element.h | 32 ++- bridge/core/dom/legacy/element_attribute.d.ts | 6 +- bridge/core/dom/legacy/element_attributes.cc | 6 +- bridge/core/dom/legacy/element_attributes.h | 13 +- bridge/core/dom/text_node.cc | 86 ------- bridge/core/dom/text_node.h | 56 ----- bridge/core/dom/text_node_test.cc | 30 --- bridge/core/html/html_template_element.cc | 34 --- bridge/core/html/html_template_element.h | 31 +-- .../code_generator/src/idl/generateSource.ts | 9 +- .../static/idl_templates/interface.cc.tpl | 2 +- 19 files changed, 311 insertions(+), 264 deletions(-) delete mode 100644 bridge/core/dom/text_node.cc delete mode 100644 bridge/core/dom/text_node.h delete mode 100644 bridge/core/dom/text_node_test.cc diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index f850cefbad..4cf804e8b3 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -322,6 +322,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/html/html_collection.h core/html/html_element.cc core/html/html_element.h + core/html/html_template_element.cc + core/html/html_template_element.h # Legacy implements, should remove them in the future. core/dom/legacy/space_split_string.cc core/dom/legacy/space_split_string.h @@ -370,6 +372,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_event_target.h out/qjs_node.h out/qjs_node.cc + out/qjs_element.cc + out/qjs_element.h + out/qjs_element_attribute.cc + out/qjs_element_attribute.h out/event_type_names.h out/event_type_names.cc out/built_in_string.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index c8a621471a..0c001ca24d 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -25,6 +25,7 @@ #include "qjs_event_listener_options.h" #include "qjs_node.h" #include "qjs_scroll_to_options.h" +#include "qjs_element_attribute.h" namespace kraken { @@ -486,6 +487,13 @@ struct Converter : ConverterBase { } }; -} // namespace kraken +template<> +struct Converter : ConverterBase { + static JSValue ToValue(JSContext* ctx, ImplType value) { + return value->ToQuickJS(); + } +}; + +}; // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index 8023772071..9345c542d5 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -31,14 +31,21 @@ void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) { if (state_ == kResolving) { JSValue arguments[] = {value}; JSValue return_value = JS_Call(context_->ctx(), resolve_func_, JS_NULL, 1, arguments); + if (JS_IsException(return_value)) { + context_->HandleException(&return_value); + } JS_FreeValue(context_->ctx(), return_value); } else { assert(state_ == kRejecting); JSValue arguments[] = {value}; JSValue return_value = JS_Call(context_->ctx(), reject_func_, JS_NULL, 1, arguments); + if (JS_IsException(return_value)) { + context_->HandleException(&return_value); + } JS_FreeValue(context_->ctx(), return_value); } } + context_->DrainPendingPromiseJobs(); } } // namespace kraken diff --git a/bridge/bindings/qjs/to_quickjs.h b/bridge/bindings/qjs/to_quickjs.h index 1e13f60a3a..78f332753d 100644 --- a/bridge/bindings/qjs/to_quickjs.h +++ b/bridge/bindings/qjs/to_quickjs.h @@ -25,6 +25,9 @@ inline JSValue toQuickJS(JSContext* ctx, int32_t 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) { diff --git a/bridge/core/dom/binding_call_methods.json b/bridge/core/dom/binding_call_methods.json index 7504dcbe3a..33a21c6044 100644 --- a/bridge/core/dom/binding_call_methods.json +++ b/bridge/core/dom/binding_call_methods.json @@ -4,6 +4,14 @@ }, "data": [ "click", + "scroll", + "scrollBy", + "clientTop", + "clientLeft", + "clientWidth", + "clientHeight", + "scrollLeft", + "scrollTop", "getBoundingClientRect", ["getPropertyMagic", "%g"], ["setPropertyMagic", "%s"] diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc index 253a90ea81..31fbd91300 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/dom/binding_object.cc @@ -26,7 +26,7 @@ NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, const NativeValue* argv, ExceptionState& exception_state) const { if (binding_object_.invoke_bindings_methods_from_native == nullptr) { - exception_state.ThrowException(context_->ctx(), RangeError, + exception_state.ThrowException(context_->ctx(), ErrorType::InternalError, "Failed to call dart method: invokeBindingMethod not initialized."); return Native_NewNull(); } diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 12c5d2cef4..a8ebf80b0d 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -6,15 +6,19 @@ #include "element.h" #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 "foundation/native_value_converter.h" +#include "core/fileapi/blob.h" +#include "core/html/html_template_element.h" namespace kraken { Element::Element(Document* document, const AtomicString& tag_name, Node::ConstructionType construction_type) - : ContainerNode(document, construction_type), attributes_(MakeGarbageCollected(this)) {} + : ContainerNode(document, construction_type), attributes_(ElementAttributes::Create(this)) {} bool Element::hasAttribute(const AtomicString& name, ExceptionState& exception_state) const { - return attributes_->HasAttribute(name); + return attributes_->hasAttribute(name, exception_state); } AtomicString Element::getAttribute(const AtomicString& name, ExceptionState& exception_state) const { @@ -27,14 +31,14 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value) } void Element::setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { - if (attributes_->HasAttribute(name)) { + if (attributes_->hasAttribute(name, exception_state)) { AtomicString&& oldAttribute = attributes_->GetAttribute(name); - if (!attributes_->SetAttribute(name, value, exception_state)) { + if (!attributes_->setAttribute(name, value, exception_state)) { return; }; _didModifyAttribute(name, oldAttribute, value); } else { - if (!attributes_->SetAttribute(name, value, exception_state)) { + if (!attributes_->setAttribute(name, value, exception_state)) { return; }; _didModifyAttribute(name, AtomicString::Empty(ctx()), value); @@ -48,7 +52,7 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value, } void Element::removeAttribute(const AtomicString& name, ExceptionState& exception_state) { - attributes_->RemoveAttribute(name); + attributes_->removeAttribute(name, exception_state); } BoundingClientRect* Element::getBoundingClientRect(ExceptionState& exception_state) { @@ -64,8 +68,216 @@ void Element::click(ExceptionState& exception_state) { 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()->dartMethodPtr()->flushUICommand(); + const NativeValue args[] = { + NativeValueConverter::ToNativeValue(x), + NativeValueConverter::ToNativeValue(y), + }; + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); +} + +// TODO: add this support. +void Element::scroll(const std::shared_ptr& options, ExceptionState& exception_state) { + exception_state.ThrowException(ctx(), ErrorType::InternalError, + "scroll API which accept scrollToOptions not supported."); +} + +void Element::scrollBy(ExceptionState& exception_state) { + return scrollBy(0, 0, exception_state); +} + +void Element::scrollBy(double x, double y, ExceptionState& exception_state) { + GetExecutingContext()->dartMethodPtr()->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) { + exception_state.ThrowException(ctx(), ErrorType::InternalError, + "scrollBy API which accept scrollToOptions not supported."); +} + +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::HasEquivalentAttributes(const Element& other) const { return other.attributes_->IsEquivalent(*attributes_); } +class ElementSnapshotReader { + public: + ElementSnapshotReader(ExecutingContext* context, Element* element, ScriptPromiseResolver* resolver, double device_pixel_ratio) + : context_(context), element_(element), resolver_(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_; + ScriptPromiseResolver* resolver_; + double device_pixel_ratio_; +}; + +void ElementSnapshotReader::Start() { + context_->dartMethodPtr()->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) { + Blob* blob = Blob::Create(context_); + blob->AppendBytes(bytes, length); + resolver_->Resolve(blob); +} + +void ElementSnapshotReader::HandleFailed(const char* error) { + ExceptionState exception_state; + exception_state.ThrowException(context_->ctx(), ErrorType::InternalError, error); + resolver_->Reject(exception_state); +} + +ScriptPromise Element::toBlob(ExceptionState& exception_state) { + return toBlob(1.0, exception_state); +} + +ScriptPromise Element::toBlob(double device_pixel_ratio, ExceptionState& exception_state) { + auto* resolver = ScriptPromiseResolver::Create(GetExecutingContext()); + new ElementSnapshotReader(GetExecutingContext(), this, resolver, device_pixel_ratio); + return resolver->Promise(); +} + +double Element::clientHeight() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientHeight, exception_state)); +} + +double Element::clientWidth() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientWidth, exception_state)); +} + +double Element::clientLeft() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientLeft, exception_state)); +} + +double Element::clientTop() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientTop, exception_state)); +} + +double Element::scrollTop() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kscrollTop, exception_state)); +} + +void Element::setScrollTop(double v) { + ExceptionState exception_state; + SetBindingProperty(binding_call_methods::kscrollTop, NativeValueConverter::ToNativeValue(v), exception_state); +} + +double Element::scrollLeft() const { + ExceptionState exception_state; + return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientTop, exception_state)); +} + +void Element::setScrollLeft(double v) { + ExceptionState exception_state; + SetBindingProperty(binding_call_methods::kscrollLeft, NativeValueConverter::ToNativeValue(v), exception_state); +} + +std::string Element::outerHTML() const { + std::string s = "<" + tag_name_.ToStdString(); + + // Read attributes + std::string attributes = 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; +} + +void Element::setOuterHTML(const AtomicString& value) { + +} + +std::string Element::innerHTML() const { + std::string s; + + // If Element is TemplateElement, the innerHTML content is the content of documentFragment. + const Node* parent = DynamicTo(this); + + if (auto* template_element = DynamicTo(this)) { + parent = DynamicTo(template_element->content()); + } + +// TODO: add innerHTML support. +// // 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 Element::setInnerHTML(const AtomicString& value) { + +} + } // namespace kraken diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index 40c5a7be60..cfabec47e3 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -1,6 +1,7 @@ import {Node} from "./node"; import {Document} from "./document"; import {ScrollToOptions} from "./scroll_to_options"; +import { ElementAttributes } from './legacy/element_attribute'; interface Element extends Node { readonly attributes: ElementAttributes; @@ -9,17 +10,11 @@ interface Element extends Node { readonly clientLeft: number; readonly clientTop: number; readonly clientWidth: number; - /** - * Returns the value of element's id content attribute. Can be set to change it. - */ - id: string; outerHTML: string; innerHTML: string; readonly ownerDocument: Document; - readonly scrollHeight: number; scrollLeft: number; scrollTop: number; - readonly scrollWidth: number; /** * Returns the HTML-uppercased qualified name. */ @@ -44,4 +39,7 @@ interface Element extends Node { scrollBy(x: number, y: number): void; scrollTo(options?: ScrollToOptions): void; scrollTo(x: number, y: number): void; + + // Kraken special API. + toBlob(devicePixelRatioValue?: double): Promise; } diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 6497ac0725..97a35c360c 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -20,6 +20,8 @@ class Element : public ContainerNode { public: Element(Document* document, const AtomicString& tag_name, ConstructionType = kCreateElement); + ElementAttributes* attributes() const { return attributes_; } + bool hasAttribute(const AtomicString&, ExceptionState& exception_state) const; AtomicString getAttribute(const AtomicString&, ExceptionState& exception_state) const; @@ -33,18 +35,38 @@ class Element : public ContainerNode { void scroll(ExceptionState& exception_state); void scroll(const std::shared_ptr& options, ExceptionState& exception_state); void scroll(double x, double y, ExceptionState& exception_state); + void scrollTo(ExceptionState& exception_state); + void scrollTo(const std::shared_ptr& options, ExceptionState& exception_state); + void scrollTo(double x, double y, ExceptionState& exception_state); + void scrollBy(ExceptionState& exception_state); + void scrollBy(double x, double y, ExceptionState& exception_state); + void scrollBy(const std::shared_ptr& options, ExceptionState& exception_state); + + ScriptPromise toBlob(double device_pixel_ratio, ExceptionState& exception_state); + ScriptPromise toBlob(ExceptionState& exception_state); + + double clientHeight() const; + double clientWidth() const; + double clientLeft() const; + double clientTop() const; - // 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); + double scrollTop() const; + void setScrollTop(double v); + double scrollLeft() const; + void setScrollLeft(double v); - AtomicString TagName() const { return tag_name_; } + std::string outerHTML() const; + void setOuterHTML(const AtomicString& value); + std::string innerHTML() const; + void setInnerHTML(const AtomicString& value); + + AtomicString tagName() const { return tag_name_; } bool HasEquivalentAttributes(const Element& other) const; protected: private: + void _notifyNodeRemoved(Node* node); void _notifyChildRemoved(); void _notifyNodeInsert(Node* insertNode); diff --git a/bridge/core/dom/legacy/element_attribute.d.ts b/bridge/core/dom/legacy/element_attribute.d.ts index 625d52fa77..22d1b30c98 100644 --- a/bridge/core/dom/legacy/element_attribute.d.ts +++ b/bridge/core/dom/legacy/element_attribute.d.ts @@ -1,3 +1,5 @@ -interface ElementAttribute { - +export interface ElementAttributes { + setAttribute(name: string, value: string): void; + hasAttribute(name: string): boolean; + removeAttribute(name: string): void; } diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 48fb4f3e92..c98235330c 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -28,7 +28,7 @@ AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { return attributes_[name]; } -bool ElementAttributes::SetAttribute(const AtomicString& name, +bool ElementAttributes::setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { bool numberIndex = IsNumberIndex(name.ToStdString()); @@ -50,7 +50,7 @@ bool ElementAttributes::SetAttribute(const AtomicString& name, return true; } -bool ElementAttributes::HasAttribute(const AtomicString& name) { +bool ElementAttributes::hasAttribute(const AtomicString& name, ExceptionState& exception_state) { bool numberIndex = IsNumberIndex(name.ToStdString()); if (numberIndex) { @@ -60,7 +60,7 @@ bool ElementAttributes::HasAttribute(const AtomicString& name) { return attributes_.count(name) > 0; } -void ElementAttributes::RemoveAttribute(const AtomicString& name) { +void ElementAttributes::removeAttribute(const AtomicString& name, ExceptionState& exception_state) { attributes_.erase(name); } diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index ae63f154a3..7c534aeb1f 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -18,15 +18,20 @@ 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(element); + } + ElementAttributes(Element) = delete; ElementAttributes(Element* element); AtomicString GetAttribute(const AtomicString& name); - bool SetAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state); - bool HasAttribute(const AtomicString& name); - void RemoveAttribute(const AtomicString& name); + bool setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state); + bool hasAttribute(const AtomicString& name, ExceptionState& exception_state); + void removeAttribute(const AtomicString& name, ExceptionState& exception_state); void CopyWith(ElementAttributes* attributes); std::shared_ptr ClassName(); std::string ToString(); diff --git a/bridge/core/dom/text_node.cc b/bridge/core/dom/text_node.cc deleted file mode 100644 index b05c90388f..0000000000 --- a/bridge/core/dom/text_node.cc +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "text_node.h" -#include "document.h" - -namespace kraken { - -std::once_flag kTextNodeInitFlag; - -void bindTextNode(std::unique_ptr& context) { - JSValue constructor = TextNode::constructor(context.get()); - JSValue prototype = TextNode::prototype(context.get()); - - // Install readonly properties. - INSTALL_READONLY_PROPERTY(TextNode, prototype, nodeName); - - // Install properties. - INSTALL_PROPERTY(TextNode, prototype, data); - INSTALL_PROPERTY(TextNode, prototype, nodeValue); - - context->defineGlobalProperty("Text", constructor); -} - -JSClassID TextNode::classId{0}; - -JSValue TextNode::constructor(ExecutionContext* context) { - return context->contextData()->constructorForType(&textNodeType); -} - -JSValue TextNode::prototype(ExecutionContext* context) { - return context->contextData()->prototypeForType(&textNodeType); -} - -TextNode* TextNode::create(JSContext* ctx, JSValue textContent) { - return makeGarbageCollected(textContent)->initialize(ctx, &classId); -} - -TextNode::TextNode(JSValueConst textContent) { - m_data = jsValueToStdString(m_ctx, textContent); - std::unique_ptr args_01 = stringToNativeString(m_data); - context()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::createTextNode, *args_01, nativeEventTarget); -} - -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"); -} - -std::string TextNode::toString() { - return m_data; -} - -JSValue TextNode::internalGetTextContent() { - return JS_NewString(m_ctx, m_data.c_str()); -} -void TextNode::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); - context()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::setProperty, *args_01, *args_02, nullptr); -} -} // namespace kraken diff --git a/bridge/core/dom/text_node.h b/bridge/core/dom/text_node.h deleted file mode 100644 index 4aeb075ed8..0000000000 --- a/bridge/core/dom/text_node.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_TEXT_NODE_H -#define KRAKENBRIDGE_TEXT_NODE_H - -#include "node.h" - -namespace kraken { - -class TextNodeInstance; - -void bindTextNode(ExecutionContext* context); - -class TextNode : public Node { - public: - static JSClassID classId; - static JSValue constructor(ExecutionContext* context); - static JSValue prototype(ExecutionContext* context); - static TextNode* create(JSContext* ctx, JSValue textContent); - - TextNode() = delete; - explicit TextNode(JSValueConst textContent); - - std::string toString(); - - DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); - - DEFINE_PROTOTYPE_PROPERTY(data); - DEFINE_PROTOTYPE_PROPERTY(nodeValue); - - protected: - JSValue internalGetTextContent() override; - void internalSetTextContent(JSValue content) override; - std::string m_data; -}; - -auto textNodeCreator = - [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) - -> JSValue { - JSValue textContent = JS_NULL; - if (argc == 1) { - textContent = argv[0]; - } - - TextNode* textNode = TextNode::create(ctx, textContent); - return textNode->toQuickJS(); -}; - -const WrapperTypeInfo textNodeType = {"TextNode", &nodeTypeInfo, textNodeCreator}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_TEXT_NODE_H diff --git a/bridge/core/dom/text_node_test.cc b/bridge/core/dom/text_node_test.cc deleted file mode 100644 index 997f7a3fb5..0000000000 --- a/bridge/core/dom/text_node_test.cc +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "event_target.h" -#include "gtest/gtest.h" -#include "kraken_test_env.h" -#include "page.h" - -TEST(TextNode, instanceofNode) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "true true"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "let text = document.createTextNode('1234');" - "console.log(text instanceof Node, text instanceof Text);"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index 2a622154b3..a0579425bb 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -4,43 +4,9 @@ */ #include "html_template_element.h" -#include "bindings/qjs/dom/text_node.h" -#include "bindings/qjs/qjs_engine_patch.h" -#include "page.h" namespace kraken { -TemplateElement::TemplateElement(ExecutionContext* context) : Element(context) { - JS_SetPrototype(m_ctx, m_prototypeObject, Element::instance(m_context)->prototype()); -} -void bindTemplateElement(ExecutionContext* context) { - auto* constructor = TemplateElement::instance(context); - context->defineGlobalProperty("HTMLTemplateElement", constructor->jsObject); -} - -JSValue TemplateElement::instanceConstructor(JSContext* ctx, - JSValue func_obj, - JSValue this_val, - int argc, - JSValue* argv) { - auto instance = new TemplateElementInstance(this); - return instance->jsObject; -} - -DocumentFragmentInstance* TemplateElementInstance::content() const { - return static_cast(JS_GetOpaque(m_content.value(), DocumentFragment::classId())); -} - -TemplateElementInstance::TemplateElementInstance(TemplateElement* element) - : ElementInstance(element, "template", true) { - setNodeFlag(NodeFlag::IsTemplateElement); -} - -TemplateElementInstance::~TemplateElementInstance() {} - -void TemplateElementInstance::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - ElementInstance::trace(rt, val, mark_func); -} } // namespace kraken diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index 492fb88789..1746120c98 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -6,41 +6,20 @@ #ifndef KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H #define KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H -#include "bindings/qjs/dom/document_fragment.h" -#include "bindings/qjs/dom/element.h" +#include "core/dom/element.h" namespace kraken { -void bindTemplateElement(ExecutionContext* context); -class TemplateElementInstance; +class DocumentFragment; -class TemplateElement : public Element { +class HTMLTemplateElement : public Element { + DEFINE_WRAPPERTYPEINFO(); 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); + DocumentFragment* content() const; private: - friend TemplateElementInstance; -}; - -class TemplateElementInstance : public ElementInstance { - public: - TemplateElementInstance() = delete; - explicit TemplateElementInstance(TemplateElement* element); - ~TemplateElementInstance(); - DocumentFragmentInstance* content() const; - - protected: - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) override; - - private: - ObjectProperty m_content{m_context, jsObject, "content", - JS_CallConstructor(m_ctx, DocumentFragment::instance(m_context)->jsObject, 0, nullptr)}; - friend TemplateElement; }; } // namespace kraken diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 43754a3e44..2ee4a4119e 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -203,7 +203,7 @@ function generateOverLoadSwitchBody(overloadMethods: FunctionDeclaration[]) { return ` ${callBodyList.join('\n')} -return ${overloadMethods[0].name}_overload_${0}(ctx, this_val, argc, argv) +return ${overloadMethods[0].name}_overload_${0}(ctx, this_val, argc, argv); `; } @@ -284,11 +284,13 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { }); let overloadMethods = {}; - object.methods.forEach(method => { + let filtedMethods: FunctionDeclaration[] = []; + object.methods.forEach((method, i) => { if (overloadMethods.hasOwnProperty(method.name)) { - overloadMethods[method.name].push(method); + overloadMethods[method.name].push(method) } else { overloadMethods[method.name] = [method]; + filtedMethods.push(method); options.classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) } }); @@ -306,6 +308,7 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass generateFunctionBody, generateOverLoadSwitchBody, overloadMethods, + filtedMethods, generateTypeConverter }); } diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index d19e753fd6..1792494ec8 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -4,7 +4,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob } <% } %> -<% _.forEach(object.methods, function(method, index) { %> +<% _.forEach(filtedMethods, function(method, index) { %> <% if (overloadMethods[method.name] && overloadMethods[method.name].length > 1) { %> <% _.forEach(overloadMethods[method.name], function(overloadMethod, index) { %> From 3fcb52f4251a71fa1536ace5cf95da9b6eadf1f7 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Tue, 12 Apr 2022 17:14:34 +0800 Subject: [PATCH 078/498] fix: fix compile. --- bridge/CMakeLists.txt | 9 +- bridge/bindings/qjs/converter_impl.h | 2 +- bridge/bindings/qjs/wrapper_type_info.h | 3 +- bridge/core/dom/character_data.cc | 2 +- bridge/core/dom/character_data.h | 2 +- bridge/core/dom/collection_index_cache.h | 1 + bridge/core/dom/container_node.cc | 33 +++--- bridge/core/dom/document.cc | 8 +- bridge/core/dom/document.h | 4 + bridge/core/dom/document_fragment.cc | 12 +-- bridge/core/dom/document_fragment.h | 2 +- bridge/core/dom/element.cc | 80 +++++++++----- bridge/core/dom/element.h | 8 +- bridge/core/dom/events/event.h | 2 +- bridge/core/dom/events/event_target.cc | 4 +- ...attribute.d.ts => element_attributes.d.ts} | 0 bridge/core/dom/{ => ng}/attr.cc | 0 bridge/core/dom/{ => ng}/attr.h | 0 bridge/core/dom/node.cc | 37 +++---- bridge/core/dom/node.h | 6 +- bridge/core/dom/node_list.h | 1 - bridge/core/executing_context.cc | 10 -- bridge/core/executing_context.h | 1 - bridge/core/fileapi/blob.cc | 4 + bridge/core/fileapi/blob.h | 1 + bridge/foundation/native_value.cc | 102 ------------------ bridge/foundation/native_value_converter.cc | 46 ++++---- .../static/idl_templates/interface.cc.tpl | 7 +- 28 files changed, 160 insertions(+), 227 deletions(-) rename bridge/core/dom/legacy/{element_attribute.d.ts => element_attributes.d.ts} (100%) rename bridge/core/dom/{ => ng}/attr.cc (100%) rename bridge/core/dom/{ => ng}/attr.h (100%) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 4cf804e8b3..c726541a18 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -98,6 +98,7 @@ list(APPEND BRIDGE_SOURCE foundation/native_value.h foundation/native_type.h foundation/native_value_converter.h + foundation/native_value_converter.cc foundation/casting.h foundation/ui_command_buffer.cc foundation/ui_command_buffer.h @@ -289,8 +290,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/binding_object.cc core/dom/node.cc core/dom/node.h - core/dom/attr.cc - core/dom/attr.h core/dom/node_traversal.cc core/dom/node_traversal.h core/dom/template_content_document_fragment.h @@ -374,12 +373,14 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_node.cc out/qjs_element.cc out/qjs_element.h - out/qjs_element_attribute.cc - out/qjs_element_attribute.h + out/qjs_element_attributes.cc + out/qjs_element_attributes.h out/event_type_names.h out/event_type_names.cc out/built_in_string.cc out/built_in_string.h + out/binding_call_methods.cc + out/binding_call_methods.h out/qjs_scroll_options.cc out/qjs_scroll_options.h out/qjs_scroll_to_options.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 0c001ca24d..ef6ef93a78 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -25,7 +25,7 @@ #include "qjs_event_listener_options.h" #include "qjs_node.h" #include "qjs_scroll_to_options.h" -#include "qjs_element_attribute.h" +#include "qjs_element_attributes.h" namespace kraken { diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 71921458ea..7a3460038b 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -21,7 +21,8 @@ enum { JS_CLASS_NODE, JS_CLASS_ELEMENT, JS_CLASS_DOCUMENT, - JS_CLASS_BOUNDINGCLIENTRECT + JS_CLASS_BOUNDINGCLIENTRECT, + JS_CLASS_ELEMENTATTRIBUTES }; // This struct provides a way to store a bunch of information that is helpful diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index 2425d064f3..e7766c63f4 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -6,7 +6,7 @@ namespace kraken { -void CharacterData::setData(const std::string& data) { +void CharacterData::setData(const AtomicString& data) { data_ = data; } } // namespace kraken diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index c011fe6848..6c097edc6a 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -14,7 +14,7 @@ class CharacterData : public Node { public: const AtomicString& data() const { return data_; } - void setData(const std::string& data); + void setData(const AtomicString& data); protected: CharacterData(Document& tree_scope, const AtomicString& text, ConstructionType type) diff --git a/bridge/core/dom/collection_index_cache.h b/bridge/core/dom/collection_index_cache.h index e185cadd53..9d4a366ed8 100644 --- a/bridge/core/dom/collection_index_cache.h +++ b/bridge/core/dom/collection_index_cache.h @@ -5,6 +5,7 @@ #ifndef KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ #define KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ +#include #include #include "bindings/qjs/gc_visitor.h" #include "foundation/macros.h" diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 61e52f8abd..805f1cf72c 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -9,7 +9,10 @@ namespace kraken { -HTMLCollection* ContainerNode::Children() {} +HTMLCollection* ContainerNode::Children() { + //TODO: add children implements. + return nullptr; +} unsigned ContainerNode::CountChildren() const { unsigned count = 0; @@ -60,16 +63,12 @@ bool ContainerNode::IsHostIncludingInclusiveAncestorOfThis(const Node& new_child return false; bool child_contains_parent = false; - if (GetDocument().IsTemplateDocument()) { + const Node& root = TreeRoot(); + auto* fragment = DynamicTo(root); + if (fragment && fragment->IsTemplateContent()) { child_contains_parent = new_child.ContainsIncludingHostElements(*this); } else { - 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); - } + 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."); @@ -274,14 +273,14 @@ bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, 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); - } +// 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. diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 52b2529ded..884abda752 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -5,4 +5,10 @@ #include "document.h" -namespace kraken {} // namespace kraken +namespace kraken { + +Text* Document::createTextNode(const AtomicString& value) { + return nullptr; +} + +} // namespace kraken diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 80c1963bbf..a16a0d0c08 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -11,6 +11,8 @@ namespace kraken { +class Text; + // 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. @@ -20,6 +22,8 @@ class Document : public Node, TreeScope { public: using ImplType = Document*; + Text* createTextNode(const AtomicString& value); + void IncrementNodeCount() { node_count_++; } void DecrementNodeCount() { assert(node_count_ > 0); diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index df2b6361d4..54afd06a52 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -8,8 +8,7 @@ namespace kraken { -DocumentFragment* DocumentFragment::Create(ExecutingContext* context, - Document* document, +DocumentFragment* DocumentFragment::Create(Document* document, ExceptionState& exception_state) { return MakeGarbageCollected(document, ConstructionType::kCreateDocumentFragment); } @@ -25,10 +24,11 @@ Node::NodeType DocumentFragment::nodeType() const { } Node* DocumentFragment::Clone(Document& factory, CloneChildrenFlag flag) const { - DocumentFragment* clone = Create(factory); - if (flag != CloneChildrenFlag::kSkip) - clone->CloneChildNodesFrom(*this, flag); - return clone; +// ExceptionState exception_state; +// DocumentFragment* clone = Create(&factory, exception_state); +// if (flag != CloneChildrenFlag::kSkip) +// clone->CloneChildNodesFrom(*this, flag); +// return clone; } bool DocumentFragment::ChildTypeAllowed(NodeType type) const { diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index 7e13ba97b3..4f1705749e 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -14,7 +14,7 @@ class DocumentFragment : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); public: - static DocumentFragment* Create(ExecutingContext* context, Document* document, ExceptionState& exception_state); + static DocumentFragment* Create(Document* document, ExceptionState& exception_state); DocumentFragment(Document* document, ConstructionType type); diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index a8ebf80b0d..db010daefd 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -202,8 +202,7 @@ double Element::scrollTop() const { return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kscrollTop, exception_state)); } -void Element::setScrollTop(double v) { - ExceptionState exception_state; +void Element::setScrollTop(double v, ExceptionState& exception_state) { SetBindingProperty(binding_call_methods::kscrollTop, NativeValueConverter::ToNativeValue(v), exception_state); } @@ -212,36 +211,35 @@ double Element::scrollLeft() const { return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientTop, exception_state)); } -void Element::setScrollLeft(double v) { - ExceptionState exception_state; +void Element::setScrollLeft(double v, ExceptionState& exception_state) { SetBindingProperty(binding_call_methods::kscrollLeft, NativeValueConverter::ToNativeValue(v), exception_state); } std::string Element::outerHTML() const { - std::string s = "<" + tag_name_.ToStdString(); - - // Read attributes - std::string attributes = 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 += ""; +// std::string s = "<" + tag_name_.ToStdString(); +// +// // Read attributes +// std::string attributes = 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; +// return s; } -void Element::setOuterHTML(const AtomicString& value) { +void Element::setOuterHTML(const AtomicString& value, ExceptionState& exception_state) { } @@ -251,9 +249,9 @@ std::string Element::innerHTML() const { // If Element is TemplateElement, the innerHTML content is the content of documentFragment. const Node* parent = DynamicTo(this); - if (auto* template_element = DynamicTo(this)) { - parent = DynamicTo(template_element->content()); - } +// if (auto* template_element = DynamicTo(this)) { +// parent = DynamicTo(template_element->content()); +// } // TODO: add innerHTML support. // // Children toString @@ -276,7 +274,31 @@ std::string Element::innerHTML() const { // return s; } -void Element::setInnerHTML(const AtomicString& value) { +void Element::setInnerHTML(const AtomicString& value, ExceptionState& exception_state) { + +} + +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) { } diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 97a35c360c..0818a33658 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -51,14 +51,14 @@ class Element : public ContainerNode { double clientTop() const; double scrollTop() const; - void setScrollTop(double v); + void setScrollTop(double v, ExceptionState& exception_state); double scrollLeft() const; - void setScrollLeft(double v); + void setScrollLeft(double v, ExceptionState& exception_state); std::string outerHTML() const; - void setOuterHTML(const AtomicString& value); + void setOuterHTML(const AtomicString& value, ExceptionState& exception_state); std::string innerHTML() const; - void setInnerHTML(const AtomicString& value); + void setInnerHTML(const AtomicString& value, ExceptionState& exception_state); AtomicString tagName() const { return tag_name_; } diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index d39213e90d..fa597089cc 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -137,7 +137,7 @@ class Event : public ScriptWrappable { virtual void DoneDispatchingEventAtCurrentTarget() {} bool cancelBubble() const { return propagationStopped(); } - void setCancelBubble(bool cancel) { + void setCancelBubble(bool cancel, ExceptionState& exception_state) { if (cancel) { propagation_stopped_ = true; } diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 5e653d15a6..26060fe499 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -192,7 +192,9 @@ DispatchEventResult EventTarget::DispatchEventInternal(Event& event, ExceptionSt return dispatch_result; } -NativeValue EventTarget::HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const {} +NativeValue EventTarget::HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const { + return Native_NewNull(); +} const char* EventTarget::GetHumanReadableName() const { return "EventTarget"; diff --git a/bridge/core/dom/legacy/element_attribute.d.ts b/bridge/core/dom/legacy/element_attributes.d.ts similarity index 100% rename from bridge/core/dom/legacy/element_attribute.d.ts rename to bridge/core/dom/legacy/element_attributes.d.ts diff --git a/bridge/core/dom/attr.cc b/bridge/core/dom/ng/attr.cc similarity index 100% rename from bridge/core/dom/attr.cc rename to bridge/core/dom/ng/attr.cc diff --git a/bridge/core/dom/attr.h b/bridge/core/dom/ng/attr.h similarity index 100% rename from bridge/core/dom/attr.h rename to bridge/core/dom/ng/attr.h diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 5a3589d99d..7c032042d5 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -3,7 +3,6 @@ */ #include "node.h" -#include "attr.h" #include "character_data.h" #include "child_node_list.h" #include "document.h" @@ -18,10 +17,10 @@ namespace kraken { Node* Node::Create(ExecutingContext* context, ExceptionState& exception_state) { - exception_state.ThrowException(ErrorType::TypeError, "Illegal constructor"); + exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor"); } -void Node::setNodeValue(const AtomicString& value) { +void Node::setNodeValue(const AtomicString& value, ExceptionState& exception_state) { // By default, setting nodeValue has no effect. } @@ -91,7 +90,7 @@ Node* Node::appendChild(Node* new_child, ExceptionState& exception_state) { if (this_node) return this_node->AppendChild(new_child, exception_state); - exception_state.ThrowException(ErrorType::TypeError, "This node type does not support this method."); + exception_state.ThrowException(ctx(), ErrorType::TypeError, "This node type does not support this method."); return nullptr; } @@ -123,16 +122,19 @@ bool Node::isEqualNode(Node* other, ExceptionState& exception_state) const { if (nodeValue() != other->nodeValue()) return false; - if (auto* this_attr = DynamicTo(this)) { - auto* other_attr = To(other); - if (this_attr->localName() != other_attr->localName()) - return false; +// if (auto* this_attr = DynamicTo(this)) { +// auto* other_attr = To(other); +// if (this_attr->localName() != other_attr->localName()) +// return false; +// +// if (this_attr->namespaceURI() != other_attr->namespaceURI()) +// return false; +// } else - if (this_attr->namespaceURI() != other_attr->namespaceURI()) - return false; - } else if (auto* this_element = DynamicTo(this)) { + + if (auto* this_element = DynamicTo(this)) { auto* other_element = DynamicTo(other); - if (this_element->TagName() != other_element->TagName()) + if (this_element->tagName() != other_element->tagName()) return false; if (!this_element->HasEquivalentAttributes(*other_element)) @@ -171,8 +173,8 @@ AtomicString Node::textContent(bool convert_brs_to_newlines) const { return character_data->data(); // Attribute nodes have their attribute values as textContent. - if (auto* attr = DynamicTo(this)) - return attr->value(); +// if (auto* attr = DynamicTo(this)) +// return attr->value(); // Documents and non-container nodes (that are not CharacterData) // have null textContent. @@ -188,12 +190,12 @@ AtomicString Node::textContent(bool convert_brs_to_newlines) const { return AtomicString(ctx(), content); } -void Node::setTextContent(const AtomicString& text) { +void Node::setTextContent(const AtomicString& text, ExceptionState& exception_state) { switch (nodeType()) { case kAttributeNode: case kTextNode: case kCommentNode: - setNodeValue(text); + setNodeValue(text, exception_state); return; case kElementNode: case kDocumentFragmentNode: { @@ -212,7 +214,7 @@ void Node::setTextContent(const AtomicString& text) { container->RemoveChildren(); } else { container->RemoveChildren(); - container->AppendChild(GetDocument().createTextNode(text), ExceptionState()); + container->AppendChild(GetDocument().createTextNode(text), exception_state); } return; } @@ -381,7 +383,6 @@ Node::Node(Document* document, ConstructionType type) node_flags_(type), parent_or_shadow_host_node_(nullptr), previous_(nullptr), - document_(document), next_(nullptr) {} void Node::Trace(GCVisitor*) const {} diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 5dfcb0150b..b90e0d2562 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -11,6 +11,7 @@ #include "events/event_target.h" #include "foundation/macros.h" #include "tree_scope.h" +#include "node_data.h" namespace kraken { @@ -23,7 +24,6 @@ class Element; class Document; class DocumentFragment; class ContainerNode; -class NodeData; class NodeList; enum class CustomElementState : uint32_t { @@ -62,7 +62,7 @@ class Node : public EventTarget { bool HasTagName(const AtomicString&) const; virtual std::string nodeName() const = 0; virtual std::string nodeValue() const; - virtual void setNodeValue(const AtomicString&); + virtual void setNodeValue(const AtomicString&, ExceptionState&); virtual NodeType nodeType() const = 0; ContainerNode* parentNode() const; @@ -92,7 +92,7 @@ class Node : public EventTarget { bool isSameNode(const Node* other, ExceptionState& exception_state) const { return this == other; } AtomicString textContent(bool convert_brs_to_newlines = false) const; - virtual void setTextContent(const AtomicString&); + virtual void setTextContent(const AtomicString&, ExceptionState& exception_state); // Other methods (not part of DOM) FORCE_INLINE bool IsTextNode() const { return GetDOMNodeType() == DOMNodeType::kText; } diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h index 863ccb0341..28838f8fbf 100644 --- a/bridge/core/dom/node_list.h +++ b/bridge/core/dom/node_list.h @@ -32,7 +32,6 @@ class NodeList : public ScriptWrappable { virtual Node* VirtualOwnerNode() const { return nullptr; } protected: - NodeList() = default; }; } // namespace kraken diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 4a8565e941..2f547716c8 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -42,7 +42,6 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& init_list_head(&node_job_list); init_list_head(&module_job_list); init_list_head(&module_callback_job_list); - init_list_head(&native_function_job_list); time_origin_ = std::chrono::system_clock::now(); @@ -84,15 +83,6 @@ ExecutingContext::~ExecutingContext() { valid_contexts[context_id_] = false; ctx_invalid_ = true; - // 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(script_state_.ctx()); if (JS_IsObject(exception) || JS_IsException(exception)) { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 87daab0c5b..3ebe816e29 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -101,7 +101,6 @@ class ExecutingContext { struct list_head node_job_list; struct list_head module_job_list; struct list_head module_callback_job_list; - struct list_head native_function_job_list; static void DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 6d46958b93..5de914c7fd 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -50,6 +50,10 @@ void BlobReaderClient::DidFinishLoading() { delete this; } +Blob* Blob::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected(context->ctx()); +} + Blob* Blob::Create(ExecutingContext* context) { return MakeGarbageCollected(context->ctx()); } diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index b8d1741859..f2b739f81b 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -21,6 +21,7 @@ class Blob : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); public: + static Blob* Create(ExecutingContext* context, ExceptionState& exception_state); static Blob* Create(ExecutingContext* context); static Blob* Create(ExecutingContext* context, std::vector>& data, diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 48e95f7329..7eb3566e4b 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -73,106 +73,4 @@ NativeValue Native_NewJSON(const ScriptValue& value) { return result; } -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_NewInt64(v); - } - } else if (JS_IsString(value)) { - // NativeString owned by NativeValue will be freed by users. - NativeString* string = jsValueToNativeString(ctx, value).release(); - return Native_NewString(string); - } else if (JS_IsFunction(ctx, value)) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - auto* functionContext = new NativeFunctionContext{context, value}; - return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); - } else if (JS_IsObject(value)) { - // auto* context = static_cast(JS_GetContextOpaque(ctx)); - // if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { - // auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); - // return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); - // } - // return Native_NewJSON(context, value); - } - - return Native_NewNull(); -} - -NativeFunctionContext::NativeFunctionContext(ExecutingContext* 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); -} - -JSValue nativeValueToJSValue(ExecutingContext* context, NativeValue& value) { - switch (value.tag) { - case NativeTag::TAG_STRING: { - // auto* string = static_cast(value.u.ptr); - // if (string == nullptr) - // return JS_NULL; - // JSValue returnedValue = JS_NewUnicodeString(context->runtime(), context->ctx(), string->string, - // string->length); string->free(); return returnedValue; - } - case NativeTag::TAG_INT: { - return JS_NewUint32(context->ctx(), value.u.int64); - } - case NativeTag::TAG_FLOAT64: { - return JS_NewFloat64(context->ctx(), value.float64); - } - case NativeTag::TAG_NULL: { - return JS_NULL; - } - case NativeTag::TAG_JSON: { - auto* str = static_cast(value.u.ptr); - JSValue returnedValue = JS_ParseJSON(context->ctx(), str, strlen(str), ""); - delete str; - return returnedValue; - } - case NativeTag::TAG_POINTER: { - auto* ptr = value.u.ptr; - int ptrType = (int)value.float64; - // if (ptrType == static_cast(JSPointerType::NativeBoundingClientRect)) { - // return (new BoundingClientRect(context, static_cast(ptr)))->jsObject; - // } else if (ptrType == static_cast(JSPointerType::NativeCanvasRenderingContext2D)) { - // return (new CanvasRenderingContext2D(context, - // static_cast(ptr)))->jsObject; - // } else if (ptrType == static_cast(JSPointerType::NativeEventTarget)) { - // auto* nativeEventTarget = static_cast(ptr); - // return JS_DupValue(context->ctx(), nativeEventTarget->instance->jsObject); - // } - } - case NativeTag::TAG_FUNCTION: { - int64_t functionId = value.u.int64; - return JS_NewCFunctionData(context->ctx(), anonymousFunction, 4, functionId, 0, nullptr); - } - case NativeTag::TAG_ASYNC_FUNCTION: { - int64_t functionId = value.u.int64; - return JS_NewCFunctionData(context->ctx(), anonymousAsyncFunction, 4, functionId, 0, nullptr); - } - } - return JS_NULL; -} - -std::string nativeStringToStdString(NativeString* nativeString) { - std::u16string u16EventType = - std::u16string(reinterpret_cast(nativeString->string()), nativeString->length()); - return toUTF8(u16EventType); -} - } // namespace kraken diff --git a/bridge/foundation/native_value_converter.cc b/bridge/foundation/native_value_converter.cc index cb0a2c6aa5..1274120fd3 100644 --- a/bridge/foundation/native_value_converter.cc +++ b/bridge/foundation/native_value_converter.cc @@ -9,29 +9,29 @@ namespace kraken { #define AnonymousFunctionCallPreFix "_anonymous_fn_" #define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" -void call_native_function(NativeFunctionContext* functionContext, - int32_t argc, - NativeValue* argv, - NativeValue* returnValue) { - // auto* context = functionContext->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; -} +//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; +//} static JSValue anonymousFunction(JSContext* ctx, JSValueConst this_val, diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index 1792494ec8..ecd64e9130 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -38,7 +38,12 @@ static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst if (exception_state.HasException()) { return exception_state.ToQuickJS(); } - <%= blob.filename %>->set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(v); + + <%= 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]); } <% } %> From 5c5192dd934644a4a817279888266a687d6850b3 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Tue, 12 Apr 2022 21:28:05 +0800 Subject: [PATCH 079/498] fix: fix compile. --- bridge/CMakeLists.txt | 20 ++++++++--- bridge/bindings/qjs/atomic_string.cc | 5 +-- bridge/bindings/qjs/atomic_string.h | 3 ++ bridge/bindings/qjs/converter_impl.h | 2 +- bridge/bindings/qjs/qjs_interface_bridge.cc | 5 --- bridge/bindings/qjs/qjs_interface_bridge.h | 5 ++- bridge/bindings/qjs/script_value.cc | 14 +++++--- bridge/bindings/qjs/script_wrappable.h | 2 ++ bridge/bindings/qjs/wrapper_type_info.h | 12 ++++--- bridge/core/dom/binding_object.h | 1 + bridge/core/dom/character_data.cc | 5 +++ bridge/core/dom/character_data.d.ts | 4 +++ bridge/core/dom/character_data.h | 3 ++ bridge/core/dom/container_node.cc | 4 +++ bridge/core/dom/container_node.h | 4 +-- bridge/core/dom/document_fragment.cc | 8 +++++ bridge/core/dom/document_fragment.h | 4 +++ bridge/core/dom/element.cc | 4 +++ bridge/core/dom/element.h | 1 + bridge/core/dom/events/event_target.h | 1 + bridge/core/dom/legacy/space_split_string.cc | 2 ++ bridge/core/dom/{ => ng}/child_node_list.cc | 2 +- bridge/core/dom/{ => ng}/child_node_list.h | 8 +++-- bridge/core/dom/{ => ng}/empty_node_list.cc | 2 +- bridge/core/dom/{ => ng}/empty_node_list.h | 4 ++- bridge/core/dom/ng/node_list.d.ts | 6 ++++ bridge/core/dom/{ => ng}/node_list.h | 3 +- bridge/core/dom/node.cc | 35 +++++++++++-------- bridge/core/dom/node.h | 3 +- bridge/core/dom/node_data.cc | 4 +-- bridge/core/dom/text.cc | 9 +++++ bridge/core/dom/text.d.ts | 5 +++ bridge/core/dom/text.h | 2 ++ .../code_generator/src/idl/generateSource.ts | 2 +- 34 files changed, 144 insertions(+), 50 deletions(-) create mode 100644 bridge/core/dom/character_data.d.ts rename bridge/core/dom/{ => ng}/child_node_list.cc (94%) rename bridge/core/dom/{ => ng}/child_node_list.h (88%) rename bridge/core/dom/{ => ng}/empty_node_list.cc (93%) rename bridge/core/dom/{ => ng}/empty_node_list.h (85%) create mode 100644 bridge/core/dom/ng/node_list.d.ts rename bridge/core/dom/{ => ng}/node_list.h (89%) create mode 100644 bridge/core/dom/text.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index c726541a18..ba2b9d48a2 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -308,11 +308,11 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/document_fragment.h core/dom/document_fragment.cc core/dom/collection_index_cache.h - core/dom/child_node_list.cc - core/dom/child_node_list.h - core/dom/empty_node_list.cc - core/dom/empty_node_list.h - core/dom/node_list.h + core/dom/ng/child_node_list.cc + core/dom/ng/child_node_list.h + core/dom/ng/empty_node_list.cc + core/dom/ng/empty_node_list.h + core/dom/ng/node_list.h core/dom/container_node.cc core/dom/container_node.h core/events/error_event.cc @@ -375,6 +375,16 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_element.h out/qjs_element_attributes.cc out/qjs_element_attributes.h + out/qjs_character_data.cc + out/qjs_character_data.h + out/qjs_document_fragment.cc + out/qjs_document_fragment.h + out/qjs_bounding_client_rect.cc + out/qjs_bounding_client_rect.h + out/qjs_text.cc + out/qjs_text.h + out/qjs_node_list.cc + out/qjs_node_list.h out/event_type_names.h out/event_type_names.cc out/built_in_string.cc diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index 7245aca238..f896c7d2cc 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -47,9 +47,10 @@ AtomicString::StringKind GetStringKind(JSValue stringValue) { } // 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)) {} + : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_NewAtom(ctx, string.c_str())), kind_(GetStringKind(string)), length_(string.size()) {} AtomicString::AtomicString(JSContext* ctx, JSValue value) - : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)), kind_(GetStringKind(value)) {} + : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)), kind_(GetStringKind(value)), length_(JS_VALUE_GET_STRING(value)->len) { +} bool AtomicString::IsNull() const { return atom_ == JS_ATOM_NULL; diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index c738657597..4a09c7b45c 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -43,6 +43,8 @@ class AtomicString { JSAtom Impl() const { return atom_; } + int64_t length() const { return length_; } + [[nodiscard]] std::string ToStdString() const; [[nodiscard]] std::unique_ptr ToNativeString() const; @@ -66,6 +68,7 @@ class AtomicString { protected: JSContext* ctx_{nullptr}; JSRuntime* runtime_{nullptr}; + int64_t length_{0}; JSAtom atom_{JS_ATOM_NULL}; mutable JSAtom atom_upper_{JS_ATOM_NULL}; mutable JSAtom atom_lower_{JS_ATOM_NULL}; diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index ef6ef93a78..bd3c4590f1 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -12,7 +12,7 @@ #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/dom/ng/node_list.h" #include "core/fileapi/blob_part.h" #include "core/fileapi/blob_property_bag.h" #include "core/html/html_element.h" diff --git a/bridge/bindings/qjs/qjs_interface_bridge.cc b/bridge/bindings/qjs/qjs_interface_bridge.cc index 6e153f5394..7f5fbe3213 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.cc +++ b/bridge/bindings/qjs/qjs_interface_bridge.cc @@ -8,9 +8,4 @@ namespace kraken { -template -bool QJSInterfaceBridge::HasInstance(ExecutingContext* context, JSValue value) { - return JS_IsInstanceOf(context->ctx(), value, context->contextData()->prototypeForType(QJST::GetWrapperTypeInfo())); -} - } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index c032ab1f63..1b5e13cf16 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -7,6 +7,7 @@ #define KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ #include "script_wrappable.h" +#include "core/executing_context.h" namespace kraken { @@ -17,7 +18,9 @@ class QJSInterfaceBridge { return HasInstance(context, value) ? toScriptWrappable(value) : nullptr; } - static bool HasInstance(ExecutingContext* context, JSValue value); + static bool HasInstance(ExecutingContext* context, JSValue value) { + return JS_IsInstanceOf(context->ctx(), value, context->contextData()->prototypeForType(QJST::GetWrapperTypeInfo())); + }; }; } // namespace kraken diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 65df25b0cd..b951c555cd 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -101,11 +101,15 @@ NativeValue ScriptValue::ToNative() const { // NativeString owned by NativeValue will be freed by users. NativeString* string = this->ToString().ToNativeString().release(); return NativeValueConverter::ToNativeValue(string); - } else if (JS_IsFunction(ctx_, value_)) { - auto* context = static_cast(JS_GetContextOpaque(ctx_)); - auto* functionContext = new NativeFunctionContext{context, value_}; - return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); - } else if (JS_IsObject(value_)) { + } + +// else if (JS_IsFunction(ctx_, value_)) { +// auto* context = static_cast(JS_GetContextOpaque(ctx_)); +// auto* functionContext = new NativeFunctionContext{context, value_}; +// return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); +// } +// + else if (JS_IsObject(value_)) { // auto* context = static_cast(JS_GetContextOpaque(ctx_)); // auto* context = static_cast(JS_GetContextOpaque(ctx)); // if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index cbfa2545fd..fb86fa9e7b 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -43,6 +43,8 @@ class ScriptWrappable : public GarbageCollected { // Returns the WrapperTypeInfo of the instance. virtual const WrapperTypeInfo* GetWrapperTypeInfo() const = 0; + void Trace(GCVisitor* visitor) const override {}; + JSValue ToQuickJS(); ScriptValue ToValue(); FORCE_INLINE ExecutingContext* GetExecutingContext() const { diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 7a3460038b..b215f5a8d8 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -16,13 +16,17 @@ enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB, JS_CLASS_EVENT, - JS_CLASS_ERROREVENT, - JS_CLASS_EVENTTARGET, + JS_CLASS_ERROR_EVENT, + JS_CLASS_EVENT_TARGET, JS_CLASS_NODE, JS_CLASS_ELEMENT, JS_CLASS_DOCUMENT, - JS_CLASS_BOUNDINGCLIENTRECT, - JS_CLASS_ELEMENTATTRIBUTES + JS_CLASS_CHARACTER_DATA, + JS_CLASS_TEXT, + JS_CLASS_NODE_LIST, + JS_CLASS_DOCUMENT_FRAGMENT, + JS_CLASS_BOUNDING_CLIENT_RECT, + JS_CLASS_ELEMENT_ATTRIBUTES, }; // This struct provides a way to store a bunch of information that is helpful diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h index f83db5d5ff..9f8b1a90ba 100644 --- a/bridge/core/dom/binding_object.h +++ b/bridge/core/dom/binding_object.h @@ -46,6 +46,7 @@ struct NativeBindingObject { class BindingObject { public: BindingObject() = delete; + ~BindingObject() = default; explicit BindingObject(ExecutingContext* context); // Handle call from dart side. diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index e7766c63f4..27b5229d95 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -9,4 +9,9 @@ namespace kraken { void CharacterData::setData(const AtomicString& data) { data_ = data; } + +std::string CharacterData::nodeValue() const { + return data_.ToStdString(); +} + } // namespace kraken diff --git a/bridge/core/dom/character_data.d.ts b/bridge/core/dom/character_data.d.ts new file mode 100644 index 0000000000..f1eb304843 --- /dev/null +++ b/bridge/core/dom/character_data.d.ts @@ -0,0 +1,4 @@ +export interface CharacterData { + readonly data: string; + readonly length: int64; +} diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index 6c097edc6a..6b50dbfddd 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -14,8 +14,11 @@ class CharacterData : public Node { public: const AtomicString& data() const { return data_; } + int64_t length() const { return data_.length(); }; void setData(const AtomicString& data); + std::string nodeValue() const override; + protected: CharacterData(Document& tree_scope, const AtomicString& text, ConstructionType type) : Node(&tree_scope, type), data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 805f1cf72c..2ed88352cc 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -316,6 +316,10 @@ void ContainerNode::RemoveChildren() { } } +std::string ContainerNode::nodeValue() const { + return ""; +} + ContainerNode::ContainerNode(Document* document, ConstructionType type) : Node(document, type) {} void ContainerNode::RemoveBetween(Node* previous_child, Node* next_child, Node& old_child) { diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index ca82bed511..a5c9cba33a 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -20,8 +20,6 @@ using NodeVector = std::vector; class ContainerNode : public Node { public: - ~ContainerNode() override; - Node* firstChild() const { return first_child_; } Node* lastChild() const { return last_child_; } bool hasChildren() const { return first_child_; } @@ -46,6 +44,8 @@ class ContainerNode : public Node { void RemoveChildren(); + std::string nodeValue() const override; + virtual bool ChildrenCanHaveStyle() const { return true; } void Trace(GCVisitor* visitor) const override; diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index 54afd06a52..ee07b7b0ab 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -13,6 +13,10 @@ DocumentFragment* DocumentFragment::Create(Document* document, return MakeGarbageCollected(document, ConstructionType::kCreateDocumentFragment); } +DocumentFragment * DocumentFragment::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected(context->document(), ConstructionType::kCreateDocumentFragment); +} + DocumentFragment::DocumentFragment(Document* document, ConstructionType type) : ContainerNode(document, type) {} std::string DocumentFragment::nodeName() const { @@ -23,6 +27,10 @@ Node::NodeType DocumentFragment::nodeType() const { return NodeType::kDocumentFragmentNode; } +std::string DocumentFragment::nodeValue() const { + return ""; +} + Node* DocumentFragment::Clone(Document& factory, CloneChildrenFlag flag) const { // ExceptionState exception_state; // DocumentFragment* clone = Create(&factory, exception_state); diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index 4f1705749e..03a2504256 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -15,14 +15,18 @@ class DocumentFragment : public ContainerNode { public: static DocumentFragment* Create(Document* document, ExceptionState& exception_state); + 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; diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index db010daefd..8e502468be 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -117,6 +117,10 @@ void Element::scrollTo(const std::shared_ptr& options, Exceptio return scroll(options, exception_state); } +std::string Element::nodeValue() const { + return ""; +} + bool Element::HasEquivalentAttributes(const Element& other) const { return other.attributes_->IsEquivalent(*attributes_); } diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 0818a33658..9ce61ec3cf 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -60,6 +60,7 @@ class Element : public ContainerNode { std::string innerHTML() const; void setInnerHTML(const AtomicString& value, ExceptionState& exception_state); + std::string nodeValue() const override; AtomicString tagName() const { return tag_name_; } bool HasEquivalentAttributes(const Element& other) const; diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 3405d47d45..38f18cc2ac 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -87,6 +87,7 @@ class EventTarget : public ScriptWrappable, public BindingObject { static EventTarget* Create(ExecutingContext* context, ExceptionState& exception_state); EventTarget() = delete; + ~EventTarget() = default; explicit EventTarget(ExecutingContext* context); bool addEventListener(const AtomicString& event_type, diff --git a/bridge/core/dom/legacy/space_split_string.cc b/bridge/core/dom/legacy/space_split_string.cc index 0226161e71..085425ce2f 100644 --- a/bridge/core/dom/legacy/space_split_string.cc +++ b/bridge/core/dom/legacy/space_split_string.cc @@ -6,6 +6,8 @@ namespace kraken { +std::string SpaceSplitString::delimiter_{""}; + void SpaceSplitString::set(std::string& string) { size_t pos = 0; std::string token; diff --git a/bridge/core/dom/child_node_list.cc b/bridge/core/dom/ng/child_node_list.cc similarity index 94% rename from bridge/core/dom/child_node_list.cc rename to bridge/core/dom/ng/child_node_list.cc index 70e044e7d8..155d206406 100644 --- a/bridge/core/dom/child_node_list.cc +++ b/bridge/core/dom/ng/child_node_list.cc @@ -13,7 +13,7 @@ Node* ChildNodeList::VirtualOwnerNode() const { return &OwnerNode(); } -Node* ChildNodeList::item(unsigned index) const { +Node* ChildNodeList::item(unsigned index, ExceptionState& exception_state) const { return collection_index_cache_.NodeAt(*this, index); } diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/ng/child_node_list.h similarity index 88% rename from bridge/core/dom/child_node_list.h rename to bridge/core/dom/ng/child_node_list.h index abbae27720..0d367da9f6 100644 --- a/bridge/core/dom/child_node_list.h +++ b/bridge/core/dom/ng/child_node_list.h @@ -6,12 +6,14 @@ #define KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ #include "bindings/qjs/gc_visitor.h" -#include "collection_index_cache.h" -#include "container_node.h" +#include "core/dom/collection_index_cache.h" +#include "core/dom/container_node.h" #include "node_list.h" namespace kraken { +class ExceptionState; + class ChildNodeList : public NodeList { public: explicit ChildNodeList(ContainerNode* root_node); @@ -20,7 +22,7 @@ class ChildNodeList : public NodeList { // DOM API. unsigned length() const override { return collection_index_cache_.NodeCount(*this); } - Node* item(unsigned index) const override; + Node* item(unsigned index, ExceptionState& exception_state) const override; // Non-DOM API. void InvalidateCache() { collection_index_cache_.Invalidate(); } diff --git a/bridge/core/dom/empty_node_list.cc b/bridge/core/dom/ng/empty_node_list.cc similarity index 93% rename from bridge/core/dom/empty_node_list.cc rename to bridge/core/dom/ng/empty_node_list.cc index ae7349ee64..09ea1983c6 100644 --- a/bridge/core/dom/empty_node_list.cc +++ b/bridge/core/dom/ng/empty_node_list.cc @@ -3,7 +3,7 @@ */ #include "empty_node_list.h" -#include "node.h" +#include "core/dom/node.h" namespace kraken { diff --git a/bridge/core/dom/empty_node_list.h b/bridge/core/dom/ng/empty_node_list.h similarity index 85% rename from bridge/core/dom/empty_node_list.h rename to bridge/core/dom/ng/empty_node_list.h index d57fa2a07f..2e391cbf74 100644 --- a/bridge/core/dom/empty_node_list.h +++ b/bridge/core/dom/ng/empty_node_list.h @@ -9,6 +9,8 @@ namespace kraken { +class ExceptionState; + class EmptyNodeList : public NodeList { public: explicit EmptyNodeList(Node* root_node); @@ -18,7 +20,7 @@ class EmptyNodeList : public NodeList { private: unsigned length() const override { return 0; } - Node* item(unsigned) const override { return nullptr; } + Node* item(unsigned, ExceptionState& exception_state) const override { return nullptr; } bool IsEmptyNodeList() const override { return true; } Node* VirtualOwnerNode() const override; diff --git a/bridge/core/dom/ng/node_list.d.ts b/bridge/core/dom/ng/node_list.d.ts new file mode 100644 index 0000000000..019ef9365b --- /dev/null +++ b/bridge/core/dom/ng/node_list.d.ts @@ -0,0 +1,6 @@ +import {Node} from "../node"; + +export interface NodeList { + readonly length: int64; + item(index: number): Node; +} diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/ng/node_list.h similarity index 89% rename from bridge/core/dom/node_list.h rename to bridge/core/dom/ng/node_list.h index 28838f8fbf..a2f8b783dc 100644 --- a/bridge/core/dom/node_list.h +++ b/bridge/core/dom/ng/node_list.h @@ -10,6 +10,7 @@ namespace kraken { class Node; +class ExceptionState; class NodeList : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); @@ -21,7 +22,7 @@ class NodeList : public ScriptWrappable { // DOM methods & attributes for NodeList virtual unsigned length() const = 0; - virtual Node* item(unsigned index) const = 0; + virtual Node* item(unsigned index, ExceptionState& exception_state) const = 0; // Other methods (not part of DOM) virtual bool IsEmptyNodeList() const { return false; } diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 7c032042d5..eb22ad0a6f 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -4,12 +4,11 @@ #include "node.h" #include "character_data.h" -#include "child_node_list.h" #include "document.h" #include "document_fragment.h" -#include "empty_node_list.h" +#include "ng/child_node_list.h" +#include "ng/empty_node_list.h" #include "node_data.h" -#include "node_list.h" #include "node_traversal.h" #include "template_content_document_fragment.h" #include "text.h" @@ -28,6 +27,10 @@ ContainerNode* Node::parentNode() const { return ParentOrShadowHostNode(); } +Element* Node::parentElement() const { + return nullptr; +} + NodeList* Node::childNodes() { auto* this_node = DynamicTo(this); if (this_node) @@ -35,6 +38,11 @@ NodeList* Node::childNodes() { return EnsureData().EnsureEmptyChildNodeList(*this); } +EventTargetData* Node::GetEventTargetData() { + return nullptr; +} +EventTargetData& Node::EnsureEventTargetData() {} + NodeData& Node::CreateData() { data_ = std::make_unique(); return *Data(); @@ -122,15 +130,14 @@ bool Node::isEqualNode(Node* other, ExceptionState& exception_state) const { if (nodeValue() != other->nodeValue()) return false; -// if (auto* this_attr = DynamicTo(this)) { -// auto* other_attr = To(other); -// if (this_attr->localName() != other_attr->localName()) -// return false; -// -// if (this_attr->namespaceURI() != other_attr->namespaceURI()) -// return false; -// } else - + // if (auto* this_attr = DynamicTo(this)) { + // auto* other_attr = To(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(this)) { auto* other_element = DynamicTo(other); @@ -173,8 +180,8 @@ AtomicString Node::textContent(bool convert_brs_to_newlines) const { return character_data->data(); // Attribute nodes have their attribute values as textContent. -// if (auto* attr = DynamicTo(this)) -// return attr->value(); + // if (auto* attr = DynamicTo(this)) + // return attr->value(); // Documents and non-container nodes (that are not CharacterData) // have null textContent. diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index b90e0d2562..f5164287e5 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -61,7 +61,7 @@ class Node : public EventTarget { // DOM methods & attributes for Node bool HasTagName(const AtomicString&) const; virtual std::string nodeName() const = 0; - virtual std::string nodeValue() const; + virtual std::string nodeValue() const = 0; virtual void setNodeValue(const AtomicString&, ExceptionState&); virtual NodeType nodeType() const = 0; @@ -287,6 +287,7 @@ class Node : public EventTarget { void SetTreeScope(TreeScope* scope) { tree_scope_ = scope; } Node(Document*, ConstructionType); + Node() = delete; private: uint32_t node_flags_; diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index 8aa14299f1..431691d3be 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -4,9 +4,9 @@ #include "node_data.h" #include "bindings/qjs/garbage_collected.h" -#include "child_node_list.h" +#include "ng/child_node_list.h" +#include "ng/empty_node_list.h" #include "container_node.h" -#include "empty_node_list.h" namespace kraken { diff --git a/bridge/core/dom/text.cc b/bridge/core/dom/text.cc index 95d4cdab31..985e4d7c9e 100644 --- a/bridge/core/dom/text.cc +++ b/bridge/core/dom/text.cc @@ -10,6 +10,15 @@ Text* Text::Create(Document& document, const AtomicString& value) { return MakeGarbageCollected(document, value, ConstructionType::kCreateText); } +Text* Text::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected(*context->document(), AtomicString::Empty(context->ctx()), + ConstructionType::kCreateText); +} + +Text* Text::Create(ExecutingContext* context, const AtomicString& value, ExceptionState& executing_context) { + return MakeGarbageCollected(*context->document(), value, ConstructionType::kCreateText); +} + Node::NodeType Text::nodeType() const { return Node::kTextNode; } 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 index 473f96ce13..28f7779f47 100644 --- a/bridge/core/dom/text.h +++ b/bridge/core/dom/text.h @@ -16,6 +16,8 @@ class Text : public CharacterData { 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(Document& document, const AtomicString& data, ConstructionType type) : CharacterData(document, data, type) {} diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 2ee4a4119e..cd3f621af0 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -299,7 +299,7 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { } options.wrapperTypeInfoInit = ` -const WrapperTypeInfo QJS${getClassName(blob)}::wrapper_type_info_ {JS_CLASS_${getClassName(blob).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ${object.construct ? `QJS${getClassName(blob)}::ConstructorCallback` : 'nullptr'}}; +const WrapperTypeInfo QJS${getClassName(blob)}::wrapper_type_info_ {JS_CLASS_${_.snakeCase(getClassName(blob)).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ${object.construct ? `QJS${getClassName(blob)}::ConstructorCallback` : 'nullptr'}}; const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::wrapper_type_info_;`; return _.template(readTemplate('interface'))({ className: getClassName(blob), From 80b3f99272dea4e41b7b3964db4a30a14e8b29f1 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Thu, 14 Apr 2022 21:38:57 +0800 Subject: [PATCH 080/498] feat: support generate element factory and html names. feat: add string view. --- bridge/CMakeLists.txt | 6 + bridge/bindings/qjs/atomic_string.cc | 20 +- bridge/bindings/qjs/atomic_string.h | 9 +- bridge/bindings/qjs/atomic_string_test.cc | 4 +- bridge/bindings/qjs/native_string_utils.cc | 5 + bridge/bindings/qjs/native_string_utils.h | 9 +- bridge/bindings/qjs/script_value.cc | 13 +- bridge/core/built_in_string.json | 7 +- bridge/core/dom/binding_call_methods.json | 7 +- bridge/core/dom/document.cc | 50 ++ bridge/core/dom/document.h | 5 + bridge/core/dom/element.cc | 167 +++--- bridge/core/dom/element.h | 11 +- bridge/core/dom/events/event_target_test.cc | 514 +++++++++--------- bridge/core/dom/legacy/element_attributes.cc | 8 +- bridge/core/events/event_type_names.json | 7 +- bridge/core/html/html_div_element.cc | 12 + bridge/core/html/html_div_element.h | 24 + bridge/core/html/html_element.cc | 4 +- bridge/core/html/html_element.h | 10 +- bridge/core/html/html_tags.json | 168 ++++++ bridge/core/html_element_factory.cc | 144 +++++ bridge/core/html_element_factory.h | 24 + bridge/core/html_names.cc | 6 - bridge/core/html_names.h | 11 - bridge/foundation/ascii_types.h | 67 +++ bridge/foundation/string_view.cc | 16 + bridge/foundation/string_view.h | 53 ++ .../code_generator/bin/code_generator.js | 8 +- .../code_generator/src/json/generator.ts | 8 +- .../json_templates/element_factory.cc.tpl | 97 ++++ .../json_templates/element_factory.h.tpl | 23 + .../static/json_templates/make_names.cc.tpl | 3 +- .../static/json_templates/make_names.h.tpl | 12 +- bridge/test/kraken_test_env.cc | 1 + 35 files changed, 1138 insertions(+), 395 deletions(-) create mode 100644 bridge/core/html/html_div_element.cc create mode 100644 bridge/core/html/html_div_element.h create mode 100644 bridge/core/html/html_tags.json create mode 100644 bridge/core/html_element_factory.cc create mode 100644 bridge/core/html_element_factory.h delete mode 100644 bridge/core/html_names.cc delete mode 100644 bridge/core/html_names.h create mode 100644 bridge/foundation/ascii_types.h create mode 100644 bridge/foundation/string_view.cc create mode 100644 bridge/foundation/string_view.h create mode 100644 bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl create mode 100644 bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index ba2b9d48a2..8df9b40971 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -94,6 +94,8 @@ list(APPEND BRIDGE_SOURCE foundation/inspector_task_queue.cc foundation/task_queue.cc foundation/task_queue.h + foundation/string_view.cc + foundation/string_view.h foundation/native_value.cc foundation/native_value.h foundation/native_type.h @@ -247,6 +249,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/page.cc core/executing_context_data.cc core/executing_context_data.h + core/html_element_factory.cc + core/html_element_factory.h core/dart_methods.h core/fileapi/blob.h core/fileapi/blob.cc @@ -321,6 +325,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/html/html_collection.h core/html/html_element.cc core/html/html_element.h + core/html/html_div_element.cc + core/html/html_div_element.h core/html/html_template_element.cc core/html/html_template_element.h # Legacy implements, should remove them in the future. diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index f896c7d2cc..935e98d5dd 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -47,9 +47,17 @@ AtomicString::StringKind GetStringKind(JSValue stringValue) { } // 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()) {} + : runtime_(JS_GetRuntime(ctx)), + ctx_(ctx), + atom_(JS_NewAtom(ctx, string.c_str())), + kind_(GetStringKind(string)), + length_(string.size()) {} AtomicString::AtomicString(JSContext* ctx, JSValue value) - : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)), kind_(GetStringKind(value)), length_(JS_VALUE_GET_STRING(value)->len) { + : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)) { + if (JS_IsString(value)) { + kind_ = GetStringKind(value); + length_ = JS_VALUE_GET_STRING(value)->len; + } } bool AtomicString::IsNull() const { @@ -75,6 +83,14 @@ std::unique_ptr AtomicString::ToNativeString() const { 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_); diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 4a09c7b45c..695785c2fe 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -9,6 +9,7 @@ #include #include #include "foundation/macros.h" +#include "foundation/string_view.h" #include "foundation/native_string.h" #include "native_string_utils.h" #include "qjs_engine_patch.h" @@ -20,6 +21,7 @@ namespace kraken { // identical. Comparing two AtomicString instances is much faster than comparing // two String instances because we just check string storage identity. class AtomicString { + KRAKEN_DISALLOW_NEW(); public: enum class StringKind { kIsLowerCase, kIsUpperCase, kIsMixed }; @@ -36,7 +38,10 @@ class AtomicString { ~AtomicString() { JS_FreeAtomRT(runtime_, atom_); }; // Return the undefined string value from atom key. - JSValue ToQuickJS(JSContext* ctx) const { return JS_AtomToValue(ctx, atom_); }; + JSValue ToQuickJS(JSContext* ctx) const { + assert(ctx_ != nullptr); + return JS_AtomToValue(ctx, atom_); + }; bool IsNull() const; bool IsEmpty() const; @@ -48,6 +53,8 @@ class AtomicString { [[nodiscard]] std::string ToStdString() const; [[nodiscard]] std::unique_ptr ToNativeString() const; + StringView ToStringView() const; + AtomicString ToUpperIfNecessary() const; const AtomicString ToUpperSlow() const; diff --git a/bridge/bindings/qjs/atomic_string_test.cc b/bridge/bindings/qjs/atomic_string_test.cc index 1964d20564..997f53ba4b 100644 --- a/bridge/bindings/qjs/atomic_string_test.cc +++ b/bridge/bindings/qjs/atomic_string_test.cc @@ -90,7 +90,7 @@ TEST(AtomicString, CopyAssignment) { struct P { AtomicString str; }; - P p; + P p{AtomicString::Empty(ctx)}; p.str = str; EXPECT_EQ(p.str == str, true); }); @@ -106,7 +106,7 @@ TEST(AtomicString, MoveAssignment) { TEST(AtomicString, CopyToRightReference) { TestAtomicString([](JSContext* ctx) { - AtomicString str; + AtomicString str = AtomicString::Empty(ctx); if (1 + 1 == 2) { str = AtomicString(ctx, "helloworld"); } diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index 7b2f569fdd..593cfd88fc 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -35,6 +35,11 @@ std::unique_ptr stringToNativeString(const std::string& string) { 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); diff --git a/bridge/bindings/qjs/native_string_utils.h b/bridge/bindings/qjs/native_string_utils.h index 309c734e8e..0731ec811d 100644 --- a/bridge/bindings/qjs/native_string_utils.h +++ b/bridge/bindings/qjs/native_string_utils.h @@ -22,14 +22,7 @@ std::unique_ptr jsValueToNativeString(JSContext* ctx, JSValue valu // 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); +std::string nativeStringToStdString(const NativeString* native_string); template std::string toUTF8(const std::basic_string, std::allocator>& source) { diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index b951c555cd..4509850895 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -65,15 +65,12 @@ JSValue ScriptValue::QJSValue() const { } ScriptValue ScriptValue::ToJSONStringify(ExceptionState* exception) const { - JSValue stringifyedValue = JS_JSONStringify(ctx_, value_, JS_NULL, JS_NULL); - ScriptValue result = ScriptValue(ctx_); + ScriptValue result = ScriptValue(ctx_, JS_JSONStringify(ctx_, value_, 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)) { - exception->ThrowException(ctx_, stringifyedValue); - } else { - result = ScriptValue(ctx_, stringifyedValue); + if (result.IsException()) { + exception->ThrowException(ctx_, result.value_); + result = ScriptValue::Empty(ctx_); } - JS_FreeValue(ctx_, stringifyedValue); return result; } @@ -108,7 +105,7 @@ NativeValue ScriptValue::ToNative() const { // auto* functionContext = new NativeFunctionContext{context, value_}; // return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); // } -// +// else if (JS_IsObject(value_)) { // auto* context = static_cast(JS_GetContextOpaque(ctx_)); // auto* context = static_cast(JS_GetContextOpaque(ctx)); diff --git a/bridge/core/built_in_string.json b/bridge/core/built_in_string.json index 3dee453081..0134d23d7e 100644 --- a/bridge/core/built_in_string.json +++ b/bridge/core/built_in_string.json @@ -1,6 +1,11 @@ { "metadata": { - "templates": ["make_names"] + "templates": [ + { + "template": "make_names", + "filename": "built_in_string" + } + ] }, "data": [ ["null", "null"], diff --git a/bridge/core/dom/binding_call_methods.json b/bridge/core/dom/binding_call_methods.json index 33a21c6044..043eb7c2de 100644 --- a/bridge/core/dom/binding_call_methods.json +++ b/bridge/core/dom/binding_call_methods.json @@ -1,6 +1,11 @@ { "metadata": { - "templates": ["make_names"] + "templates": [ + { + "template": "make_names", + "filename": "binding_call_methods" + } + ] }, "data": [ "click", diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 884abda752..6ba8d9b4c8 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -4,11 +4,61 @@ */ #include "document.h" +#include "foundation/ascii_types.h" namespace kraken { +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; + } + + +} + Text* Document::createTextNode(const AtomicString& value) { return nullptr; } +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; +} + } // namespace kraken diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index a16a0d0c08..c174e5a6ba 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -22,6 +22,7 @@ class Document : public Node, TreeScope { public: using ImplType = Document*; + Element* createElement(const AtomicString& name, ExceptionState& exception_state); Text* createTextNode(const AtomicString& value); void IncrementNodeCount() { node_count_++; } @@ -31,6 +32,10 @@ class Document : public Node, TreeScope { } int NodeCount() const { return node_count_; } + // The following implements the rule from HTML 4 for what valid names are. + static bool IsValidName(const AtomicString& name); + + private: int node_count_; }; diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 8e502468be..96b94e4974 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -8,14 +8,14 @@ #include "bindings/qjs/exception_state.h" #include "bindings/qjs/script_promise.h" #include "bindings/qjs/script_promise_resolver.h" -#include "foundation/native_value_converter.h" #include "core/fileapi/blob.h" #include "core/html/html_template_element.h" +#include "foundation/native_value_converter.h" namespace kraken { -Element::Element(Document* document, const AtomicString& tag_name, Node::ConstructionType construction_type) - : ContainerNode(document, construction_type), attributes_(ElementAttributes::Create(this)) {} +Element::Element(const AtomicString& tag_name, Document* document, Node::ConstructionType construction_type) + : ContainerNode(document, construction_type), tag_name_(tag_name), attributes_(ElementAttributes::Create(this)) {} bool Element::hasAttribute(const AtomicString& name, ExceptionState& exception_state) const { return attributes_->hasAttribute(name, exception_state); @@ -121,13 +121,24 @@ std::string Element::nodeValue() const { return ""; } +std::string Element::nodeName() const { + return tag_name_.ToStdString(); +} + bool Element::HasEquivalentAttributes(const Element& other) const { return other.attributes_->IsEquivalent(*attributes_); } +Node* Element::Clone(Document& factory, CloneChildrenFlag flag) const { + return nullptr; +} + class ElementSnapshotReader { public: - ElementSnapshotReader(ExecutingContext* context, Element* element, ScriptPromiseResolver* resolver, double device_pixel_ratio) + ElementSnapshotReader(ExecutingContext* context, + Element* element, + ScriptPromiseResolver* resolver, + double device_pixel_ratio) : context_(context), element_(element), resolver_(resolver), device_pixel_ratio_(device_pixel_ratio) { Start(); }; @@ -156,7 +167,8 @@ void ElementSnapshotReader::Start() { delete reader; }; - context_->dartMethodPtr()->toBlob(this, context_->contextId(), callback, element_->eventTargetId(), device_pixel_ratio_); + context_->dartMethodPtr()->toBlob(this, context_->contextId(), callback, element_->eventTargetId(), + device_pixel_ratio_); } void ElementSnapshotReader::HandleSnapshot(uint8_t* bytes, int32_t length) { @@ -183,69 +195,75 @@ ScriptPromise Element::toBlob(double device_pixel_ratio, ExceptionState& excepti double Element::clientHeight() const { ExceptionState exception_state; - return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientHeight, exception_state)); + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kclientHeight, exception_state)); } double Element::clientWidth() const { ExceptionState exception_state; - return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientWidth, exception_state)); + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kclientWidth, exception_state)); } double Element::clientLeft() const { ExceptionState exception_state; - return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientLeft, exception_state)); + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kclientLeft, exception_state)); } double Element::clientTop() const { ExceptionState exception_state; - return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientTop, exception_state)); + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kclientTop, exception_state)); } double Element::scrollTop() const { ExceptionState exception_state; - return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kscrollTop, exception_state)); + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kscrollTop, exception_state)); } void Element::setScrollTop(double v, ExceptionState& exception_state) { - SetBindingProperty(binding_call_methods::kscrollTop, NativeValueConverter::ToNativeValue(v), exception_state); + SetBindingProperty(binding_call_methods::kscrollTop, NativeValueConverter::ToNativeValue(v), + exception_state); } double Element::scrollLeft() const { ExceptionState exception_state; - return NativeValueConverter::FromNativeValue(GetBindingProperty(binding_call_methods::kclientTop, exception_state)); + return NativeValueConverter::FromNativeValue( + GetBindingProperty(binding_call_methods::kclientTop, exception_state)); } void Element::setScrollLeft(double v, ExceptionState& exception_state) { - SetBindingProperty(binding_call_methods::kscrollLeft, NativeValueConverter::ToNativeValue(v), exception_state); + SetBindingProperty(binding_call_methods::kscrollLeft, NativeValueConverter::ToNativeValue(v), + exception_state); } std::string Element::outerHTML() const { -// std::string s = "<" + tag_name_.ToStdString(); -// -// // Read attributes -// std::string attributes = 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; -} - -void Element::setOuterHTML(const AtomicString& value, ExceptionState& exception_state) { - -} + // std::string s = "<" + tag_name_.ToStdString(); + // + // // Read attributes + // std::string attributes = 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; +} + +void Element::setOuterHTML(const AtomicString& value, ExceptionState& exception_state) {} std::string Element::innerHTML() const { std::string s; @@ -253,57 +271,48 @@ std::string Element::innerHTML() const { // If Element is TemplateElement, the innerHTML content is the content of documentFragment. const Node* parent = DynamicTo(this); -// if (auto* template_element = DynamicTo(this)) { -// parent = DynamicTo(template_element->content()); -// } + // if (auto* template_element = DynamicTo(this)) { + // parent = DynamicTo(template_element->content()); + // } -// TODO: add innerHTML support. -// // 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; + // TODO: add innerHTML support. + // // 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 Element::setInnerHTML(const AtomicString& value, ExceptionState& exception_state) { +void Element::setInnerHTML(const AtomicString& value, ExceptionState& exception_state) {} -} - -void Element::_notifyNodeRemoved(Node* node) { - -} - -void Element::_notifyChildRemoved() { +void Element::_notifyNodeRemoved(Node* node) {} -} +void Element::_notifyChildRemoved() {} -void Element::_notifyNodeInsert(Node* insertNode) { +void Element::_notifyNodeInsert(Node* insertNode){ }; -void Element::_notifyChildInsert() { +void Element::_notifyChildInsert() {} -} +void Element::_didModifyAttribute(const AtomicString& name, const AtomicString& oldId, const AtomicString& newId) {} -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; } - -void Element::_beforeUpdateId(JSValue oldIdValue, JSValue newIdValue) { - -} - } // namespace kraken diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 9ce61ec3cf..7450ac685d 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -18,7 +18,7 @@ class Element : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); public: - Element(Document* document, const AtomicString& tag_name, ConstructionType = kCreateElement); + Element(const AtomicString& tag_name, Document* document, ConstructionType = kCreateElement); ElementAttributes* attributes() const { return attributes_; } @@ -62,12 +62,19 @@ class Element : public ContainerNode { std::string nodeValue() const override; AtomicString tagName() const { return tag_name_; } + std::string nodeName() const override; + + NodeType nodeType() const override; bool HasEquivalentAttributes(const Element& other) const; protected: private: + // Clone is private so that non-virtual CloneElementWithChildren and + // CloneElementWithoutChildren are used inst + Node* Clone(Document&, CloneChildrenFlag) const; + void _notifyNodeRemoved(Node* node); void _notifyChildRemoved(); void _notifyNodeInsert(Node* insertNode); @@ -76,7 +83,7 @@ class Element : public ContainerNode { void _beforeUpdateId(JSValue oldIdValue, JSValue newIdValue); ElementAttributes* attributes_{nullptr}; - AtomicString tag_name_; + AtomicString tag_name_ = AtomicString::Empty(ctx()); }; } // namespace kraken diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index ceb88fff4a..18541f6bb8 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -29,260 +29,260 @@ TEST(EventTarget, addEventListener) { EXPECT_EQ(errorCalled, false); } -TEST(EventTarget, removeEventListener) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); " - "div.removeEventListener('click', f); div.dispatchEvent(new Event('click'));"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(logCalled, false); -} - -TEST(EventTarget, setNoEventTargetProperties) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "{name: 1}"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div'); div._a = { name: 1}; console.log(div._a); " - "document.body.appendChild(div);"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); -} - -TEST(EventTarget, propertyEventHandler) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "ƒ () 1234"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div'); " - "div.onclick = function() { return 1234; };" - "document.body.appendChild(div);" - "let f = div.onclick;" - "console.log(f, div.onclick());"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, setUnExpectedAttributeEventHandler) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = false; - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div'); " - "div.onclick = function() { return 1234; };" - "document.body.appendChild(div);" - "div.onclick = undefined;" - "div.click()"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, false); -} - -TEST(EventTarget, propertyEventOnWindow) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "1234"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "window.onclick = function() { console.log(1234); };" - "window.dispatchEvent(new Event('click'));"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, asyncFunctionCallback) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "done"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - std::string code = R"( - const img = document.createElement('img'); - img.style.width = '100px'; - img.style.height = '100px'; - img.src = "assets/kraken.png"; - document.body.appendChild(img); - const img2 = img.cloneNode(false); - document.body.appendChild(img2); - - let anotherImgHasLoad = false; - async function loadImg() { - if (anotherImgHasLoad) { - console.log('done'); - } else { - anotherImgHasLoad = true; - } - } - - img.addEventListener('load', loadImg); - img2.addEventListener('load', loadImg); - - img.dispatchEvent(new Event('load')); - img2.dispatchEvent(new Event('load')); -)"; - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, ClassInheritEventTarget) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "ƒ () ƒ ()"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - std::string code = std::string(R"( -class Sample extends EventTarget { - constructor() { - super(); - } -} - -let s = new Sample(); -console.log(s.addEventListener, s.removeEventListener) -)"); - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, wontLeakWithStringProperty) { - auto bridge = TEST_init(); - std::string code = - "var img = new Image();\n" - "img.any = '1234'"; - bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); -} - -TEST(EventTarget, dispatchEventOnGC) { - using namespace kraken; - - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "1234"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); - std::string code = std::string(R"( -{ -// Wrap div in a block scope will be freed by GC -let div = document.createElement('div'); -} -window.onclick = () => {console.log(1234);} - -setTimeout(() => {}); -)"); - - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - - static auto* window = static_cast(JS_GetOpaque(context->global(), 1)); - static int32_t contextId = context->getContextId(); - - TEST_registerEventTargetDisposedCallback(context->uniqueId, [](EventTargetInstance* eventTargetInstance) { - // Check to not crash when trigger click on disposed eventTarget - TEST_dispatchEvent(contextId, eventTargetInstance, "click"); - - // Check to not crash when trigger event on any eventTarget. - TEST_dispatchEvent(contextId, window, "click"); - }); - - // Run gc to trigger eventTarget been disposed by GC. - JS_RunGC(context->runtime()); - - TEST_runLoop(context); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, globalBindListener) { - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "clicked"); - }; - auto bridge = TEST_init(); - std::string code = "addEventListener('click', () => {console.log('clicked'); }); dispatchEvent(new Event('click'))"; - bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); - EXPECT_EQ(logCalled, true); -} - -TEST(EventTarget, shouldKeepAtom) { - auto bridge = TEST_init(); - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "2"); - }; - std::string code = "addEventListener('click', () => {console.log(1)});"; - bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); - JS_RunGC(bridge->getContext()->runtime()); - - std::string code2 = "addEventListener('appear', () => {console.log(2)});"; - bridge->evaluateScript(code2.c_str(), code2.size(), "internal://", 0); - - JS_RunGC(bridge->getContext()->runtime()); - - std::string code3 = "(function() { var eeee = new Event('appear'); dispatchEvent(eeee); } )();"; - bridge->evaluateScript(code3.c_str(), code3.size(), "internal://", 0); - EXPECT_EQ(logCalled, true); -} +//TEST(EventTarget, removeEventListener) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->getContext(); +// const char* code = +// "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); " +// "div.removeEventListener('click', f); div.dispatchEvent(new Event('click'));"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(logCalled, false); +//} +// +//TEST(EventTarget, setNoEventTargetProperties) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "{name: 1}"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// +// auto context = bridge->getContext(); +// const char* code = +// "let div = document.createElement('div'); div._a = { name: 1}; console.log(div._a); " +// "document.body.appendChild(div);"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// EXPECT_EQ(errorCalled, false); +//} +// +//TEST(EventTarget, propertyEventHandler) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "ƒ () 1234"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->getContext(); +// const char* code = +// "let div = document.createElement('div'); " +// "div.onclick = function() { return 1234; };" +// "document.body.appendChild(div);" +// "let f = div.onclick;" +// "console.log(f, div.onclick());"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(EventTarget, setUnExpectedAttributeEventHandler) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = false; +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->getContext(); +// const char* code = +// "let div = document.createElement('div'); " +// "div.onclick = function() { return 1234; };" +// "document.body.appendChild(div);" +// "div.onclick = undefined;" +// "div.click()"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, false); +//} +// +//TEST(EventTarget, propertyEventOnWindow) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "1234"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->getContext(); +// const char* code = +// "window.onclick = function() { console.log(1234); };" +// "window.dispatchEvent(new Event('click'));"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(EventTarget, asyncFunctionCallback) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "done"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->getContext(); +// std::string code = R"( +// const img = document.createElement('img'); +// img.style.width = '100px'; +// img.style.height = '100px'; +// img.src = "assets/kraken.png"; +// document.body.appendChild(img); +// const img2 = img.cloneNode(false); +// document.body.appendChild(img2); +// +// let anotherImgHasLoad = false; +// async function loadImg() { +// if (anotherImgHasLoad) { +// console.log('done'); +// } else { +// anotherImgHasLoad = true; +// } +// } +// +// img.addEventListener('load', loadImg); +// img2.addEventListener('load', loadImg); +// +// img.dispatchEvent(new Event('load')); +// img2.dispatchEvent(new Event('load')); +//)"; +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(EventTarget, ClassInheritEventTarget) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "ƒ () ƒ ()"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->getContext(); +// std::string code = std::string(R"( +//class Sample extends EventTarget { +// constructor() { +// super(); +// } +//} +// +//let s = new Sample(); +//console.log(s.addEventListener, s.removeEventListener) +//)"); +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(EventTarget, wontLeakWithStringProperty) { +// auto bridge = TEST_init(); +// std::string code = +// "var img = new Image();\n" +// "img.any = '1234'"; +// bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); +//} +// +//TEST(EventTarget, dispatchEventOnGC) { +// using namespace kraken; +// +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "1234"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); +// auto context = bridge->getContext(); +// std::string code = std::string(R"( +//{ +//// Wrap div in a block scope will be freed by GC +//let div = document.createElement('div'); +//} +//window.onclick = () => {console.log(1234);} +// +//setTimeout(() => {}); +//)"); +// +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// +// static auto* window = static_cast(JS_GetOpaque(context->global(), 1)); +// static int32_t contextId = context->getContextId(); +// +// TEST_registerEventTargetDisposedCallback(context->uniqueId, [](EventTargetInstance* eventTargetInstance) { +// // Check to not crash when trigger click on disposed eventTarget +// TEST_dispatchEvent(contextId, eventTargetInstance, "click"); +// +// // Check to not crash when trigger event on any eventTarget. +// TEST_dispatchEvent(contextId, window, "click"); +// }); +// +// // Run gc to trigger eventTarget been disposed by GC. +// JS_RunGC(context->runtime()); +// +// TEST_runLoop(context); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(EventTarget, globalBindListener) { +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "clicked"); +// }; +// auto bridge = TEST_init(); +// std::string code = "addEventListener('click', () => {console.log('clicked'); }); dispatchEvent(new Event('click'))"; +// bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(EventTarget, shouldKeepAtom) { +// auto bridge = TEST_init(); +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "2"); +// }; +// std::string code = "addEventListener('click', () => {console.log(1)});"; +// bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); +// JS_RunGC(bridge->getContext()->runtime()); +// +// std::string code2 = "addEventListener('appear', () => {console.log(2)});"; +// bridge->evaluateScript(code2.c_str(), code2.size(), "internal://", 0); +// +// JS_RunGC(bridge->getContext()->runtime()); +// +// std::string code3 = "(function() { var eeee = new Event('appear'); dispatchEvent(eeee); } )();"; +// bridge->evaluateScript(code3.c_str(), code3.size(), "internal://", 0); +// EXPECT_EQ(logCalled, true); +//} diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index c98235330c..be9169796b 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -9,7 +9,7 @@ namespace kraken { -static inline bool IsNumberIndex(const std::string& name) { +static inline bool IsNumberIndex(const std::string_view& name) { if (name.empty()) return false; char f = name[0]; @@ -19,7 +19,7 @@ static inline bool IsNumberIndex(const std::string& name) { ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()) {} AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { - bool numberIndex = IsNumberIndex(name.ToStdString()); + bool numberIndex = IsNumberIndex(name.ToStringView8()); if (numberIndex) { AtomicString::Empty(ctx()); @@ -31,7 +31,7 @@ AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { bool ElementAttributes::setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { - bool numberIndex = IsNumberIndex(name.ToStdString()); + bool numberIndex = IsNumberIndex(name.ToStringView8()); if (numberIndex) { exception_state.ThrowException( @@ -51,7 +51,7 @@ bool ElementAttributes::setAttribute(const AtomicString& name, } bool ElementAttributes::hasAttribute(const AtomicString& name, ExceptionState& exception_state) { - bool numberIndex = IsNumberIndex(name.ToStdString()); + bool numberIndex = IsNumberIndex(name.ToStringView8()); if (numberIndex) { return false; diff --git a/bridge/core/events/event_type_names.json b/bridge/core/events/event_type_names.json index bd242b2b3c..6b1d1756da 100644 --- a/bridge/core/events/event_type_names.json +++ b/bridge/core/events/event_type_names.json @@ -1,7 +1,12 @@ { "annotation": "Simplified from https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/events/event_type_names.json5", "metadata": { - "templates": ["make_names"] + "templates": [ + { + "template": "make_names", + "filename": "event_type_names" + } + ] }, "data": [ "DOMActivate", diff --git a/bridge/core/html/html_div_element.cc b/bridge/core/html/html_div_element.cc new file mode 100644 index 0000000000..cc505278c1 --- /dev/null +++ b/bridge/core/html/html_div_element.cc @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "html_div_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLDivElement::HTMLDivElement(Document& document) : HTMLElement(html_names::kdiv, &document){} + +} diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h new file mode 100644 index 0000000000..33fbbfee7b --- /dev/null +++ b/bridge/core/html/html_div_element.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ + +#include "html_element.h" + +namespace kraken { + +class HTMLDivElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + public: + + explicit HTMLDivElement(Document&); + + private: + +}; + +} + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ diff --git a/bridge/core/html/html_element.cc b/bridge/core/html/html_element.cc index 60d4da607f..faff21dfe0 100644 --- a/bridge/core/html/html_element.cc +++ b/bridge/core/html/html_element.cc @@ -4,4 +4,6 @@ #include "html_element.h" -namespace kraken {} +namespace kraken { + +} // namespace kraken diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index 885676830d..74e0e36ae9 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -11,11 +11,19 @@ namespace kraken { class HTMLElement : public Element { DEFINE_WRAPPERTYPEINFO(); - public: + HTMLElement(const AtomicString& tag_name, Document* document, ConstructionType); + private: }; +inline HTMLElement::HTMLElement(const AtomicString& tag_name, + Document* document, + ConstructionType type = kCreateHTMLElement) + : Element(tag_name, document, type) { +} + + } // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_tags.json b/bridge/core/html/html_tags.json new file mode 100644 index 0000000000..44829f9b9a --- /dev/null +++ b/bridge/core/html/html_tags.json @@ -0,0 +1,168 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "html_names" + }, + { + "template": "element_factory", + "filename": "element_factory" + } + ] + }, + "data": [ + { + "name": "a", + "interfaceName": "HTMLAnchorElement" + }, + "area", + { + "name": "b", + "interfaceName": "HTMLElement" + }, + "base", + { + "name": "audio", + "interfaceHeaderDir": "core/html/media" + }, + "body", + { + "name": "br", + "interfaceName": "HTMLBRElement" + }, + { + "name": "button", + "interfaceHeaderDir": "core/html/forms" + }, + { + "name": "canvas", + "interfaceHeaderDir": "core/html/canvas" + }, + { + "name": "code", + "interfaceName": "HTMLElement" + }, + { + "name": "dd", + "interfaceName": "HTMLElement" + }, + "details", + "dialog", + "div", + { + "name": "em", + "interfaceName": "HTMLElement" + }, + "font", + { + "name": "form", + "interfaceHeaderDir": "core/html/forms" + }, + "frame", + { + "name": "h1", + "interfaceName": "HTMLHeadingElement" + }, + { + "name": "h2", + "interfaceName": "HTMLHeadingElement" + }, + { + "name": "h3", + "interfaceName": "HTMLHeadingElement" + }, + { + "name": "h4", + "interfaceName": "HTMLHeadingElement" + }, + { + "name": "h5", + "interfaceName": "HTMLHeadingElement" + }, + { + "name": "h6", + "interfaceName": "HTMLHeadingElement" + }, + "head", + { + "name": "header", + "interfaceName": "HTMLElement" + }, + { + "name": "hgroup", + "interfaceName": "HTMLElement" + }, + { + "name": "hr", + "interfaceName": "HTMLHRElement" + }, + "html", + { + "name": "i", + "interfaceName": "HTMLElement" + }, + { + "name": "iframe", + "interfaceName": "HTMLIFrameElement" + }, + { + "name": "image", + "interfaceName": "HTMLUnknownElement" + }, + { + "name": "img", + "interfaceName": "HTMLImageElement" + }, + { + "name": "input", + "interfaceHeaderDir": "core/html/forms" + }, + { + "name": "li", + "interfaceName": "HTMLLIElement" + }, + "link", + "map", + "menu", + { + "name": "p", + "interfaceName": "HTMLParagraphElement" + }, + "param", + { + "name": "popup", + "interfaceName": "HTMLPopupElement", + "runtimeEnabled": "HTMLPopupElement" + }, + "pre", + "script", + { + "name": "section", + "interfaceName": "HTMLElement" + }, + { + "name": "select", + "interfaceHeaderDir": "core/html/forms" + }, + "span", + { + "name": "strong", + "interfaceName": "HTMLElement" + }, + { + "name": "style" + }, + "template", + { + "name": "textarea", + "interfaceName": "HTMLTextAreaElement", + "interfaceHeaderDir": "core/html/forms" + }, + "title", + { + "name": "video", + "interfaceHeaderDir": "core/html/media" + } + ] +} diff --git a/bridge/core/html_element_factory.cc b/bridge/core/html_element_factory.cc new file mode 100644 index 0000000000..865bb0f7af --- /dev/null +++ b/bridge/core/html_element_factory.cc @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ +// Generated from template: +// code_generator/src/json/templates/element_factory.cc.tmp +// and input files: +// /Users/andycall/work/kraken_main/bridge/core/html/html_tags.json +#include "html_element_factory.h" +#include +#include "html_names.h" +#include "bindings/qjs/garbage_collected.h" +#include "core/html/html_a_element.h" +#include "core/html/html_area_element.h" +#include "core/html/html_b_element.h" +#include "core/html/html_base_element.h" +#include "core/html/media/html_audio_element.h" +#include "core/html/html_body_element.h" +#include "core/html/html_br_element.h" +#include "core/html/forms/html_button_element.h" +#include "core/html/canvas/html_canvas_element.h" +#include "core/html/html_code_element.h" +#include "core/html/html_dd_element.h" +#include "core/html/html_details_element.h" +#include "core/html/html_dialog_element.h" +#include "core/html/html_div_element.h" +#include "core/html/html_em_element.h" +#include "core/html/html_font_element.h" +#include "core/html/forms/html_form_element.h" +#include "core/html/html_frame_element.h" +#include "core/html/html_h1_element.h" +#include "core/html/html_h2_element.h" +#include "core/html/html_h3_element.h" +#include "core/html/html_h4_element.h" +#include "core/html/html_h5_element.h" +#include "core/html/html_h6_element.h" +#include "core/html/html_head_element.h" +#include "core/html/html_header_element.h" +#include "core/html/html_hgroup_element.h" +#include "core/html/html_hr_element.h" +#include "core/html/html_html_element.h" +#include "core/html/html_i_element.h" +#include "core/html/html_iframe_element.h" +#include "core/html/html_image_element.h" +#include "core/html/html_img_element.h" +#include "core/html/forms/html_input_element.h" +#include "core/html/html_li_element.h" +#include "core/html/html_link_element.h" +#include "core/html/html_map_element.h" +#include "core/html/html_menu_element.h" +#include "core/html/html_p_element.h" +#include "core/html/html_param_element.h" +#include "core/html/html_popup_element.h" +#include "core/html/html_pre_element.h" +#include "core/html/html_script_element.h" +#include "core/html/html_section_element.h" +#include "core/html/forms/html_select_element.h" +#include "core/html/html_span_element.h" +#include "core/html/html_strong_element.h" +#include "core/html/html_style_element.h" +#include "core/html/html_template_element.h" +#include "core/html/forms/html_textarea_element.h" +#include "core/html/html_title_element.h" +#include "core/html/media/html_video_element.h" +namespace kraken { +using HTMLConstructorFunction = HTMLElement* (*)(Document&); +using HTMLFunctionMap = std::unordered_map; +static HTMLFunctionMap* g_html_constructors = nullptr; +struct CreateHTMLFunctionMapData { + const AtomicString& tag; + HTMLConstructorFunction func; +}; + +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[] = { + {html_names::a, HTMLAnchorElementConstructor}, + {html_names::karea, HTMLAreaConstructor} + {html_names::b, HTMLElementConstructor} + {html_names::kbase, HTMLBaseConstructor} + {html_names::audio, HTMLAudioConstructor} + {html_names::kbody, HTMLBodyConstructor} + {html_names::br, HTMLBRElementConstructor} + {html_names::button, HTMLButtonConstructor} + {html_names::canvas, HTMLCanvasConstructor} + {html_names::code, HTMLElementConstructor} + {html_names::dd, HTMLElementConstructor} + {html_names::kdetails, HTMLDetailsConstructor} + {html_names::kdialog, HTMLDialogConstructor} + {html_names::kdiv, HTMLDivConstructor} + {html_names::em, HTMLElementConstructor} + {html_names::kfont, HTMLFontConstructor} + {html_names::form, HTMLFormConstructor} + {html_names::kframe, HTMLFrameConstructor} + {html_names::h1, HTMLHeadingElementConstructor} + {html_names::h2, HTMLHeadingElementConstructor} + {html_names::h3, HTMLHeadingElementConstructor} + {html_names::h4, HTMLHeadingElementConstructor} + {html_names::h5, HTMLHeadingElementConstructor} + {html_names::h6, HTMLHeadingElementConstructor} + {html_names::khead, HTMLHeadConstructor} + {html_names::header, HTMLElementConstructor} + {html_names::hgroup, HTMLElementConstructor} + {html_names::hr, HTMLHRElementConstructor} + {html_names::khtml, HTMLHtmlConstructor} + {html_names::i, HTMLElementConstructor} + {html_names::iframe, HTMLIFrameElementConstructor} + {html_names::image, HTMLUnknownElementConstructor} + {html_names::img, HTMLImageElementConstructor} + {html_names::input, HTMLInputConstructor} + {html_names::li, HTMLLIElementConstructor} + {html_names::klink, HTMLLinkConstructor} + {html_names::kmap, HTMLMapConstructor} + {html_names::kmenu, HTMLMenuConstructor} + {html_names::p, HTMLParagraphElementConstructor} + {html_names::kparam, HTMLParamConstructor} + {html_names::popup, HTMLPopupElementConstructor} + {html_names::kpre, HTMLPreConstructor} + {html_names::kscript, HTMLScriptConstructor} + {html_names::section, HTMLElementConstructor} + {html_names::select, HTMLSelectConstructor} + {html_names::kspan, HTMLSpanConstructor} + {html_names::strong, HTMLElementConstructor} + {html_names::style, HTMLStyleConstructor} + {html_names::ktemplate, HTMLTemplateConstructor} + {html_names::textarea, HTMLTextAreaElementConstructor} + {html_names::ktitle, HTMLTitleConstructor} + {html_names::video, HTMLVideoConstructor} + }; + 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); +} +} // namespace kraken diff --git a/bridge/core/html_element_factory.h b/bridge/core/html_element_factory.h new file mode 100644 index 0000000000..61dba040fa --- /dev/null +++ b/bridge/core/html_element_factory.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ +#define KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace kraken { + +class Document; +class HTMLElement; + +class HTMLElementFactory { + public: + // If |local_name| is unknown, nullptr is returned. + static HTMLElement* Create(const AtomicString& local_name, Document&); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ diff --git a/bridge/core/html_names.cc b/bridge/core/html_names.cc deleted file mode 100644 index a7d678d821..0000000000 --- a/bridge/core/html_names.cc +++ /dev/null @@ -1,6 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "html_names.h" diff --git a/bridge/core/html_names.h b/bridge/core/html_names.h deleted file mode 100644 index 243244de8e..0000000000 --- a/bridge/core/html_names.h +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_CORE_HTML_NAMES_H_ -#define KRAKENBRIDGE_CORE_HTML_NAMES_H_ - -namespace kraken {} - -#endif // KRAKENBRIDGE_CORE_HTML_NAMES_H_ diff --git a/bridge/foundation/ascii_types.h b/bridge/foundation/ascii_types.h new file mode 100644 index 0000000000..137b70e9ef --- /dev/null +++ b/bridge/foundation/ascii_types.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + + +#ifndef KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ +#define KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ + +namespace kraken { + +template +inline bool IsASCII(CharType c) { + return !(c & ~0x7F); +} + +template +inline bool IsASCIIAlpha(CharType c) { + return (c | 0x20) >= 'a' && (c | 0x20) <= 'z'; +} + +template +inline bool IsASCIIDigit(CharType c) { + return c >= '0' && c <= '9'; +} + +template +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 +inline bool IsASCIISpace(CharType c) { + return c <= ' ' && (c == ' ' || (c <= 0xD && c >= 0x9)); +} + +template +inline bool IsASCIIUpper(CharType c) { + return c >= 'A' && c <= 'Z'; +} + +template +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; +} + +} + +#endif // KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ diff --git a/bridge/foundation/string_view.cc b/bridge/foundation/string_view.cc new file mode 100644 index 0000000000..1fbb9ff313 --- /dev/null +++ b/bridge/foundation/string_view.cc @@ -0,0 +1,16 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "string_view.h" + +namespace kraken { + +StringView::StringView(const std::string& string) : bytes_(string.data()), length_(string.length()), is_8bit_(true) {} + +StringView::StringView(const NativeString* string) + : bytes_(string->string()), length_(string->length()), is_8bit_(false) {} + +StringView::StringView(void* bytes, unsigned length, bool is_wide_char): bytes_(bytes), length_(length), is_8bit_(!is_wide_char) {} +} // namespace kraken diff --git a/bridge/foundation/string_view.h b/bridge/foundation/string_view.h new file mode 100644 index 0000000000..c1b578720c --- /dev/null +++ b/bridge/foundation/string_view.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ +#define KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ + +#include +#include "ascii_types.h" +#include "native_string.h" + +namespace kraken { + +class StringView final { + public: + + StringView() = delete; + + explicit StringView(const std::string& string); + explicit StringView(const NativeString* string); + explicit StringView(void* bytes, unsigned length, bool is_wide_char); + + bool Is8Bit() const { + return is_8bit_; + } + + bool IsLowerASCII() const { + if (is_8bit_) { + return kraken::IsLowerASCII(Characters8(), length()); + } + return kraken::IsLowerASCII(Characters16(), length()); + } + + const char* Characters8() const { + return static_cast(bytes_); + } + + const char16_t* Characters16() const { + return static_cast(bytes_); + } + + unsigned length() const{ return length_; } + + private: + const void* bytes_; + unsigned length_; + unsigned is_8bit_ : 1; +}; + +} + +#endif // KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 6ab6007a55..0d93504e62 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -77,12 +77,12 @@ function genCodeFromJSONData() { for (let i = 0; i < blobs.length; i ++) { let blob = blobs[i]; blob.json.metadata.templates.forEach((targetTemplate) => { - let targetTemplateHeaderData = templates.find(t => t.filename === targetTemplate + '.h'); - let targetTemplateBodyData = templates.find(t => t.filename === targetTemplate + '.cc'); + let targetTemplateHeaderData = templates.find(t => t.filename === targetTemplate.template + '.h'); + let targetTemplateBodyData = templates.find(t => t.filename === targetTemplate.template + '.cc'); + blob.filename = targetTemplate.filename; let result = generateJSONTemplate(blobs[i], targetTemplateHeaderData, targetTemplateBodyData); let dist = blob.dist; - - let genFilePath = path.join(dist, blob.filename); + let genFilePath = path.join(dist, targetTemplate.filename); fs.writeFileSync(genFilePath + '.h', result.header); result.source && fs.writeFileSync(genFilePath + '.cc', result.source); }); diff --git a/bridge/scripts/code_generator/src/json/generator.ts b/bridge/scripts/code_generator/src/json/generator.ts index cb8ae77a55..5588fe66c0 100644 --- a/bridge/scripts/code_generator/src/json/generator.ts +++ b/bridge/scripts/code_generator/src/json/generator.ts @@ -9,7 +9,9 @@ function generateHeader(blob: JSONBlob, template: JSONTemplate): string { name: blob.filename, template_path: blob.source, data: blob.json.data - }); + }).split('\n').filter(str => { + return str.trim().length > 0; + }).join('\n'); } function generateBody(blob: JSONBlob, template: JSONTemplate): string { @@ -18,7 +20,9 @@ function generateBody(blob: JSONBlob, template: JSONTemplate): string { template_path: blob.source, name: blob.filename, data: blob.json.data - }); + }).split('\n').filter(str => { + return str.trim().length > 0; + }).join('\n'); } export function generateJSONTemplate(blob: JSONBlob, headerTemplate: JSONTemplate, bodyTemplate?: JSONTemplate) { diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl new file mode 100644 index 0000000000..b5ec101fc3 --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + + // Generated from template: + // code_generator/src/json/templates/element_factory.cc.tmp + // and input files: + // <%= template_path %> + +#include "html_element_factory.h" +#include +#include "html_names.h" +#include "bindings/qjs/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.name %>_element.h" + <% } else { %> +#include "core/html/html_<%= item.name %>_element.h" + <% } %> + <% } %> +<% }); %> + + +namespace kraken { + +using HTMLConstructorFunction = HTMLElement* (*)(Document&); + +using HTMLFunctionMap = std::unordered_map; + +static HTMLFunctionMap* g_html_constructors = nullptr; + +struct CreateHTMLFunctionMapData { + const AtomicString& tag; + HTMLConstructorFunction func; +}; + + +<% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> + +static HTMLElement* HTML<%= item[0].toUpperCase() + item.slice(1) %>Constructor(Document& document) { + return MakeGarbageCollectedElement>(document); +} + <% } else if (_.isObject(item)) { %> + <% if (item.interfaceName) { %> +static HTMLElement* <%= item.interfaceName %>Constructor(Document& document) { + return MakeGarbageCollected<<%= item.interfaceName %>>(document); +} + <% } else { %> +static HTMLElement* HTML<%= item.name[0].toUpperCase() + item.name.slice(1) %>Constructor(Document& document) { + return MakeGarbageCollectedElement>(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<%= item[0].toUpperCase() + item.slice(1) %>Constructor}, + <% } else if (_.isObject(item)) { %> + <% if (item.interfaceName) { %> + {html_names::k<%= item.name %>, <%= item.interfaceName %>Constructor}, + <% } else { %> + {html_names::k<%= item.name %>, HTML<%= item.name[0].toUpperCase() + item.name.slice(1) %>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); +} + +} // namespace kraken diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl new file mode 100644 index 0000000000..dfc1da07bd --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ +#define KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace kraken { + +class Document; +class HTMLElement; + +class HTMLElementFactory { + public: + // If |local_name| is unknown, nullptr is returned. + static HTMLElement* Create(const AtomicString& local_name, Document&); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index 8ecb83a1ac..1cb4dea565 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -11,6 +11,7 @@ namespace <%= name %> { void* names_storage[kNamesCount * ((sizeof(AtomicString) + sizeof(void *) - 1) / sizeof(void *))]; <% _.forEach(data, function(name, index) { %><% if (_.isArray(name)) { %>const AtomicString& k<%= name[0] %> = reinterpret_cast(&names_storage)[<%= index %>]; +<% } else if (_.isObject(name)) { %>const AtomicString& k<%= name.name %> = reinterpret_cast(&names_storage)[<%= index %>]; <% } else { %>const AtomicString& k<%= name %> = reinterpret_cast(&names_storage)[<%= index %>];<% } %> <% }) %> @@ -20,7 +21,7 @@ void Init(JSContext* ctx) { }; static const NameEntry kNames[] = { - <% _.forEach(data, function(name) { %><% if (Array.isArray(name)) { %>{ "<%= name[1] %>" },<% } else { %>{ "<%= name %>" },<% } %> + <% _.forEach(data, function(name) { %><% if (Array.isArray(name)) { %>{ "<%= name[1] %>" },<% } else if(_.isObject(name)) { %>{ "<%= name.name %>" },<% } else { %>{ "<%= name %>" },<% } %> <% }); %> }; diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl index 504fcc1463..44fb2dbb49 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl @@ -12,9 +12,15 @@ namespace kraken { namespace <%= name %> { -<% _.forEach(data, function(name, index) { %><% if (_.isArray(name)) { %>extern const AtomicString& k<%= name[0] %>; -<% } else { %>extern const AtomicString& k<%= name %>; -<% } %><% }) %> +<% _.forEach(data, function(name, index) { %> + <% if (_.isArray(name)) { %> + extern const AtomicString& k<%= name[0] %>; + <% } else if (_.isObject(name)) { %> + extern const AtomicString& k<%= name.name %>; + <% } else { %> + extern const AtomicString& k<%= name %>; + <% } %> +<% }) %> constexpr unsigned kNamesCount = <%= data.length %>; diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index b8fa403932..ff61287e80 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -10,6 +10,7 @@ #include "core/frame/dom_timer.h" #include "core/page.h" #include "foundation/native_string.h" +#include "bindings/qjs/native_string_utils.h" #include "kraken_bridge_test.h" #include "kraken_test_env.h" From 7a161110683e13fa5a36b1fada4fb379682c0155 Mon Sep 17 00:00:00 2001 From: openkraken-bot Date: Thu, 14 Apr 2022 13:39:42 +0000 Subject: [PATCH 081/498] Committing clang-format changes --- bridge/bindings/qjs/atomic_string.h | 3 +- bridge/bindings/qjs/converter_impl.h | 8 +- bridge/bindings/qjs/native_string_utils.cc | 3 +- bridge/bindings/qjs/qjs_interface_bridge.cc | 4 +- bridge/bindings/qjs/qjs_interface_bridge.h | 2 +- bridge/bindings/qjs/script_value.cc | 12 +-- bridge/bindings/qjs/script_wrappable.h | 2 +- bridge/core/dom/container_node.cc | 18 ++-- bridge/core/dom/document.cc | 11 +-- bridge/core/dom/document.h | 1 - bridge/core/dom/document_fragment.cc | 15 ++-- bridge/core/dom/document_fragment.h | 2 +- bridge/core/dom/element.h | 1 - bridge/core/dom/events/event_target_test.cc | 38 ++++----- bridge/core/dom/legacy/element_attributes.h | 5 +- bridge/core/dom/node.h | 2 +- bridge/core/dom/node_data.cc | 2 +- bridge/core/html/html_div_element.cc | 4 +- bridge/core/html/html_div_element.h | 5 +- bridge/core/html/html_element.cc | 4 +- bridge/core/html/html_element.h | 5 +- bridge/core/html/html_template_element.cc | 6 +- bridge/core/html/html_template_element.h | 3 +- bridge/core/html_element_factory.cc | 94 ++++++++------------- bridge/foundation/ascii_types.h | 6 +- bridge/foundation/native_value_converter.cc | 2 +- bridge/foundation/string_view.cc | 3 +- bridge/foundation/string_view.h | 17 ++-- bridge/test/kraken_test_env.cc | 2 +- 29 files changed, 114 insertions(+), 166 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 695785c2fe..741c8ace87 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -9,8 +9,8 @@ #include #include #include "foundation/macros.h" -#include "foundation/string_view.h" #include "foundation/native_string.h" +#include "foundation/string_view.h" #include "native_string_utils.h" #include "qjs_engine_patch.h" @@ -22,6 +22,7 @@ namespace kraken { // two String instances because we just check string storage identity. class AtomicString { KRAKEN_DISALLOW_NEW(); + public: enum class StringKind { kIsLowerCase, kIsUpperCase, kIsMixed }; diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index bd3c4590f1..92a45624f1 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -20,12 +20,12 @@ #include "js_event_listener.h" #include "native_string_utils.h" #include "qjs_add_event_listener_options.h" +#include "qjs_element_attributes.h" #include "qjs_error_event_init.h" #include "qjs_event_init.h" #include "qjs_event_listener_options.h" #include "qjs_node.h" #include "qjs_scroll_to_options.h" -#include "qjs_element_attributes.h" namespace kraken { @@ -487,11 +487,9 @@ struct Converter : ConverterBase { } }; -template<> +template <> struct Converter : ConverterBase { - static JSValue ToValue(JSContext* ctx, ImplType value) { - return value->ToQuickJS(); - } + static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } }; }; // namespace kraken diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index 593cfd88fc..c8326788df 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -36,7 +36,8 @@ std::unique_ptr stringToNativeString(const std::string& string) { } std::string nativeStringToStdString(const NativeString* native_string) { - std::u16string u16EventType = std::u16string(reinterpret_cast(native_string->string()), native_string->length()); + std::u16string u16EventType = + std::u16string(reinterpret_cast(native_string->string()), native_string->length()); return toUTF8(u16EventType); } diff --git a/bridge/bindings/qjs/qjs_interface_bridge.cc b/bridge/bindings/qjs/qjs_interface_bridge.cc index 7f5fbe3213..8c6e207cef 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.cc +++ b/bridge/bindings/qjs/qjs_interface_bridge.cc @@ -6,6 +6,4 @@ #include "qjs_interface_bridge.h" #include "core/executing_context.h" -namespace kraken { - -} // namespace kraken +namespace kraken {} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index 1b5e13cf16..79da8d3079 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ -#include "script_wrappable.h" #include "core/executing_context.h" +#include "script_wrappable.h" namespace kraken { diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 4509850895..685d0a8536 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -100,12 +100,12 @@ NativeValue ScriptValue::ToNative() const { return NativeValueConverter::ToNativeValue(string); } -// else if (JS_IsFunction(ctx_, value_)) { -// auto* context = static_cast(JS_GetContextOpaque(ctx_)); -// auto* functionContext = new NativeFunctionContext{context, value_}; -// return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); -// } -// + // else if (JS_IsFunction(ctx_, value_)) { + // auto* context = static_cast(JS_GetContextOpaque(ctx_)); + // auto* functionContext = new NativeFunctionContext{context, value_}; + // return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); + // } + // else if (JS_IsObject(value_)) { // auto* context = static_cast(JS_GetContextOpaque(ctx_)); // auto* context = static_cast(JS_GetContextOpaque(ctx)); diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index fb86fa9e7b..6bb326a222 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -43,7 +43,7 @@ class ScriptWrappable : public GarbageCollected { // Returns the WrapperTypeInfo of the instance. virtual const WrapperTypeInfo* GetWrapperTypeInfo() const = 0; - void Trace(GCVisitor* visitor) const override {}; + void Trace(GCVisitor* visitor) const override{}; JSValue ToQuickJS(); ScriptValue ToValue(); diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 2ed88352cc..c1aca96839 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -10,7 +10,7 @@ namespace kraken { HTMLCollection* ContainerNode::Children() { - //TODO: add children implements. + // TODO: add children implements. return nullptr; } @@ -273,14 +273,14 @@ bool ContainerNode::EnsurePreInsertionValidity(const Node& new_child, 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); -// } + // 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. diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 6ba8d9b4c8..2c128c1588 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -10,11 +10,10 @@ namespace kraken { 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."); + exception_state.ThrowException(ctx(), ErrorType::InternalError, + "The tag name provided ('" + name.ToStdString() + "') is not a valid name."); return nullptr; } - - } Text* Document::createTextNode(const AtomicString& value) { @@ -22,16 +21,14 @@ Text* Document::createTextNode(const AtomicString& value) { } template -static inline bool IsValidNameASCII(const CharType* characters, - unsigned length) { +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 == '.')) + if (!(IsASCIIAlphanumeric(c) || c == ':' || c == '_' || c == '-' || c == '.')) return false; } diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index c174e5a6ba..01509ee6cf 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -35,7 +35,6 @@ class Document : public Node, TreeScope { // The following implements the rule from HTML 4 for what valid names are. static bool IsValidName(const AtomicString& name); - private: int node_count_; }; diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index ee07b7b0ab..21a17374cc 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -8,12 +8,11 @@ namespace kraken { -DocumentFragment* DocumentFragment::Create(Document* document, - ExceptionState& exception_state) { +DocumentFragment* DocumentFragment::Create(Document* document, ExceptionState& exception_state) { return MakeGarbageCollected(document, ConstructionType::kCreateDocumentFragment); } -DocumentFragment * DocumentFragment::Create(ExecutingContext* context, ExceptionState& exception_state) { +DocumentFragment* DocumentFragment::Create(ExecutingContext* context, ExceptionState& exception_state) { return MakeGarbageCollected(context->document(), ConstructionType::kCreateDocumentFragment); } @@ -32,11 +31,11 @@ std::string DocumentFragment::nodeValue() const { } Node* DocumentFragment::Clone(Document& factory, CloneChildrenFlag flag) const { -// ExceptionState exception_state; -// DocumentFragment* clone = Create(&factory, exception_state); -// if (flag != CloneChildrenFlag::kSkip) -// clone->CloneChildNodesFrom(*this, flag); -// return clone; + // ExceptionState exception_state; + // DocumentFragment* clone = Create(&factory, exception_state); + // if (flag != CloneChildrenFlag::kSkip) + // clone->CloneChildNodesFrom(*this, flag); + // return clone; } bool DocumentFragment::ChildTypeAllowed(NodeType type) const { diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index 03a2504256..c8a0a9beaf 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -18,7 +18,7 @@ class DocumentFragment : public ContainerNode { static DocumentFragment* Create(ExecutingContext* context, ExceptionState& exception_state); DocumentFragment(Document* document, ConstructionType type); - ~DocumentFragment() override {}; + ~DocumentFragment() override{}; virtual bool IsTemplateContent() const { return false; } diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 7450ac685d..ca3e56522d 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -70,7 +70,6 @@ class Element : public ContainerNode { protected: private: - // Clone is private so that non-virtual CloneElementWithChildren and // CloneElementWithoutChildren are used inst Node* Clone(Document&, CloneChildrenFlag) const; diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index 18541f6bb8..13c82475cd 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -29,7 +29,7 @@ TEST(EventTarget, addEventListener) { EXPECT_EQ(errorCalled, false); } -//TEST(EventTarget, removeEventListener) { +// TEST(EventTarget, removeEventListener) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -41,14 +41,14 @@ TEST(EventTarget, addEventListener) { // }); // auto context = bridge->getContext(); // const char* code = -// "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); " -// "div.removeEventListener('click', f); div.dispatchEvent(new Event('click'));"; +// "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); +// " "div.removeEventListener('click', f); div.dispatchEvent(new Event('click'));"; // bridge->evaluateScript(code, strlen(code), "vm://", 0); // // EXPECT_EQ(logCalled, false); //} // -//TEST(EventTarget, setNoEventTargetProperties) { +// TEST(EventTarget, setNoEventTargetProperties) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -68,7 +68,7 @@ TEST(EventTarget, addEventListener) { // EXPECT_EQ(errorCalled, false); //} // -//TEST(EventTarget, propertyEventHandler) { +// TEST(EventTarget, propertyEventHandler) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -91,7 +91,7 @@ TEST(EventTarget, addEventListener) { // EXPECT_EQ(logCalled, true); //} // -//TEST(EventTarget, setUnExpectedAttributeEventHandler) { +// TEST(EventTarget, setUnExpectedAttributeEventHandler) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -113,7 +113,7 @@ TEST(EventTarget, addEventListener) { // EXPECT_EQ(logCalled, false); //} // -//TEST(EventTarget, propertyEventOnWindow) { +// TEST(EventTarget, propertyEventOnWindow) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -133,7 +133,7 @@ TEST(EventTarget, addEventListener) { // EXPECT_EQ(logCalled, true); //} // -//TEST(EventTarget, asyncFunctionCallback) { +// TEST(EventTarget, asyncFunctionCallback) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -175,7 +175,7 @@ TEST(EventTarget, addEventListener) { // EXPECT_EQ(logCalled, true); //} // -//TEST(EventTarget, ClassInheritEventTarget) { +// TEST(EventTarget, ClassInheritEventTarget) { // bool static errorCalled = false; // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -188,14 +188,14 @@ TEST(EventTarget, addEventListener) { // }); // auto context = bridge->getContext(); // std::string code = std::string(R"( -//class Sample extends EventTarget { +// class Sample extends EventTarget { // constructor() { // super(); // } //} // -//let s = new Sample(); -//console.log(s.addEventListener, s.removeEventListener) +// let s = new Sample(); +// console.log(s.addEventListener, s.removeEventListener) //)"); // bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); // @@ -203,7 +203,7 @@ TEST(EventTarget, addEventListener) { // EXPECT_EQ(logCalled, true); //} // -//TEST(EventTarget, wontLeakWithStringProperty) { +// TEST(EventTarget, wontLeakWithStringProperty) { // auto bridge = TEST_init(); // std::string code = // "var img = new Image();\n" @@ -211,7 +211,7 @@ TEST(EventTarget, addEventListener) { // bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); //} // -//TEST(EventTarget, dispatchEventOnGC) { +// TEST(EventTarget, dispatchEventOnGC) { // using namespace kraken; // // bool static errorCalled = false; @@ -225,11 +225,11 @@ TEST(EventTarget, addEventListener) { // std::string code = std::string(R"( //{ //// Wrap div in a block scope will be freed by GC -//let div = document.createElement('div'); +// let div = document.createElement('div'); //} -//window.onclick = () => {console.log(1234);} +// window.onclick = () => {console.log(1234);} // -//setTimeout(() => {}); +// setTimeout(() => {}); //)"); // // bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); @@ -254,7 +254,7 @@ TEST(EventTarget, addEventListener) { // EXPECT_EQ(logCalled, true); //} // -//TEST(EventTarget, globalBindListener) { +// TEST(EventTarget, globalBindListener) { // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { // logCalled = true; @@ -266,7 +266,7 @@ TEST(EventTarget, addEventListener) { // EXPECT_EQ(logCalled, true); //} // -//TEST(EventTarget, shouldKeepAtom) { +// TEST(EventTarget, shouldKeepAtom) { // auto bridge = TEST_init(); // bool static logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index 7c534aeb1f..d8d7189f3a 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -18,12 +18,11 @@ 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(element); - } + static ElementAttributes* Create(Element* element) { return MakeGarbageCollected(element); } ElementAttributes(Element) = delete; ElementAttributes(Element* element); diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index f5164287e5..9d7c38f275 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -10,8 +10,8 @@ #include "events/event_target.h" #include "foundation/macros.h" -#include "tree_scope.h" #include "node_data.h" +#include "tree_scope.h" namespace kraken { diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index 431691d3be..e5d9a65db4 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -4,9 +4,9 @@ #include "node_data.h" #include "bindings/qjs/garbage_collected.h" +#include "container_node.h" #include "ng/child_node_list.h" #include "ng/empty_node_list.h" -#include "container_node.h" namespace kraken { diff --git a/bridge/core/html/html_div_element.cc b/bridge/core/html/html_div_element.cc index cc505278c1..b43750987d 100644 --- a/bridge/core/html/html_div_element.cc +++ b/bridge/core/html/html_div_element.cc @@ -7,6 +7,6 @@ namespace kraken { -HTMLDivElement::HTMLDivElement(Document& document) : HTMLElement(html_names::kdiv, &document){} +HTMLDivElement::HTMLDivElement(Document& document) : HTMLElement(html_names::kdiv, &document) {} -} +} // namespace kraken diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h index 33fbbfee7b..a1b923069e 100644 --- a/bridge/core/html/html_div_element.h +++ b/bridge/core/html/html_div_element.h @@ -11,14 +11,13 @@ namespace kraken { class HTMLDivElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); - public: + public: explicit HTMLDivElement(Document&); private: - }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ diff --git a/bridge/core/html/html_element.cc b/bridge/core/html/html_element.cc index faff21dfe0..7fb68b04aa 100644 --- a/bridge/core/html/html_element.cc +++ b/bridge/core/html/html_element.cc @@ -4,6 +4,4 @@ #include "html_element.h" -namespace kraken { - -} // namespace kraken +namespace kraken {} // namespace kraken diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index 74e0e36ae9..b2409794d7 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -11,6 +11,7 @@ namespace kraken { class HTMLElement : public Element { DEFINE_WRAPPERTYPEINFO(); + public: HTMLElement(const AtomicString& tag_name, Document* document, ConstructionType); @@ -20,9 +21,7 @@ class HTMLElement : public Element { inline HTMLElement::HTMLElement(const AtomicString& tag_name, Document* document, ConstructionType type = kCreateHTMLElement) - : Element(tag_name, document, type) { -} - + : Element(tag_name, document, type) {} } // namespace kraken diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index a0579425bb..6ec9bebc59 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -5,8 +5,4 @@ #include "html_template_element.h" -namespace kraken { - - - -} // namespace kraken +namespace kraken {} // namespace kraken diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index 1746120c98..6da92ffa3e 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -14,12 +14,11 @@ class DocumentFragment; class HTMLTemplateElement : public Element { DEFINE_WRAPPERTYPEINFO(); - public: + public: DocumentFragment* content() const; private: - }; } // namespace kraken diff --git a/bridge/core/html_element_factory.cc b/bridge/core/html_element_factory.cc index 865bb0f7af..7bcc7090b0 100644 --- a/bridge/core/html_element_factory.cc +++ b/bridge/core/html_element_factory.cc @@ -7,17 +7,19 @@ // /Users/andycall/work/kraken_main/bridge/core/html/html_tags.json #include "html_element_factory.h" #include -#include "html_names.h" #include "bindings/qjs/garbage_collected.h" +#include "core/html/canvas/html_canvas_element.h" +#include "core/html/forms/html_button_element.h" +#include "core/html/forms/html_form_element.h" +#include "core/html/forms/html_input_element.h" +#include "core/html/forms/html_select_element.h" +#include "core/html/forms/html_textarea_element.h" #include "core/html/html_a_element.h" #include "core/html/html_area_element.h" #include "core/html/html_b_element.h" #include "core/html/html_base_element.h" -#include "core/html/media/html_audio_element.h" #include "core/html/html_body_element.h" #include "core/html/html_br_element.h" -#include "core/html/forms/html_button_element.h" -#include "core/html/canvas/html_canvas_element.h" #include "core/html/html_code_element.h" #include "core/html/html_dd_element.h" #include "core/html/html_details_element.h" @@ -25,7 +27,6 @@ #include "core/html/html_div_element.h" #include "core/html/html_em_element.h" #include "core/html/html_font_element.h" -#include "core/html/forms/html_form_element.h" #include "core/html/html_frame_element.h" #include "core/html/html_h1_element.h" #include "core/html/html_h2_element.h" @@ -42,7 +43,6 @@ #include "core/html/html_iframe_element.h" #include "core/html/html_image_element.h" #include "core/html/html_img_element.h" -#include "core/html/forms/html_input_element.h" #include "core/html/html_li_element.h" #include "core/html/html_link_element.h" #include "core/html/html_map_element.h" @@ -53,14 +53,14 @@ #include "core/html/html_pre_element.h" #include "core/html/html_script_element.h" #include "core/html/html_section_element.h" -#include "core/html/forms/html_select_element.h" #include "core/html/html_span_element.h" #include "core/html/html_strong_element.h" #include "core/html/html_style_element.h" #include "core/html/html_template_element.h" -#include "core/html/forms/html_textarea_element.h" #include "core/html/html_title_element.h" +#include "core/html/media/html_audio_element.h" #include "core/html/media/html_video_element.h" +#include "html_names.h" namespace kraken { using HTMLConstructorFunction = HTMLElement* (*)(Document&); using HTMLFunctionMap = std::unordered_map; @@ -77,58 +77,32 @@ static void CreateHTMLFunctionMap() { // compile in MSVC. If tags list is empty, add check to skip this. static const CreateHTMLFunctionMapData data[] = { {html_names::a, HTMLAnchorElementConstructor}, - {html_names::karea, HTMLAreaConstructor} - {html_names::b, HTMLElementConstructor} - {html_names::kbase, HTMLBaseConstructor} - {html_names::audio, HTMLAudioConstructor} - {html_names::kbody, HTMLBodyConstructor} - {html_names::br, HTMLBRElementConstructor} - {html_names::button, HTMLButtonConstructor} - {html_names::canvas, HTMLCanvasConstructor} - {html_names::code, HTMLElementConstructor} - {html_names::dd, HTMLElementConstructor} - {html_names::kdetails, HTMLDetailsConstructor} - {html_names::kdialog, HTMLDialogConstructor} - {html_names::kdiv, HTMLDivConstructor} - {html_names::em, HTMLElementConstructor} - {html_names::kfont, HTMLFontConstructor} - {html_names::form, HTMLFormConstructor} - {html_names::kframe, HTMLFrameConstructor} - {html_names::h1, HTMLHeadingElementConstructor} - {html_names::h2, HTMLHeadingElementConstructor} - {html_names::h3, HTMLHeadingElementConstructor} - {html_names::h4, HTMLHeadingElementConstructor} - {html_names::h5, HTMLHeadingElementConstructor} - {html_names::h6, HTMLHeadingElementConstructor} - {html_names::khead, HTMLHeadConstructor} - {html_names::header, HTMLElementConstructor} - {html_names::hgroup, HTMLElementConstructor} - {html_names::hr, HTMLHRElementConstructor} - {html_names::khtml, HTMLHtmlConstructor} - {html_names::i, HTMLElementConstructor} - {html_names::iframe, HTMLIFrameElementConstructor} - {html_names::image, HTMLUnknownElementConstructor} - {html_names::img, HTMLImageElementConstructor} - {html_names::input, HTMLInputConstructor} - {html_names::li, HTMLLIElementConstructor} - {html_names::klink, HTMLLinkConstructor} - {html_names::kmap, HTMLMapConstructor} - {html_names::kmenu, HTMLMenuConstructor} - {html_names::p, HTMLParagraphElementConstructor} - {html_names::kparam, HTMLParamConstructor} - {html_names::popup, HTMLPopupElementConstructor} - {html_names::kpre, HTMLPreConstructor} - {html_names::kscript, HTMLScriptConstructor} - {html_names::section, HTMLElementConstructor} - {html_names::select, HTMLSelectConstructor} - {html_names::kspan, HTMLSpanConstructor} - {html_names::strong, HTMLElementConstructor} - {html_names::style, HTMLStyleConstructor} - {html_names::ktemplate, HTMLTemplateConstructor} - {html_names::textarea, HTMLTextAreaElementConstructor} - {html_names::ktitle, HTMLTitleConstructor} - {html_names::video, HTMLVideoConstructor} - }; + {html_names::karea, HTMLAreaConstructor} {html_names::b, HTMLElementConstructor} { + html_names::kbase, HTMLBaseConstructor} {html_names::audio, HTMLAudioConstructor} { + html_names::kbody, HTMLBodyConstructor} {html_names::br, HTMLBRElementConstructor} { + html_names::button, HTMLButtonConstructor} {html_names::canvas, HTMLCanvasConstructor} { + html_names::code, HTMLElementConstructor} {html_names::dd, HTMLElementConstructor} { + html_names::kdetails, HTMLDetailsConstructor} {html_names::kdialog, HTMLDialogConstructor} { + html_names::kdiv, HTMLDivConstructor} {html_names::em, HTMLElementConstructor} { + html_names::kfont, HTMLFontConstructor} {html_names::form, HTMLFormConstructor} { + html_names::kframe, HTMLFrameConstructor} {html_names::h1, HTMLHeadingElementConstructor} { + html_names::h2, HTMLHeadingElementConstructor} {html_names::h3, HTMLHeadingElementConstructor} { + html_names::h4, HTMLHeadingElementConstructor} {html_names::h5, HTMLHeadingElementConstructor} { + html_names::h6, HTMLHeadingElementConstructor} {html_names::khead, HTMLHeadConstructor} { + html_names::header, HTMLElementConstructor} {html_names::hgroup, HTMLElementConstructor} { + html_names::hr, HTMLHRElementConstructor} {html_names::khtml, HTMLHtmlConstructor} { + html_names::i, HTMLElementConstructor} {html_names::iframe, HTMLIFrameElementConstructor} { + html_names::image, HTMLUnknownElementConstructor} {html_names::img, HTMLImageElementConstructor} { + html_names::input, HTMLInputConstructor} {html_names::li, HTMLLIElementConstructor} { + html_names::klink, HTMLLinkConstructor} {html_names::kmap, HTMLMapConstructor} { + html_names::kmenu, HTMLMenuConstructor} {html_names::p, HTMLParagraphElementConstructor} { + html_names::kparam, HTMLParamConstructor} {html_names::popup, HTMLPopupElementConstructor} { + html_names::kpre, HTMLPreConstructor} {html_names::kscript, HTMLScriptConstructor} { + html_names::section, HTMLElementConstructor} {html_names::select, HTMLSelectConstructor} { + html_names::kspan, HTMLSpanConstructor} {html_names::strong, HTMLElementConstructor} { + html_names::style, HTMLStyleConstructor} {html_names::ktemplate, HTMLTemplateConstructor} { + html_names::textarea, HTMLTextAreaElementConstructor} {html_names::ktitle, HTMLTitleConstructor} { + html_names::video, HTMLVideoConstructor}}; for (size_t i = 0; i < std::size(data); i++) g_html_constructors->insert(std::make_pair(data[i].tag, data[i].func)); } diff --git a/bridge/foundation/ascii_types.h b/bridge/foundation/ascii_types.h index 137b70e9ef..4242b5e8df 100644 --- a/bridge/foundation/ascii_types.h +++ b/bridge/foundation/ascii_types.h @@ -3,7 +3,6 @@ * Author: Kraken Team. */ - #ifndef KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ #define KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ @@ -53,8 +52,7 @@ inline bool IsASCIIUpper(CharType c) { } template -inline bool IsLowerASCII(const CharacterType* characters, - size_t length) { +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]); @@ -62,6 +60,6 @@ inline bool IsLowerASCII(const CharacterType* characters, return !contains_upper_case; } -} +} // namespace kraken #endif // KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ diff --git a/bridge/foundation/native_value_converter.cc b/bridge/foundation/native_value_converter.cc index 1274120fd3..4c52bd2d7f 100644 --- a/bridge/foundation/native_value_converter.cc +++ b/bridge/foundation/native_value_converter.cc @@ -9,7 +9,7 @@ namespace kraken { #define AnonymousFunctionCallPreFix "_anonymous_fn_" #define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" -//void call_native_function(NativeFunctionContext* functionContext, +// void call_native_function(NativeFunctionContext* functionContext, // int32_t argc, // NativeValue* argv, // NativeValue* returnValue) { diff --git a/bridge/foundation/string_view.cc b/bridge/foundation/string_view.cc index 1fbb9ff313..bd59577be3 100644 --- a/bridge/foundation/string_view.cc +++ b/bridge/foundation/string_view.cc @@ -12,5 +12,6 @@ StringView::StringView(const std::string& string) : bytes_(string.data()), lengt 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) {} +StringView::StringView(void* bytes, unsigned length, bool is_wide_char) + : bytes_(bytes), length_(length), is_8bit_(!is_wide_char) {} } // namespace kraken diff --git a/bridge/foundation/string_view.h b/bridge/foundation/string_view.h index c1b578720c..83691292d3 100644 --- a/bridge/foundation/string_view.h +++ b/bridge/foundation/string_view.h @@ -14,16 +14,13 @@ namespace kraken { class StringView final { public: - StringView() = delete; explicit StringView(const std::string& string); explicit StringView(const NativeString* string); explicit StringView(void* bytes, unsigned length, bool is_wide_char); - bool Is8Bit() const { - return is_8bit_; - } + bool Is8Bit() const { return is_8bit_; } bool IsLowerASCII() const { if (is_8bit_) { @@ -32,15 +29,11 @@ class StringView final { return kraken::IsLowerASCII(Characters16(), length()); } - const char* Characters8() const { - return static_cast(bytes_); - } + const char* Characters8() const { return static_cast(bytes_); } - const char16_t* Characters16() const { - return static_cast(bytes_); - } + const char16_t* Characters16() const { return static_cast(bytes_); } - unsigned length() const{ return length_; } + unsigned length() const { return length_; } private: const void* bytes_; @@ -48,6 +41,6 @@ class StringView final { unsigned is_8bit_ : 1; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index ff61287e80..4b84aa67b6 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -6,11 +6,11 @@ #include #include +#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 "bindings/qjs/native_string_utils.h" #include "kraken_bridge_test.h" #include "kraken_test_env.h" From aa69148c6e970e5b5a7d24a0c5a869059634018a Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 15 Apr 2022 08:22:34 +0800 Subject: [PATCH 082/498] fix: fix html element factory. --- bridge/CMakeLists.txt | 5 +- .../core/html/canvas/html_canvas_element.cc | 5 + bridge/core/html/canvas/html_canvas_element.h | 10 ++ bridge/core/html/forms/html_button_element.cc | 5 + bridge/core/html/forms/html_button_element.h | 10 ++ bridge/core/html/html_area_element.cc | 5 + bridge/core/html/html_area_element.h | 10 ++ bridge/core/html/html_base_element.cc | 5 + bridge/core/html/html_base_element.h | 10 ++ bridge/core/html/html_body_element.cc | 5 + bridge/core/html/html_body_element.h | 10 ++ bridge/core/html/html_tags.json | 2 +- bridge/core/html/media/html_audio_element.cc | 5 + bridge/core/html/media/html_audio_element.h | 10 ++ bridge/core/html_element_factory.cc | 118 ------------------ bridge/core/html_element_factory.h | 24 ---- .../json_templates/element_factory.cc.tpl | 6 +- .../static/json_templates/make_names.cc.tpl | 19 ++- 18 files changed, 112 insertions(+), 152 deletions(-) create mode 100644 bridge/core/html/canvas/html_canvas_element.cc create mode 100644 bridge/core/html/canvas/html_canvas_element.h create mode 100644 bridge/core/html/forms/html_button_element.cc create mode 100644 bridge/core/html/forms/html_button_element.h create mode 100644 bridge/core/html/html_area_element.cc create mode 100644 bridge/core/html/html_area_element.h create mode 100644 bridge/core/html/html_base_element.cc create mode 100644 bridge/core/html/html_base_element.h create mode 100644 bridge/core/html/html_body_element.cc create mode 100644 bridge/core/html/html_body_element.h create mode 100644 bridge/core/html/media/html_audio_element.cc create mode 100644 bridge/core/html/media/html_audio_element.h delete mode 100644 bridge/core/html_element_factory.cc delete mode 100644 bridge/core/html_element_factory.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 8df9b40971..9e7100ae8a 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -249,8 +249,7 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/page.cc core/executing_context_data.cc core/executing_context_data.h - core/html_element_factory.cc - core/html_element_factory.h + core/dart_methods.h core/dart_methods.h core/fileapi/blob.h core/fileapi/blob.cc @@ -401,6 +400,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_scroll_options.h out/qjs_scroll_to_options.cc out/qjs_scroll_to_options.h + out/html_element_factory.cc + out/html_element_factory.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 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..cfa6c260f2 --- /dev/null +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -0,0 +1,5 @@ +// +// Created by yhtree on 2022/4/15. +// + +#include "html_canvas_element.h" 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..76547e3c56 --- /dev/null +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -0,0 +1,10 @@ +// +// Created by yhtree on 2022/4/15. +// + +#ifndef KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ + +class html_canvas_element {}; + +#endif // KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_button_element.cc b/bridge/core/html/forms/html_button_element.cc new file mode 100644 index 0000000000..e19c6343eb --- /dev/null +++ b/bridge/core/html/forms/html_button_element.cc @@ -0,0 +1,5 @@ +// +// Created by yhtree on 2022/4/15. +// + +#include "html_button_element.h" diff --git a/bridge/core/html/forms/html_button_element.h b/bridge/core/html/forms/html_button_element.h new file mode 100644 index 0000000000..4a5f5bb516 --- /dev/null +++ b/bridge/core/html/forms/html_button_element.h @@ -0,0 +1,10 @@ +// +// Created by yhtree on 2022/4/15. +// + +#ifndef KRAKENBRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ + +class html_button_element {}; + +#endif // KRAKENBRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ diff --git a/bridge/core/html/html_area_element.cc b/bridge/core/html/html_area_element.cc new file mode 100644 index 0000000000..55a0203bf3 --- /dev/null +++ b/bridge/core/html/html_area_element.cc @@ -0,0 +1,5 @@ +// +// Created by yhtree on 2022/4/15. +// + +#include "html_area_element.h" diff --git a/bridge/core/html/html_area_element.h b/bridge/core/html/html_area_element.h new file mode 100644 index 0000000000..2858052de9 --- /dev/null +++ b/bridge/core/html/html_area_element.h @@ -0,0 +1,10 @@ +// +// Created by yhtree on 2022/4/15. +// + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_AREA_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_AREA_ELEMENT_H_ + +class html_area_element {}; + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_AREA_ELEMENT_H_ diff --git a/bridge/core/html/html_base_element.cc b/bridge/core/html/html_base_element.cc new file mode 100644 index 0000000000..df183d622c --- /dev/null +++ b/bridge/core/html/html_base_element.cc @@ -0,0 +1,5 @@ +// +// Created by yhtree on 2022/4/15. +// + +#include "html_base_element.h" diff --git a/bridge/core/html/html_base_element.h b/bridge/core/html/html_base_element.h new file mode 100644 index 0000000000..d26f47d97f --- /dev/null +++ b/bridge/core/html/html_base_element.h @@ -0,0 +1,10 @@ +// +// Created by yhtree on 2022/4/15. +// + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_BASE_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_BASE_ELEMENT_H_ + +class html_base_element {}; + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_BASE_ELEMENT_H_ diff --git a/bridge/core/html/html_body_element.cc b/bridge/core/html/html_body_element.cc new file mode 100644 index 0000000000..73b334f2dc --- /dev/null +++ b/bridge/core/html/html_body_element.cc @@ -0,0 +1,5 @@ +// +// Created by yhtree on 2022/4/15. +// + +#include "html_body_element.h" diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h new file mode 100644 index 0000000000..e31a237c36 --- /dev/null +++ b/bridge/core/html/html_body_element.h @@ -0,0 +1,10 @@ +// +// Created by yhtree on 2022/4/15. +// + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ + +class html_body_element {}; + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ diff --git a/bridge/core/html/html_tags.json b/bridge/core/html/html_tags.json index 44829f9b9a..91c3fc76dc 100644 --- a/bridge/core/html/html_tags.json +++ b/bridge/core/html/html_tags.json @@ -7,7 +7,7 @@ }, { "template": "element_factory", - "filename": "element_factory" + "filename": "html_element_factory" } ] }, diff --git a/bridge/core/html/media/html_audio_element.cc b/bridge/core/html/media/html_audio_element.cc new file mode 100644 index 0000000000..5200d779e5 --- /dev/null +++ b/bridge/core/html/media/html_audio_element.cc @@ -0,0 +1,5 @@ +// +// Created by yhtree on 2022/4/15. +// + +#include "html_audio_element.h" diff --git a/bridge/core/html/media/html_audio_element.h b/bridge/core/html/media/html_audio_element.h new file mode 100644 index 0000000000..7dde1096c9 --- /dev/null +++ b/bridge/core/html/media/html_audio_element.h @@ -0,0 +1,10 @@ +// +// Created by yhtree on 2022/4/15. +// + +#ifndef KRAKENBRIDGE_CORE_HTML_MEDIA_HTML_AUDIO_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_MEDIA_HTML_AUDIO_ELEMENT_H_ + +class html_audio_element {}; + +#endif // KRAKENBRIDGE_CORE_HTML_MEDIA_HTML_AUDIO_ELEMENT_H_ diff --git a/bridge/core/html_element_factory.cc b/bridge/core/html_element_factory.cc deleted file mode 100644 index 7bcc7090b0..0000000000 --- a/bridge/core/html_element_factory.cc +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ -// Generated from template: -// code_generator/src/json/templates/element_factory.cc.tmp -// and input files: -// /Users/andycall/work/kraken_main/bridge/core/html/html_tags.json -#include "html_element_factory.h" -#include -#include "bindings/qjs/garbage_collected.h" -#include "core/html/canvas/html_canvas_element.h" -#include "core/html/forms/html_button_element.h" -#include "core/html/forms/html_form_element.h" -#include "core/html/forms/html_input_element.h" -#include "core/html/forms/html_select_element.h" -#include "core/html/forms/html_textarea_element.h" -#include "core/html/html_a_element.h" -#include "core/html/html_area_element.h" -#include "core/html/html_b_element.h" -#include "core/html/html_base_element.h" -#include "core/html/html_body_element.h" -#include "core/html/html_br_element.h" -#include "core/html/html_code_element.h" -#include "core/html/html_dd_element.h" -#include "core/html/html_details_element.h" -#include "core/html/html_dialog_element.h" -#include "core/html/html_div_element.h" -#include "core/html/html_em_element.h" -#include "core/html/html_font_element.h" -#include "core/html/html_frame_element.h" -#include "core/html/html_h1_element.h" -#include "core/html/html_h2_element.h" -#include "core/html/html_h3_element.h" -#include "core/html/html_h4_element.h" -#include "core/html/html_h5_element.h" -#include "core/html/html_h6_element.h" -#include "core/html/html_head_element.h" -#include "core/html/html_header_element.h" -#include "core/html/html_hgroup_element.h" -#include "core/html/html_hr_element.h" -#include "core/html/html_html_element.h" -#include "core/html/html_i_element.h" -#include "core/html/html_iframe_element.h" -#include "core/html/html_image_element.h" -#include "core/html/html_img_element.h" -#include "core/html/html_li_element.h" -#include "core/html/html_link_element.h" -#include "core/html/html_map_element.h" -#include "core/html/html_menu_element.h" -#include "core/html/html_p_element.h" -#include "core/html/html_param_element.h" -#include "core/html/html_popup_element.h" -#include "core/html/html_pre_element.h" -#include "core/html/html_script_element.h" -#include "core/html/html_section_element.h" -#include "core/html/html_span_element.h" -#include "core/html/html_strong_element.h" -#include "core/html/html_style_element.h" -#include "core/html/html_template_element.h" -#include "core/html/html_title_element.h" -#include "core/html/media/html_audio_element.h" -#include "core/html/media/html_video_element.h" -#include "html_names.h" -namespace kraken { -using HTMLConstructorFunction = HTMLElement* (*)(Document&); -using HTMLFunctionMap = std::unordered_map; -static HTMLFunctionMap* g_html_constructors = nullptr; -struct CreateHTMLFunctionMapData { - const AtomicString& tag; - HTMLConstructorFunction func; -}; - -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[] = { - {html_names::a, HTMLAnchorElementConstructor}, - {html_names::karea, HTMLAreaConstructor} {html_names::b, HTMLElementConstructor} { - html_names::kbase, HTMLBaseConstructor} {html_names::audio, HTMLAudioConstructor} { - html_names::kbody, HTMLBodyConstructor} {html_names::br, HTMLBRElementConstructor} { - html_names::button, HTMLButtonConstructor} {html_names::canvas, HTMLCanvasConstructor} { - html_names::code, HTMLElementConstructor} {html_names::dd, HTMLElementConstructor} { - html_names::kdetails, HTMLDetailsConstructor} {html_names::kdialog, HTMLDialogConstructor} { - html_names::kdiv, HTMLDivConstructor} {html_names::em, HTMLElementConstructor} { - html_names::kfont, HTMLFontConstructor} {html_names::form, HTMLFormConstructor} { - html_names::kframe, HTMLFrameConstructor} {html_names::h1, HTMLHeadingElementConstructor} { - html_names::h2, HTMLHeadingElementConstructor} {html_names::h3, HTMLHeadingElementConstructor} { - html_names::h4, HTMLHeadingElementConstructor} {html_names::h5, HTMLHeadingElementConstructor} { - html_names::h6, HTMLHeadingElementConstructor} {html_names::khead, HTMLHeadConstructor} { - html_names::header, HTMLElementConstructor} {html_names::hgroup, HTMLElementConstructor} { - html_names::hr, HTMLHRElementConstructor} {html_names::khtml, HTMLHtmlConstructor} { - html_names::i, HTMLElementConstructor} {html_names::iframe, HTMLIFrameElementConstructor} { - html_names::image, HTMLUnknownElementConstructor} {html_names::img, HTMLImageElementConstructor} { - html_names::input, HTMLInputConstructor} {html_names::li, HTMLLIElementConstructor} { - html_names::klink, HTMLLinkConstructor} {html_names::kmap, HTMLMapConstructor} { - html_names::kmenu, HTMLMenuConstructor} {html_names::p, HTMLParagraphElementConstructor} { - html_names::kparam, HTMLParamConstructor} {html_names::popup, HTMLPopupElementConstructor} { - html_names::kpre, HTMLPreConstructor} {html_names::kscript, HTMLScriptConstructor} { - html_names::section, HTMLElementConstructor} {html_names::select, HTMLSelectConstructor} { - html_names::kspan, HTMLSpanConstructor} {html_names::strong, HTMLElementConstructor} { - html_names::style, HTMLStyleConstructor} {html_names::ktemplate, HTMLTemplateConstructor} { - html_names::textarea, HTMLTextAreaElementConstructor} {html_names::ktitle, HTMLTitleConstructor} { - html_names::video, HTMLVideoConstructor}}; - 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); -} -} // namespace kraken diff --git a/bridge/core/html_element_factory.h b/bridge/core/html_element_factory.h deleted file mode 100644 index 61dba040fa..0000000000 --- a/bridge/core/html_element_factory.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ -#define KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ - -#include "bindings/qjs/atomic_string.h" - -namespace kraken { - -class Document; -class HTMLElement; - -class HTMLElementFactory { - public: - // If |local_name| is unknown, nullptr is returned. - static HTMLElement* Create(const AtomicString& local_name, Document&); -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl index b5ec101fc3..e628e286ec 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl @@ -18,7 +18,7 @@ <% } else if (_.isObject(item)) { %> <% if (item.interfaceHeaderDir) { %> #include "<%= item.interfaceHeaderDir %>/html_<%= item.name %>_element.h" - <% } else { %> + <% } else if (!item.interfaceName) { %> #include "core/html/html_<%= item.name %>_element.h" <% } %> <% } %> @@ -47,7 +47,7 @@ static HTMLElement* HTML<%= item[0].toUpperCase() + item.slice(1) %>Constructor( } <% } else if (_.isObject(item)) { %> <% if (item.interfaceName) { %> -static HTMLElement* <%= item.interfaceName %>Constructor(Document& document) { +static HTMLElement* HTML<%= item.name[0].toUpperCase() + item.name.slice(1) %>Constructor(Document& document) { return MakeGarbageCollected<<%= item.interfaceName %>>(document); } <% } else { %> @@ -71,7 +71,7 @@ static void CreateHTMLFunctionMap() { {html_names::k<%= item %>, HTML<%= item[0].toUpperCase() + item.slice(1) %>Constructor}, <% } else if (_.isObject(item)) { %> <% if (item.interfaceName) { %> - {html_names::k<%= item.name %>, <%= item.interfaceName %>Constructor}, + {html_names::k<%= item.name %>, HTML<%= item.name[0].toUpperCase() + item.name.slice(1) %>Constructor}, <% } else { %> {html_names::k<%= item.name %>, HTML<%= item.name[0].toUpperCase() + item.name.slice(1) %>Constructor}, <% } %> diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index 1cb4dea565..5a67cae366 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -10,9 +10,13 @@ namespace <%= name %> { void* names_storage[kNamesCount * ((sizeof(AtomicString) + sizeof(void *) - 1) / sizeof(void *))]; -<% _.forEach(data, function(name, index) { %><% if (_.isArray(name)) { %>const AtomicString& k<%= name[0] %> = reinterpret_cast(&names_storage)[<%= index %>]; -<% } else if (_.isObject(name)) { %>const AtomicString& k<%= name.name %> = reinterpret_cast(&names_storage)[<%= index %>]; -<% } else { %>const AtomicString& k<%= name %> = reinterpret_cast(&names_storage)[<%= index %>];<% } %> +<% _.forEach(data, function(name, index) { %> + <% if (_.isArray(name)) { %> +const AtomicString& k<%= name[0] %> = reinterpret_cast(&names_storage)[<%= index %>]; + <% } else if (_.isObject(name)) { %> +const AtomicString& k<%= name.name %> = reinterpret_cast(&names_storage)[<%= index %>]; + <% } else { %> +const AtomicString& k<%= name %> = reinterpret_cast(&names_storage)[<%= index %>];<% } %> <% }) %> void Init(JSContext* ctx) { @@ -21,7 +25,14 @@ void Init(JSContext* ctx) { }; static const NameEntry kNames[] = { - <% _.forEach(data, function(name) { %><% if (Array.isArray(name)) { %>{ "<%= name[1] %>" },<% } else if(_.isObject(name)) { %>{ "<%= name.name %>" },<% } else { %>{ "<%= name %>" },<% } %> + <% _.forEach(data, function(name) { %> + <% if (Array.isArray(name)) { %> + { "<%= name[1] %>" }, + <% } else if(_.isObject(name)) { %> + { "<%= name.name %>" }, + <% } else { %> + { "<%= name %>" }, + <% } %> <% }); %> }; From 163a185e7335cb39d4be96d4b148d5aeccd139a5 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 15 Apr 2022 17:10:57 +0800 Subject: [PATCH 083/498] feat: add html element registry. --- bridge/CMakeLists.txt | 12 ++ .../core/html/canvas/html_canvas_element.cc | 13 +- .../{ => canvas}/html_canvas_element.d.ts | 4 +- bridge/core/html/canvas/html_canvas_element.h | 18 +- bridge/core/html/forms/html_input_element.cc | 13 ++ .../html/{ => forms}/html_input_element.d.ts | 4 +- bridge/core/html/forms/html_input_element.h | 20 +++ .../core/html/forms/html_textarea_element.cc | 14 ++ .../html/forms/html_textarea_element.d.ts | 22 +++ .../core/html/forms/html_textarea_element.h | 20 +++ bridge/core/html/html_anchor_element.cc | 14 +- bridge/core/html/html_anchor_element.d.ts | 2 + bridge/core/html/html_anchor_element.h | 18 +- bridge/core/html/html_area_element.cc | 5 - bridge/core/html/html_area_element.h | 10 -- bridge/core/html/html_base_element.cc | 5 - bridge/core/html/html_base_element.h | 10 -- bridge/core/html/html_body_element.cc | 14 +- bridge/core/html/html_body_element.h | 19 +- bridge/core/html/html_canvas_element.cc | 5 - bridge/core/html/html_canvas_element.h | 10 -- bridge/core/html/html_element.d.ts | 5 + bridge/core/html/html_image_element.cc | 122 ++----------- bridge/core/html/html_image_element.h | 72 +++----- bridge/core/html/html_input_element.cc | 5 - bridge/core/html/html_input_element.h | 10 -- bridge/core/html/html_object_element.cc | 5 - bridge/core/html/html_object_element.d.ts | 4 - bridge/core/html/html_object_element.h | 10 -- bridge/core/html/html_script_element.cc | 14 +- bridge/core/html/html_script_element.d.ts | 3 - bridge/core/html/html_script_element.h | 29 ++- bridge/core/html/html_tag_names.json | 42 +++++ bridge/core/html/html_tags.json | 168 ------------------ bridge/core/html/html_template_element.cc | 6 +- bridge/core/html/html_template_element.h | 8 +- .../json_templates/element_factory.cc.tpl | 6 +- 37 files changed, 314 insertions(+), 447 deletions(-) rename bridge/core/html/{ => canvas}/html_canvas_element.d.ts (95%) create mode 100644 bridge/core/html/forms/html_input_element.cc rename bridge/core/html/{ => forms}/html_input_element.d.ts (81%) create mode 100644 bridge/core/html/forms/html_input_element.h create mode 100644 bridge/core/html/forms/html_textarea_element.cc create mode 100644 bridge/core/html/forms/html_textarea_element.d.ts create mode 100644 bridge/core/html/forms/html_textarea_element.h delete mode 100644 bridge/core/html/html_area_element.cc delete mode 100644 bridge/core/html/html_area_element.h delete mode 100644 bridge/core/html/html_base_element.cc delete mode 100644 bridge/core/html/html_base_element.h delete mode 100644 bridge/core/html/html_canvas_element.cc delete mode 100644 bridge/core/html/html_canvas_element.h create mode 100644 bridge/core/html/html_element.d.ts delete mode 100644 bridge/core/html/html_input_element.cc delete mode 100644 bridge/core/html/html_input_element.h delete mode 100644 bridge/core/html/html_object_element.cc delete mode 100644 bridge/core/html/html_object_element.d.ts delete mode 100644 bridge/core/html/html_object_element.h delete mode 100644 bridge/core/html/html_script_element.d.ts create mode 100644 bridge/core/html/html_tag_names.json delete mode 100644 bridge/core/html/html_tags.json diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 9e7100ae8a..dc5ccc8fcc 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -326,8 +326,20 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/html/html_element.h core/html/html_div_element.cc core/html/html_div_element.h + core/html/html_body_element.h + core/html/html_body_element.cc + core/html/html_anchor_element.h + core/html/html_anchor_element.cc core/html/html_template_element.cc core/html/html_template_element.h + core/html/forms/html_input_element.cc + core/html/forms/html_input_element.h + core/html/forms/html_textarea_element.cc + core/html/forms/html_textarea_element.h + core/html/html_image_element.cc + core/html/html_image_element.h + core/html/html_script_element.cc + core/html/html_script_element.h # Legacy implements, should remove them in the future. core/dom/legacy/space_split_string.cc core/dom/legacy/space_split_string.h diff --git a/bridge/core/html/canvas/html_canvas_element.cc b/bridge/core/html/canvas/html_canvas_element.cc index cfa6c260f2..751a45642c 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -1,5 +1,12 @@ -// -// Created by yhtree on 2022/4/15. -// +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "html_canvas_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLCanvasElement::HTMLCanvasElement(Document& document) : HTMLElement(html_names::kcanvas, &document) {} +} // namespace kraken diff --git a/bridge/core/html/html_canvas_element.d.ts b/bridge/core/html/canvas/html_canvas_element.d.ts similarity index 95% rename from bridge/core/html/html_canvas_element.d.ts rename to bridge/core/html/canvas/html_canvas_element.d.ts index eab9f245a9..42682a36a6 100644 --- a/bridge/core/html/html_canvas_element.d.ts +++ b/bridge/core/html/canvas/html_canvas_element.d.ts @@ -1,3 +1,5 @@ +import {HTMLElement} from "../html_element"; + interface CanvasRenderingContext2D { fillStyle: string; direction: string; @@ -43,7 +45,7 @@ interface CanvasRenderingContext2D { translate(x: number, y: number): void; } -interface CanvasElement extends Element { +interface HTMLCanvasElement extends HTMLElement { width: int64; height: int64; getContext: (contextType: string) => CanvasRenderingContext2D; diff --git a/bridge/core/html/canvas/html_canvas_element.h b/bridge/core/html/canvas/html_canvas_element.h index 76547e3c56..be1d652ddc 100644 --- a/bridge/core/html/canvas/html_canvas_element.h +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -1,10 +1,20 @@ -// -// Created by yhtree on 2022/4/15. -// +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ -class html_canvas_element {}; +#include "core/html/html_element.h" + +namespace kraken { + +class HTMLCanvasElement : public HTMLElement { + public: + explicit HTMLCanvasElement(Document&); +}; + +} #endif // KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_input_element.cc b/bridge/core/html/forms/html_input_element.cc new file mode 100644 index 0000000000..b854bd64ee --- /dev/null +++ b/bridge/core/html/forms/html_input_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_input_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLInputElement::HTMLInputElement(Document& document) : HTMLElement(html_names::kinput, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/html_input_element.d.ts b/bridge/core/html/forms/html_input_element.d.ts similarity index 81% rename from bridge/core/html/html_input_element.d.ts rename to bridge/core/html/forms/html_input_element.d.ts index b9daa1eee4..d59fadaa7d 100644 --- a/bridge/core/html/html_input_element.d.ts +++ b/bridge/core/html/forms/html_input_element.d.ts @@ -1,4 +1,6 @@ -interface InputElement extends Element { +import {HTMLElement} from "../html_element"; + +interface HTMLInputElement extends HTMLElement { width: number; height: number; value: string; diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h new file mode 100644 index 0000000000..0aa897fbc5 --- /dev/null +++ b/bridge/core/html/forms/html_input_element.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace kraken { + +class HTMLInputElement : public HTMLElement { + public: + explicit HTMLInputElement(Document&); +}; + +} + +#endif // KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_textarea_element.cc b/bridge/core/html/forms/html_textarea_element.cc new file mode 100644 index 0000000000..3eb626fbcd --- /dev/null +++ b/bridge/core/html/forms/html_textarea_element.cc @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_textarea_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLTextareaElement::HTMLTextareaElement(Document& document) : HTMLElement(html_names::ktextarea, &document) {} + + +} diff --git a/bridge/core/html/forms/html_textarea_element.d.ts b/bridge/core/html/forms/html_textarea_element.d.ts new file mode 100644 index 0000000000..15a84ca12b --- /dev/null +++ b/bridge/core/html/forms/html_textarea_element.d.ts @@ -0,0 +1,22 @@ +// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element +import {HTMLElement} from "../html_element"; + +interface HTMLTextAreaElement extends HTMLElement { + defaultValue: string; + value: string; + cols: double; + rows: double; + wrap: string; + autofocus: boolean; + autocomplete: string; + disabled: boolean; + minLength: double; + maxLength: double; + name: string; + placeholder: string; + readonly: boolean; + required: boolean; + inputMode: string; + focus(): void; + blur(): void; +} diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h new file mode 100644 index 0000000000..b644018528 --- /dev/null +++ b/bridge/core/html/forms/html_textarea_element.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace kraken { + +class HTMLTextareaElement : public HTMLElement { + public: + explicit HTMLTextareaElement(Document&); +}; + +} + +#endif // KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ diff --git a/bridge/core/html/html_anchor_element.cc b/bridge/core/html/html_anchor_element.cc index 4cb0181231..d4d8ba5848 100644 --- a/bridge/core/html/html_anchor_element.cc +++ b/bridge/core/html/html_anchor_element.cc @@ -1,5 +1,13 @@ -// -// Created by andycall on 2022/1/29. -// +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "html_anchor_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLAnchorElement::HTMLAnchorElement(Document& document): HTMLElement(html_names::ka, &document) {} + +} diff --git a/bridge/core/html/html_anchor_element.d.ts b/bridge/core/html/html_anchor_element.d.ts index a8ea6fbf15..9b45b80a9f 100644 --- a/bridge/core/html/html_anchor_element.d.ts +++ b/bridge/core/html/html_anchor_element.d.ts @@ -1,3 +1,5 @@ +import {Element} from "../dom/element"; + interface AnchorElement extends Element { href: string; target: string; diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h index 5060a7b75c..4dbc3141b2 100644 --- a/bridge/core/html/html_anchor_element.h +++ b/bridge/core/html/html_anchor_element.h @@ -1,10 +1,20 @@ -// -// Created by andycall on 2022/1/29. -// +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H #define KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H -class html_anchor_element {}; +#include "html_element.h" + +namespace kraken { + +class HTMLAnchorElement : public HTMLElement { + public: + explicit HTMLAnchorElement(Document&); +}; + +} #endif // KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H diff --git a/bridge/core/html/html_area_element.cc b/bridge/core/html/html_area_element.cc deleted file mode 100644 index 55a0203bf3..0000000000 --- a/bridge/core/html/html_area_element.cc +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by yhtree on 2022/4/15. -// - -#include "html_area_element.h" diff --git a/bridge/core/html/html_area_element.h b/bridge/core/html/html_area_element.h deleted file mode 100644 index 2858052de9..0000000000 --- a/bridge/core/html/html_area_element.h +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by yhtree on 2022/4/15. -// - -#ifndef KRAKENBRIDGE_CORE_HTML_HTML_AREA_ELEMENT_H_ -#define KRAKENBRIDGE_CORE_HTML_HTML_AREA_ELEMENT_H_ - -class html_area_element {}; - -#endif // KRAKENBRIDGE_CORE_HTML_HTML_AREA_ELEMENT_H_ diff --git a/bridge/core/html/html_base_element.cc b/bridge/core/html/html_base_element.cc deleted file mode 100644 index df183d622c..0000000000 --- a/bridge/core/html/html_base_element.cc +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by yhtree on 2022/4/15. -// - -#include "html_base_element.h" diff --git a/bridge/core/html/html_base_element.h b/bridge/core/html/html_base_element.h deleted file mode 100644 index d26f47d97f..0000000000 --- a/bridge/core/html/html_base_element.h +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by yhtree on 2022/4/15. -// - -#ifndef KRAKENBRIDGE_CORE_HTML_HTML_BASE_ELEMENT_H_ -#define KRAKENBRIDGE_CORE_HTML_HTML_BASE_ELEMENT_H_ - -class html_base_element {}; - -#endif // KRAKENBRIDGE_CORE_HTML_HTML_BASE_ELEMENT_H_ diff --git a/bridge/core/html/html_body_element.cc b/bridge/core/html/html_body_element.cc index 73b334f2dc..a05e93ccb4 100644 --- a/bridge/core/html/html_body_element.cc +++ b/bridge/core/html/html_body_element.cc @@ -1,5 +1,13 @@ -// -// Created by yhtree on 2022/4/15. -// +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "html_body_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLBodyElement::HTMLBodyElement(Document& document): HTMLElement(html_names::kbody, &document){} + +} diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index e31a237c36..46b30b1630 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -1,10 +1,21 @@ -// -// Created by yhtree on 2022/4/15. -// +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ -class html_body_element {}; +#include "html_element.h" + +namespace kraken { + +class HTMLBodyElement : public HTMLElement { + public: + explicit HTMLBodyElement(Document&); +}; + + +} #endif // KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ diff --git a/bridge/core/html/html_canvas_element.cc b/bridge/core/html/html_canvas_element.cc deleted file mode 100644 index f67320f440..0000000000 --- a/bridge/core/html/html_canvas_element.cc +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by andycall on 2022/1/29. -// - -#include "html_canvas_element.h" diff --git a/bridge/core/html/html_canvas_element.h b/bridge/core/html/html_canvas_element.h deleted file mode 100644 index c27cb8fdb2..0000000000 --- a/bridge/core/html/html_canvas_element.h +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by andycall on 2022/1/29. -// - -#ifndef KRAKENBRIDGE_HTML_CANVAS_ELEMENT_H -#define KRAKENBRIDGE_HTML_CANVAS_ELEMENT_H - -class html_canvas_element {}; - -#endif // KRAKENBRIDGE_HTML_CANVAS_ELEMENT_H diff --git a/bridge/core/html/html_element.d.ts b/bridge/core/html/html_element.d.ts new file mode 100644 index 0000000000..7238e803a7 --- /dev/null +++ b/bridge/core/html/html_element.d.ts @@ -0,0 +1,5 @@ +import {Element} from "../dom/element"; + +export interface HTMLElement extends Element { + +} diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index 7625f51328..101398b81b 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -1,109 +1,13 @@ -///* -// * Copyright (C) 2021 Alibaba Inc. All rights reserved. -// * Author: Kraken Team. -// */ -// -//#include "html_image_element.h" -//#include "bindings/qjs/qjs_patch.h" -//#include "page.h" -// -// namespace kraken { -// -// 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->getNativeProperty("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::setProperty, *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->getNativeProperty("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::setProperty, *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->getNativeProperty("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->getNativeProperty("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->getNativeProperty("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::setProperty, *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->getNativeProperty("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::setProperty, *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->nativeEvent->type->string), -// event->nativeEvent->type->length); std::string eventType = toUTF8(u16EventType); bool result = -// EventTargetInstance::dispatchEvent(event); -// -// // Free image instance after load or error event triggered. -// if ((eventType == "load" || eventType == "error") && !freed) { -// freed = true; -// unrefer(); -// } -// -// return result; -//} -// -//} // namespace kraken +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_image_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLImageElement::HTMLImageElement(Document& document): HTMLElement(html_names::kimg, &document) {} + +} diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index 4df787ea41..bf03c34813 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -1,48 +1,24 @@ -///* -// * Copyright (C) 2021 Alibaba Inc. All rights reserved. -// * Author: Kraken Team. -// */ -// -//#ifndef KRAKENBRIDGE_HTML_IMAGE_ELEMENT_H -//#define KRAKENBRIDGE_HTML_IMAGE_ELEMENT_H -// -//#include "bindings/qjs/dom/element.h" -// -// namespace kraken { -// -// 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); -// friend ImageElementInstance; -//}; -// -// class ImageElementInstance : public ElementInstance { -// public: -// ImageElementInstance() = delete; -// explicit ImageElementInstance(ImageElement* element); -// bool dispatchEvent(EventInstance* event); -// -// private: -// bool freed{false}; -// friend ImageElement; -//}; -// -//} // namespace kraken -// -//#endif // KRAKENBRIDGE_IMAGE_ELEMENTT_H +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ + +#include "html_element.h" + +namespace kraken { + +class HTMLImageElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLImageElement(Document& document); + + private: +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ diff --git a/bridge/core/html/html_input_element.cc b/bridge/core/html/html_input_element.cc deleted file mode 100644 index a805cb1822..0000000000 --- a/bridge/core/html/html_input_element.cc +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by andycall on 2022/1/29. -// - -#include "html_input_element.h" diff --git a/bridge/core/html/html_input_element.h b/bridge/core/html/html_input_element.h deleted file mode 100644 index 5de2b3fd30..0000000000 --- a/bridge/core/html/html_input_element.h +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by andycall on 2022/1/29. -// - -#ifndef KRAKENBRIDGE_HTML_INPUT_ELEMENT_H -#define KRAKENBRIDGE_HTML_INPUT_ELEMENT_H - -class html_input_element {}; - -#endif // KRAKENBRIDGE_HTML_INPUT_ELEMENT_H diff --git a/bridge/core/html/html_object_element.cc b/bridge/core/html/html_object_element.cc deleted file mode 100644 index 85470bacba..0000000000 --- a/bridge/core/html/html_object_element.cc +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by andycall on 2022/1/29. -// - -#include "html_object_element.h" diff --git a/bridge/core/html/html_object_element.d.ts b/bridge/core/html/html_object_element.d.ts deleted file mode 100644 index 686636772a..0000000000 --- a/bridge/core/html/html_object_element.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -interface ObjectElement extends Element { - type: string; - data: string; -} diff --git a/bridge/core/html/html_object_element.h b/bridge/core/html/html_object_element.h deleted file mode 100644 index 043a7c60ca..0000000000 --- a/bridge/core/html/html_object_element.h +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by andycall on 2022/1/29. -// - -#ifndef KRAKENBRIDGE_HTML_OBJECT_ELEMENT_H -#define KRAKENBRIDGE_HTML_OBJECT_ELEMENT_H - -class html_object_element {}; - -#endif // KRAKENBRIDGE_HTML_OBJECT_ELEMENT_H diff --git a/bridge/core/html/html_script_element.cc b/bridge/core/html/html_script_element.cc index 5fcdb836d0..63e62b9044 100644 --- a/bridge/core/html/html_script_element.cc +++ b/bridge/core/html/html_script_element.cc @@ -1,5 +1,13 @@ -// -// Created by andycall on 2022/1/29. -// +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "html_script_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLScriptElement::HTMLScriptElement(Document& document) : HTMLElement(html_names::kscript, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/html_script_element.d.ts b/bridge/core/html/html_script_element.d.ts deleted file mode 100644 index 1ebfba5b31..0000000000 --- a/bridge/core/html/html_script_element.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -interface ScriptElement extends Element { - src: string; -} diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h index 168a58386c..94b7251144 100644 --- a/bridge/core/html/html_script_element.h +++ b/bridge/core/html/html_script_element.h @@ -1,10 +1,25 @@ -// -// Created by andycall on 2022/1/29. -// +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ -#ifndef KRAKENBRIDGE_HTML_SCRIPT_ELEMENT_H -#define KRAKENBRIDGE_HTML_SCRIPT_ELEMENT_H +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ -class html_script_element {}; +#include "html_element.h" -#endif // KRAKENBRIDGE_HTML_SCRIPT_ELEMENT_H +namespace kraken { + +class HTMLScriptElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + public: + + explicit HTMLScriptElement(Document& document); + + private: + +}; + +} + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ diff --git a/bridge/core/html/html_tag_names.json b/bridge/core/html/html_tag_names.json new file mode 100644 index 0000000000..f0e43c8a45 --- /dev/null +++ b/bridge/core/html/html_tag_names.json @@ -0,0 +1,42 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "html_names" + }, + { + "template": "element_factory", + "filename": "html_element_factory" + } + ] + }, + "data": [ + { + "name": "canvas", + "interfaceHeaderDir": "core/html/canvas" + }, + { + "name": "a", + "interfaceName": "HTMLAnchorElement", + "filename": "html_anchor_element" + }, + "body", + { + "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_tags.json b/bridge/core/html/html_tags.json deleted file mode 100644 index 91c3fc76dc..0000000000 --- a/bridge/core/html/html_tags.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "metadata": { - "templates": [ - { - "template": "make_names", - "filename": "html_names" - }, - { - "template": "element_factory", - "filename": "html_element_factory" - } - ] - }, - "data": [ - { - "name": "a", - "interfaceName": "HTMLAnchorElement" - }, - "area", - { - "name": "b", - "interfaceName": "HTMLElement" - }, - "base", - { - "name": "audio", - "interfaceHeaderDir": "core/html/media" - }, - "body", - { - "name": "br", - "interfaceName": "HTMLBRElement" - }, - { - "name": "button", - "interfaceHeaderDir": "core/html/forms" - }, - { - "name": "canvas", - "interfaceHeaderDir": "core/html/canvas" - }, - { - "name": "code", - "interfaceName": "HTMLElement" - }, - { - "name": "dd", - "interfaceName": "HTMLElement" - }, - "details", - "dialog", - "div", - { - "name": "em", - "interfaceName": "HTMLElement" - }, - "font", - { - "name": "form", - "interfaceHeaderDir": "core/html/forms" - }, - "frame", - { - "name": "h1", - "interfaceName": "HTMLHeadingElement" - }, - { - "name": "h2", - "interfaceName": "HTMLHeadingElement" - }, - { - "name": "h3", - "interfaceName": "HTMLHeadingElement" - }, - { - "name": "h4", - "interfaceName": "HTMLHeadingElement" - }, - { - "name": "h5", - "interfaceName": "HTMLHeadingElement" - }, - { - "name": "h6", - "interfaceName": "HTMLHeadingElement" - }, - "head", - { - "name": "header", - "interfaceName": "HTMLElement" - }, - { - "name": "hgroup", - "interfaceName": "HTMLElement" - }, - { - "name": "hr", - "interfaceName": "HTMLHRElement" - }, - "html", - { - "name": "i", - "interfaceName": "HTMLElement" - }, - { - "name": "iframe", - "interfaceName": "HTMLIFrameElement" - }, - { - "name": "image", - "interfaceName": "HTMLUnknownElement" - }, - { - "name": "img", - "interfaceName": "HTMLImageElement" - }, - { - "name": "input", - "interfaceHeaderDir": "core/html/forms" - }, - { - "name": "li", - "interfaceName": "HTMLLIElement" - }, - "link", - "map", - "menu", - { - "name": "p", - "interfaceName": "HTMLParagraphElement" - }, - "param", - { - "name": "popup", - "interfaceName": "HTMLPopupElement", - "runtimeEnabled": "HTMLPopupElement" - }, - "pre", - "script", - { - "name": "section", - "interfaceName": "HTMLElement" - }, - { - "name": "select", - "interfaceHeaderDir": "core/html/forms" - }, - "span", - { - "name": "strong", - "interfaceName": "HTMLElement" - }, - { - "name": "style" - }, - "template", - { - "name": "textarea", - "interfaceName": "HTMLTextAreaElement", - "interfaceHeaderDir": "core/html/forms" - }, - "title", - { - "name": "video", - "interfaceHeaderDir": "core/html/media" - } - ] -} diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index 6ec9bebc59..f73824135f 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -4,5 +4,9 @@ */ #include "html_template_element.h" +#include "html_names.h" -namespace kraken {} // namespace kraken +namespace kraken { + +HTMLTemplateElement::HTMLTemplateElement(Document& document) : HTMLElement(html_names::ktemplate, &document) {} +} // namespace kraken diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index 6da92ffa3e..4cb1edeb1c 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -6,16 +6,18 @@ #ifndef KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H #define KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H -#include "core/dom/element.h" +#include "html_element.h" namespace kraken { class DocumentFragment; -class HTMLTemplateElement : public Element { +class HTMLTemplateElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); - public: + + explicit HTMLTemplateElement(Document& document); + DocumentFragment* content() const; private: diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl index e628e286ec..20f52b65c9 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl @@ -17,9 +17,9 @@ #include "core/html/html_<%= item %>_element.h" <% } else if (_.isObject(item)) { %> <% if (item.interfaceHeaderDir) { %> -#include "<%= item.interfaceHeaderDir %>/html_<%= item.name %>_element.h" - <% } else if (!item.interfaceName) { %> -#include "core/html/html_<%= item.name %>_element.h" +#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" <% } %> <% } %> <% }); %> From fd47af38b1188e57d56ac2f45eaa660f510cfda4 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Fri, 15 Apr 2022 19:53:56 +0800 Subject: [PATCH 084/498] fix: fix compile. --- bridge/CMakeLists.txt | 40 +++++++++++------ bridge/bindings/qjs/binding_initializer.cc | 4 ++ bridge/bindings/qjs/wrapper_type_info.h | 3 ++ ...t_in_string.json => built_in_string.json5} | 0 ...ethods.json => binding_call_methods.json5} | 0 bridge/core/dom/document.cc | 31 +++++++++++++ bridge/core/dom/document.d.ts | 9 ++-- bridge/core/dom/document.h | 10 ++++- bridge/core/dom/element.h | 2 +- bridge/core/dom/legacy/element_attributes.cc | 12 +++--- ...type_names.json => event_type_names.json5} | 0 bridge/core/html/html_div_element.d.ts | 5 +++ bridge/core/html/html_tag_names.json | 42 ------------------ bridge/core/html/html_tag_names.json5 | 43 +++++++++++++++++++ bridge/core/html/html_unknown_element.cc | 13 ++++++ bridge/core/html/html_unknown_element.d.ts | 3 ++ bridge/core/html/html_unknown_element.h | 23 ++++++++++ bridge/foundation/string_view.h | 1 + .../code_generator/bin/code_generator.js | 2 +- bridge/scripts/code_generator/package.json | 1 + .../scripts/code_generator/src/idl/utils.ts | 4 ++ .../code_generator/src/json/JSONBlob.ts | 3 +- 22 files changed, 181 insertions(+), 70 deletions(-) rename bridge/core/{built_in_string.json => built_in_string.json5} (100%) rename bridge/core/dom/{binding_call_methods.json => binding_call_methods.json5} (100%) rename bridge/core/events/{event_type_names.json => event_type_names.json5} (100%) create mode 100644 bridge/core/html/html_div_element.d.ts delete mode 100644 bridge/core/html/html_tag_names.json create mode 100644 bridge/core/html/html_tag_names.json5 create mode 100644 bridge/core/html/html_unknown_element.cc create mode 100644 bridge/core/html/html_unknown_element.d.ts create mode 100644 bridge/core/html/html_unknown_element.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index dc5ccc8fcc..fcc1e895cf 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -326,20 +326,22 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/html/html_element.h core/html/html_div_element.cc core/html/html_div_element.h - core/html/html_body_element.h - core/html/html_body_element.cc - core/html/html_anchor_element.h - core/html/html_anchor_element.cc - core/html/html_template_element.cc - core/html/html_template_element.h - core/html/forms/html_input_element.cc - core/html/forms/html_input_element.h - core/html/forms/html_textarea_element.cc - core/html/forms/html_textarea_element.h - core/html/html_image_element.cc - core/html/html_image_element.h - core/html/html_script_element.cc - core/html/html_script_element.h +# core/html/html_body_element.h +# core/html/html_body_element.cc +# core/html/html_anchor_element.h +# core/html/html_anchor_element.cc +# core/html/html_template_element.cc +# core/html/html_template_element.h +# core/html/forms/html_input_element.cc +# core/html/forms/html_input_element.h +# core/html/forms/html_textarea_element.cc +# core/html/forms/html_textarea_element.h +# core/html/html_image_element.cc +# core/html/html_image_element.h +# core/html/html_script_element.cc +# core/html/html_script_element.h + core/html/html_unknown_element.cc + core/html/html_unknown_element.h # Legacy implements, should remove them in the future. core/dom/legacy/space_split_string.cc core/dom/legacy/space_split_string.h @@ -388,6 +390,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_event_target.h out/qjs_node.h out/qjs_node.cc + out/qjs_document.cc + out/qjs_document.h out/qjs_element.cc out/qjs_element.h out/qjs_element_attributes.cc @@ -412,8 +416,16 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_scroll_options.h out/qjs_scroll_to_options.cc out/qjs_scroll_to_options.h + out/qjs_html_element.cc + out/qjs_html_element.h + out/qjs_html_div_element.cc + out/qjs_html_div_element.h + out/qjs_html_unknown_element.cc + out/qjs_html_unknown_element.h out/html_element_factory.cc out/html_element_factory.h + out/html_names.cc + out/html_names.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index c77ac6303a..1eb1b815e9 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -11,6 +11,8 @@ #include "qjs_event_target.h" #include "qjs_module_manager.h" #include "qjs_window.h" +#include "qjs_document.h" +#include "qjs_node.h" namespace kraken { @@ -20,6 +22,8 @@ void InstallBindings(ExecutingContext* context) { QJSConsole::Install(context); QJSEventTarget::Install(context); QJSEvent::Install(context); + QJSNode::Install(context); + QJSDocument::Install(context); } } // namespace kraken diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index b215f5a8d8..c166e87dd0 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -27,6 +27,9 @@ enum { JS_CLASS_DOCUMENT_FRAGMENT, JS_CLASS_BOUNDING_CLIENT_RECT, JS_CLASS_ELEMENT_ATTRIBUTES, + JS_CLASS_HTML_ELEMENT, + JS_CLASS_HTML_DIV_ELEMENT, + JS_CLASS_HTML_UNKNOWN_ELEMENT }; // This struct provides a way to store a bunch of information that is helpful diff --git a/bridge/core/built_in_string.json b/bridge/core/built_in_string.json5 similarity index 100% rename from bridge/core/built_in_string.json rename to bridge/core/built_in_string.json5 diff --git a/bridge/core/dom/binding_call_methods.json b/bridge/core/dom/binding_call_methods.json5 similarity index 100% rename from bridge/core/dom/binding_call_methods.json rename to bridge/core/dom/binding_call_methods.json5 diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 2c128c1588..4962366b34 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -4,22 +4,49 @@ */ #include "document.h" +#include "core/html/html_element.h" +#include "core/html/html_unknown_element.h" #include "foundation/ascii_types.h" +#include "html_element_factory.h" namespace kraken { +Document* Document::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected(context); +} + +Document::Document(ExecutingContext* context) : Node(this, ConstructionType::kCreateDocument), TreeScope(*this) {} + Element* Document::createElement(const AtomicString& name, ExceptionState& exception_state) { if (!IsValidName(name)) { exception_state.ThrowException(ctx(), ErrorType::InternalError, "The tag name provided ('" + name.ToStdString() + "') is not a valid name."); return nullptr; } + + if (auto* element = HTMLElementFactory::Create(name, *this)) { + return element; + } + + return MakeGarbageCollected(name, *this); } Text* Document::createTextNode(const AtomicString& value) { return nullptr; } +std::string Document::nodeName() const { + return "#document"; +} + +std::string Document::nodeValue() const { + return ""; +} + +Node::NodeType Document::nodeType() const { + return kDocumentNode; +} + template static inline bool IsValidNameASCII(const CharType* characters, unsigned length) { CharType c = characters[0]; @@ -58,4 +85,8 @@ bool Document::IsValidName(const AtomicString& name) { return false; } +Node* Document::Clone(Document&, CloneChildrenFlag) const { + return nullptr; +} + } // namespace kraken diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts index 0c363a5999..4c5c4d8f60 100644 --- a/bridge/core/dom/document.d.ts +++ b/bridge/core/dom/document.d.ts @@ -1,8 +1,9 @@ import {Node} from "./node"; +import {Element} from "./element"; interface Document extends Node { - /** - * Returns the children. - */ - readonly childNodes: number; + + createElement(tagName: string): Element; + + new(): Document; } diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 01509ee6cf..c9ea3fdd49 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -18,13 +18,21 @@ class Text; // (typically, HTML) resource. class Document : public Node, TreeScope { DEFINE_WRAPPERTYPEINFO(); - public: using ImplType = Document*; + explicit Document(ExecutingContext* context); + + static Document* Create(ExecutingContext* context, ExceptionState& exception_state); + Element* createElement(const AtomicString& name, ExceptionState& exception_state); Text* createTextNode(const AtomicString& value); + std::string nodeName() const override; + std::string nodeValue() const override; + NodeType nodeType() const override; + Node * Clone(Document &, CloneChildrenFlag) const override; + void IncrementNodeCount() { node_count_++; } void DecrementNodeCount() { assert(node_count_ > 0); diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index ca3e56522d..9b340bf776 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -72,7 +72,7 @@ class Element : public ContainerNode { private: // Clone is private so that non-virtual CloneElementWithChildren and // CloneElementWithoutChildren are used inst - Node* Clone(Document&, CloneChildrenFlag) const; + Node* Clone(Document&, CloneChildrenFlag) const override; void _notifyNodeRemoved(Node* node); void _notifyChildRemoved(); diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index be9169796b..27127cf61f 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -9,17 +9,17 @@ namespace kraken { -static inline bool IsNumberIndex(const std::string_view& name) { - if (name.empty()) +static inline bool IsNumberIndex(const StringView& name) { + if (name.Empty()) return false; - char f = name[0]; + char f = name.Characters8()[0]; return f >= '0' && f <= '9'; } ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()) {} AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { - bool numberIndex = IsNumberIndex(name.ToStringView8()); + bool numberIndex = IsNumberIndex(name.ToStringView()); if (numberIndex) { AtomicString::Empty(ctx()); @@ -31,7 +31,7 @@ AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { bool ElementAttributes::setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { - bool numberIndex = IsNumberIndex(name.ToStringView8()); + bool numberIndex = IsNumberIndex(name.ToStringView()); if (numberIndex) { exception_state.ThrowException( @@ -51,7 +51,7 @@ bool ElementAttributes::setAttribute(const AtomicString& name, } bool ElementAttributes::hasAttribute(const AtomicString& name, ExceptionState& exception_state) { - bool numberIndex = IsNumberIndex(name.ToStringView8()); + bool numberIndex = IsNumberIndex(name.ToStringView()); if (numberIndex) { return false; diff --git a/bridge/core/events/event_type_names.json b/bridge/core/events/event_type_names.json5 similarity index 100% rename from bridge/core/events/event_type_names.json rename to bridge/core/events/event_type_names.json5 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..ed145cc692 --- /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 { + +} diff --git a/bridge/core/html/html_tag_names.json b/bridge/core/html/html_tag_names.json deleted file mode 100644 index f0e43c8a45..0000000000 --- a/bridge/core/html/html_tag_names.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "metadata": { - "templates": [ - { - "template": "make_names", - "filename": "html_names" - }, - { - "template": "element_factory", - "filename": "html_element_factory" - } - ] - }, - "data": [ - { - "name": "canvas", - "interfaceHeaderDir": "core/html/canvas" - }, - { - "name": "a", - "interfaceName": "HTMLAnchorElement", - "filename": "html_anchor_element" - }, - "body", - { - "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_tag_names.json5 b/bridge/core/html/html_tag_names.json5 new file mode 100644 index 0000000000..e03f42bc1f --- /dev/null +++ b/bridge/core/html/html_tag_names.json5 @@ -0,0 +1,43 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "html_names" + }, + { + "template": "element_factory", + "filename": "html_element_factory" + } + ] + }, + "data": [ +// { +// "name": "canvas", +// "interfaceHeaderDir": "core/html/canvas" +// }, +// { +// "name": "a", +// "interfaceName": "HTMLAnchorElement", +// "filename": "html_anchor_element" +// }, +// "body", + "div", +// { +// "name": "input", +// "interfaceHeaderDir": "core/html/forms" +// }, +// { +// "name": "textarea", +// "interfaceName": "HTMLTextareaElement", +// "interfaceHeaderDir": "core/html/forms" +// }, +// "template", +// { +// "name": "img", +// "interfaceName": "HTMLImageElement", +// "filename": "html_image_element" +// }, +// "script" + ] +} diff --git a/bridge/core/html/html_unknown_element.cc b/bridge/core/html/html_unknown_element.cc new file mode 100644 index 0000000000..708d82558f --- /dev/null +++ b/bridge/core/html/html_unknown_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_unknown_element.h" + +namespace kraken { + +HTMLUnknownElement::HTMLUnknownElement(const AtomicString& tag_name, Document& document) + : HTMLElement(tag_name, &document) {} + +} // namespace kraken diff --git a/bridge/core/html/html_unknown_element.d.ts b/bridge/core/html/html_unknown_element.d.ts new file mode 100644 index 0000000000..c2659304b2 --- /dev/null +++ b/bridge/core/html/html_unknown_element.d.ts @@ -0,0 +1,3 @@ +import {HTMLElement} from "./html_element"; + +export interface HTMLUnknownElement extends HTMLElement {} diff --git a/bridge/core/html/html_unknown_element.h b/bridge/core/html/html_unknown_element.h new file mode 100644 index 0000000000..730a191ec0 --- /dev/null +++ b/bridge/core/html/html_unknown_element.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ + +#include "core/html/html_element.h" + +namespace kraken { + +class HTMLUnknownElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + public: + explicit HTMLUnknownElement(const AtomicString&, Document& document); + private: + +}; + +} + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ diff --git a/bridge/foundation/string_view.h b/bridge/foundation/string_view.h index 83691292d3..a8b2871618 100644 --- a/bridge/foundation/string_view.h +++ b/bridge/foundation/string_view.h @@ -34,6 +34,7 @@ class StringView final { const char16_t* Characters16() const { return static_cast(bytes_); } unsigned length() const { return length_; } + bool Empty() const { return length_ == 0; } private: const void* bytes_; diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 0d93504e62..a9ab44b832 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -57,7 +57,7 @@ function genCodeFromTypeDefine() { // Generate code from json data. function genCodeFromJSONData() { - let jsonFiles = glob.sync('**/*.json', { + let jsonFiles = glob.sync('**/*.json5', { cwd: source }); let templateFiles = glob.sync('**/*.tpl', { diff --git a/bridge/scripts/code_generator/package.json b/bridge/scripts/code_generator/package.json index 47c80250fd..9595178807 100644 --- a/bridge/scripts/code_generator/package.json +++ b/bridge/scripts/code_generator/package.json @@ -13,6 +13,7 @@ "@types/node": "^16.9.2", "commander": "^8.1.0", "glob": "^7.1.7", + "json5": "^2.2.1", "lodash": "^4.17.21", "typescript": "^4.3.5" } diff --git a/bridge/scripts/code_generator/src/idl/utils.ts b/bridge/scripts/code_generator/src/idl/utils.ts index 6bd5b4c0de..c4eb0ad376 100644 --- a/bridge/scripts/code_generator/src/idl/utils.ts +++ b/bridge/scripts/code_generator/src/idl/utils.ts @@ -15,6 +15,10 @@ export function addIndent(str: String, space: number) { 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); + } + return `${raw[0].toUpperCase() + raw.slice(1)}`; } diff --git a/bridge/scripts/code_generator/src/json/JSONBlob.ts b/bridge/scripts/code_generator/src/json/JSONBlob.ts index ddbc50f73a..5a24233cfa 100644 --- a/bridge/scripts/code_generator/src/json/JSONBlob.ts +++ b/bridge/scripts/code_generator/src/json/JSONBlob.ts @@ -1,5 +1,6 @@ import {ClassObject, FunctionObject} from "../idl/declaration"; import fs from "fs"; +import JSON5 from 'json5'; export class JSONBlob { raw: string; @@ -13,6 +14,6 @@ export class JSONBlob { this.raw = fs.readFileSync(source, {encoding: 'utf-8'}); this.dist = dist; this.filename = filename; - this.json = JSON.parse(this.raw); + this.json = JSON5.parse(this.raw); } } From 12557fe452e018ea296166c97ff7890714229271 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Mon, 18 Apr 2022 20:07:44 +0800 Subject: [PATCH 085/498] feat: add document api. --- bridge/CMakeLists.txt | 4 + bridge/bindings/qjs/binding_initializer.cc | 14 ++ bridge/bindings/qjs/wrapper_type_info.h | 2 + bridge/core/dom/character_data.cc | 5 + bridge/core/dom/character_data.d.ts | 4 +- bridge/core/dom/character_data.h | 7 +- bridge/core/dom/comment.cc | 49 ++----- bridge/core/dom/comment.d.ts | 5 + bridge/core/dom/comment.h | 34 ++--- bridge/core/dom/container_node.cc | 3 +- bridge/core/dom/document.cc | 15 ++- bridge/core/dom/document.d.ts | 6 + bridge/core/dom/document.h | 9 +- bridge/core/dom/document_fragment.cc | 4 +- bridge/core/dom/document_fragment.h | 2 +- bridge/core/dom/document_test.cc | 124 ++++++++++-------- bridge/core/dom/element.cc | 4 + bridge/core/dom/element.d.ts | 4 +- bridge/core/dom/element.h | 1 + bridge/core/dom/legacy/element_attributes.h | 4 +- bridge/core/dom/legacy/space_split_string.cc | 18 +-- bridge/core/dom/legacy/space_split_string.h | 4 +- bridge/core/dom/node.cc | 6 +- bridge/core/dom/node.d.ts | 10 +- bridge/core/dom/node.h | 2 +- bridge/core/executing_context.cc | 76 ++--------- bridge/core/executing_context.h | 14 +- bridge/core/html/html_div_element.d.ts | 2 +- bridge/core/html/html_element.d.ts | 2 +- bridge/core/script_state.cc | 8 ++ .../code_generator/src/idl/analyzer.ts | 1 + .../static/idl_templates/base.cc.tpl | 2 +- .../json_templates/element_factory.cc.tpl | 4 + .../json_templates/element_factory.h.tpl | 1 + bridge/test/test.cmake | 1 + 35 files changed, 220 insertions(+), 231 deletions(-) create mode 100644 bridge/core/dom/comment.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index fcc1e895cf..4bb970d624 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -298,6 +298,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/template_content_document_fragment.h core/dom/character_data.cc core/dom/character_data.h + core/dom/comment.cc + core/dom/comment.h core/dom/text.cc core/dom/text.h core/dom/tree_scope.cc @@ -398,6 +400,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_element_attributes.h out/qjs_character_data.cc out/qjs_character_data.h + out/qjs_comment.cc + out/qjs_comment.h out/qjs_document_fragment.cc out/qjs_document_fragment.h out/qjs_bounding_client_rect.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 1eb1b815e9..d4981b4a8c 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -13,10 +13,18 @@ #include "qjs_window.h" #include "qjs_document.h" #include "qjs_node.h" +#include "qjs_character_data.h" +#include "qjs_text.h" +#include "qjs_comment.h" +#include "qjs_element.h" +#include "qjs_html_element.h" +#include "qjs_html_div_element.h" namespace kraken { void InstallBindings(ExecutingContext* context) { + // Must follow the inheritance order when install. + // Exp: Node extends EventTarget, EventTarget must be install first. QJSWindow::installGlobalFunctions(context); QJSModuleManager::Install(context); QJSConsole::Install(context); @@ -24,6 +32,12 @@ void InstallBindings(ExecutingContext* context) { QJSEvent::Install(context); QJSNode::Install(context); QJSDocument::Install(context); + QJSCharacterData::Install(context); + QJSText::Install(context); + QJSComment::Install(context); + QJSElement::Install(context); + QJSHTMLElement::Install(context); + QJSHTMLDivElement::Install(context); } } // namespace kraken diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index c166e87dd0..9d840d10a7 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -12,6 +12,7 @@ namespace kraken { +// Define all built-in wrapper class id. enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB, @@ -23,6 +24,7 @@ enum { 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, diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index 27b5229d95..d17d50551d 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -3,6 +3,7 @@ */ #include "character_data.h" +#include "core/dom/document.h" namespace kraken { @@ -13,5 +14,9 @@ void CharacterData::setData(const AtomicString& data) { std::string CharacterData::nodeValue() const { return data_.ToStdString(); } +CharacterData::CharacterData(Document& document, const AtomicString& text, Node::ConstructionType type) + : Node(document.GetExecutingContext(), &document, type), data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { + assert(type == kCreateOther || type == kCreateText); +} } // namespace kraken diff --git a/bridge/core/dom/character_data.d.ts b/bridge/core/dom/character_data.d.ts index f1eb304843..cb67be1030 100644 --- a/bridge/core/dom/character_data.d.ts +++ b/bridge/core/dom/character_data.d.ts @@ -1,4 +1,6 @@ -export interface CharacterData { +import {Node} from "./node"; + +export interface CharacterData extends Node { readonly data: string; readonly length: int64; } diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index 6b50dbfddd..f1852d2355 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -9,6 +9,8 @@ namespace kraken { +class Document; + class CharacterData : public Node { DEFINE_WRAPPERTYPEINFO(); @@ -20,10 +22,7 @@ class CharacterData : public Node { std::string nodeValue() const override; protected: - CharacterData(Document& tree_scope, const AtomicString& text, ConstructionType type) - : Node(&tree_scope, type), data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { - assert(type == kCreateOther || type == kCreateText); - } + CharacterData(Document& document, const AtomicString& text, ConstructionType type); private: AtomicString data_; diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc index 4e033739ab..2ff5cb6e88 100644 --- a/bridge/core/dom/comment.cc +++ b/bridge/core/dom/comment.cc @@ -4,53 +4,30 @@ */ #include "comment.h" -#include "document.h" +#include "built_in_string.h" namespace kraken { -void bindCommentNode(std::unique_ptr& context) { - // auto* constructor = Comment::instance(context.get()); - // context->defineGlobalProperty("Comment", constructor->jsObject); +Comment* Comment::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected(*context->document(), ConstructionType::kCreateOther); } -JSClassID Comment::classId{0}; - -Comment* Comment::create(JSContext* ctx) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - auto* comment = makeGarbageCollected()->initialize(ctx, &classId); - - JSValue prototype = context->contextData()->prototypeForType(&commentTypeInfo); - - // Let eventTarget instance inherit EventTarget prototype methods. - JS_SetPrototype(ctx, comment->toQuickJS(), prototype); - - return comment; +Comment* Comment::Create(Document& document) { + return MakeGarbageCollected(document, ConstructionType::kCreateOther); } -// 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()); -//} +Comment::Comment(Document& document, ConstructionType type) + : CharacterData(document, built_in_string::kempty_string, type) {} -JSValue Comment::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) { - return (new CommentInstance(this))->jsObject; +Node::NodeType Comment::nodeType() const { + return Node::kCommentNode; } - -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); +std::string Comment::nodeName() const { + return "#comment"; } -CommentInstance::CommentInstance(Comment* comment) - : NodeInstance(comment, NodeType::COMMENT_NODE, Comment::classId(), "Comment") { - m_context->uiCommandBuffer()->addCommand(m_eventTargetId, UICommand::createComment, nativeEventTarget); +Node* Comment::Clone(Document& factory, CloneChildrenFlag flag) const { + return Create(factory); } } // namespace kraken diff --git a/bridge/core/dom/comment.d.ts b/bridge/core/dom/comment.d.ts new file mode 100644 index 0000000000..60461cb888 --- /dev/null +++ b/bridge/core/dom/comment.d.ts @@ -0,0 +1,5 @@ +import {CharacterData} from "./character_data"; + +export interface Comment extends CharacterData { + new(): Comment; +} diff --git a/bridge/core/dom/comment.h b/bridge/core/dom/comment.h index 0a7e88eddf..f70c69b02a 100644 --- a/bridge/core/dom/comment.h +++ b/bridge/core/dom/comment.h @@ -6,42 +6,26 @@ #ifndef KRAKENBRIDGE_COMMENT_H #define KRAKENBRIDGE_COMMENT_H -#include "node.h" +#include "character_data.h" namespace kraken { -void bindCommentNode(ExecutionContext* context); +class Comment : public CharacterData { + DEFINE_WRAPPERTYPEINFO(); -class CommentInstance; - -class Comment : public Node { public: - static JSClassID classId; - static Comment* create(JSContext* ctx); - static JSValue constructor(ExecutionContext* context); - static JSValue prototype(ExecutionContext* context); + static Comment* Create(ExecutingContext* context, ExceptionState& exception_state); + static Comment* Create(Document&); - // static JSClassID kCommentClassId; - // static JSClassID classId(); - // Comment() = delete; - // explicit Comment(ExecutionContext* context); + explicit Comment(Document& document, ConstructionType type); - // JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) override; + NodeType nodeType() const override; private: - DEFINE_PROTOTYPE_READONLY_PROPERTY(data); - DEFINE_PROTOTYPE_READONLY_PROPERTY(nodeName); - DEFINE_PROTOTYPE_READONLY_PROPERTY(length); - - friend CommentInstance; + std::string nodeName() const override; + Node* Clone(Document&, CloneChildrenFlag) const override; }; -auto commentCreator = - [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) - -> JSValue {}; - -const WrapperTypeInfo commentTypeInfo = {"Comment", &nodeTypeInfo, commentCreator}; - } // namespace kraken #endif // KRAKENBRIDGE_COMMENT_H diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index c1aca96839..7736a677fd 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -6,6 +6,7 @@ #include "bindings/qjs/garbage_collected.h" #include "document_fragment.h" #include "node_traversal.h" +#include "document.h" namespace kraken { @@ -320,7 +321,7 @@ std::string ContainerNode::nodeValue() const { return ""; } -ContainerNode::ContainerNode(Document* document, ConstructionType type) : Node(document, type) {} +ContainerNode::ContainerNode(Document* document, ConstructionType type) : Node(document->GetExecutingContext(), document, type) {} void ContainerNode::RemoveBetween(Node* previous_child, Node* next_child, Node& old_child) { assert(old_child.parentNode() == this); diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 4962366b34..b9f2525bb2 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -15,7 +15,8 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ return MakeGarbageCollected(context); } -Document::Document(ExecutingContext* context) : Node(this, ConstructionType::kCreateDocument), TreeScope(*this) {} +Document::Document(ExecutingContext* context) + : Node(context, this, ConstructionType::kCreateDocument), TreeScope(*this) {} Element* Document::createElement(const AtomicString& name, ExceptionState& exception_state) { if (!IsValidName(name)) { @@ -31,8 +32,16 @@ Element* Document::createElement(const AtomicString& name, ExceptionState& excep return MakeGarbageCollected(name, *this); } -Text* Document::createTextNode(const AtomicString& value) { - return nullptr; +Text* Document::createTextNode(const AtomicString& value, ExceptionState& exception_state) { + return Text::Create(*this, value); +} + +DocumentFragment* Document::createDocumentFragment(ExceptionState& exception_state) { + return DocumentFragment::Create(*this); +} + +Comment* Document::createComment(ExceptionState& exception_state) { + return Comment::Create(*this); } std::string Document::nodeName() const { diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts index 4c5c4d8f60..27fbc00cea 100644 --- a/bridge/core/dom/document.d.ts +++ b/bridge/core/dom/document.d.ts @@ -1,9 +1,15 @@ import {Node} from "./node"; import {Element} from "./element"; +import {Text} from "./text"; +import {Comment} from "./comment"; +import {DocumentFragment} from "./document_fragment"; interface Document extends Node { createElement(tagName: string): Element; + createTextNode(value: string): Text; + createDocumentFragment(): DocumentFragment; + createComment(): Comment; new(): Document; } diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index c9ea3fdd49..6e0082bd6b 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -8,11 +8,12 @@ #include "container_node.h" #include "tree_scope.h" +#include "core/dom/document_fragment.h" +#include "core/dom/text.h" +#include "core/dom/comment.h" namespace kraken { -class Text; - // 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. @@ -26,7 +27,9 @@ class Document : public Node, TreeScope { static Document* Create(ExecutingContext* context, ExceptionState& exception_state); Element* createElement(const AtomicString& name, ExceptionState& exception_state); - Text* createTextNode(const AtomicString& value); + Text* createTextNode(const AtomicString& value, ExceptionState& exception_state); + DocumentFragment* createDocumentFragment(ExceptionState& exception_state); + Comment* createComment(ExceptionState& exception_state); std::string nodeName() const override; std::string nodeValue() const override; diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index 21a17374cc..02d43ba84e 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -8,8 +8,8 @@ namespace kraken { -DocumentFragment* DocumentFragment::Create(Document* document, ExceptionState& exception_state) { - return MakeGarbageCollected(document, ConstructionType::kCreateDocumentFragment); +DocumentFragment* DocumentFragment::Create(Document& document) { + return MakeGarbageCollected(&document, ConstructionType::kCreateDocumentFragment); } DocumentFragment* DocumentFragment::Create(ExecutingContext* context, ExceptionState& exception_state) { diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index c8a0a9beaf..413e5bd3f8 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -14,7 +14,7 @@ class DocumentFragment : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); public: - static DocumentFragment* Create(Document* document, ExceptionState& exception_state); + static DocumentFragment* Create(Document& document); static DocumentFragment* Create(ExecutingContext* context, ExceptionState& exception_state); DocumentFragment(Document* document, ConstructionType type); diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 19f5d1073a..805a8deff3 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -3,12 +3,12 @@ * Author: Kraken Team. */ -#include "event_target.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" -TEST(Document, createTextNode) { +using namespace kraken; + +TEST(Document, createElement) { bool static errorCalled = false; bool static logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { @@ -22,57 +22,77 @@ TEST(Document, createTextNode) { auto context = bridge->getContext(); const char* code = "let div = document.createElement('div');" - "div.setAttribute('hello', 1234);" - "document.body.appendChild(div);" - "let text = document.createTextNode('1234');" - "div.appendChild(text);" - "console.log(div);"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(Document, instanceofNode) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "true true true"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - const char* code = - "console.log(document instanceof Node, document instanceof Document, document instanceof EventTarget)"; +"console.log(div)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } -TEST(Document, createElementShouldWorkWithMultipleContext) { - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - - kraken::KrakenPage* bridge1; - - const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; - - { - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); - auto context = bridge->getContext(); - bridge->evaluateScript(code, strlen(code), "vm://", 0); - bridge1 = bridge.release(); - } - - { - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); - auto context = bridge->getContext(); - const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - } - - bridge1->evaluateScript(code, strlen(code), "vm://", 0); - - delete bridge1; -} +// TEST(Document, createTextNode) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "

"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->getContext(); +// const char* code = +// "let div = document.createElement('div');" +// "div.setAttribute('hello', 1234);" +// "document.body.appendChild(div);" +// "let text = document.createTextNode('1234');" +// "div.appendChild(text);" +// "console.log(div);"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +// } +// +// TEST(Document, instanceofNode) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "true true true"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->getContext(); +// const char* code = +// "console.log(document instanceof Node, document instanceof Document, document instanceof EventTarget)"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +// } +// +// TEST(Document, createElementShouldWorkWithMultipleContext) { +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; +// +// kraken::KrakenPage* bridge1; +// +// const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; +// +// { +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); +// auto context = bridge->getContext(); +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// bridge1 = bridge.release(); +// } +// +// { +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); +// auto context = bridge->getContext(); +// const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// } +// +// bridge1->evaluateScript(code, strlen(code), "vm://", 0); +// +// delete bridge1; +// } diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 96b94e4974..b5e0c42f8c 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -117,6 +117,10 @@ void Element::scrollTo(const std::shared_ptr& options, Exceptio return scroll(options, exception_state); } +bool Element::HasTagName(const AtomicString& name) const { + return name == tag_name_; +} + std::string Element::nodeValue() const { return ""; } diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index cfabec47e3..0d9f38f7d7 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -1,7 +1,7 @@ import {Node} from "./node"; import {Document} from "./document"; import {ScrollToOptions} from "./scroll_to_options"; -import { ElementAttributes } from './legacy/element_attribute'; +import { ElementAttributes } from './legacy/element_attributes'; interface Element extends Node { readonly attributes: ElementAttributes; @@ -42,4 +42,6 @@ interface Element extends Node { // Kraken special API. toBlob(devicePixelRatioValue?: double): Promise; + + new(): void; } diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 9b340bf776..5dd07c266a 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -60,6 +60,7 @@ class Element : public ContainerNode { std::string innerHTML() const; void setInnerHTML(const AtomicString& value, ExceptionState& exception_state); + bool HasTagName(const AtomicString&) const; std::string nodeValue() const override; AtomicString tagName() const { return tag_name_; } std::string nodeName() const override; diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index d8d7189f3a..fa31017568 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -22,7 +22,9 @@ class ElementAttributes : public ScriptWrappable { public: using ImplType = ElementAttributes*; - static ElementAttributes* Create(Element* element) { return MakeGarbageCollected(element); } + static ElementAttributes* Create(Element* element) { + return MakeGarbageCollected(element); + } ElementAttributes(Element) = delete; ElementAttributes(Element* element); diff --git a/bridge/core/dom/legacy/space_split_string.cc b/bridge/core/dom/legacy/space_split_string.cc index 085425ce2f..a53fa721e1 100644 --- a/bridge/core/dom/legacy/space_split_string.cc +++ b/bridge/core/dom/legacy/space_split_string.cc @@ -6,22 +6,22 @@ namespace kraken { -std::string SpaceSplitString::delimiter_{""}; +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(delimiter_)) != std::string::npos) { + while ((pos = s.find(m_delimiter)) != std::string::npos) { token = s.substr(0, pos); - sz_data_.push_back(token); - s.erase(0, pos + delimiter_.length()); + m_szData.push_back(token); + s.erase(0, pos + m_delimiter.length()); } - sz_data_.push_back(s); + m_szData.push_back(s); } bool SpaceSplitString::contains(std::string& string) { - for (std::string& s : sz_data_) { + for (std::string& s : m_szData) { if (s == string) { return true; } @@ -34,17 +34,17 @@ bool SpaceSplitString::containsAll(std::string s) { size_t pos = 0; std::string token; - while ((pos = s.find(delimiter_)) != std::string::npos) { + while ((pos = s.find(m_delimiter)) != std::string::npos) { token = s.substr(0, pos); szData.push_back(token); - s.erase(0, pos + delimiter_.length()); + 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 : sz_data_) { + for (std::string& data : m_szData) { if (data == str) { isContains = true; break; diff --git a/bridge/core/dom/legacy/space_split_string.h b/bridge/core/dom/legacy/space_split_string.h index e2881deaa2..d7e40492a1 100644 --- a/bridge/core/dom/legacy/space_split_string.h +++ b/bridge/core/dom/legacy/space_split_string.h @@ -20,8 +20,8 @@ class SpaceSplitString { bool containsAll(std::string s); private: - static std::string delimiter_; - std::vector sz_data_; + static std::string m_delimiter; + std::vector m_szData; }; } // namespace kraken diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index eb22ad0a6f..ab6e97264d 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -221,7 +221,7 @@ void Node::setTextContent(const AtomicString& text, ExceptionState& exception_st container->RemoveChildren(); } else { container->RemoveChildren(); - container->AppendChild(GetDocument().createTextNode(text), exception_state); + container->AppendChild(GetDocument().createTextNode(text, exception_state), exception_state); } return; } @@ -385,8 +385,8 @@ Node* Node::CommonAncestor(const Node& other, ContainerNode* (*parent)(const Nod return nullptr; } -Node::Node(Document* document, ConstructionType type) - : EventTarget(document->GetExecutingContext()), +Node::Node(ExecutingContext* context, Document* document, ConstructionType type) + : EventTarget(context), node_flags_(type), parent_or_shadow_host_node_(nullptr), previous_(nullptr), diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts index 9521dc6ddb..2e49a08bc9 100644 --- a/bridge/core/dom/node.d.ts +++ b/bridge/core/dom/node.d.ts @@ -3,10 +3,10 @@ import { Document } from './document'; /** Node is an interface from which a number of DOM API object types inherit. It allows those types to be treated similarly; for example, inheriting the same set of methods, or being tested in the same way. */ interface Node extends EventTarget { - /** - * Returns the children. - */ - readonly childNodes: NodeList; + // /** + // * Returns the children. + // */ + // readonly childNodes: NodeList; /** * Returns the first child. */ @@ -68,5 +68,5 @@ interface Node extends EventTarget { removeChild(oldChild: Node): Node; replaceChild(newChild: Node, oldChild: Node): Node; - new(): Node; + new(): void; } diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 9d7c38f275..5997747ca0 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -286,7 +286,7 @@ class Node : public EventTarget { void SetTreeScope(TreeScope* scope) { tree_scope_ = scope; } - Node(Document*, ConstructionType); + Node(ExecutingContext* context, Document*, ConstructionType); Node() = delete; private: diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 2f547716c8..173d344611 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -7,6 +7,7 @@ #include "built_in_string.h" #include "event_type_names.h" #include "polyfill.h" +#include "core/dom/document.h" #include "foundation/logging.h" @@ -63,6 +64,9 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& // Register all built-in native bindings. InstallBindings(this); + + InstallDocument(); + #if ENABLE_PROFILE nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); @@ -364,6 +368,11 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { // return &pending_promises_; //} +void ExecutingContext::InstallDocument() { + document_ = MakeGarbageCollected(this); + DefineGlobalProperty("document", document_->ToQuickJS()); +} + // An lock free context validator. bool isContextValid(int32_t contextId) { if (contextId > running_context_list) @@ -371,71 +380,4 @@ bool isContextValid(int32_t contextId) { 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; -} - } // namespace kraken diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 3ebe816e29..c50ecf73cc 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -112,6 +112,9 @@ class ExecutingContext { static std::unordered_map pluginByteCode; private: + + void InstallDocument(); + static void promiseRejectTracker(JSContext* ctx, JSValueConst promise, JSValueConst reason, @@ -159,17 +162,6 @@ class ObjectProperty { std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); -// JS array operation utilities. -void arrayPushValue(JSContext* ctx, JSValue array, JSValue val); -void arrayInsert(JSContext* ctx, JSValue array, uint32_t start, JSValue targetValue); -int32_t arrayGetLength(JSContext* ctx, JSValue array); -int32_t arrayFindIdx(JSContext* ctx, JSValue array, JSValue target); -void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount); -void arraySpliceValue(JSContext* ctx, JSValue array, uint32_t start, uint32_t deleteCount, JSValue replacedValue); - -// JS object operation utilities. -JSValue objectGetKeys(JSContext* ctx, JSValue obj); - } // namespace kraken #endif // KRAKENBRIDGE_JS_CONTEXT_H diff --git a/bridge/core/html/html_div_element.d.ts b/bridge/core/html/html_div_element.d.ts index ed145cc692..2edd591451 100644 --- a/bridge/core/html/html_div_element.d.ts +++ b/bridge/core/html/html_div_element.d.ts @@ -1,5 +1,5 @@ import {HTMLElement} from "./html_element"; export interface HTMLDivElement extends HTMLElement { - + new(): void; } diff --git a/bridge/core/html/html_element.d.ts b/bridge/core/html/html_element.d.ts index 7238e803a7..7865df7552 100644 --- a/bridge/core/html/html_element.d.ts +++ b/bridge/core/html/html_element.d.ts @@ -1,5 +1,5 @@ import {Element} from "../dom/element"; export interface HTMLElement extends Element { - + new(): void; } diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 5b97e2e07d..32cdaa9d19 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -6,6 +6,9 @@ #include "script_state.h" #include "built_in_string.h" #include "event_type_names.h" +#include "html_names.h" +#include "binding_call_methods.h" +#include "html_element_factory.h" namespace kraken { @@ -26,6 +29,8 @@ ScriptState::ScriptState() { if (first_loaded) { built_in_string::Init(ctx_); event_type_names::Init(ctx_); + html_names::Init(ctx_); + binding_call_methods::Init(ctx_); } } @@ -44,6 +49,9 @@ ScriptState::~ScriptState() { // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. built_in_string::Dispose(); event_type_names::Dispose(); + html_names::Dispose(); + binding_call_methods::Dispose(); + HTMLElementFactory::Dispose(); JS_FreeRuntime(runtime_); runtime_ = nullptr; diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index e7fdb6b9ea..476c9fda36 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -191,6 +191,7 @@ function walkProgram(statement: ts.Statement) { let p = paramsNodeToArguments(params); c.args.push(p); }); + c.returnType = getParameterType(m.type); obj.construct = c; break; } diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl index d2b14f5010..3cd1d00c4e 100644 --- a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl @@ -17,7 +17,7 @@ namespace kraken { <% } %> <%= content %> -<% if (globalFunctionInstallList.length > 0 || classPropsInstallList.length > 0 || classMethodsInstallList.length > 0) { %> +<% 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); <% } %> diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl index 20f52b65c9..1b2b516618 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl @@ -94,4 +94,8 @@ HTMLElement* HTMLElementFactory::Create(const AtomicString& name, Document& docu return function(document); } +void HTMLElementFactory::Dispose() { + delete g_html_constructors; +} + } // namespace kraken diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl index dfc1da07bd..6aaf25e7eb 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl @@ -16,6 +16,7 @@ class HTMLElementFactory { public: // If |local_name| is unknown, nullptr is returned. static HTMLElement* Create(const AtomicString& local_name, Document&); + static void Dispose(); }; } // namespace kraken diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 62b8f08b7d..1845cd21f5 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -22,6 +22,7 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./core/frame/console_test.cc ./core/frame/module_manager_test.cc ./core/dom/events/event_target_test.cc + ./core/dom/document_test.cc # ./bindings/qjs/bom/timer_test.cc # ./bindings/qjs/qjs_patch_test.cc # ./bindings/qjs/garbage_collected_test.cc From 56f1da69d711138fd0aee5375deac74bb6eb9d2a Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Tue, 19 Apr 2022 18:06:25 +0800 Subject: [PATCH 086/498] fix: create element works. --- bridge/bindings/qjs/binding_initializer.cc | 2 ++ bridge/bindings/qjs/garbage_collected.h | 12 +++++------- bridge/bindings/qjs/gc_visitor.cc | 2 +- bridge/bindings/qjs/gc_visitor.h | 1 + bridge/bindings/qjs/script_wrappable.cc | 11 ++--------- bridge/bindings/qjs/script_wrappable.h | 6 ++++-- bridge/bindings/qjs/wrapper_type_info.h | 3 ++- bridge/core/dom/character_data.d.ts | 1 + bridge/core/dom/document.cc | 8 ++++---- bridge/core/dom/document.d.ts | 2 +- bridge/core/dom/document.h | 2 +- bridge/core/dom/document_test.cc | 2 +- bridge/core/dom/element.cc | 4 ++++ bridge/core/dom/element.h | 5 ++++- bridge/core/dom/events/event.cc | 4 ---- bridge/core/dom/events/event.h | 1 - bridge/core/dom/events/event_target.cc | 4 ---- bridge/core/dom/events/event_target.h | 2 -- bridge/core/dom/legacy/bounding_client_rect.h | 1 - bridge/core/dom/legacy/element_attributes.cc | 4 +++- bridge/core/dom/legacy/element_attributes.d.ts | 2 ++ bridge/core/dom/legacy/element_attributes.h | 5 ++++- bridge/core/dom/ng/node_list.h | 2 -- bridge/core/executing_context.cc | 2 +- bridge/core/executing_context_data.cc | 2 ++ bridge/core/fileapi/blob.cc | 3 --- bridge/core/fileapi/blob.h | 1 - 27 files changed, 45 insertions(+), 49 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index d4981b4a8c..0cba283879 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -19,6 +19,7 @@ #include "qjs_element.h" #include "qjs_html_element.h" #include "qjs_html_div_element.h" +#include "qjs_element_attributes.h" namespace kraken { @@ -38,6 +39,7 @@ void InstallBindings(ExecutingContext* context) { QJSElement::Install(context); QJSHTMLElement::Install(context); QJSHTMLDivElement::Install(context); + QJSElementAttributes::Install(context); } } // namespace kraken diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index 1db1f4f12a..f5d41d36fa 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -10,6 +10,7 @@ #include #include "foundation/macros.h" +#include "foundation/casting.h" #include "gc_visitor.h" #include "qjs_engine_patch.h" @@ -44,13 +45,7 @@ class GarbageCollected { */ virtual void Trace(GCVisitor* visitor) 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 = 0; + virtual void InitializeQuickJSObject() = 0; protected: GarbageCollected(){}; @@ -64,6 +59,9 @@ class MakeGarbageCollectedTrait { template static T* Allocate(Args&&... args) { T* object = ::new T(std::forward(args)...); + if (auto* scriptwrappable = DynamicTo(object)) { + scriptwrappable->InitializeQuickJSObject(); + } return object; } diff --git a/bridge/bindings/qjs/gc_visitor.cc b/bridge/bindings/qjs/gc_visitor.cc index 2489b0b2de..d98a05c345 100644 --- a/bridge/bindings/qjs/gc_visitor.cc +++ b/bridge/bindings/qjs/gc_visitor.cc @@ -10,7 +10,7 @@ namespace kraken { void GCVisitor::Trace(ScriptWrappable* target) { if (target != nullptr) { - JS_MarkValue(runtime_, target->ToQuickJS(), markFunc_); + JS_MarkValue(runtime_, target->jsObject_, markFunc_); } } diff --git a/bridge/bindings/qjs/gc_visitor.h b/bridge/bindings/qjs/gc_visitor.h index 549a02392c..b03d5c6f77 100644 --- a/bridge/bindings/qjs/gc_visitor.h +++ b/bridge/bindings/qjs/gc_visitor.h @@ -27,6 +27,7 @@ class GCVisitor final { private: JSRuntime* runtime_{nullptr}; JS_MarkFunc* markFunc_{nullptr}; + friend class ScriptWrappable; }; } // namespace kraken diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index fc892fa5f0..32637104b1 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -11,14 +11,7 @@ namespace kraken { ScriptWrappable::ScriptWrappable(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)) {} JSValue ScriptWrappable::ToQuickJS() { - if (wrapped_) { - return JS_DupValue(ctx_, jsObject_); - } - - // Initialize the corresponding quickjs object. - InitializeQuickJSObject(); - - return jsObject_; + return JS_DupValue(ctx_, jsObject_); } ScriptValue ScriptWrappable::ToValue() { @@ -34,7 +27,7 @@ void ScriptWrappable::InitializeQuickJSObject() { /// Basic template to describe the behavior about this class. JSClassDef def{}; - def.class_name = GetHumanReadableName(); + def.class_name = wrapperTypeInfo->className; /// 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 diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 6bb326a222..bd2ab8d70f 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -53,12 +53,14 @@ class ScriptWrappable : public GarbageCollected { FORCE_INLINE JSContext* ctx() const { return ctx_; } FORCE_INLINE JSRuntime* runtime() const { return runtime_; } + void InitializeQuickJSObject() override; + private: - bool wrapped_{false}; - void InitializeQuickJSObject(); JSValue jsObject_{JS_NULL}; + bool wrapped_{false}; JSContext* ctx_{nullptr}; JSRuntime* runtime_{nullptr}; + friend class GCVisitor; }; // Converts a QuickJS object back to a ScriptWrappable. diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 9d840d10a7..c58f74029b 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -14,7 +14,8 @@ namespace kraken { // Define all built-in wrapper class id. enum { - JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, + // We assume there will no other class id could overwrite by JS_NewClassID, at least 200. + JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 200, JS_CLASS_BLOB, JS_CLASS_EVENT, JS_CLASS_ERROR_EVENT, diff --git a/bridge/core/dom/character_data.d.ts b/bridge/core/dom/character_data.d.ts index cb67be1030..b0a6ef5309 100644 --- a/bridge/core/dom/character_data.d.ts +++ b/bridge/core/dom/character_data.d.ts @@ -3,4 +3,5 @@ import {Node} from "./node"; export interface CharacterData extends Node { readonly data: string; readonly length: int64; + new(): void; } diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index b9f2525bb2..2cf4a7af1d 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -18,18 +18,18 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ Document::Document(ExecutingContext* context) : Node(context, this, ConstructionType::kCreateDocument), TreeScope(*this) {} -Element* Document::createElement(const AtomicString& name, ExceptionState& exception_state) { +ScriptValue 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; + return ScriptValue::Empty(ctx()); } if (auto* element = HTMLElementFactory::Create(name, *this)) { - return element; + return element->ToValue(); } - return MakeGarbageCollected(name, *this); + return MakeGarbageCollected(name, *this)->ToValue(); } Text* Document::createTextNode(const AtomicString& value, ExceptionState& exception_state) { diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts index 27fbc00cea..fd1cc47a48 100644 --- a/bridge/core/dom/document.d.ts +++ b/bridge/core/dom/document.d.ts @@ -6,7 +6,7 @@ import {DocumentFragment} from "./document_fragment"; interface Document extends Node { - createElement(tagName: string): Element; + createElement(tagName: string): any; createTextNode(value: string): Text; createDocumentFragment(): DocumentFragment; createComment(): Comment; diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 6e0082bd6b..178c1bbf24 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -26,7 +26,7 @@ class Document : public Node, TreeScope { static Document* Create(ExecutingContext* context, ExceptionState& exception_state); - Element* createElement(const AtomicString& name, ExceptionState& exception_state); + ScriptValue createElement(const AtomicString& name, ExceptionState& exception_state); Text* createTextNode(const AtomicString& value, ExceptionState& exception_state); DocumentFragment* createDocumentFragment(ExceptionState& exception_state); Comment* createComment(ExceptionState& exception_state); diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 805a8deff3..41c44f3f23 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -22,7 +22,7 @@ TEST(Document, createElement) { auto context = bridge->getContext(); const char* code = "let div = document.createElement('div');" -"console.log(div)"; +"console.log(div);"; 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 index b5e0c42f8c..52c3640136 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -133,6 +133,10 @@ bool Element::HasEquivalentAttributes(const Element& other) const { return other.attributes_->IsEquivalent(*attributes_); } +void Element::Trace(GCVisitor* visitor) const { + visitor->Trace(attributes_); +} + Node* Element::Clone(Document& factory, CloneChildrenFlag flag) const { return nullptr; } diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 5dd07c266a..ddce956e6c 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -20,7 +20,9 @@ class Element : public ContainerNode { public: Element(const AtomicString& tag_name, Document* document, ConstructionType = kCreateElement); - ElementAttributes* attributes() const { return attributes_; } + ElementAttributes* attributes() const { + return attributes_; + } bool hasAttribute(const AtomicString&, ExceptionState& exception_state) const; AtomicString getAttribute(const AtomicString&, ExceptionState& exception_state) const; @@ -69,6 +71,7 @@ class Element : public ContainerNode { bool HasEquivalentAttributes(const Element& other) const; + void Trace(GCVisitor *visitor) const override; protected: private: // Clone is private so that non-virtual CloneElementWithChildren and diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index a55e1902e9..ed0ec394ee 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -64,10 +64,6 @@ Event::Event(ExecutingContext* context, current_target_(nullptr), time_stamp_(time_stamp) {} -const char* Event::GetHumanReadableName() const { - return "Event"; -} - void Event::SetType(const AtomicString& type) { type_ = type; } diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index fa597089cc..f800e853b5 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -114,7 +114,6 @@ class Event : public ScriptWrappable { ComposedMode composed_mode, double timeStamp); - const char* GetHumanReadableName() const override; bool propagationStopped() const { return propagation_stopped_; } bool bubbles() { return bubbles_; }; double timeStamp() { return time_stamp_; } diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 26060fe499..b384ba930d 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -196,10 +196,6 @@ NativeValue EventTarget::HandleCallFromDartSide(NativeString* method, int32_t ar return Native_NewNull(); } -const char* EventTarget::GetHumanReadableName() const { - return "EventTarget"; -} - bool EventTarget::FireEventListeners(Event& event, EventTargetData* d, EventListenerVector& entry, diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 38f18cc2ac..790dddd044 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -135,8 +135,6 @@ class EventTarget : public ScriptWrappable, public BindingObject { virtual EventTargetData* GetEventTargetData() = 0; virtual EventTargetData& EnsureEventTargetData() = 0; - const char* GetHumanReadableName() const override; - private: int32_t event_target_id_; bool FireEventListeners(Event&, EventTargetData*, EventListenerVector&, ExceptionState&); diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index f566f82295..ca7925b6f7 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -30,7 +30,6 @@ class BoundingClientRect : public ScriptWrappable { static BoundingClientRect* Create(ExecutingContext* context, NativeBoundingClientRect* native_bounding_client_rect); explicit BoundingClientRect(ExecutingContext* context, NativeBoundingClientRect* nativeBoundingClientRect); - FORCE_INLINE const char* GetHumanReadableName() const override { return "BoundingClientRect"; } void Trace(GCVisitor* visitor) const override; double x() const { return x_; } diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 27127cf61f..42decbc098 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -16,7 +16,9 @@ static inline bool IsNumberIndex(const StringView& name) { return f >= '0' && f <= '9'; } -ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()) {} +ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()) { +} +ElementAttributes::ElementAttributes(ExecutingContext* context): ScriptWrappable(context->ctx()) {} AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { bool numberIndex = IsNumberIndex(name.ToStringView()); diff --git a/bridge/core/dom/legacy/element_attributes.d.ts b/bridge/core/dom/legacy/element_attributes.d.ts index 22d1b30c98..b5ffd4b28a 100644 --- a/bridge/core/dom/legacy/element_attributes.d.ts +++ b/bridge/core/dom/legacy/element_attributes.d.ts @@ -1,5 +1,7 @@ export interface ElementAttributes { + // Legacy methods: these methods are not W3C standard. setAttribute(name: string, value: string): void; hasAttribute(name: string): boolean; removeAttribute(name: string): void; + new(): void; } diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index fa31017568..bf58c63dc2 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -25,9 +25,13 @@ class ElementAttributes : public ScriptWrappable { static ElementAttributes* Create(Element* element) { return MakeGarbageCollected(element); } + static ElementAttributes* Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected(context); + } ElementAttributes(Element) = delete; ElementAttributes(Element* element); + ElementAttributes(ExecutingContext* context); AtomicString GetAttribute(const AtomicString& name); bool setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state); @@ -39,7 +43,6 @@ class ElementAttributes : public ScriptWrappable { bool IsEquivalent(const ElementAttributes& other) const; - FORCE_INLINE const char* GetHumanReadableName() const override { return "ElementAttributes"; } void Trace(GCVisitor* visitor) const override; private: diff --git a/bridge/core/dom/ng/node_list.h b/bridge/core/dom/ng/node_list.h index a2f8b783dc..40a7964426 100644 --- a/bridge/core/dom/ng/node_list.h +++ b/bridge/core/dom/ng/node_list.h @@ -28,8 +28,6 @@ class NodeList : public ScriptWrappable { virtual bool IsEmptyNodeList() const { return false; } virtual bool IsChildNodeList() const { return false; } - const char* GetHumanReadableName() const override { return "NodeList"; }; - virtual Node* VirtualOwnerNode() const { return nullptr; } protected: diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 173d344611..1bfdd8772b 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -370,7 +370,7 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { void ExecutingContext::InstallDocument() { document_ = MakeGarbageCollected(this); - DefineGlobalProperty("document", document_->ToQuickJS()); + DefineGlobalProperty("document", document_->ToValue().QJSValue()); } // An lock free context validator. diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc index 5439b4e126..b74903387a 100644 --- a/bridge/core/executing_context_data.cc +++ b/bridge/core/executing_context_data.cc @@ -32,6 +32,8 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty // Allocate a new unique classID from QuickJS. JS_NewClassID(&class_id); + assert(class_id < JS_CLASS_GC_TRACKER); + // Create class template for behavior. JSClassDef def{}; def.class_name = type->className; diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 5de914c7fd..048ed75e64 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -79,9 +79,6 @@ uint8_t* Blob::bytes() { return _data.data(); } -const char* Blob::GetHumanReadableName() const { - return "Blob"; -} void Blob::Trace(GCVisitor* visitor) const {} Blob* Blob::slice(ExceptionState& exception_state) { diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index f2b739f81b..30db15ed3e 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -63,7 +63,6 @@ class Blob : public ScriptWrappable { std::string StringResult(); ArrayBufferData ArrayBufferResult(); - const char* GetHumanReadableName() const override; void Trace(GCVisitor* visitor) const override; protected: From 541635643c2915bd56a666d75da188f3eb2189b9 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" Date: Wed, 20 Apr 2022 20:11:31 +0800 Subject: [PATCH 087/498] feat: add document.body and document.head. --- bridge/CMakeLists.txt | 16 +- bridge/bindings/qjs/binding_initializer.cc | 8 + bridge/bindings/qjs/converter_impl.h | 10 +- bridge/bindings/qjs/garbage_collected.h | 4 +- bridge/bindings/qjs/js_based_event_listener.h | 10 +- bridge/bindings/qjs/js_event_handler.h | 8 + bridge/bindings/qjs/js_event_listener.h | 8 + bridge/bindings/qjs/wrapper_type_info.h | 3 + bridge/core/dom/character_data.cc | 4 +- bridge/core/dom/character_data.h | 7 +- bridge/core/dom/comment.cc | 6 +- bridge/core/dom/comment.h | 2 +- bridge/core/dom/container_node.cc | 7 +- bridge/core/dom/container_node.h | 12 +- bridge/core/dom/document.cc | 56 +- bridge/core/dom/document.d.ts | 8 +- bridge/core/dom/document.h | 39 +- bridge/core/dom/document_fragment.cc | 1 + bridge/core/dom/document_fragment.h | 5 + bridge/core/dom/document_test.cc | 21 +- bridge/core/dom/element.cc | 16 +- bridge/core/dom/element.h | 22 + bridge/core/dom/element_traversal.h | 484 ++++++++++++++++++ bridge/core/dom/events/event.cc | 44 ++ bridge/core/dom/events/event.h | 16 + bridge/core/dom/events/event_listener.h | 2 + bridge/core/dom/node.cc | 10 +- bridge/core/dom/node.h | 2 +- bridge/core/dom/text.cc | 1 + bridge/core/dom/text.h | 9 +- bridge/core/dom/tree_scope.cc | 6 +- bridge/core/dom/tree_scope.h | 11 +- bridge/core/events/error_event.h | 5 + bridge/core/executing_context.cc | 5 + .../core/html/canvas/html_canvas_element.d.ts | 6 +- bridge/core/html/html_body_element.d.ts | 5 + bridge/core/html/html_body_element.h | 1 + bridge/core/html/html_element.h | 16 + bridge/core/html/html_head_element.cc | 14 + bridge/core/html/html_head_element.d.ts | 5 + bridge/core/html/html_head_element.h | 24 + bridge/core/html/html_html_element.cc | 13 + bridge/core/html/html_html_element.d.ts | 5 + bridge/core/html/html_html_element.h | 25 + bridge/core/html/html_tag_names.json5 | 8 +- bridge/foundation/casting.h | 67 ++- bridge/polyfill/src/dom.ts | 21 +- bridge/polyfill/src/index.ts | 2 +- .../json_templates/element_factory.cc.tpl | 16 +- .../json_templates/element_type_helper.h.tpl | 63 +++ 50 files changed, 1092 insertions(+), 67 deletions(-) create mode 100644 bridge/core/dom/element_traversal.h create mode 100644 bridge/core/html/html_body_element.d.ts create mode 100644 bridge/core/html/html_head_element.cc create mode 100644 bridge/core/html/html_head_element.d.ts create mode 100644 bridge/core/html/html_head_element.h create mode 100644 bridge/core/html/html_html_element.cc create mode 100644 bridge/core/html/html_html_element.d.ts create mode 100644 bridge/core/html/html_html_element.h create mode 100644 bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 4bb970d624..7b4a410475 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -306,6 +306,7 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/tree_scope.h core/dom/element.cc core/dom/element.h + core/dom/element_traversal.h core/dom/document.cc core/dom/document.h core/dom/node_data.cc @@ -328,8 +329,12 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/html/html_element.h core/html/html_div_element.cc core/html/html_div_element.h -# core/html/html_body_element.h -# core/html/html_body_element.cc + core/html/html_head_element.cc + core/html/html_head_element.h + core/html/html_body_element.h + core/html/html_body_element.cc + core/html/html_html_element.cc + core/html/html_html_element.h # core/html/html_anchor_element.h # core/html/html_anchor_element.cc # core/html/html_template_element.cc @@ -424,6 +429,13 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_html_element.h out/qjs_html_div_element.cc out/qjs_html_div_element.h + out/qjs_html_head_element.cc + out/qjs_html_head_element.h + out/qjs_html_body_element.cc + out/qjs_html_body_element.h + out/qjs_html_html_element.cc + out/qjs_html_html_element.h + out/html_element_type_helper.h out/qjs_html_unknown_element.cc out/qjs_html_unknown_element.h out/html_element_factory.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 0cba283879..7c77ede70b 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -19,6 +19,9 @@ #include "qjs_element.h" #include "qjs_html_element.h" #include "qjs_html_div_element.h" +#include "qjs_html_head_element.h" +#include "qjs_html_body_element.h" +#include "qjs_html_html_element.h" #include "qjs_element_attributes.h" namespace kraken { @@ -39,6 +42,11 @@ void InstallBindings(ExecutingContext* context) { QJSElement::Install(context); QJSHTMLElement::Install(context); QJSHTMLDivElement::Install(context); + QJSHTMLHeadElement::Install(context); + QJSHTMLBodyElement::Install(context); + QJSHTMLHtmlElement::Install(context); + + // Legacy bindings, not standard. QJSElementAttributes::Install(context); } diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 92a45624f1..e6720334aa 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -15,7 +15,11 @@ #include "core/dom/ng/node_list.h" #include "core/fileapi/blob_part.h" #include "core/fileapi/blob_property_bag.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 "idl_type.h" #include "js_event_listener.h" #include "native_string_utils.h" @@ -465,7 +469,7 @@ struct Converter : public ConverterBase : public ConverterBase { \ static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { \ assert(!JS_IsException(value)); \ - return toScriptWrappable(value); \ + return toScriptWrappable(JS_DupValue(ctx, value)); \ } \ static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } \ }; @@ -473,6 +477,10 @@ struct Converter : public ConverterBase struct Converter : public ConverterBase { diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index f5d41d36fa..d3c8971444 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -59,9 +59,7 @@ class MakeGarbageCollectedTrait { template static T* Allocate(Args&&... args) { T* object = ::new T(std::forward(args)...); - if (auto* scriptwrappable = DynamicTo(object)) { - scriptwrappable->InitializeQuickJSObject(); - } + object->InitializeQuickJSObject(); return object; } diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index 04cba9fbdf..b505d230d2 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -20,7 +20,7 @@ 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); + 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 @@ -33,6 +33,7 @@ class JSBasedEventListener : public EventListener { // throwing any exception. virtual JSValue GetEffectiveFunction(EventTarget&) = 0; + bool IsJSBasedEventListener() const override { return true; } virtual bool IsJSEventListener() const { return false; } virtual bool IsJSEventHandler() const { return false; } @@ -49,6 +50,13 @@ class JSBasedEventListener : public EventListener { virtual void InvokeInternal(EventTarget&, Event&, ExceptionState& exception_state) = 0; }; +template <> +struct DowncastTraits { + static bool AllowFrom(const EventListener& event_listener) { + return event_listener.IsJSBasedEventListener(); + } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index 429d2248d4..2783bb3971 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -65,6 +65,14 @@ class JSEventHandler : public JSBasedEventListener { const HandlerType type_; }; +template <> +struct DowncastTraits { + static bool AllowFrom(const EventListener& event_listener) { + auto* js_based = DynamicTo(event_listener); + return js_based && js_based->IsJSEventHandler(); + } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h index 82626c673f..c04c4125db 100644 --- a/bridge/bindings/qjs/js_event_listener.h +++ b/bridge/bindings/qjs/js_event_listener.h @@ -43,6 +43,14 @@ class JSEventListener final : public JSBasedEventListener { const std::shared_ptr event_listener_; }; +template <> +struct DowncastTraits { + static bool AllowFrom(const EventListener& event_listener) { + auto* js_based = DynamicTo(event_listener); + return js_based && js_based->IsJSEventListener(); + } +}; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index c58f74029b..da544412e8 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -32,6 +32,9 @@ enum { JS_CLASS_ELEMENT_ATTRIBUTES, JS_CLASS_HTML_ELEMENT, JS_CLASS_HTML_DIV_ELEMENT, + JS_CLASS_HTML_BODY_ELEMENT, + JS_CLASS_HTML_HEAD_ELEMENT, + JS_CLASS_HTML_HTML_ELEMENT, JS_CLASS_HTML_UNKNOWN_ELEMENT }; diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index d17d50551d..c223a964db 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -14,8 +14,8 @@ void CharacterData::setData(const AtomicString& data) { std::string CharacterData::nodeValue() const { return data_.ToStdString(); } -CharacterData::CharacterData(Document& document, const AtomicString& text, Node::ConstructionType type) - : Node(document.GetExecutingContext(), &document, type), data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { +CharacterData::CharacterData(TreeScope& tree_scope, const AtomicString& text, Node::ConstructionType type) + : Node(tree_scope.GetDocument().GetExecutingContext(), &tree_scope, type), data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { assert(type == kCreateOther || type == kCreateText); } diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index f1852d2355..c15d643efa 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -22,12 +22,17 @@ class CharacterData : public Node { std::string nodeValue() const override; protected: - CharacterData(Document& document, const AtomicString& text, ConstructionType type); + CharacterData(TreeScope& tree_scope, const AtomicString& text, ConstructionType type); private: AtomicString data_; }; +template<> +struct DowncastTraits { + static bool AllowFrom(const Node& node) { return node.IsCharacterDataNode(); } +}; + } // namespace kraken #endif // KRAKENBRIDGE_CHARACTER_DATA_H diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc index 2ff5cb6e88..cf458b04dd 100644 --- a/bridge/core/dom/comment.cc +++ b/bridge/core/dom/comment.cc @@ -4,6 +4,8 @@ */ #include "comment.h" +#include "tree_scope.h" +#include "document.h" #include "built_in_string.h" namespace kraken { @@ -16,8 +18,8 @@ Comment* Comment::Create(Document& document) { return MakeGarbageCollected(document, ConstructionType::kCreateOther); } -Comment::Comment(Document& document, ConstructionType type) - : CharacterData(document, built_in_string::kempty_string, type) {} +Comment::Comment(TreeScope& tree_scope, ConstructionType type) + : CharacterData(tree_scope, built_in_string::kempty_string, type) {} Node::NodeType Comment::nodeType() const { return Node::kCommentNode; diff --git a/bridge/core/dom/comment.h b/bridge/core/dom/comment.h index f70c69b02a..5156573832 100644 --- a/bridge/core/dom/comment.h +++ b/bridge/core/dom/comment.h @@ -17,7 +17,7 @@ class Comment : public CharacterData { static Comment* Create(ExecutingContext* context, ExceptionState& exception_state); static Comment* Create(Document&); - explicit Comment(Document& document, ConstructionType type); + explicit Comment(TreeScope& tree_scope, ConstructionType type); NodeType nodeType() const override; diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 7736a677fd..09da4908d1 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -321,7 +321,8 @@ std::string ContainerNode::nodeValue() const { return ""; } -ContainerNode::ContainerNode(Document* document, ConstructionType type) : Node(document->GetExecutingContext(), document, type) {} +ContainerNode::ContainerNode(TreeScope* tree_scope, ConstructionType type) : Node(tree_scope->GetDocument().GetExecutingContext(), tree_scope, type) {} +ContainerNode::ContainerNode(ExecutingContext* context, Document* document, ConstructionType type): Node(context, document, type) {} void ContainerNode::RemoveBetween(Node* previous_child, Node* next_child, Node& old_child) { assert(old_child.parentNode() == this); @@ -415,6 +416,10 @@ void ContainerNode::NotifyNodeRemoved(Node& root) { } void ContainerNode::Trace(GCVisitor* visitor) const { + for(Node& node: NodeTraversal::ChildrenOf(*this)) { + visitor->Trace(&node); + } + Node::Trace(visitor); } diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index a5c9cba33a..57bc6ac27d 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -51,7 +51,8 @@ class ContainerNode : public Node { void Trace(GCVisitor* visitor) const override; protected: - ContainerNode(Document* document, ConstructionType = kCreateContainer); + 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; } @@ -81,8 +82,8 @@ class ContainerNode : public Node { inline bool IsChildTypeAllowed(const Node& child) const; inline bool IsHostIncludingInclusiveAncestorOfThis(const Node&, ExceptionState&) const; - Node* first_child_; - Node* last_child_; + Node* first_child_{nullptr}; + Node* last_child_{nullptr}; }; inline Node* Node::firstChild() const { @@ -109,6 +110,11 @@ inline bool ContainerNode::HasChildCount(unsigned count) const { return !count && !child; } +template <> +struct DowncastTraits { + static bool AllowFrom(const Node& node) { return node.IsContainerNode(); } +}; + } // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 2cf4a7af1d..7465a99ec7 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -4,8 +4,13 @@ */ #include "document.h" +#include "core/dom/element.h" #include "core/html/html_element.h" +#include "core/html/html_html_element.h" #include "core/html/html_unknown_element.h" +#include "core/html/html_body_element.h" +#include "core/html/html_head_element.h" +#include "element_traversal.h" #include "foundation/ascii_types.h" #include "html_element_factory.h" @@ -16,7 +21,8 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ } Document::Document(ExecutingContext* context) - : Node(context, this, ConstructionType::kCreateDocument), TreeScope(*this) {} + : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) { +} ScriptValue Document::createElement(const AtomicString& name, ExceptionState& exception_state) { if (!IsValidName(name)) { @@ -56,6 +62,28 @@ Node::NodeType Document::nodeType() const { return kDocumentNode; } +bool Document::ChildTypeAllowed(NodeType type) const { + switch (type) { + case kAttributeNode: + case kDocumentFragmentNode: + case kDocumentNode: + case kTextNode: + return false; + case kCommentNode: + return true; + case kDocumentTypeNode: + case kElementNode: + // Documents may contain no more than one of each of these. + // (One Element and one DocumentType.) + for (Node& c : NodeTraversal::ChildrenOf(*this)) { + if (c.nodeType() == type) + return false; + } + return true; + } + return false; +} + template static inline bool IsValidNameASCII(const CharType* characters, unsigned length) { CharType c = characters[0]; @@ -98,4 +126,30 @@ Node* Document::Clone(Document&, CloneChildrenFlag) const { return nullptr; } + +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; +} + +HTMLHeadElement* Document::head() const { + Node* de = documentElement(); + if (de == nullptr) + return nullptr; + + return Traversal::FirstChild(*de); +} + +void Document::Trace(GCVisitor* visitor) const { + ContainerNode::Trace(visitor); +} + } // namespace kraken diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts index fd1cc47a48..c0e50f41ad 100644 --- a/bridge/core/dom/document.d.ts +++ b/bridge/core/dom/document.d.ts @@ -1,11 +1,15 @@ import {Node} from "./node"; -import {Element} from "./element"; 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"; interface Document extends Node { - + readonly body: HTMLBodyElement | null; + readonly head: HTMLHeadElement | null; + readonly documentElement: HTMLHtmlElement; createElement(tagName: string): any; createTextNode(value: string): Text; createDocumentFragment(): DocumentFragment; diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 178c1bbf24..a02ff4f485 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -7,18 +7,23 @@ #define KRAKENBRIDGE_DOCUMENT_H #include "container_node.h" -#include "tree_scope.h" +#include "core/dom/comment.h" #include "core/dom/document_fragment.h" #include "core/dom/text.h" -#include "core/dom/comment.h" +#include "tree_scope.h" namespace kraken { +class HTMLBodyElement; +class HTMLHeadElement; +class HTMLHtmlElement; + // A document (https://dom.spec.whatwg.org/#concept-document) is the root node // of a tree of DOM nodes, generally resulting from the parsing of a markup // (typically, HTML) resource. -class Document : public Node, TreeScope { +class Document : public ContainerNode, public TreeScope { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = Document*; @@ -31,10 +36,26 @@ class Document : public Node, TreeScope { DocumentFragment* createDocumentFragment(ExceptionState& exception_state); Comment* createComment(ExceptionState& exception_state); - std::string nodeName() const override; - std::string nodeValue() const override; - NodeType nodeType() const override; - Node * Clone(Document &, CloneChildrenFlag) const override; + [[nodiscard]] std::string nodeName() const override; + [[nodiscard]] std::string nodeValue() const override; + [[nodiscard]] NodeType nodeType() const override; + [[nodiscard]] bool ChildTypeAllowed(NodeType) const override; + + // The following implements the rule from HTML 4 for what valid names are. + static bool IsValidName(const AtomicString& name); + + Node* Clone(Document&, CloneChildrenFlag) const override; + + [[nodiscard]] Element* documentElement() const { return document_element_; } + void SetDocumentElement(Element* element) { + document_element_ = element; + }; + + // "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; + [[nodiscard]] HTMLHeadElement* head() const; void IncrementNodeCount() { node_count_++; } void DecrementNodeCount() { @@ -43,11 +64,11 @@ class Document : public Node, TreeScope { } int NodeCount() const { return node_count_; } - // The following implements the rule from HTML 4 for what valid names are. - static bool IsValidName(const AtomicString& name); + void Trace(GCVisitor* visitor) const override; private: int node_count_; + Element* document_element_{nullptr}; }; } // namespace kraken diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index 02d43ba84e..6b11742c9f 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -5,6 +5,7 @@ #include "document_fragment.h" #include "events/event_target.h" +#include "document.h" namespace kraken { diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index 413e5bd3f8..e88ab58faa 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -36,6 +36,11 @@ class DocumentFragment : public ContainerNode { bool ChildTypeAllowed(NodeType) const override; }; +template<> +struct DowncastTraits { + static bool AllowFrom(const Node& node) { return node.IsDocumentFragment(); } +}; + } // namespace kraken #endif // KRAKENBRIDGE_DOCUMENT_FRAGMENT_H diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 41c44f3f23..a77bbba1e5 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -28,6 +28,25 @@ TEST(Document, createElement) { EXPECT_EQ(logCalled, true); } +TEST(Document, body) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "
"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->getContext(); + const char* code = + "console.log(document.body)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + // TEST(Document, createTextNode) { // bool static errorCalled = false; // bool static logCalled = false; @@ -51,7 +70,7 @@ TEST(Document, createElement) { // EXPECT_EQ(errorCalled, false); // EXPECT_EQ(logCalled, true); // } -// +//// // TEST(Document, instanceofNode) { // bool static errorCalled = false; // bool static logCalled = false; diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 52c3640136..17af6e0efa 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -135,6 +135,7 @@ bool Element::HasEquivalentAttributes(const Element& other) const { void Element::Trace(GCVisitor* visitor) const { visitor->Trace(attributes_); + ContainerNode::Trace(visitor); } Node* Element::Clone(Document& factory, CloneChildrenFlag flag) const { @@ -277,7 +278,7 @@ std::string Element::innerHTML() const { std::string s; // If Element is TemplateElement, the innerHTML content is the content of documentFragment. - const Node* parent = DynamicTo(this); + const Node* parent = To(this); // if (auto* template_element = DynamicTo(this)) { // parent = DynamicTo(template_element->content()); @@ -323,4 +324,17 @@ void Element::_beforeUpdateId(JSValue oldIdValue, JSValue newIdValue) {} Node::NodeType Element::nodeType() const { return kElementNode; } + +bool Element::ChildTypeAllowed(NodeType type) const { + switch (type) { + case kElementNode: + case kTextNode: + case kCommentNode: + return true; + default: + break; + } + return false; +} + } // namespace kraken diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index ddce956e6c..c24e7a7c5c 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -68,6 +68,7 @@ class Element : public ContainerNode { std::string nodeName() const override; NodeType nodeType() const override; + bool ChildTypeAllowed(NodeType) const override; bool HasEquivalentAttributes(const Element& other) const; @@ -89,6 +90,27 @@ class Element : public ContainerNode { AtomicString tag_name_ = AtomicString::Empty(ctx()); }; +template +bool IsElementOfType(const Node&); +template <> +inline bool IsElementOfType(const Node& node) { + return node.IsElementNode(); +} +template +inline bool IsElementOfType(const Element& element) { + return IsElementOfType(static_cast(element)); +} +template <> +inline bool IsElementOfType(const Element&) { + return true; +} + +template <> +struct DowncastTraits { + static bool AllowFrom(const Node& node) { return node.IsElementNode(); } +}; + + } // namespace kraken #endif // KRAKENBRIDGE_ELEMENT_H diff --git a/bridge/core/dom/element_traversal.h b/bridge/core/dom/element_traversal.h new file mode 100644 index 0000000000..96bf7d65b5 --- /dev/null +++ b/bridge/core/dom/element_traversal.h @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ +#define KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ + +#include "foundation/macros.h" +#include "node_traversal.h" +#include "traversal_range.h" +#include "element.h" +#include "html_element_type_helper.h" + +namespace kraken { + +class HasTagName { + KRAKEN_STACK_ALLOCATED(); + public: + explicit HasTagName(const AtomicString& tag_name) : tag_name_(tag_name) {} + bool operator()(const Element& element) const { + return element.HasTagName(tag_name_); + } + + private: + const AtomicString tag_name_; +}; + +// This class is used to traverse the DOM tree. It isn't meant to be +// constructed; instead, callers invoke the static methods, after templating it +// so that ElementType is the type of element they are interested in traversing. +// Traversals can also be predicated on a matcher, which will be used to +// filter the returned elements. A matcher is a callable - an object of a class +// that defines operator(). HasTagName above is an example of a matcher. +// +// For example, a caller could do this: +// Traversal::firstChild(some_node, +// HasTagName(html_names::kTitleTag)); +// +// This invocation would return the first child of |some_node| (which has to be +// a ContainerNode) for which HasTagName(html_names::kTitleTag) returned true, +// so it would return the first child of |someNode| which is a element. +// If the caller needs to traverse a Node this way, it's necessary to first +// check Node::IsContainerNode() and then use To<ContainerNode>(). Another way +// to achieve same behaviour is to use DynamicTo<ContainerNode>() which +// checks Node::IsContainerNode() and then returns container +// node. If the conditional check fails then it returns nullptr. +// DynamicTo<ContainerNode>() wraps IsContainerNode() so there is no need of +// an explicit conditional check. +// +// When looking for a specific element type, it is more efficient to do this: +// Traversal<HTMLTitleElement>::firstChild(someNode); +// +// Traversal can also be used to find ancestors and descendants; see the +// documentation in the class body below. +// +// Note that these functions do not traverse into child shadow trees of any +// shadow hosts they encounter. If you need to traverse the shadow DOM, you can +// manually traverse the shadow trees using a second Traversal, or use +// FlatTreeTraversal. +// +// ElementTraversal is a specialized version of Traversal<Element>. +template <class ElementType> +class Traversal { + KRAKEN_STATIC_ONLY(Traversal); + + public: + using TraversalNodeType = ElementType; + // First or last ElementType child of the node. + static ElementType* FirstChild(const ContainerNode& current) { + return FirstChildTemplate(current); + } + static ElementType* FirstChild(const Node& current) { + return FirstChildTemplate(current); + } + template <class MatchFunc> + static ElementType* FirstChild(const ContainerNode&, MatchFunc); + static ElementType* LastChild(const ContainerNode& current) { + return LastChildTemplate(current); + } + static ElementType* LastChild(const Node& current) { + return LastChildTemplate(current); + } + template <class MatchFunc> + static ElementType* LastChild(const ContainerNode&, MatchFunc); + + // First ElementType ancestor of the node. + static ElementType* FirstAncestor(const Node& current); + static ElementType* FirstAncestorOrSelf(Node& current) { + return FirstAncestorOrSelfTemplate(current); + } + static ElementType* FirstAncestorOrSelf(Element& current) { + return FirstAncestorOrSelfTemplate(current); + } + static const ElementType* FirstAncestorOrSelf(const Node& current) { + return FirstAncestorOrSelfTemplate(const_cast<Node&>(current)); + } + static const ElementType* FirstAncestorOrSelf(const Element& current) { + return FirstAncestorOrSelfTemplate(const_cast<Element&>(current)); + } + + // First or last ElementType descendant of the node. + // For pure Elements firstWithin() is always the same as firstChild(). + static ElementType* FirstWithin(const ContainerNode& current) { + return FirstWithinTemplate(current); + } + static ElementType* FirstWithin(const Node& current) { + return FirstWithinTemplate(current); + } + template <typename MatchFunc> + static ElementType* FirstWithin(const ContainerNode&, MatchFunc); + + static ElementType* InclusiveFirstWithin(Node& current) { + if (IsElementOfType<const ElementType>(current)) + return To<ElementType>(¤t); + return FirstWithin(current); + } + + static ElementType* LastWithin(const ContainerNode& current) { + return LastWithinTemplate(current); + } + static ElementType* LastWithin(const Node& current) { + return LastWithinTemplate(current); + } + template <class MatchFunc> + static ElementType* LastWithin(const ContainerNode&, MatchFunc); + static ElementType* LastWithinOrSelf(ElementType&); + + // Pre-order traversal skipping non-element nodes. + static ElementType* Next(const ContainerNode& current) { + return NextTemplate(current); + } + static ElementType* Next(const Node& current) { + return NextTemplate(current); + } + static ElementType* Next(const ContainerNode& current, + const Node* stay_within) { + return NextTemplate(current, stay_within); + } + static ElementType* Next(const Node& current, const Node* stay_within) { + return NextTemplate(current, stay_within); + } + template <class MatchFunc> + static ElementType* Next(const ContainerNode& current, + const Node* stay_within, + MatchFunc); + static ElementType* Previous(const Node&); + static ElementType* Previous(const Node&, const Node* stay_within); + template <class MatchFunc> + static ElementType* Previous(const ContainerNode& current, + const Node* stay_within, + MatchFunc); + + // Like next, but skips children. + static ElementType* NextSkippingChildren(const Node&); + static ElementType* NextSkippingChildren(const Node&, + const Node* stay_within); + // Previous / Next sibling. + static ElementType* PreviousSibling(const Node&); + template <class MatchFunc> + static ElementType* PreviousSibling(const Node&, MatchFunc); + static ElementType* NextSibling(const Node&); + template <class MatchFunc> + static ElementType* NextSibling(const Node&, MatchFunc); + + static TraversalSiblingRange<Traversal<ElementType>> ChildrenOf(const Node&); + static TraversalDescendantRange<Traversal<ElementType>> DescendantsOf( + const Node&); + static TraversalInclusiveDescendantRange<Traversal<ElementType>> + InclusiveDescendantsOf(const ElementType&); + static TraversalNextRange<Traversal<ElementType>> StartsAt( + const ElementType&); + static TraversalNextRange<Traversal<ElementType>> StartsAfter(const Node&); + + private: + template <class NodeType> + static ElementType* FirstChildTemplate(NodeType&); + template <class NodeType> + static ElementType* LastChildTemplate(NodeType&); + template <class NodeType> + static ElementType* FirstAncestorOrSelfTemplate(NodeType&); + template <class NodeType> + static ElementType* FirstWithinTemplate(NodeType&); + template <class NodeType> + static ElementType* LastWithinTemplate(NodeType&); + template <class NodeType> + static ElementType* NextTemplate(NodeType&); + template <class NodeType> + static ElementType* NextTemplate(NodeType&, const Node* stay_within); +}; + +typedef Traversal<Element> ElementTraversal; + +template <class ElementType> +inline TraversalSiblingRange<Traversal<ElementType>> +Traversal<ElementType>::ChildrenOf(const Node& start) { + return TraversalSiblingRange<Traversal<ElementType>>( + Traversal<ElementType>::FirstChild(start)); +} + +template <class ElementType> +inline TraversalDescendantRange<Traversal<ElementType>> +Traversal<ElementType>::DescendantsOf(const Node& root) { + return TraversalDescendantRange<Traversal<ElementType>>(&root); +} + +template <class ElementType> +inline TraversalInclusiveDescendantRange<Traversal<ElementType>> +Traversal<ElementType>::InclusiveDescendantsOf(const ElementType& root) { + return TraversalInclusiveDescendantRange<Traversal<ElementType>>(&root); +} + +template <class ElementType> +inline TraversalNextRange<Traversal<ElementType>> +Traversal<ElementType>::StartsAt(const ElementType& start) { + return TraversalNextRange<Traversal<ElementType>>(&start); +} + +template <class ElementType> +inline TraversalNextRange<Traversal<ElementType>> +Traversal<ElementType>::StartsAfter(const Node& start) { + return TraversalNextRange<Traversal<ElementType>>( + Traversal<ElementType>::Next(start)); +} + +// Specialized for pure Element to exploit the fact that Elements parent is +// always either another Element or the root. +template <> +template <class NodeType> +inline Element* Traversal<Element>::FirstWithinTemplate(NodeType& current) { + return FirstChildTemplate(current); +} + +template <> +template <class NodeType> +inline Element* Traversal<Element>::NextTemplate(NodeType& current) { + Node* node = NodeTraversal::Next(current); + while (node && !node->IsElementNode()) + node = NodeTraversal::NextSkippingChildren(*node); + return To<Element>(node); +} + +template <> +template <class NodeType> +inline Element* Traversal<Element>::NextTemplate(NodeType& current, + const Node* stay_within) { + Node* node = NodeTraversal::Next(current, stay_within); + while (node && !node->IsElementNode()) + node = NodeTraversal::NextSkippingChildren(*node, stay_within); + return To<Element>(node); +} + +// Generic versions. +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::FirstChildTemplate( + NodeType& current) { + Node* node = current.firstChild(); + while (node && !IsElementOfType<const ElementType>(*node)) + node = node->nextSibling(); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::FirstChild( + const ContainerNode& current, + MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::FirstChild(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::NextSibling(*element); + return element; +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::FirstAncestor(const Node& current) { + ContainerNode* ancestor = current.parentNode(); + while (ancestor && !IsElementOfType<const ElementType>(*ancestor)) + ancestor = ancestor->parentNode(); + return To<ElementType>(ancestor); +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::FirstAncestorOrSelfTemplate( + NodeType& current) { + if (IsElementOfType<const ElementType>(current)) + return &To<ElementType>(current); + return FirstAncestor(current); +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::LastChildTemplate( + NodeType& current) { + Node* node = current.lastChild(); + while (node && !IsElementOfType<const ElementType>(*node)) + node = node->previousSibling(); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::LastChild( + const ContainerNode& current, + MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::LastChild(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::PreviousSibling(*element); + return element; +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::FirstWithinTemplate( + NodeType& current) { + Node* node = current.firstChild(); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Next(*node, ¤t); + return To<ElementType>(node); +} + +template <class ElementType> +template <typename MatchFunc> +inline ElementType* Traversal<ElementType>::FirstWithin( + const ContainerNode& current, + MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::FirstWithin(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::Next(*element, ¤t, is_match); + return element; +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::LastWithinTemplate( + NodeType& current) { + Node* node = NodeTraversal::LastWithin(current); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Previous(*node, ¤t); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::LastWithin( + const ContainerNode& current, + MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::LastWithin(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::Previous(*element, ¤t, is_match); + return element; +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::LastWithinOrSelf( + ElementType& current) { + if (ElementType* last_descendant = LastWithin(current)) + return last_descendant; + return ¤t; +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::NextTemplate(NodeType& current) { + Node* node = NodeTraversal::Next(current); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Next(*node); + return To<ElementType>(node); +} + +template <class ElementType> +template <class NodeType> +inline ElementType* Traversal<ElementType>::NextTemplate( + NodeType& current, + const Node* stay_within) { + Node* node = NodeTraversal::Next(current, stay_within); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Next(*node, stay_within); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::Next(const ContainerNode& current, + const Node* stay_within, + MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::Next(current, stay_within); + while (element && !is_match(*element)) + element = Traversal<ElementType>::Next(*element, stay_within); + return element; +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::Previous(const Node& current) { + Node* node = NodeTraversal::Previous(current); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Previous(*node); + return To<ElementType>(node); +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::Previous(const Node& current, + const Node* stay_within) { + Node* node = NodeTraversal::Previous(current, stay_within); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::Previous(*node, stay_within); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::Previous( + const ContainerNode& current, + const Node* stay_within, + MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::Previous(current, stay_within); + while (element && !is_match(*element)) + element = Traversal<ElementType>::Previous(*element, stay_within); + return element; +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::NextSkippingChildren( + const Node& current) { + Node* node = NodeTraversal::NextSkippingChildren(current); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::NextSkippingChildren(*node); + return To<ElementType>(node); +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::NextSkippingChildren( + const Node& current, + const Node* stay_within) { + Node* node = NodeTraversal::NextSkippingChildren(current, stay_within); + while (node && !IsElementOfType<const ElementType>(*node)) + node = NodeTraversal::NextSkippingChildren(*node, stay_within); + return To<ElementType>(node); +} + + +template <class ElementType> +inline ElementType* Traversal<ElementType>::PreviousSibling( + const Node& current) { + Node* node = current.previousSibling(); + while (node && !IsElementOfType<const ElementType>(*node)) + node = node->previousSibling(); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::PreviousSibling( + const Node& current, + MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::PreviousSibling(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::PreviousSibling(*element); + return element; +} + +template <class ElementType> +inline ElementType* Traversal<ElementType>::NextSibling(const Node& current) { + Node* node = current.nextSibling(); + while (node && !IsElementOfType<const ElementType>(*node)) + node = node->nextSibling(); + return To<ElementType>(node); +} + +template <class ElementType> +template <class MatchFunc> +inline ElementType* Traversal<ElementType>::NextSibling(const Node& current, + MatchFunc is_match) { + ElementType* element = Traversal<ElementType>::NextSibling(current); + while (element && !is_match(*element)) + element = Traversal<ElementType>::NextSibling(*element); + return element; +} + + +} + +#endif // KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index ed0ec394ee..5c569b6795 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -88,6 +88,50 @@ void Event::SetCurrentTarget(EventTarget* target) { current_target_ = target; } +bool Event::IsUIEvent() const { + return false; +} + +bool Event::IsMouseEvent() const { + return false; +} + +bool Event::IsFocusEvent() const { + return false; +} + +bool Event::IsKeyboardEvent() const { + return false; +} + +bool Event::IsTouchEvent() const { + return false; +} + +bool Event::IsGestureEvent() const { + return false; +} + +bool Event::IsPointerEvent() const { + return false; +} + +bool Event::IsInputEvent() const { + return false; +} + +bool Event::IsDragEvent() const { + return false; +} + +bool Event::IsBeforeUnloadEvent() const { + return false; +} + +bool Event::IsErrorEvent() const { + return false; +} + void Event::preventDefault(ExceptionState& exception_state) { if (handling_passive_ != PassiveMode::kNotPassive && handling_passive_ != PassiveMode::kNotPassiveDefault) { return; diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index f800e853b5..88f4783549 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -129,6 +129,22 @@ class Event : public ScriptWrappable { uint8_t eventPhase() const { return event_phase_; } void SetEventPhase(uint8_t event_phase) { event_phase_ = event_phase; } + // These events are general classes of events. + virtual bool IsUIEvent() const; + virtual bool IsMouseEvent() const; + virtual bool IsFocusEvent() const; + virtual bool IsKeyboardEvent() const; + virtual bool IsTouchEvent() const; + virtual bool IsGestureEvent() const; + virtual bool IsPointerEvent() const; + virtual bool IsInputEvent() const; + + // Drag events are a subset of mouse events. + virtual bool IsDragEvent() const; + + virtual bool IsBeforeUnloadEvent() const; + virtual bool IsErrorEvent() const; + // This callback is invoked when an event listener has been dispatched // at the current target. It should only be used to influence UMA metrics // and not change functionality since observing the presence of listeners diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h index c57e210b17..c131c041e9 100644 --- a/bridge/core/dom/events/event_listener.h +++ b/bridge/core/dom/events/event_listener.h @@ -33,6 +33,8 @@ class EventListener { // 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; } diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index ab6e97264d..59aa8fbe56 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -385,13 +385,17 @@ Node* Node::CommonAncestor(const Node& other, ContainerNode* (*parent)(const Nod return nullptr; } -Node::Node(ExecutingContext* context, Document* document, ConstructionType type) +Node::Node(ExecutingContext* context, TreeScope* tree_scope, ConstructionType type) : EventTarget(context), node_flags_(type), parent_or_shadow_host_node_(nullptr), previous_(nullptr), - next_(nullptr) {} + tree_scope_(tree_scope), + next_(nullptr), + data_(nullptr) {} -void Node::Trace(GCVisitor*) const {} +void Node::Trace(GCVisitor* visitor) const { + EventTarget::Trace(visitor); +} } // namespace kraken diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 5997747ca0..ab1dcd06da 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -286,7 +286,7 @@ class Node : public EventTarget { void SetTreeScope(TreeScope* scope) { tree_scope_ = scope; } - Node(ExecutingContext* context, Document*, ConstructionType); + Node(ExecutingContext* context, TreeScope*, ConstructionType); Node() = delete; private: diff --git a/bridge/core/dom/text.cc b/bridge/core/dom/text.cc index 985e4d7c9e..31053d3796 100644 --- a/bridge/core/dom/text.cc +++ b/bridge/core/dom/text.cc @@ -3,6 +3,7 @@ */ #include "text.h" +#include "document.h" namespace kraken { diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h index 28f7779f47..ef12820d18 100644 --- a/bridge/core/dom/text.h +++ b/bridge/core/dom/text.h @@ -19,15 +19,22 @@ class Text : public CharacterData { static Text* Create(ExecutingContext* context, ExceptionState& executing_context); static Text* Create(ExecutingContext* context, const AtomicString& value, ExceptionState& executing_context); - Text(Document& document, const AtomicString& data, ConstructionType type) : CharacterData(document, data, type) {} + Text(TreeScope& tree_scope, const AtomicString& data, ConstructionType type) : CharacterData(tree_scope, data, type) {} NodeType nodeType() const override; + + private: std::string nodeName() const override; Node* Clone(Document&, CloneChildrenFlag) const override; }; +template<> +struct DowncastTraits<Text> { + static bool AllowFrom(const Node& node) { return node.IsTextNode(); }; +}; + } // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_TEXT_H_ diff --git a/bridge/core/dom/tree_scope.cc b/bridge/core/dom/tree_scope.cc index 76ba007173..3c12ba9362 100644 --- a/bridge/core/dom/tree_scope.cc +++ b/bridge/core/dom/tree_scope.cc @@ -3,12 +3,12 @@ */ #include "tree_scope.h" -#include "container_node.h" +#include "document.h" namespace kraken { -TreeScope::TreeScope(ContainerNode& root_node, Document& document) : root_node_(&root_node), document_(&document) { - root_node.SetTreeScope(this); +TreeScope::TreeScope(Document& document) : root_node_(&document), document_(&document) { + root_node_->SetTreeScope(this); } } // namespace kraken diff --git a/bridge/core/dom/tree_scope.h b/bridge/core/dom/tree_scope.h index 03227212e1..59580fb592 100644 --- a/bridge/core/dom/tree_scope.h +++ b/bridge/core/dom/tree_scope.h @@ -5,13 +5,20 @@ #ifndef KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ #define KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ -#include <assert.h> +#include <cassert> namespace kraken { class ContainerNode; class Document; +// The root node of a document tree (in which case this is a Document) or of a +// shadow tree (in which case this is a ShadowRoot). Various things, like +// element IDs, are scoped to the TreeScope in which they are rooted, if any. +// +// A class which inherits both Node and TreeScope must call clearRareData() in +// its destructor so that the Node destructor no longer does problematic +// NodeList cache manipulation in the destructor. class TreeScope { friend class Node; @@ -22,7 +29,7 @@ class TreeScope { } protected: - explicit TreeScope(ContainerNode&, Document&); + explicit TreeScope(Document&); private: ContainerNode* root_node_; diff --git a/bridge/core/events/error_event.h b/bridge/core/events/error_event.h index 52777d916d..0f49ef5149 100644 --- a/bridge/core/events/error_event.h +++ b/bridge/core/events/error_event.h @@ -49,6 +49,11 @@ class ErrorEvent : public Event { ScriptValue error_; }; +template <> +struct DowncastTraits<ErrorEvent> { + static bool AllowFrom(const Event& event) { return event.IsErrorEvent(); } +}; + } // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 1bfdd8772b..1505317d89 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -8,6 +8,7 @@ #include "event_type_names.h" #include "polyfill.h" #include "core/dom/document.h" +#include "core/html/html_html_element.h" #include "foundation/logging.h" @@ -370,6 +371,10 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { void ExecutingContext::InstallDocument() { document_ = MakeGarbageCollected<Document>(this); + HTMLHtmlElement* html_element = MakeGarbageCollected<HTMLHtmlElement>(*document_); + ExceptionState exception_state; + document_->AppendChild(html_element, exception_state); + document_->SetDocumentElement(html_element); DefineGlobalProperty("document", document_->ToValue().QJSValue()); } diff --git a/bridge/core/html/canvas/html_canvas_element.d.ts b/bridge/core/html/canvas/html_canvas_element.d.ts index 42682a36a6..ee9c7d5018 100644 --- a/bridge/core/html/canvas/html_canvas_element.d.ts +++ b/bridge/core/html/canvas/html_canvas_element.d.ts @@ -21,9 +21,9 @@ interface CanvasRenderingContext2D { clearRect(x: number, y: number, w: number, h: number): void; closePath(): void; clip(path?: string): void; - drawImage(image: CanvasImageSource, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void; - drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void; - drawImage(image: CanvasImageSource, dx: number, dy: number): void; + drawImage(image: HTMLImageElement, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void; + drawImage(image: HTMLImageElement, dx: number, dy: number, dw: number, dh: number): void; + drawImage(image: HTMLImageElement, dx: number, dy: number): void; ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void; fill(path?: string): void; fillRect(x: number, y: number, w: number, h: number): void; diff --git a/bridge/core/html/html_body_element.d.ts b/bridge/core/html/html_body_element.d.ts new file mode 100644 index 0000000000..b2b9c4ca94 --- /dev/null +++ b/bridge/core/html/html_body_element.d.ts @@ -0,0 +1,5 @@ +import {HTMLElement} from "./html_element"; + +export interface HTMLBodyElement extends HTMLElement { + new(): void; +} diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index 46b30b1630..e9d86d8fff 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -11,6 +11,7 @@ namespace kraken { class HTMLBodyElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); public: explicit HTMLBodyElement(Document&); }; diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index b2409794d7..eadc06b214 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -23,6 +23,22 @@ inline HTMLElement::HTMLElement(const AtomicString& tag_name, ConstructionType type = kCreateHTMLElement) : Element(tag_name, document, type) {} + +template <typename T> +bool IsElementOfType(const HTMLElement&); +template <> +inline bool IsElementOfType<const HTMLElement>(const HTMLElement&) { + return true; +} +template <> +inline bool IsElementOfType<const HTMLElement>(const Node& node) { + return IsA<HTMLElement>(node); +} +template <> +struct DowncastTraits<HTMLElement> { + static bool AllowFrom(const Node& node) { return node.IsHTMLElement(); } +}; + } // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_head_element.cc b/bridge/core/html/html_head_element.cc new file mode 100644 index 0000000000..16840096a3 --- /dev/null +++ b/bridge/core/html/html_head_element.cc @@ -0,0 +1,14 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_head_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLHeadElement::HTMLHeadElement(Document& document) : HTMLElement(html_names::khead, &document) {} + + +} 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..829571a04c --- /dev/null +++ b/bridge/core/html/html_head_element.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ + +#include "html_element.h" + +namespace kraken { + +class HTMLHeadElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLHeadElement(Document&); + + private: +}; + +} + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ diff --git a/bridge/core/html/html_html_element.cc b/bridge/core/html/html_html_element.cc new file mode 100644 index 0000000000..2eb0932712 --- /dev/null +++ b/bridge/core/html/html_html_element.cc @@ -0,0 +1,13 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "html_html_element.h" +#include "html_names.h" + +namespace kraken { + +HTMLHtmlElement::HTMLHtmlElement(Document& document) : HTMLElement(html_names::khtml, &document) {} + +} 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..70ca7518f7 --- /dev/null +++ b/bridge/core/html/html_html_element.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ +#define KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ + +#include "html_element.h" + +namespace kraken { + +class HTMLHtmlElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + + public: + explicit HTMLHtmlElement(Document&); + + private: +}; + + +} + +#endif // KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_tag_names.json5 b/bridge/core/html/html_tag_names.json5 index e03f42bc1f..b49e934125 100644 --- a/bridge/core/html/html_tag_names.json5 +++ b/bridge/core/html/html_tag_names.json5 @@ -8,6 +8,10 @@ { "template": "element_factory", "filename": "html_element_factory" + }, + { + "template": "element_type_helper", + "filename": "html_element_type_helper" } ] }, @@ -21,7 +25,9 @@ // "interfaceName": "HTMLAnchorElement", // "filename": "html_anchor_element" // }, -// "body", + "html", + "body", + "head", "div", // { // "name": "input", diff --git a/bridge/foundation/casting.h b/bridge/foundation/casting.h index 521f105058..9e691384d9 100644 --- a/bridge/foundation/casting.h +++ b/bridge/foundation/casting.h @@ -7,14 +7,79 @@ #define KRAKENBRIDGE_FOUNDATION_CASTING_H_ #include <type_traits> +#include <cassert> namespace kraken { +// Helpers for downcasting in a class hierarchy. +// +// IsA<T>(x): returns true if |x| can be safely downcast to T*. Usage of this +// should not be common; if it is paired with a call to To<T>, consider +// using DynamicTo<T> instead (see below). Note that this also returns +// false if |x| is nullptr. +// +// To<T>(x): unconditionally downcasts and returns |x| as a T*. +// Use when IsA<T>(x) is known to be true due to external invariants. +// If |x| is nullptr, returns nullptr. +// +// DynamicTo<T>(x): downcasts and returns |x| as a T* iff IsA<T>(x) is true, +// and nullptr otherwise. This is useful for combining a conditional +// branch on IsA<T>(x) and an invocation of To<T>(x), e.g.: +// if (IsA<DerivedClass>(x)) +// To<DerivedClass>(x)->... +// can be written: +// if (auto* derived = DynamicTo<DerivedClass>(x)) +// derived->...; +// +// Marking downcasts as safe is done by specializing the DowncastTraits +// template: +// +// template <> +// struct DowncastTraits<DerivedClass> { +// static bool AllowFrom(const BaseClass& b) { +// return b.IsDerivedClass(); +// } +// static bool AllowFrom(const AnotherBaseClass& b) { +// return b.type() == AnotherBaseClass::kDerivedClassTag; +// } +// }; +// +// int main() { +// BaseClass* base = CreateDerived(); +// AnotherBaseClass* another_base = CreateDerived(); +// UnrelatedClass* unrelated = CreateUnrelated(); +// +// std::cout << std::boolalpha; +// std::cout << IsA<Derived>(base) << '\n'; // prints true +// std::cout << IsA<Derived>(another_base) << '\n'; // prints true +// std::cout << IsA<Derived>(unrelated) << '\n'; // prints false +// } +template <typename T> +struct DowncastTraits { + template <typename U> + static bool AllowFrom(const U&) { + static_assert(sizeof(U) == 0, "no downcast traits specialization for T"); + assert(false); + return false; + } +}; + +namespace internal { + +// Though redundant with the return type inferred by `auto`, the trailing return +// type is needed for SFINAE. +template <typename Derived, typename Base> +auto IsDowncastAllowedHelper(const Base& from) -> decltype(DowncastTraits<Derived>::AllowFrom(from)) { + return DowncastTraits<Derived>::AllowFrom(from); +} + +} // namespace internal + // Returns true iff the conversion from Base to Derived is allowed. For the // pointer overloads, returns false if the input pointer is nullptr. template <typename Derived, typename Base> bool IsA(const Base& from) { - return std::is_base_of<Derived, Base>::value; + return internal::IsDowncastAllowedHelper<Derived>(from); } template <typename Derived, typename Base> diff --git a/bridge/polyfill/src/dom.ts b/bridge/polyfill/src/dom.ts index c64e6aa3c9..31de9c62a7 100644 --- a/bridge/polyfill/src/dom.ts +++ b/bridge/polyfill/src/dom.ts @@ -1,6 +1,3 @@ -let html = document.createElement('html'); -document.appendChild(html); - let head = document.createElement('head'); document.documentElement.appendChild(head); @@ -8,12 +5,12 @@ let body = document.createElement('body'); document.documentElement.appendChild(body); // @ts-ignore -class SVGElement extends Element { - constructor() { - super(); - } -} - -Object.defineProperty(window, 'SVGElement', { - value: SVGElement -}); +// class SVGElement extends Element { +// constructor() { +// super(); +// } +// } +// +// Object.defineProperty(window, 'SVGElement', { +// value: SVGElement +// }); diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index 3969d0d368..536f259a97 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -1,4 +1,4 @@ -// import './dom'; +import './dom'; // import './query-selector'; import { console } from './console'; // import { fetch, Request, Response, Headers } from './fetch'; diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl index 1b2b516618..3dce90fa23 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl @@ -42,17 +42,17 @@ struct CreateHTMLFunctionMapData { <% _.forEach(data, (item, index) => { %> <% if (_.isString(item)) { %> -static HTMLElement* HTML<%= item[0].toUpperCase() + item.slice(1) %>Constructor(Document& document) { - return MakeGarbageCollected<HTML<%= item[0].toUpperCase() + item.slice(1) %>Element>(document); +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<%= item.name[0].toUpperCase() + item.name.slice(1) %>Constructor(Document& document) { +static HTMLElement* HTML<%= _.upperFirst(item.name) %>Constructor(Document& document) { return MakeGarbageCollected<<%= item.interfaceName %>>(document); } <% } else { %> -static HTMLElement* HTML<%= item.name[0].toUpperCase() + item.name.slice(1) %>Constructor(Document& document) { - return MakeGarbageCollected<HTML<%= item.name[0].toUpperCase() + item.name.slice(1) %>Element>(document); +static HTMLElement* HTML<%= _.upperFirst(item.name) %>Constructor(Document& document) { + return MakeGarbageCollected<HTML<%= _.upperFirst(item.name) %>Element>(document); } <% } %> <% } %> @@ -68,12 +68,12 @@ static void CreateHTMLFunctionMap() { <% _.forEach(data, (item, index) => { %> <% if (_.isString(item)) { %> - {html_names::k<%= item %>, HTML<%= item[0].toUpperCase() + item.slice(1) %>Constructor}, + {html_names::k<%= item %>, HTML<%= _.upperFirst(item) %>Constructor}, <% } else if (_.isObject(item)) { %> <% if (item.interfaceName) { %> - {html_names::k<%= item.name %>, HTML<%= item.name[0].toUpperCase() + item.name.slice(1) %>Constructor}, + {html_names::k<%= item.name %>, HTML<%= _.upperFirst(item.name) %>Constructor}, <% } else { %> - {html_names::k<%= item.name %>, HTML<%= item.name[0].toUpperCase() + item.name.slice(1) %>Constructor}, + {html_names::k<%= item.name %>, HTML<%= _.upperFirst(item.name) %>Constructor}, <% } %> <% } %> <% }); %> diff --git a/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl b/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl new file mode 100644 index 0000000000..47c98314b7 --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl @@ -0,0 +1,63 @@ + // Generated from template: + // code_generator/src/json/templates/element_type_helper.h.tpl + // and input files: + // <%= template_path %> + +#ifndef KRAKENBRIDGE_CORE_HTML_TYPE_HELPER_H_ +#define KRAKENBRIDGE_CORE_HTML_TYPE_HELPER_H_ + + +#include "core/dom/element.h" +#include "html_names.h" + +<% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> +#include "core/html/html_<%= item %>_element.h" + <% } else if (_.isObject(item)) { %> + <% if (item.interfaceHeaderDir) { %> +#include "<%= item.interfaceHeaderDir %>/html_<%= item.filename ? item.filename : item.name %>_element.h" + <% } else if (item.interfaceName != 'HTMLElement'){ %> +#include "core/html/<%= item.filename ? item.filename : `html_${item.name}_element` %>.h" + <% } %> + <% } %> +<% }); %> + + +namespace kraken { + + +<% function generateTypeHelperTemplate(name) { + return ` +class HTML${_.upperFirst(name)}Element; +template <> +inline bool IsElementOfType<const HTML${_.upperFirst(name)}Element>(const Node& node) { + return IsA<HTML${_.upperFirst(name)}Element>(node); +} +template <> +inline bool IsElementOfType<const HTML${_.upperFirst(name)}Element>(const HTMLElement& element) { + return IsA<HTML${_.upperFirst(name)}Element>(element); +} +template <> +struct DowncastTraits<HTML${_.upperFirst(name)}Element> { + static bool AllowFrom(const Element& element) { + return element.HasTagName(html_names::k${name}); + } + static bool AllowFrom(const Node& node) { + return node.IsHTMLElement() && IsA<HTML${_.upperFirst(name)}Element>(To<HTMLElement>(node)); + } +}; +`; +} %> + +<% _.forEach(data, (item, index) => { %> + <% if (_.isString(item)) { %> + <%= generateTypeHelperTemplate(item) %> + <% } else if (_.isObject(item)) { %> + <%= generateTypeHelperTemplate(item.name) %> + <% } %> +<% }) %> + +} + + +#endif From 14697573e04b692a36dc3218ea6be848c24e38a1 Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Wed, 20 Apr 2022 12:12:28 +0000 Subject: [PATCH 088/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 22 +-- bridge/bindings/qjs/garbage_collected.h | 2 +- bridge/bindings/qjs/js_based_event_listener.h | 4 +- bridge/core/dom/character_data.cc | 3 +- bridge/core/dom/character_data.h | 2 +- bridge/core/dom/comment.cc | 4 +- bridge/core/dom/container_node.cc | 10 +- bridge/core/dom/document.cc | 8 +- bridge/core/dom/document.h | 4 +- bridge/core/dom/document_fragment.cc | 2 +- bridge/core/dom/document_fragment.h | 2 +- bridge/core/dom/document_test.cc | 5 +- bridge/core/dom/element.h | 8 +- bridge/core/dom/element_traversal.h | 178 ++++++------------ bridge/core/dom/legacy/element_attributes.cc | 5 +- bridge/core/dom/legacy/element_attributes.h | 4 +- bridge/core/dom/text.h | 7 +- bridge/core/executing_context.cc | 5 +- bridge/core/executing_context.h | 1 - bridge/core/html/canvas/html_canvas_element.h | 2 +- bridge/core/html/forms/html_input_element.h | 2 +- .../core/html/forms/html_textarea_element.cc | 3 +- .../core/html/forms/html_textarea_element.h | 2 +- bridge/core/html/html_anchor_element.cc | 4 +- bridge/core/html/html_anchor_element.h | 2 +- bridge/core/html/html_body_element.cc | 4 +- bridge/core/html/html_body_element.h | 4 +- bridge/core/html/html_element.h | 1 - bridge/core/html/html_head_element.cc | 3 +- bridge/core/html/html_head_element.h | 2 +- bridge/core/html/html_html_element.cc | 2 +- bridge/core/html/html_html_element.h | 3 +- bridge/core/html/html_image_element.cc | 4 +- bridge/core/html/html_script_element.h | 5 +- bridge/core/html/html_template_element.h | 2 +- bridge/core/html/html_unknown_element.h | 5 +- bridge/core/script_state.cc | 4 +- bridge/foundation/casting.h | 2 +- 38 files changed, 123 insertions(+), 209 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 7c77ede70b..e32c9f923a 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -6,23 +6,23 @@ #include "binding_initializer.h" #include "core/executing_context.h" -#include "qjs_console.h" -#include "qjs_event.h" -#include "qjs_event_target.h" -#include "qjs_module_manager.h" -#include "qjs_window.h" -#include "qjs_document.h" -#include "qjs_node.h" #include "qjs_character_data.h" -#include "qjs_text.h" #include "qjs_comment.h" +#include "qjs_console.h" +#include "qjs_document.h" #include "qjs_element.h" -#include "qjs_html_element.h" +#include "qjs_element_attributes.h" +#include "qjs_event.h" +#include "qjs_event_target.h" +#include "qjs_html_body_element.h" #include "qjs_html_div_element.h" +#include "qjs_html_element.h" #include "qjs_html_head_element.h" -#include "qjs_html_body_element.h" #include "qjs_html_html_element.h" -#include "qjs_element_attributes.h" +#include "qjs_module_manager.h" +#include "qjs_node.h" +#include "qjs_text.h" +#include "qjs_window.h" namespace kraken { diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h index d3c8971444..f7b196e9dc 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/garbage_collected.h @@ -9,8 +9,8 @@ #include <quickjs/quickjs.h> #include <memory> -#include "foundation/macros.h" #include "foundation/casting.h" +#include "foundation/macros.h" #include "gc_visitor.h" #include "qjs_engine_patch.h" diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index b505d230d2..5674320164 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -52,9 +52,7 @@ class JSBasedEventListener : public EventListener { template <> struct DowncastTraits<JSBasedEventListener> { - static bool AllowFrom(const EventListener& event_listener) { - return event_listener.IsJSBasedEventListener(); - } + static bool AllowFrom(const EventListener& event_listener) { return event_listener.IsJSBasedEventListener(); } }; } // namespace kraken diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index c223a964db..54b1bdd6dc 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -15,7 +15,8 @@ std::string CharacterData::nodeValue() const { return data_.ToStdString(); } CharacterData::CharacterData(TreeScope& tree_scope, const AtomicString& text, Node::ConstructionType type) - : Node(tree_scope.GetDocument().GetExecutingContext(), &tree_scope, type), data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { + : Node(tree_scope.GetDocument().GetExecutingContext(), &tree_scope, type), + data_(!text.IsNull() ? text : AtomicString::Empty(ctx())) { assert(type == kCreateOther || type == kCreateText); } diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index c15d643efa..1a4edacc99 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -28,7 +28,7 @@ class CharacterData : public Node { AtomicString data_; }; -template<> +template <> struct DowncastTraits<CharacterData> { static bool AllowFrom(const Node& node) { return node.IsCharacterDataNode(); } }; diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc index cf458b04dd..4884475957 100644 --- a/bridge/core/dom/comment.cc +++ b/bridge/core/dom/comment.cc @@ -4,9 +4,9 @@ */ #include "comment.h" -#include "tree_scope.h" -#include "document.h" #include "built_in_string.h" +#include "document.h" +#include "tree_scope.h" namespace kraken { diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 09da4908d1..5574c27301 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -4,9 +4,9 @@ #include "container_node.h" #include "bindings/qjs/garbage_collected.h" +#include "document.h" #include "document_fragment.h" #include "node_traversal.h" -#include "document.h" namespace kraken { @@ -321,8 +321,10 @@ std::string ContainerNode::nodeValue() const { return ""; } -ContainerNode::ContainerNode(TreeScope* tree_scope, ConstructionType type) : Node(tree_scope->GetDocument().GetExecutingContext(), tree_scope, type) {} -ContainerNode::ContainerNode(ExecutingContext* context, Document* document, ConstructionType type): Node(context, document, type) {} +ContainerNode::ContainerNode(TreeScope* tree_scope, ConstructionType type) + : Node(tree_scope->GetDocument().GetExecutingContext(), tree_scope, type) {} +ContainerNode::ContainerNode(ExecutingContext* context, Document* document, ConstructionType type) + : Node(context, document, type) {} void ContainerNode::RemoveBetween(Node* previous_child, Node* next_child, Node& old_child) { assert(old_child.parentNode() == this); @@ -416,7 +418,7 @@ void ContainerNode::NotifyNodeRemoved(Node& root) { } void ContainerNode::Trace(GCVisitor* visitor) const { - for(Node& node: NodeTraversal::ChildrenOf(*this)) { + for (Node& node : NodeTraversal::ChildrenOf(*this)) { visitor->Trace(&node); } diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 7465a99ec7..6be5739225 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -5,11 +5,11 @@ #include "document.h" #include "core/dom/element.h" +#include "core/html/html_body_element.h" #include "core/html/html_element.h" +#include "core/html/html_head_element.h" #include "core/html/html_html_element.h" #include "core/html/html_unknown_element.h" -#include "core/html/html_body_element.h" -#include "core/html/html_head_element.h" #include "element_traversal.h" #include "foundation/ascii_types.h" #include "html_element_factory.h" @@ -21,8 +21,7 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ } Document::Document(ExecutingContext* context) - : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) { -} + : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) {} ScriptValue Document::createElement(const AtomicString& name, ExceptionState& exception_state) { if (!IsValidName(name)) { @@ -126,7 +125,6 @@ Node* Document::Clone(Document&, CloneChildrenFlag) const { return nullptr; } - HTMLBodyElement* Document::body() const { if (!IsA<HTMLHtmlElement>(documentElement())) return nullptr; diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index a02ff4f485..a5bbf8b078 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -47,9 +47,7 @@ class Document : public ContainerNode, public TreeScope { Node* Clone(Document&, CloneChildrenFlag) const override; [[nodiscard]] Element* documentElement() const { return document_element_; } - void SetDocumentElement(Element* element) { - document_element_ = element; - }; + void SetDocumentElement(Element* element) { document_element_ = element; }; // "body element" as defined by HTML5 // (https://html.spec.whatwg.org/C/#the-body-element-2). diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index 6b11742c9f..5c1d7e102e 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -4,8 +4,8 @@ */ #include "document_fragment.h" -#include "events/event_target.h" #include "document.h" +#include "events/event_target.h" namespace kraken { diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index e88ab58faa..2815ac17a0 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -36,7 +36,7 @@ class DocumentFragment : public ContainerNode { bool ChildTypeAllowed(NodeType) const override; }; -template<> +template <> struct DowncastTraits<DocumentFragment> { static bool AllowFrom(const Node& node) { return node.IsDocumentFragment(); } }; diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index a77bbba1e5..377450d26b 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -22,7 +22,7 @@ TEST(Document, createElement) { auto context = bridge->getContext(); const char* code = "let div = document.createElement('div');" -"console.log(div);"; + "console.log(div);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); @@ -40,8 +40,7 @@ TEST(Document, body) { errorCalled = true; }); auto context = bridge->getContext(); - const char* code = - "console.log(document.body)"; + const char* code = "console.log(document.body)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index c24e7a7c5c..e624b84fb5 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -20,9 +20,7 @@ class Element : public ContainerNode { public: Element(const AtomicString& tag_name, Document* document, ConstructionType = kCreateElement); - ElementAttributes* attributes() const { - return attributes_; - } + ElementAttributes* attributes() const { return attributes_; } bool hasAttribute(const AtomicString&, ExceptionState& exception_state) const; AtomicString getAttribute(const AtomicString&, ExceptionState& exception_state) const; @@ -72,7 +70,8 @@ class Element : public ContainerNode { bool HasEquivalentAttributes(const Element& other) const; - void Trace(GCVisitor *visitor) const override; + void Trace(GCVisitor* visitor) const override; + protected: private: // Clone is private so that non-virtual CloneElementWithChildren and @@ -110,7 +109,6 @@ struct DowncastTraits<Element> { static bool AllowFrom(const Node& node) { return node.IsElementNode(); } }; - } // namespace kraken #endif // KRAKENBRIDGE_ELEMENT_H diff --git a/bridge/core/dom/element_traversal.h b/bridge/core/dom/element_traversal.h index 96bf7d65b5..774c7029ce 100644 --- a/bridge/core/dom/element_traversal.h +++ b/bridge/core/dom/element_traversal.h @@ -6,21 +6,20 @@ #ifndef KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ #define KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ +#include "element.h" #include "foundation/macros.h" +#include "html_element_type_helper.h" #include "node_traversal.h" #include "traversal_range.h" -#include "element.h" -#include "html_element_type_helper.h" namespace kraken { class HasTagName { KRAKEN_STACK_ALLOCATED(); + public: explicit HasTagName(const AtomicString& tag_name) : tag_name_(tag_name) {} - bool operator()(const Element& element) const { - return element.HasTagName(tag_name_); - } + bool operator()(const Element& element) const { return element.HasTagName(tag_name_); } private: const AtomicString tag_name_; @@ -67,31 +66,19 @@ class 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); - } + 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); - } + 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 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)); } @@ -101,12 +88,8 @@ class Traversal { // 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); - } + 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); @@ -116,45 +99,29 @@ class Traversal { return FirstWithin(current); } - static ElementType* LastWithin(const ContainerNode& current) { - return LastWithinTemplate(current); - } - static ElementType* LastWithin(const Node& current) { - return LastWithinTemplate(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) { + 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* 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); + 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); + static ElementType* NextSkippingChildren(const Node&, const Node* stay_within); // Previous / Next sibling. static ElementType* PreviousSibling(const Node&); template <class MatchFunc> @@ -164,12 +131,9 @@ class Traversal { 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 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: @@ -192,35 +156,29 @@ class Traversal { 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)); +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) { +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) { +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) { +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)); +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 @@ -242,8 +200,7 @@ inline Element* Traversal<Element>::NextTemplate(NodeType& current) { template <> template <class NodeType> -inline Element* Traversal<Element>::NextTemplate(NodeType& current, - const Node* stay_within) { +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); @@ -253,8 +210,7 @@ inline Element* Traversal<Element>::NextTemplate(NodeType& current, // Generic versions. template <class ElementType> template <class NodeType> -inline ElementType* Traversal<ElementType>::FirstChildTemplate( - NodeType& current) { +inline ElementType* Traversal<ElementType>::FirstChildTemplate(NodeType& current) { Node* node = current.firstChild(); while (node && !IsElementOfType<const ElementType>(*node)) node = node->nextSibling(); @@ -263,9 +219,7 @@ inline ElementType* Traversal<ElementType>::FirstChildTemplate( template <class ElementType> template <class MatchFunc> -inline ElementType* Traversal<ElementType>::FirstChild( - const ContainerNode& current, - MatchFunc is_match) { +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); @@ -282,8 +236,7 @@ inline ElementType* Traversal<ElementType>::FirstAncestor(const Node& current) { template <class ElementType> template <class NodeType> -inline ElementType* Traversal<ElementType>::FirstAncestorOrSelfTemplate( - NodeType& current) { +inline ElementType* Traversal<ElementType>::FirstAncestorOrSelfTemplate(NodeType& current) { if (IsElementOfType<const ElementType>(current)) return &To<ElementType>(current); return FirstAncestor(current); @@ -291,8 +244,7 @@ inline ElementType* Traversal<ElementType>::FirstAncestorOrSelfTemplate( template <class ElementType> template <class NodeType> -inline ElementType* Traversal<ElementType>::LastChildTemplate( - NodeType& current) { +inline ElementType* Traversal<ElementType>::LastChildTemplate(NodeType& current) { Node* node = current.lastChild(); while (node && !IsElementOfType<const ElementType>(*node)) node = node->previousSibling(); @@ -301,9 +253,7 @@ inline ElementType* Traversal<ElementType>::LastChildTemplate( template <class ElementType> template <class MatchFunc> -inline ElementType* Traversal<ElementType>::LastChild( - const ContainerNode& current, - MatchFunc is_match) { +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); @@ -312,8 +262,7 @@ inline ElementType* Traversal<ElementType>::LastChild( template <class ElementType> template <class NodeType> -inline ElementType* Traversal<ElementType>::FirstWithinTemplate( - NodeType& current) { +inline ElementType* Traversal<ElementType>::FirstWithinTemplate(NodeType& current) { Node* node = current.firstChild(); while (node && !IsElementOfType<const ElementType>(*node)) node = NodeTraversal::Next(*node, ¤t); @@ -322,9 +271,7 @@ inline ElementType* Traversal<ElementType>::FirstWithinTemplate( template <class ElementType> template <typename MatchFunc> -inline ElementType* Traversal<ElementType>::FirstWithin( - const ContainerNode& current, - MatchFunc is_match) { +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); @@ -333,8 +280,7 @@ inline ElementType* Traversal<ElementType>::FirstWithin( template <class ElementType> template <class NodeType> -inline ElementType* Traversal<ElementType>::LastWithinTemplate( - NodeType& current) { +inline ElementType* Traversal<ElementType>::LastWithinTemplate(NodeType& current) { Node* node = NodeTraversal::LastWithin(current); while (node && !IsElementOfType<const ElementType>(*node)) node = NodeTraversal::Previous(*node, ¤t); @@ -343,9 +289,7 @@ inline ElementType* Traversal<ElementType>::LastWithinTemplate( template <class ElementType> template <class MatchFunc> -inline ElementType* Traversal<ElementType>::LastWithin( - const ContainerNode& current, - MatchFunc is_match) { +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); @@ -353,8 +297,7 @@ inline ElementType* Traversal<ElementType>::LastWithin( } template <class ElementType> -inline ElementType* Traversal<ElementType>::LastWithinOrSelf( - ElementType& current) { +inline ElementType* Traversal<ElementType>::LastWithinOrSelf(ElementType& current) { if (ElementType* last_descendant = LastWithin(current)) return last_descendant; return ¤t; @@ -371,9 +314,7 @@ inline ElementType* Traversal<ElementType>::NextTemplate(NodeType& current) { template <class ElementType> template <class NodeType> -inline ElementType* Traversal<ElementType>::NextTemplate( - NodeType& current, - const Node* stay_within) { +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); @@ -400,8 +341,7 @@ inline ElementType* Traversal<ElementType>::Previous(const Node& current) { } template <class ElementType> -inline ElementType* Traversal<ElementType>::Previous(const Node& current, - const Node* stay_within) { +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); @@ -410,10 +350,9 @@ inline ElementType* Traversal<ElementType>::Previous(const Node& current, template <class ElementType> template <class MatchFunc> -inline ElementType* Traversal<ElementType>::Previous( - const ContainerNode& current, - const Node* stay_within, - MatchFunc is_match) { +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); @@ -421,8 +360,7 @@ inline ElementType* Traversal<ElementType>::Previous( } template <class ElementType> -inline ElementType* Traversal<ElementType>::NextSkippingChildren( - const Node& current) { +inline ElementType* Traversal<ElementType>::NextSkippingChildren(const Node& current) { Node* node = NodeTraversal::NextSkippingChildren(current); while (node && !IsElementOfType<const ElementType>(*node)) node = NodeTraversal::NextSkippingChildren(*node); @@ -430,19 +368,15 @@ inline ElementType* Traversal<ElementType>::NextSkippingChildren( } template <class ElementType> -inline ElementType* Traversal<ElementType>::NextSkippingChildren( - const Node& current, - const Node* stay_within) { +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) { +inline ElementType* Traversal<ElementType>::PreviousSibling(const Node& current) { Node* node = current.previousSibling(); while (node && !IsElementOfType<const ElementType>(*node)) node = node->previousSibling(); @@ -451,9 +385,7 @@ inline ElementType* Traversal<ElementType>::PreviousSibling( template <class ElementType> template <class MatchFunc> -inline ElementType* Traversal<ElementType>::PreviousSibling( - const Node& current, - MatchFunc is_match) { +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); @@ -470,15 +402,13 @@ inline ElementType* Traversal<ElementType>::NextSibling(const Node& current) { template <class ElementType> template <class MatchFunc> -inline ElementType* Traversal<ElementType>::NextSibling(const Node& current, - MatchFunc is_match) { +inline ElementType* Traversal<ElementType>::NextSibling(const Node& current, MatchFunc is_match) { ElementType* element = Traversal<ElementType>::NextSibling(current); while (element && !is_match(*element)) element = Traversal<ElementType>::NextSibling(*element); return element; } - -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 42decbc098..084512c8ca 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -16,9 +16,8 @@ static inline bool IsNumberIndex(const StringView& name) { return f >= '0' && f <= '9'; } -ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()) { -} -ElementAttributes::ElementAttributes(ExecutingContext* context): ScriptWrappable(context->ctx()) {} +ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()) {} +ElementAttributes::ElementAttributes(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { bool numberIndex = IsNumberIndex(name.ToStringView()); diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index bf58c63dc2..1857d8c9e2 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -22,9 +22,7 @@ class ElementAttributes : public ScriptWrappable { public: using ImplType = ElementAttributes*; - static ElementAttributes* Create(Element* element) { - return MakeGarbageCollected<ElementAttributes>(element); - } + static ElementAttributes* Create(Element* element) { return MakeGarbageCollected<ElementAttributes>(element); } static ElementAttributes* Create(ExecutingContext* context, ExceptionState& exception_state) { return MakeGarbageCollected<ElementAttributes>(context); } diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h index ef12820d18..a1863d6cc5 100644 --- a/bridge/core/dom/text.h +++ b/bridge/core/dom/text.h @@ -19,18 +19,17 @@ class Text : public CharacterData { 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) {} + Text(TreeScope& tree_scope, const AtomicString& data, ConstructionType type) + : CharacterData(tree_scope, data, type) {} NodeType nodeType() const override; - - private: std::string nodeName() const override; Node* Clone(Document&, CloneChildrenFlag) const override; }; -template<> +template <> struct DowncastTraits<Text> { static bool AllowFrom(const Node& node) { return node.IsTextNode(); }; }; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 1505317d89..4a1fad9d83 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -5,10 +5,10 @@ #include "executing_context.h" #include "built_in_string.h" -#include "event_type_names.h" -#include "polyfill.h" #include "core/dom/document.h" #include "core/html/html_html_element.h" +#include "event_type_names.h" +#include "polyfill.h" #include "foundation/logging.h" @@ -65,7 +65,6 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& // Register all built-in native bindings. InstallBindings(this); - InstallDocument(); #if ENABLE_PROFILE diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index c50ecf73cc..993e95e6c9 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -112,7 +112,6 @@ class ExecutingContext { static std::unordered_map<std::string, NativeByteCode> pluginByteCode; private: - void InstallDocument(); static void promiseRejectTracker(JSContext* ctx, diff --git a/bridge/core/html/canvas/html_canvas_element.h b/bridge/core/html/canvas/html_canvas_element.h index be1d652ddc..a1731a0790 100644 --- a/bridge/core/html/canvas/html_canvas_element.h +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -15,6 +15,6 @@ class HTMLCanvasElement : public HTMLElement { explicit HTMLCanvasElement(Document&); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h index 0aa897fbc5..e83c6e4355 100644 --- a/bridge/core/html/forms/html_input_element.h +++ b/bridge/core/html/forms/html_input_element.h @@ -15,6 +15,6 @@ class HTMLInputElement : public HTMLElement { explicit HTMLInputElement(Document&); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_textarea_element.cc b/bridge/core/html/forms/html_textarea_element.cc index 3eb626fbcd..6c9523d5ec 100644 --- a/bridge/core/html/forms/html_textarea_element.cc +++ b/bridge/core/html/forms/html_textarea_element.cc @@ -10,5 +10,4 @@ namespace kraken { HTMLTextareaElement::HTMLTextareaElement(Document& document) : HTMLElement(html_names::ktextarea, &document) {} - -} +} // namespace kraken diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h index b644018528..b985795c48 100644 --- a/bridge/core/html/forms/html_textarea_element.h +++ b/bridge/core/html/forms/html_textarea_element.h @@ -15,6 +15,6 @@ class HTMLTextareaElement : public HTMLElement { explicit HTMLTextareaElement(Document&); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ diff --git a/bridge/core/html/html_anchor_element.cc b/bridge/core/html/html_anchor_element.cc index d4d8ba5848..f13705e0bf 100644 --- a/bridge/core/html/html_anchor_element.cc +++ b/bridge/core/html/html_anchor_element.cc @@ -8,6 +8,6 @@ namespace kraken { -HTMLAnchorElement::HTMLAnchorElement(Document& document): HTMLElement(html_names::ka, &document) {} +HTMLAnchorElement::HTMLAnchorElement(Document& document) : HTMLElement(html_names::ka, &document) {} -} +} // namespace kraken diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h index 4dbc3141b2..40e2c5c9d3 100644 --- a/bridge/core/html/html_anchor_element.h +++ b/bridge/core/html/html_anchor_element.h @@ -15,6 +15,6 @@ class HTMLAnchorElement : public HTMLElement { explicit HTMLAnchorElement(Document&); }; -} +} // namespace kraken #endif // KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H diff --git a/bridge/core/html/html_body_element.cc b/bridge/core/html/html_body_element.cc index a05e93ccb4..d543f07f0a 100644 --- a/bridge/core/html/html_body_element.cc +++ b/bridge/core/html/html_body_element.cc @@ -8,6 +8,6 @@ namespace kraken { -HTMLBodyElement::HTMLBodyElement(Document& document): HTMLElement(html_names::kbody, &document){} +HTMLBodyElement::HTMLBodyElement(Document& document) : HTMLElement(html_names::kbody, &document) {} -} +} // namespace kraken diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index e9d86d8fff..29bf0068df 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -12,11 +12,11 @@ namespace kraken { class HTMLBodyElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); + public: explicit HTMLBodyElement(Document&); }; - -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index eadc06b214..a1d2498dff 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -23,7 +23,6 @@ inline HTMLElement::HTMLElement(const AtomicString& tag_name, ConstructionType type = kCreateHTMLElement) : Element(tag_name, document, type) {} - template <typename T> bool IsElementOfType(const HTMLElement&); template <> diff --git a/bridge/core/html/html_head_element.cc b/bridge/core/html/html_head_element.cc index 16840096a3..1014e0dbc2 100644 --- a/bridge/core/html/html_head_element.cc +++ b/bridge/core/html/html_head_element.cc @@ -10,5 +10,4 @@ namespace kraken { HTMLHeadElement::HTMLHeadElement(Document& document) : HTMLElement(html_names::khead, &document) {} - -} +} // namespace kraken diff --git a/bridge/core/html/html_head_element.h b/bridge/core/html/html_head_element.h index 829571a04c..71f6fd1254 100644 --- a/bridge/core/html/html_head_element.h +++ b/bridge/core/html/html_head_element.h @@ -19,6 +19,6 @@ class HTMLHeadElement : public HTMLElement { private: }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ diff --git a/bridge/core/html/html_html_element.cc b/bridge/core/html/html_html_element.cc index 2eb0932712..2fb735034e 100644 --- a/bridge/core/html/html_html_element.cc +++ b/bridge/core/html/html_html_element.cc @@ -10,4 +10,4 @@ namespace kraken { HTMLHtmlElement::HTMLHtmlElement(Document& document) : HTMLElement(html_names::khtml, &document) {} -} +} // namespace kraken diff --git a/bridge/core/html/html_html_element.h b/bridge/core/html/html_html_element.h index 70ca7518f7..7eb2f1e821 100644 --- a/bridge/core/html/html_html_element.h +++ b/bridge/core/html/html_html_element.h @@ -19,7 +19,6 @@ class HTMLHtmlElement : public HTMLElement { private: }; - -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index 101398b81b..ad1792d841 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -8,6 +8,6 @@ namespace kraken { -HTMLImageElement::HTMLImageElement(Document& document): HTMLElement(html_names::kimg, &document) {} +HTMLImageElement::HTMLImageElement(Document& document) : HTMLElement(html_names::kimg, &document) {} -} +} // namespace kraken diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h index 94b7251144..6998bff6c7 100644 --- a/bridge/core/html/html_script_element.h +++ b/bridge/core/html/html_script_element.h @@ -12,14 +12,13 @@ namespace kraken { class HTMLScriptElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); - public: + public: explicit HTMLScriptElement(Document& document); private: - }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index 4cb1edeb1c..1969d2f784 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -14,8 +14,8 @@ class DocumentFragment; class HTMLTemplateElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); - public: + public: explicit HTMLTemplateElement(Document& document); DocumentFragment* content() const; diff --git a/bridge/core/html/html_unknown_element.h b/bridge/core/html/html_unknown_element.h index 730a191ec0..f3cd657b3f 100644 --- a/bridge/core/html/html_unknown_element.h +++ b/bridge/core/html/html_unknown_element.h @@ -12,12 +12,13 @@ namespace kraken { class HTMLUnknownElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); + public: explicit HTMLUnknownElement(const AtomicString&, Document& document); - private: + private: }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 32cdaa9d19..d458944605 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -4,11 +4,11 @@ */ #include "script_state.h" +#include "binding_call_methods.h" #include "built_in_string.h" #include "event_type_names.h" -#include "html_names.h" -#include "binding_call_methods.h" #include "html_element_factory.h" +#include "html_names.h" namespace kraken { diff --git a/bridge/foundation/casting.h b/bridge/foundation/casting.h index 9e691384d9..603e981266 100644 --- a/bridge/foundation/casting.h +++ b/bridge/foundation/casting.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_FOUNDATION_CASTING_H_ #define KRAKENBRIDGE_FOUNDATION_CASTING_H_ -#include <type_traits> #include <cassert> +#include <type_traits> namespace kraken { From 0e06f29d767aca8b8f8ba62a0cb14dd5c98f4fff Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Thu, 21 Apr 2022 06:36:06 +0800 Subject: [PATCH 089/498] feat: add api type check. --- bridge/CMakeLists.txt | 2 + bridge/bindings/qjs/converter_impl.h | 36 ++++++++++--- bridge/bindings/qjs/exception_message.cc | 50 +++++++++++++++++++ bridge/bindings/qjs/exception_message.h | 24 +++++++++ bridge/bindings/qjs/qjs_interface_bridge.h | 2 +- bridge/core/dom/document_test.cc | 3 +- .../code_generator/src/idl/generateSource.ts | 5 +- 7 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 bridge/bindings/qjs/exception_message.cc create mode 100644 bridge/bindings/qjs/exception_message.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 7b4a410475..76e5fb8c7b 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -229,6 +229,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/generated_code_helper.h bindings/qjs/exception_state.cc bindings/qjs/exception_state.h + bindings/qjs/exception_message.cc + bindings/qjs/exception_message.h bindings/qjs/gc_visitor.cc bindings/qjs/gc_visitor.h bindings/qjs/rejected_promises.h diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index e6720334aa..91b263d9ec 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -20,14 +20,21 @@ #include "core/html/html_element.h" #include "core/html/html_head_element.h" #include "core/html/html_html_element.h" +#include "exception_message.h" #include "idl_type.h" #include "js_event_listener.h" #include "native_string_utils.h" #include "qjs_add_event_listener_options.h" +#include "qjs_document.h" #include "qjs_element_attributes.h" #include "qjs_error_event_init.h" #include "qjs_event_init.h" #include "qjs_event_listener_options.h" +#include "qjs_html_body_element.h" +#include "qjs_html_div_element.h" +#include "qjs_html_element.h" +#include "qjs_html_head_element.h" +#include "qjs_html_html_element.h" #include "qjs_node.h" #include "qjs_scroll_to_options.h" @@ -464,14 +471,27 @@ struct Converter<EventListenerOptions> : public ConverterBase<EventListenerOptio } }; -#define DEFINE_SCRIPT_WRAPPABLE_CONVERTER(class_name) \ - template <> \ - struct Converter<class_name> : public ConverterBase<class_name> { \ - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { \ - assert(!JS_IsException(value)); \ - return toScriptWrappable<class_name>(JS_DupValue(ctx, value)); \ - } \ - static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } \ +#define DEFINE_SCRIPT_WRAPPABLE_CONVERTER(class_name) \ + template <> \ + struct Converter<class_name> : public ConverterBase<class_name> { \ + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { \ + assert(!JS_IsException(value)); \ + return toScriptWrappable<class_name>(JS_DupValue(ctx, value)); \ + } \ + static ImplType ArgumentsValue(ExecutingContext* context, \ + JSValue value, \ + uint32_t argv_index, \ + ExceptionState& exception_state) { \ + assert(!JS_IsException(value)); \ + if (QJS##class_name::HasInstance(context, value)) { \ + return FromValue(context->ctx(), value, exception_state); \ + } \ + auto* wrapper_type_info = QJS##class_name::GetWrapperTypeInfo(); \ + exception_state.ThrowException(context->ctx(), ErrorType::TypeError, \ + ExceptionMessage::ArgumentNotOfType(argv_index, wrapper_type_info->className)); \ + return nullptr; \ + } \ + static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } \ }; DEFINE_SCRIPT_WRAPPABLE_CONVERTER(Node); diff --git a/bridge/bindings/qjs/exception_message.cc b/bridge/bindings/qjs/exception_message.cc new file mode 100644 index 0000000000..cd51db7716 --- /dev/null +++ b/bridge/bindings/qjs/exception_message.cc @@ -0,0 +1,50 @@ +/* +* Copyright (C) 2019 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#include "exception_message.h" +#include <vector> + +namespace kraken { + +std::string FormatString(const char* format, ...) { + va_list args; + + static const unsigned kDefaultSize = 256; + std::vector<char> 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<unsigned>(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<unsigned>(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); +} + +} diff --git a/bridge/bindings/qjs/exception_message.h b/bridge/bindings/qjs/exception_message.h new file mode 100644 index 0000000000..36e587be63 --- /dev/null +++ b/bridge/bindings/qjs/exception_message.h @@ -0,0 +1,24 @@ +/* +* Copyright (C) 2019 Alibaba Inc. All rights reserved. +* Author: Kraken Team. +*/ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ + +#include <string> + +namespace kraken { + +class ExceptionMessage { + public: + + static std::string ArgumentNotOfType(int argument_index, const char* expect_type); + + private: + +}; + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index 79da8d3079..c1649dd80c 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -19,7 +19,7 @@ class QJSInterfaceBridge { } static bool HasInstance(ExecutingContext* context, JSValue value) { - return JS_IsInstanceOf(context->ctx(), value, context->contextData()->prototypeForType(QJST::GetWrapperTypeInfo())); + return JS_IsInstanceOf(context->ctx(), value, context->contextData()->constructorForType(QJST::GetWrapperTypeInfo())); }; }; diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 377450d26b..9ff103634f 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -40,7 +40,8 @@ TEST(Document, body) { errorCalled = true; }); auto context = bridge->getContext(); - const char* code = "console.log(document.body)"; + const char* code = "let div = {};" + "document.documentElement.appendChild(div);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index cd3f621af0..d23d768e58 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -108,7 +108,10 @@ export function generateTypeConverter(type: ParameterType[]): string { function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { let type = generateTypeConverter(argument.type); - return `auto&& args_${argument.name} = Converter<${type}>::FromValue(ctx, argv[${argsIndex}], exception_state);`; + return `auto&& args_${argument.name} = Converter<${type}>::FromValue(ctx, argv[${argsIndex}], exception_state); +if (exception_state.HasException()) { + return exception_state.ToQuickJS(); +}`; } function generateCallMethodName(name: string) { From c9e56bbda3bb4d4ca468a4ce125996ef9f816263 Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Wed, 20 Apr 2022 22:36:53 +0000 Subject: [PATCH 090/498] Committing clang-format changes --- bridge/bindings/qjs/exception_message.cc | 11 +++++------ bridge/bindings/qjs/exception_message.h | 10 ++++------ bridge/bindings/qjs/qjs_interface_bridge.h | 3 ++- bridge/core/dom/document_test.cc | 3 ++- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/bridge/bindings/qjs/exception_message.cc b/bridge/bindings/qjs/exception_message.cc index cd51db7716..da2fc9105f 100644 --- a/bridge/bindings/qjs/exception_message.cc +++ b/bridge/bindings/qjs/exception_message.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #include "exception_message.h" #include <vector> @@ -43,8 +43,7 @@ std::string FormatString(const char* format, ...) { } 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); + return FormatString("parameter %d is not of type '%s'.", argument_index + 1, expected_type); } -} +} // namespace kraken diff --git a/bridge/bindings/qjs/exception_message.h b/bridge/bindings/qjs/exception_message.h index 36e587be63..b8d2606eec 100644 --- a/bridge/bindings/qjs/exception_message.h +++ b/bridge/bindings/qjs/exception_message.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019 Alibaba Inc. All rights reserved. -* Author: Kraken Team. -*/ + * Copyright (C) 2019 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ #ifndef KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ @@ -12,13 +12,11 @@ namespace kraken { class ExceptionMessage { public: - static std::string ArgumentNotOfType(int argument_index, const char* expect_type); private: - }; -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index c1649dd80c..8358d6caef 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -19,7 +19,8 @@ class QJSInterfaceBridge { } static bool HasInstance(ExecutingContext* context, JSValue value) { - return JS_IsInstanceOf(context->ctx(), value, context->contextData()->constructorForType(QJST::GetWrapperTypeInfo())); + return JS_IsInstanceOf(context->ctx(), value, + context->contextData()->constructorForType(QJST::GetWrapperTypeInfo())); }; }; diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 9ff103634f..6521536748 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -40,7 +40,8 @@ TEST(Document, body) { errorCalled = true; }); auto context = bridge->getContext(); - const char* code = "let div = {};" + const char* code = + "let div = {};" "document.documentElement.appendChild(div);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); From 235ae961d30a87c729114bd8d21d7ed2699d7c71 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Thu, 21 Apr 2022 21:12:25 +0800 Subject: [PATCH 091/498] feat: add node member memory manage. --- bridge/CMakeLists.txt | 10 +- bridge/bindings/qjs/converter_impl.h | 191 ++++-------------- .../qjs/{ => cppgc}/garbage_collected.h | 4 +- bridge/bindings/qjs/{ => cppgc}/gc_visitor.cc | 8 +- bridge/bindings/qjs/{ => cppgc}/gc_visitor.h | 9 +- bridge/bindings/qjs/cppgc/member.cc | 5 + bridge/bindings/qjs/cppgc/member.h | 73 +++++++ bridge/bindings/qjs/dictionary_base.h | 2 +- bridge/bindings/qjs/qjs_dom_timer.cc | 5 - bridge/bindings/qjs/qjs_dom_timer.h | 10 - bridge/bindings/qjs/qjs_function.cc | 1 + bridge/bindings/qjs/qjs_function.h | 1 - bridge/bindings/qjs/qjs_page.cc | 12 -- bridge/bindings/qjs/qjs_page.h | 20 -- bridge/bindings/qjs/script_promise.h | 1 - bridge/bindings/qjs/script_value.cc | 1 + bridge/bindings/qjs/script_value.h | 2 +- bridge/bindings/qjs/script_wrappable.cc | 1 + bridge/bindings/qjs/script_wrappable.h | 3 +- bridge/bindings/qjs/wrapper_type_info.h | 2 +- bridge/core/css/css_style_declaration.h | 2 +- bridge/core/dom/collection_index_cache.h | 6 +- bridge/core/dom/container_node.cc | 11 +- bridge/core/dom/container_node.h | 16 +- bridge/core/dom/document.cc | 6 + bridge/core/dom/document.h | 7 +- bridge/core/dom/document_test.cc | 22 +- bridge/core/dom/element.h | 5 +- bridge/core/dom/events/event_target.h | 5 +- .../dom/frame_request_callback_collection.cc | 1 + .../dom/frame_request_callback_collection.h | 1 - bridge/core/dom/ng/child_node_list.cc | 1 + bridge/core/dom/ng/child_node_list.h | 6 +- bridge/core/dom/node.cc | 5 +- bridge/core/dom/node.d.ts | 3 +- bridge/core/dom/node.h | 12 +- bridge/core/dom/node_data.cc | 2 +- bridge/core/dom/node_data.h | 4 +- .../core/dom/scripted_animation_controller.h | 2 +- .../dom/template_content_document_fragment.h | 6 +- bridge/core/executing_context.cc | 8 +- bridge/core/executing_context.h | 1 - bridge/core/frame/dom_timer.cc | 2 +- bridge/core/frame/dom_timer_coordinator.h | 1 - bridge/core/frame/location.h | 2 +- bridge/core/frame/module_callback.h | 1 - bridge/core/frame/module_listener.h | 1 - bridge/core/frame/window.cc | 2 +- bridge/core/html/html_body_element.h | 1 + bridge/core/html/html_div_element.h | 1 + bridge/core/html/html_element.h | 1 + bridge/core/html/html_head_element.h | 1 + bridge/core/html/html_html_element.h | 1 + .../code_generator/src/idl/generateSource.ts | 18 +- .../json_templates/element_factory.cc.tpl | 2 +- 55 files changed, 248 insertions(+), 279 deletions(-) rename bridge/bindings/qjs/{ => cppgc}/garbage_collected.h (97%) rename bridge/bindings/qjs/{ => cppgc}/gc_visitor.cc (58%) rename bridge/bindings/qjs/{ => cppgc}/gc_visitor.h (79%) create mode 100644 bridge/bindings/qjs/cppgc/member.cc create mode 100644 bridge/bindings/qjs/cppgc/member.h delete mode 100644 bridge/bindings/qjs/qjs_dom_timer.cc delete mode 100644 bridge/bindings/qjs/qjs_dom_timer.h delete mode 100644 bridge/bindings/qjs/qjs_page.cc delete mode 100644 bridge/bindings/qjs/qjs_page.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 76e5fb8c7b..e4879eb873 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -204,7 +204,11 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/member_installer.h bindings/qjs/source_location.cc bindings/qjs/source_location.h - bindings/qjs/garbage_collected.h + bindings/qjs/cppgc/garbage_collected.h + bindings/qjs/cppgc/gc_visitor.cc + bindings/qjs/cppgc/gc_visitor.h + bindings/qjs/cppgc/member.h + bindings/qjs/cppgc/member.cc bindings/qjs/script_wrappable.cc bindings/qjs/script_wrappable.h bindings/qjs/wrapper_type_info.h @@ -231,16 +235,12 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/exception_state.h bindings/qjs/exception_message.cc bindings/qjs/exception_message.h - bindings/qjs/gc_visitor.cc - bindings/qjs/gc_visitor.h bindings/qjs/rejected_promises.h bindings/qjs/rejected_promises.cc bindings/qjs/pending_promises.cc bindings/qjs/pending_promises.h bindings/qjs/qjs_window.cc bindings/qjs/qjs_window.h - bindings/qjs/qjs_page.cc - bindings/qjs/qjs_page.h # Core sources core/executing_context.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 91b263d9ec..0be80b7da6 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -24,19 +24,6 @@ #include "idl_type.h" #include "js_event_listener.h" #include "native_string_utils.h" -#include "qjs_add_event_listener_options.h" -#include "qjs_document.h" -#include "qjs_element_attributes.h" -#include "qjs_error_event_init.h" -#include "qjs_event_init.h" -#include "qjs_event_listener_options.h" -#include "qjs_html_body_element.h" -#include "qjs_html_div_element.h" -#include "qjs_html_element.h" -#include "qjs_html_head_element.h" -#include "qjs_html_html_element.h" -#include "qjs_node.h" -#include "qjs_scroll_to_options.h" namespace kraken { @@ -58,6 +45,16 @@ struct Converter<IDLOptional<T>, std::enable_if_t<std::is_pointer<typename Conve return Converter<T>::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<T>::ArgumentsValue(context, value, argv_index, exception_state); + } + static JSValue ToValue(JSContext* ctx, typename Converter<T>::ImplType value) { if (value == nullptr) { return JS_UNDEFINED; @@ -80,6 +77,16 @@ struct Converter<IDLNullable<T>, std::enable_if_t<std::is_pointer<typename Conve return Converter<T>::FromValue(ctx, value, exception_state); } + static ImplType ArgumentsValue(ExecutingContext* context, + JSValue value, + uint32_t argv_index, + ExceptionState& exception_state) { + if (JS_IsNull(value)) { + return nullptr; + } + return Converter<T>::ArgumentsValue(context, value, argv_index, exception_state); + } + static JSValue ToValue(JSContext* ctx, typename Converter<T>::ImplType value) { if (value == nullptr) { return JS_NULL; @@ -354,71 +361,6 @@ struct Converter<BlobPropertyBag> : public ConverterBase<BlobPropertyBag> { } }; -// EventListener -// template <> -// struct Converter<EventListener> : public ConverterBase<EventListener> { -// static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { -// assert(!JS_IsException(value)); -// if (!JS_IsFunction(ctx, value)) { -// return nullptr; -// } -// -// return EventListener::Create(ctx, value); -// } -//}; -// -// template <> -// struct Converter<IDLNullable<EventListener>> : public ConverterBase<EventListener> { -// static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { -// assert(!JS_IsException(value)); -// if (JS_IsNull(value)) { -// return nullptr; -// } -// -// return Converter<EventListener>::FromValue(ctx, value, exception_state); -// } -//}; - -template <> -struct Converter<EventTarget> : public ConverterBase<EventTarget> { - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - assert(!JS_IsException(value)); - return toScriptWrappable<EventTarget>(value); - } - - static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } -}; - -template <> -struct Converter<IDLNullable<EventTarget>> : public ConverterBase<EventTarget> { - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - if (JS_IsNull(value)) { - return nullptr; - } - - assert(!JS_IsException(value)); - return Converter<EventTarget>::FromValue(ctx, value, exception_state); - } - - static JSValue ToValue(JSContext* ctx, ImplType value) { - if (value == nullptr) - return JS_NULL; - return Converter<EventTarget>::ToValue(ctx, value); - } -}; - -template <> -struct Converter<Event> : public ConverterBase<Event> { - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - assert(!JS_IsException(value)); - return toScriptWrappable<Event>(value); - } - - static JSValue ToValue(JSContext* ctx, ImplType value) { - return reinterpret_cast<ScriptWrappable*>(value)->ToQuickJS(); - } -}; - template <> struct Converter<JSEventListener> : public ConverterBase<JSEventListener> { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { @@ -439,86 +381,39 @@ struct Converter<IDLNullable<JSEventListener>> : public ConverterBase<JSEventLis } }; -template <> -struct Converter<EventInit> : public ConverterBase<EventInit> { - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { +// DictionaryBase and Derived class. +template<typename T> +struct Converter<T, typename std::enable_if_t<std::is_base_of<DictionaryBase, T>::value>> : public ConverterBase<T> { + static typename T::ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); - return EventInit::Create(ctx, value, exception_state); + return T::Create(ctx, value, exception_state); } }; -template <> -struct Converter<ErrorEventInit> : public ConverterBase<ErrorEventInit> { - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - assert(!JS_IsException(value)); - return ErrorEventInit::Create(ctx, value, exception_state); - } -}; - -template <> -struct Converter<AddEventListenerOptions> : public ConverterBase<AddEventListenerOptions> { - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - assert(!JS_IsException(value)); - return AddEventListenerOptions::Create(ctx, value, exception_state); - }; -}; - -template <> -struct Converter<EventListenerOptions> : public ConverterBase<EventListenerOptions> { - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { +// ScriptWrappable and Derived class. +template <typename T> +struct Converter<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T>::value>> : public ConverterBase<T> { + static T* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); - return EventListenerOptions::Create(ctx, value, exception_state); + return toScriptWrappable<T>(value); } -}; - -#define DEFINE_SCRIPT_WRAPPABLE_CONVERTER(class_name) \ - template <> \ - struct Converter<class_name> : public ConverterBase<class_name> { \ - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { \ - assert(!JS_IsException(value)); \ - return toScriptWrappable<class_name>(JS_DupValue(ctx, value)); \ - } \ - static ImplType ArgumentsValue(ExecutingContext* context, \ - JSValue value, \ - uint32_t argv_index, \ - ExceptionState& exception_state) { \ - assert(!JS_IsException(value)); \ - if (QJS##class_name::HasInstance(context, value)) { \ - return FromValue(context->ctx(), value, exception_state); \ - } \ - auto* wrapper_type_info = QJS##class_name::GetWrapperTypeInfo(); \ - exception_state.ThrowException(context->ctx(), ErrorType::TypeError, \ - ExceptionMessage::ArgumentNotOfType(argv_index, wrapper_type_info->className)); \ - return nullptr; \ - } \ - static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } \ - }; - -DEFINE_SCRIPT_WRAPPABLE_CONVERTER(Node); -DEFINE_SCRIPT_WRAPPABLE_CONVERTER(Document); -DEFINE_SCRIPT_WRAPPABLE_CONVERTER(HTMLElement); -DEFINE_SCRIPT_WRAPPABLE_CONVERTER(HTMLDivElement); -DEFINE_SCRIPT_WRAPPABLE_CONVERTER(HTMLBodyElement); -DEFINE_SCRIPT_WRAPPABLE_CONVERTER(HTMLHeadElement); -DEFINE_SCRIPT_WRAPPABLE_CONVERTER(HTMLHtmlElement); - -template <> -struct Converter<NodeList> : public ConverterBase<NodeList> { - static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } -}; - -template <> -struct Converter<ScrollToOptions> : ConverterBase<ScrollToOptions> { - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + static T* ArgumentsValue(ExecutingContext* context, + JSValue value, + uint32_t argv_index, + ExceptionState& exception_state) { assert(!JS_IsException(value)); - return ScrollToOptions::Create(ctx, value, exception_state); + 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(); } }; -template <> -struct Converter<ElementAttributes> : ConverterBase<ElementAttributes> { - static JSValue ToValue(JSContext* ctx, ImplType value) { return value->ToQuickJS(); } -}; }; // namespace kraken diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/cppgc/garbage_collected.h similarity index 97% rename from bridge/bindings/qjs/garbage_collected.h rename to bridge/bindings/qjs/cppgc/garbage_collected.h index f7b196e9dc..32b396a093 100644 --- a/bridge/bindings/qjs/garbage_collected.h +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -9,10 +9,9 @@ #include <quickjs/quickjs.h> #include <memory> +#include "bindings/qjs/qjs_engine_patch.h" #include "foundation/casting.h" #include "foundation/macros.h" -#include "gc_visitor.h" -#include "qjs_engine_patch.h" namespace kraken { @@ -20,6 +19,7 @@ template <typename T> class MakeGarbageCollectedTrait; class ExecutingContext; +class GCVisitor; /** * This class are mainly designed as base class for ScriptWrappable. If you wants to implement diff --git a/bridge/bindings/qjs/gc_visitor.cc b/bridge/bindings/qjs/cppgc/gc_visitor.cc similarity index 58% rename from bridge/bindings/qjs/gc_visitor.cc rename to bridge/bindings/qjs/cppgc/gc_visitor.cc index d98a05c345..5826531338 100644 --- a/bridge/bindings/qjs/gc_visitor.cc +++ b/bridge/bindings/qjs/cppgc/gc_visitor.cc @@ -4,16 +4,10 @@ */ #include "gc_visitor.h" -#include "script_wrappable.h" +#include "bindings/qjs/script_wrappable.h" namespace kraken { -void GCVisitor::Trace(ScriptWrappable* target) { - if (target != nullptr) { - JS_MarkValue(runtime_, target->jsObject_, markFunc_); - } -} - void GCVisitor::Trace(JSValue value) { JS_MarkValue(runtime_, value, markFunc_); } diff --git a/bridge/bindings/qjs/gc_visitor.h b/bridge/bindings/qjs/cppgc/gc_visitor.h similarity index 79% rename from bridge/bindings/qjs/gc_visitor.h rename to bridge/bindings/qjs/cppgc/gc_visitor.h index b03d5c6f77..54ace6dc52 100644 --- a/bridge/bindings/qjs/gc_visitor.h +++ b/bridge/bindings/qjs/cppgc/gc_visitor.h @@ -8,6 +8,7 @@ #include <quickjs/quickjs.h> #include "foundation/macros.h" +#include "member.h" namespace kraken { @@ -21,7 +22,13 @@ class GCVisitor final { public: explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc) : runtime_(rt), markFunc_(markFunc){}; - void Trace(ScriptWrappable* target); + template<typename T> + void Trace(const Member<T>& target) { + if (target.Get() != nullptr) { + JS_MarkValue(runtime_, target.Get()->jsObject_, markFunc_); + } + }; + void Trace(JSValue value); private: diff --git a/bridge/bindings/qjs/cppgc/member.cc b/bridge/bindings/qjs/cppgc/member.cc new file mode 100644 index 0000000000..104b287146 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/member.cc @@ -0,0 +1,5 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "member.h" diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h new file mode 100644 index 0000000000..dd704a4a75 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/member.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ + +#include <type_traits> +#include "bindings/qjs/script_value.h" +#include "bindings/qjs/script_wrappable.h" +#include "foundation/casting.h" + +namespace kraken { + +class ScriptWrappable; + +template <typename T, typename = std::is_base_of<ScriptWrappable, T>> +class Member { + public: + Member() = default; + Member(T* ptr): raw_(ptr) {} + + T* Get() const { return raw_; } + void Clear() { + if (raw_ == nullptr) return; + auto* wrappable = To<ScriptWrappable>(raw_); + JS_FreeValue(wrappable->ctx(), wrappable->ToValue().QJSValue()); + raw_ = nullptr; + } + + // Copy assignment. + Member& operator=(const Member& other) { + operator=(other.Get()); + other.Clear(); + return *this; + } + // Move assignment. + Member& operator=(Member&& other) noexcept { + operator=(other.Get()); + other.Clear(); + return *this; + } + + Member& operator=(T* other) { + Clear(); + SetRaw(other); + return *this; + } + Member& operator=(std::nullptr_t) { + Clear(); + return *this; + } + + explicit operator bool() const { return Get(); } + operator T*() const { return Get(); } + T* operator->() const { return Get(); } + T& operator*() const { return *Get(); } + + private: + void SetRaw(T* p) { + if (p != nullptr) { + auto* wrappable = To<ScriptWrappable>(p); + JS_DupValue(wrappable->ctx(), wrappable->ToValue().QJSValue()); + } + raw_ = p; + } + + T* raw_{nullptr}; +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ diff --git a/bridge/bindings/qjs/dictionary_base.h b/bridge/bindings/qjs/dictionary_base.h index ce60137831..e1e75631b9 100644 --- a/bridge/bindings/qjs/dictionary_base.h +++ b/bridge/bindings/qjs/dictionary_base.h @@ -6,7 +6,7 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ -#include "garbage_collected.h" +#include "bindings/qjs/cppgc/garbage_collected.h" namespace kraken { diff --git a/bridge/bindings/qjs/qjs_dom_timer.cc b/bridge/bindings/qjs/qjs_dom_timer.cc deleted file mode 100644 index 9c438d91af..0000000000 --- a/bridge/bindings/qjs/qjs_dom_timer.cc +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by andycall on 2022/3/4. -// - -#include "qjs_dom_timer.h" diff --git a/bridge/bindings/qjs/qjs_dom_timer.h b/bridge/bindings/qjs/qjs_dom_timer.h deleted file mode 100644 index b434982b65..0000000000 --- a/bridge/bindings/qjs/qjs_dom_timer.h +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by andycall on 2022/3/4. -// - -#ifndef KRAKENBRIDGE_BINDINGS_QJS_QJS_DOM_TIMER_H_ -#define KRAKENBRIDGE_BINDINGS_QJS_QJS_DOM_TIMER_H_ - -class qjs_dom_timer {}; - -#endif // KRAKENBRIDGE_BINDINGS_QJS_QJS_DOM_TIMER_H_ diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 871c29a80a..934bfdae21 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -5,6 +5,7 @@ #include "qjs_function.h" #include <algorithm> +#include "cppgc/gc_visitor.h" namespace kraken { diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 556c357dca..81951f336c 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -6,7 +6,6 @@ #ifndef KRAKENBRIDGE_QJS_FUNCTION_H #define KRAKENBRIDGE_QJS_FUNCTION_H -#include "garbage_collected.h" #include "script_value.h" namespace kraken { diff --git a/bridge/bindings/qjs/qjs_page.cc b/bridge/bindings/qjs/qjs_page.cc deleted file mode 100644 index d44ebfaf0e..0000000000 --- a/bridge/bindings/qjs/qjs_page.cc +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "qjs_page.h" - -namespace kraken { - -void QJSPage::installGlobalFunctions(JSContext* ctx) {} - -} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_page.h b/bridge/bindings/qjs/qjs_page.h deleted file mode 100644 index 6b30df2d48..0000000000 --- a/bridge/bindings/qjs/qjs_page.h +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_QJS_PAGE_H -#define KRAKENBRIDGE_QJS_PAGE_H - -#include <quickjs/quickjs.h> - -namespace kraken { - -class QJSPage final { - public: - static void installGlobalFunctions(JSContext* ctx); -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_QJS_PAGE_H diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index a2512b4f3c..5a58cb2407 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -8,7 +8,6 @@ #include <quickjs/quickjs.h> #include "foundation/macros.h" -#include "gc_visitor.h" #include "script_value.h" namespace kraken { diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 685d0a8536..d35c2aab53 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -5,6 +5,7 @@ #include "script_value.h" #include <vector> +#include "cppgc/gc_visitor.h" #include "core/executing_context.h" #include "foundation/native_value_converter.h" #include "native_string_utils.h" diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 6a456079e3..f4e37ab0ed 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -13,13 +13,13 @@ #include "exception_state.h" #include "foundation/macros.h" #include "foundation/native_string.h" -#include "gc_visitor.h" namespace kraken { class ExecutingContext; class WrapperTypeInfo; class NativeValue; +class GCVisitor; // ScriptValue is a stack allocate only QuickJS JSValue wrapper ScriptValuewhich hold all information to hide out // QuickJS running details. diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 32637104b1..15052a9866 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -5,6 +5,7 @@ #include "script_wrappable.h" #include "core/executing_context.h" +#include "cppgc/gc_visitor.h" namespace kraken { diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index bd2ab8d70f..b968cef165 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -7,12 +7,13 @@ #define KRAKENBRIDGE_SCRIPT_WRAPPABLE_H #include <quickjs/quickjs.h> -#include "garbage_collected.h" +#include "bindings/qjs/cppgc/garbage_collected.h" #include "wrapper_type_info.h" namespace kraken { class ScriptValue; +class GCVisitor; // Defines |GetWrapperTypeInfo| virtual method which returns the WrapperTypeInfo // of the instance. Also declares a static member of type WrapperTypeInfo, of diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index da544412e8..003f1fdd75 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -6,7 +6,7 @@ #ifndef KRAKENBRIDGE_WRAPPER_TYPE_INFO_H #define KRAKENBRIDGE_WRAPPER_TYPE_INFO_H -#include <assert.h> +#include <cassert> #include <quickjs/quickjs.h> #include "bindings/qjs/qjs_engine_patch.h" diff --git a/bridge/core/css/css_style_declaration.h b/bridge/core/css/css_style_declaration.h index a9e4dce8c9..a6f25fab68 100644 --- a/bridge/core/css/css_style_declaration.h +++ b/bridge/core/css/css_style_declaration.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_CSS_STYLE_DECLARATION_H #define KRAKENBRIDGE_CSS_STYLE_DECLARATION_H +#include "bindings/qjs/cppgc/garbage_collected.h" #include "bindings/qjs/dom/event_target.h" -#include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/macros.h" namespace kraken { diff --git a/bridge/core/dom/collection_index_cache.h b/bridge/core/dom/collection_index_cache.h index 9d4a366ed8..63241e50c8 100644 --- a/bridge/core/dom/collection_index_cache.h +++ b/bridge/core/dom/collection_index_cache.h @@ -7,7 +7,7 @@ #include <assert.h> #include <climits> -#include "bindings/qjs/gc_visitor.h" +#include "bindings/qjs/cppgc/gc_visitor.h" #include "foundation/macros.h" namespace kraken { @@ -46,7 +46,7 @@ class CollectionIndexCache { virtual void Trace(GCVisitor* visitor) const { visitor->Trace(current_node_); } protected: - FORCE_INLINE NodeType* CachedNode() const { return current_node_; } + FORCE_INLINE NodeType* CachedNode() const { return current_node_.Get(); } FORCE_INLINE unsigned CachedNodeIndex() const { assert(CachedNode()); return cached_node_index_; @@ -68,7 +68,7 @@ class CollectionIndexCache { NodeType* NodeBeforeCachedNode(const Collection&, unsigned index); NodeType* NodeAfterCachedNode(const Collection&, unsigned index); - NodeType* current_node_; + Member<NodeType> current_node_; unsigned cached_node_count_; unsigned cached_node_index_ : 31; unsigned is_length_cache_valid_ : 1; diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 5574c27301..161661bcb4 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -3,7 +3,8 @@ */ #include "container_node.h" -#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "bindings/qjs/cppgc/gc_visitor.h" #include "document.h" #include "document_fragment.h" #include "node_traversal.h" @@ -418,9 +419,11 @@ void ContainerNode::NotifyNodeRemoved(Node& root) { } void ContainerNode::Trace(GCVisitor* visitor) const { - for (Node& node : NodeTraversal::ChildrenOf(*this)) { - visitor->Trace(&node); - } +// for (Node& node : NodeTraversal::ChildrenOf(*this)) { +// visitor->Trace(&node); +// } + visitor->Trace(first_child_); + visitor->Trace(last_child_); Node::Trace(visitor); } diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index 57bc6ac27d..2fb6925a67 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -6,7 +6,7 @@ #define KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ #include <vector> -#include "bindings/qjs/gc_visitor.h" +#include "bindings/qjs/cppgc/gc_visitor.h" #include "node.h" namespace kraken { @@ -20,10 +20,10 @@ using NodeVector = std::vector<Node*>; class ContainerNode : public Node { public: - Node* firstChild() const { return first_child_; } - Node* lastChild() const { return last_child_; } - bool hasChildren() const { return first_child_; } - bool HasChildren() const { return first_child_; } + 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(); } @@ -82,8 +82,8 @@ class ContainerNode : public Node { inline bool IsChildTypeAllowed(const Node& child) const; inline bool IsHostIncludingInclusiveAncestorOfThis(const Node&, ExceptionState&) const; - Node* first_child_{nullptr}; - Node* last_child_{nullptr}; + Member<Node> first_child_; + Member<Node> last_child_; }; inline Node* Node::firstChild() const { @@ -102,7 +102,7 @@ inline Node* Node::lastChild() const { } inline bool ContainerNode::HasChildCount(unsigned count) const { - Node* child = first_child_; + Node* child = first_child_.Get(); while (count && child) { child = child->nextSibling(); --count; diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 6be5739225..f4d1145cbf 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -125,6 +125,11 @@ Node* Document::Clone(Document&, CloneChildrenFlag) const { return nullptr; } +void Document::InitDocumentElement() { + ExceptionState exception_state; + AppendChild(document_element_, exception_state); +} + HTMLBodyElement* Document::body() const { if (!IsA<HTMLHtmlElement>(documentElement())) return nullptr; @@ -147,6 +152,7 @@ HTMLHeadElement* Document::head() const { } void Document::Trace(GCVisitor* visitor) const { + visitor->Trace(document_element_); ContainerNode::Trace(visitor); } diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index a5bbf8b078..7f5b8e69ac 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -11,6 +11,7 @@ #include "core/dom/document_fragment.h" #include "core/dom/text.h" #include "tree_scope.h" +#include "html_element_type_helper.h" namespace kraken { @@ -46,8 +47,8 @@ class Document : public ContainerNode, public TreeScope { Node* Clone(Document&, CloneChildrenFlag) const override; - [[nodiscard]] Element* documentElement() const { return document_element_; } - void SetDocumentElement(Element* element) { document_element_ = element; }; + [[nodiscard]] HTMLHtmlElement* documentElement() const { return DynamicTo<HTMLHtmlElement>(document_element_.Get()); } + void InitDocumentElement(); // "body element" as defined by HTML5 // (https://html.spec.whatwg.org/C/#the-body-element-2). @@ -66,7 +67,7 @@ class Document : public ContainerNode, public TreeScope { private: int node_count_; - Element* document_element_{nullptr}; + Member<Element> document_element_{MakeGarbageCollected<HTMLHtmlElement>(*this)}; }; } // namespace kraken diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 6521536748..d50d595993 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -33,7 +33,7 @@ TEST(Document, body) { bool static logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "<div>"); + EXPECT_STREQ(message.c_str(), "<body>"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; @@ -41,13 +41,29 @@ TEST(Document, body) { }); auto context = bridge->getContext(); const char* code = - "let div = {};" - "document.documentElement.appendChild(div);"; + "console.log(document.body)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } +TEST(Document, appendParentWillFail) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + errorCalled = true; + }); + auto context = bridge->getContext(); + 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; diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index e624b84fb5..fb8004af95 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -6,7 +6,7 @@ #ifndef KRAKENBRIDGE_ELEMENT_H #define KRAKENBRIDGE_ELEMENT_H -#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/cppgc/garbage_collected.h" #include "container_node.h" #include "legacy/bounding_client_rect.h" #include "legacy/element_attributes.h" @@ -18,6 +18,7 @@ class Element : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); public: + using ImplType = Element*; Element(const AtomicString& tag_name, Document* document, ConstructionType = kCreateElement); ElementAttributes* attributes() const { return attributes_; } @@ -85,7 +86,7 @@ class Element : public ContainerNode { void _didModifyAttribute(const AtomicString& name, const AtomicString& oldId, const AtomicString& newId); void _beforeUpdateId(JSValue oldIdValue, JSValue newIdValue); - ElementAttributes* attributes_{nullptr}; + Member<ElementAttributes> attributes_; AtomicString tag_name_ = AtomicString::Empty(ctx()); }; diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 790dddd044..be59fda337 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -9,9 +9,11 @@ #include "bindings/qjs/js_event_listener.h" #include "bindings/qjs/qjs_function.h" #include "bindings/qjs/script_wrappable.h" +#include "bindings/qjs/cppgc/member.h" #include "core/dom/binding_object.h" #include "event_listener_map.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); @@ -22,9 +24,6 @@ void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, namespace kraken { -class AddEventListenerOptions; -class EventListenerOptions; - enum class DispatchEventResult { // Event was not canceled by event handler or default event handler. kNotCanceled, diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc index 9e34dee84f..97fcfcd270 100644 --- a/bridge/core/dom/frame_request_callback_collection.cc +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -4,6 +4,7 @@ */ #include "frame_request_callback_collection.h" +#include "bindings/qjs/cppgc/gc_visitor.h" namespace kraken { diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index 591f34f66f..203ed232f9 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -6,7 +6,6 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ #define KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ -#include "bindings/qjs/script_wrappable.h" #include "core/executing_context.h" namespace kraken { diff --git a/bridge/core/dom/ng/child_node_list.cc b/bridge/core/dom/ng/child_node_list.cc index 155d206406..c29722e6f7 100644 --- a/bridge/core/dom/ng/child_node_list.cc +++ b/bridge/core/dom/ng/child_node_list.cc @@ -3,6 +3,7 @@ */ #include "child_node_list.h" +#include "bindings/qjs/cppgc/gc_visitor.h" namespace kraken { diff --git a/bridge/core/dom/ng/child_node_list.h b/bridge/core/dom/ng/child_node_list.h index 0d367da9f6..6beb1f4333 100644 --- a/bridge/core/dom/ng/child_node_list.h +++ b/bridge/core/dom/ng/child_node_list.h @@ -5,7 +5,7 @@ #ifndef KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ #define KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ -#include "bindings/qjs/gc_visitor.h" +#include "bindings/qjs/cppgc/gc_visitor.h" #include "core/dom/collection_index_cache.h" #include "core/dom/container_node.h" #include "node_list.h" @@ -26,7 +26,7 @@ class ChildNodeList : public NodeList { // Non-DOM API. void InvalidateCache() { collection_index_cache_.Invalidate(); } - ContainerNode& OwnerNode() const { return *parent_; } + ContainerNode& OwnerNode() const { return *parent_.Get(); } ContainerNode& RootNode() const { return OwnerNode(); } @@ -43,7 +43,7 @@ class ChildNodeList : public NodeList { bool IsChildNodeList() const override { return true; } Node* VirtualOwnerNode() const override; - ContainerNode* parent_; + Member<ContainerNode> parent_; mutable CollectionIndexCache<ChildNodeList, Node> collection_index_cache_; }; diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 59aa8fbe56..99cb9adc6c 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -95,7 +95,7 @@ Node* Node::removeChild(Node* old_child, ExceptionState& exception_state) { Node* Node::appendChild(Node* new_child, ExceptionState& exception_state) { auto* this_node = DynamicTo<ContainerNode>(this); - if (this_node) + 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."); @@ -395,6 +395,9 @@ Node::Node(ExecutingContext* context, TreeScope* tree_scope, ConstructionType ty data_(nullptr) {} void Node::Trace(GCVisitor* visitor) const { + visitor->Trace(previous_); + visitor->Trace(next_); + visitor->Trace(parent_or_shadow_host_node_); EventTarget::Trace(visitor); } diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts index 2e49a08bc9..6fc41dec98 100644 --- a/bridge/core/dom/node.d.ts +++ b/bridge/core/dom/node.d.ts @@ -1,5 +1,6 @@ import { EventTarget } from './events/event_target'; import { Document } from './document'; +import {Element} from "./element"; /** 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 { @@ -40,7 +41,7 @@ interface Node extends EventTarget { * Returns the parent element. */ // @ts-ignore - readonly parentElement: HTMLElement | null; + readonly parentElement: Element | null; /** * Returns the parent. */ diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index ab1dcd06da..c9d1da61a3 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -67,8 +67,8 @@ class Node : public EventTarget { ContainerNode* parentNode() const; Element* parentElement() const; - Node* previousSibling() const { return previous_; } - Node* nextSibling() const { return next_; } + Node* previousSibling() const { return previous_.Get(); } + Node* nextSibling() const { return next_.Get(); } NodeList* childNodes(); Node* firstChild() const; Node* lastChild() const; @@ -291,15 +291,15 @@ class Node : public EventTarget { private: uint32_t node_flags_; - Node* parent_or_shadow_host_node_; - Node* previous_; - Node* next_; + Member<Node> parent_or_shadow_host_node_; + Member<Node> previous_; + Member<Node> next_; TreeScope* tree_scope_; std::unique_ptr<NodeData> data_; }; inline ContainerNode* Node::ParentOrShadowHostNode() const { - return reinterpret_cast<ContainerNode*>(parent_or_shadow_host_node_); + return reinterpret_cast<ContainerNode*>(parent_or_shadow_host_node_.Get()); } inline void Node::SetParentOrShadowHostNode(ContainerNode* parent) { diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index e5d9a65db4..fd3b9eabf3 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -3,7 +3,7 @@ */ #include "node_data.h" -#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/cppgc/garbage_collected.h" #include "container_node.h" #include "ng/child_node_list.h" #include "ng/empty_node_list.h" diff --git a/bridge/core/dom/node_data.h b/bridge/core/dom/node_data.h index d9b28d3c71..8ffb61e736 100644 --- a/bridge/core/dom/node_data.h +++ b/bridge/core/dom/node_data.h @@ -6,8 +6,8 @@ #define KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ #include <cinttypes> -#include "bindings/qjs/garbage_collected.h" -#include "bindings/qjs/gc_visitor.h" +#include "bindings/qjs/cppgc/garbage_collected.h" +#include "bindings/qjs/cppgc/gc_visitor.h" namespace kraken { diff --git a/bridge/core/dom/scripted_animation_controller.h b/bridge/core/dom/scripted_animation_controller.h index 4e3d4d92f4..c8c7833d8f 100644 --- a/bridge/core/dom/scripted_animation_controller.h +++ b/bridge/core/dom/scripted_animation_controller.h @@ -6,7 +6,7 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ -#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/cppgc/garbage_collected.h" #include "frame_request_callback_collection.h" namespace kraken { diff --git a/bridge/core/dom/template_content_document_fragment.h b/bridge/core/dom/template_content_document_fragment.h index 51c13abc03..63566025d9 100644 --- a/bridge/core/dom/template_content_document_fragment.h +++ b/bridge/core/dom/template_content_document_fragment.h @@ -5,7 +5,7 @@ #ifndef KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ #define KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ -#include "bindings/qjs/gc_visitor.h" +#include "bindings/qjs/cppgc/gc_visitor.h" #include "document_fragment.h" #include "element.h" @@ -16,7 +16,7 @@ class TemplateContentDocumentFragment final : public DocumentFragment { TemplateContentDocumentFragment(Document& document, Element* host) : DocumentFragment(&document, kCreateDocumentFragment), host_(host) {} - Element* Host() const { return host_; } + Element* Host() const { return host_.Get(); } void Trace(GCVisitor* visitor) const override { visitor->Trace(host_); @@ -25,7 +25,7 @@ class TemplateContentDocumentFragment final : public DocumentFragment { private: bool IsTemplateContent() const override { return true; } - Element* host_; + Member<Element> host_; }; } // namespace kraken diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 4a1fad9d83..2cb9a8f4d8 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -7,7 +7,6 @@ #include "built_in_string.h" #include "core/dom/document.h" #include "core/html/html_html_element.h" -#include "event_type_names.h" #include "polyfill.h" #include "foundation/logging.h" @@ -65,8 +64,10 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& // Register all built-in native bindings. InstallBindings(this); + // Install document. InstallDocument(); + #if ENABLE_PROFILE nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); @@ -370,10 +371,7 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { void ExecutingContext::InstallDocument() { document_ = MakeGarbageCollected<Document>(this); - HTMLHtmlElement* html_element = MakeGarbageCollected<HTMLHtmlElement>(*document_); - ExceptionState exception_state; - document_->AppendChild(html_element, exception_state); - document_->SetDocumentElement(html_element); + document_->InitDocumentElement(); DefineGlobalProperty("document", document_->ToValue().QJSValue()); } diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 993e95e6c9..48da07e4c5 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -20,7 +20,6 @@ #include "bindings/qjs/pending_promises.h" #include "bindings/qjs/rejected_promises.h" #include "bindings/qjs/script_value.h" -#include "bindings/qjs/script_wrappable.h" #include "foundation/macros.h" #include "foundation/ui_command_buffer.h" diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index 297bb53329..f8541ba86f 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -4,7 +4,7 @@ */ #include "dom_timer.h" -#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/cppgc/garbage_collected.h" #include "bindings/qjs/qjs_engine_patch.h" #include "core/executing_context.h" diff --git a/bridge/core/frame/dom_timer_coordinator.h b/bridge/core/frame/dom_timer_coordinator.h index 99621f08ef..d0bd6c6260 100644 --- a/bridge/core/frame/dom_timer_coordinator.h +++ b/bridge/core/frame/dom_timer_coordinator.h @@ -9,7 +9,6 @@ #include <quickjs/quickjs.h> #include <unordered_map> #include <vector> -#include "bindings/qjs/gc_visitor.h" namespace kraken { diff --git a/bridge/core/frame/location.h b/bridge/core/frame/location.h index 21cc3876b7..a02e84307b 100644 --- a/bridge/core/frame/location.h +++ b/bridge/core/frame/location.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_LOCATION_H #define KRAKENBRIDGE_LOCATION_H +#include "bindings/qjs/cppgc/garbage_collected.h" #include "bindings/qjs/executing_context.h" -#include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/wrapper_type_info.h" namespace kraken { diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h index 7268c431f5..2399f8f1b0 100644 --- a/bridge/core/frame/module_callback.h +++ b/bridge/core/frame/module_callback.h @@ -7,7 +7,6 @@ #define KRAKENBRIDGE_MODULE_CALLBACK_H #include <quickjs/list.h> -#include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/qjs_function.h" namespace kraken { diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h index 7e7961d1f9..e0efce08b1 100644 --- a/bridge/core/frame/module_listener.h +++ b/bridge/core/frame/module_listener.h @@ -6,7 +6,6 @@ #ifndef KRAKENBRIDGE_MODULE_LISTENER_H #define KRAKENBRIDGE_MODULE_LISTENER_H -#include "bindings/qjs/garbage_collected.h" #include "bindings/qjs/qjs_function.h" namespace kraken { diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index efbe606c9a..bbc760691d 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -4,9 +4,9 @@ */ #include "window.h" +#include "bindings/qjs/cppgc/garbage_collected.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_engine_patch.h" #include "dart_methods.h" diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index 29bf0068df..71a8d0ffe2 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -14,6 +14,7 @@ class HTMLBodyElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: + using ImplType = HTMLBodyElement*; explicit HTMLBodyElement(Document&); }; diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h index a1b923069e..521baa073e 100644 --- a/bridge/core/html/html_div_element.h +++ b/bridge/core/html/html_div_element.h @@ -13,6 +13,7 @@ class HTMLDivElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: + using ImplType = HTMLDivElement*; explicit HTMLDivElement(Document&); private: diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index a1d2498dff..e3549ff9ea 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -13,6 +13,7 @@ class HTMLElement : public Element { DEFINE_WRAPPERTYPEINFO(); public: + using ImplType = HTMLElement*; HTMLElement(const AtomicString& tag_name, Document* document, ConstructionType); private: diff --git a/bridge/core/html/html_head_element.h b/bridge/core/html/html_head_element.h index 71f6fd1254..877b0c5b0c 100644 --- a/bridge/core/html/html_head_element.h +++ b/bridge/core/html/html_head_element.h @@ -14,6 +14,7 @@ class HTMLHeadElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: + using ImplType = HTMLHeadElement*; explicit HTMLHeadElement(Document&); private: diff --git a/bridge/core/html/html_html_element.h b/bridge/core/html/html_html_element.h index 7eb2f1e821..373eeace57 100644 --- a/bridge/core/html/html_html_element.h +++ b/bridge/core/html/html_html_element.h @@ -14,6 +14,7 @@ class HTMLHtmlElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: + using ImplType = HTMLHtmlElement*; explicit HTMLHtmlElement(Document&); private: diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index d23d768e58..f71794a328 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -108,8 +108,18 @@ export function generateTypeConverter(type: ParameterType[]): string { function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { let type = generateTypeConverter(argument.type); - return `auto&& args_${argument.name} = Converter<${type}>::FromValue(ctx, argv[${argsIndex}], exception_state); -if (exception_state.HasException()) { + + 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(); }`; } @@ -134,7 +144,7 @@ ${returnValueAssignment} self->${generateCallMethodName(declare.name)}(${[...pre return `auto&& args_${argument.name} = Converter<IDLOptional<${generateTypeConverter(argument.type)}>>::FromValue(ctx, argv[${argsIndex}], exception_state); -if (exception_state.HasException()) { +if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } @@ -261,7 +271,7 @@ function generateFunctionBody(blob: IDLBlob, declare: FunctionDeclaration, optio ${addIndent(callBody, 4)} } while (false); - if (exception_state.HasException()) { + if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } return ${returnValueResult}; diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl index 3dce90fa23..a229afc24c 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl @@ -10,7 +10,7 @@ #include "html_element_factory.h" #include <unordered_map> #include "html_names.h" -#include "bindings/qjs/garbage_collected.h" +#include "bindings/qjs/cppgc/garbage_collected.h" <% _.forEach(data, (item, index) => { %> <% if (_.isString(item)) { %> From 449d0a28d1b68d60aee9c28e769220c54bea4e74 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Sun, 24 Apr 2022 15:49:05 +0800 Subject: [PATCH 092/498] fix: fix create new object. --- bridge/bindings/qjs/cppgc/gc_visitor.h | 2 + bridge/bindings/qjs/script_wrappable.cc | 4 ++ bridge/bindings/qjs/script_wrappable.h | 2 + bridge/core/dom/document.cc | 8 +-- bridge/core/dom/document.d.ts | 10 ++-- bridge/core/dom/document.h | 2 +- bridge/core/dom/document_test.cc | 54 +++++++++---------- .../code_generator/bin/code_generator.js | 2 + bridge/scripts/code_generator/global.d.ts | 2 + .../code_generator/src/idl/analyzer.ts | 25 ++++++--- .../code_generator/src/idl/declaration.ts | 3 ++ .../code_generator/src/idl/generateSource.ts | 12 +++-- 12 files changed, 76 insertions(+), 50 deletions(-) diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.h b/bridge/bindings/qjs/cppgc/gc_visitor.h index 54ace6dc52..a0a9b0dbaf 100644 --- a/bridge/bindings/qjs/cppgc/gc_visitor.h +++ b/bridge/bindings/qjs/cppgc/gc_visitor.h @@ -7,6 +7,8 @@ #define KRAKENBRIDGE_GC_VISITOR_H #include <quickjs/quickjs.h> +#include <bindings/qjs/script_wrappable.h> + #include "foundation/macros.h" #include "member.h" diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 15052a9866..bd18bd47dd 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -15,6 +15,10 @@ JSValue ScriptWrappable::ToQuickJS() { return JS_DupValue(ctx_, jsObject_); } +JSValue ScriptWrappable::ToQuickJSUnsafe() { + return jsObject_; +} + ScriptValue ScriptWrappable::ToValue() { return ScriptValue(ctx_, jsObject_); } diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index b968cef165..2b3ae3742c 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -47,6 +47,8 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { void Trace(GCVisitor* visitor) const override{}; JSValue ToQuickJS(); + JSValue ToQuickJSUnsafe(); + ScriptValue ToValue(); FORCE_INLINE ExecutingContext* GetExecutingContext() const { return static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx_)); diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index f4d1145cbf..fd9bba5980 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -23,18 +23,18 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ Document::Document(ExecutingContext* context) : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) {} -ScriptValue Document::createElement(const AtomicString& name, ExceptionState& exception_state) { +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 ScriptValue::Empty(ctx()); + return nullptr; } if (auto* element = HTMLElementFactory::Create(name, *this)) { - return element->ToValue(); + return element; } - return MakeGarbageCollected<HTMLUnknownElement>(name, *this)->ToValue(); + return MakeGarbageCollected<HTMLUnknownElement>(name, *this); } Text* Document::createTextNode(const AtomicString& value, ExceptionState& exception_state) { diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts index c0e50f41ad..91bb7db62d 100644 --- a/bridge/core/dom/document.d.ts +++ b/bridge/core/dom/document.d.ts @@ -5,15 +5,17 @@ import {DocumentFragment} from "./document_fragment"; import {HTMLHeadElement} from "../html/html_head_element"; import {HTMLBodyElement} from "../html/html_body_element"; import {HTMLHtmlElement} from "../html/html_html_element"; +import {Element} from "./element"; interface Document extends Node { readonly body: HTMLBodyElement | null; readonly head: HTMLHeadElement | null; readonly documentElement: HTMLHtmlElement; - createElement(tagName: string): any; - createTextNode(value: string): Text; - createDocumentFragment(): DocumentFragment; - createComment(): Comment; + + createElement(tagName: string): NewObject<Element>; + createTextNode(value: string): NewObject<Text>; + createDocumentFragment(): NewObject<DocumentFragment>; + createComment(): NewObject<Comment>; new(): Document; } diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 7f5b8e69ac..eee1bc6c0b 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -32,7 +32,7 @@ class Document : public ContainerNode, public TreeScope { static Document* Create(ExecutingContext* context, ExceptionState& exception_state); - ScriptValue createElement(const AtomicString& name, ExceptionState& exception_state); + Element* createElement(const AtomicString& name, ExceptionState& exception_state); Text* createTextNode(const AtomicString& value, ExceptionState& exception_state); DocumentFragment* createDocumentFragment(ExceptionState& exception_state); Comment* createComment(ExceptionState& exception_state); diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index d50d595993..0c29e8d989 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -40,8 +40,7 @@ TEST(Document, body) { errorCalled = true; }); auto context = bridge->getContext(); - const char* code = - "console.log(document.body)"; + const char* code = "console.log(document.body)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); @@ -53,40 +52,37 @@ TEST(Document, appendParentWillFail) { kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto context = bridge->getContext(); + const char* code = "document.body.appendChild(document.documentElement)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, true); + EXPECT_EQ(logCalled, false); +} + +TEST(Document, createTextNode) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "<div>"); + }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->getContext(); const char* code = - "document.body.appendChild(document.documentElement)"; + "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, true); - EXPECT_EQ(logCalled, false); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); } - -// TEST(Document, createTextNode) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "<div>"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// auto context = bridge->getContext(); -// const char* code = -// "let div = document.createElement('div');" -// "div.setAttribute('hello', 1234);" -// "document.body.appendChild(div);" -// "let text = document.createTextNode('1234');" -// "div.appendChild(text);" -// "console.log(div);"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -// } //// // TEST(Document, instanceofNode) { // bool static errorCalled = false; diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index a9ab44b832..551afe27a1 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -34,6 +34,8 @@ function genCodeFromTypeDefine() { cwd: source, }); + typeFiles = ['dom/document.d.ts']; + 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', ''); diff --git a/bridge/scripts/code_generator/global.d.ts b/bridge/scripts/code_generator/global.d.ts index 35cc8ad715..ac7d1555db 100644 --- a/bridge/scripts/code_generator/global.d.ts +++ b/bridge/scripts/code_generator/global.d.ts @@ -7,3 +7,5 @@ declare interface BlobPart {} declare interface BlobPropertyBag {} declare function Dictionary() : any; declare type JSEventListener = void; + +type NewObject<T> = void; diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 476c9fda36..51763fd2a5 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -52,7 +52,11 @@ function getParameterName(name: ts.BindingName) : string { export type ParameterType = FunctionArgumentType | string; -function getParameterBaseType(type: ts.TypeNode): ParameterType { +class ParameterMode { + newObject?: boolean; +} + +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) { @@ -82,28 +86,33 @@ function getParameterBaseType(type: ts.TypeNode): ParameterType { 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; } return identifier; } else if (type.kind === ts.SyntaxKind.LiteralType) { // @ts-ignore - return getParameterBaseType((type as ts.LiteralTypeNode).literal); + return getParameterBaseType((type as ts.LiteralTypeNode).literal, mode); } return FunctionArgumentType.any; } -function getParameterType(type: ts.TypeNode): ParameterType[] { +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)]; + 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)); + return types.map(type => getParameterBaseType(type as unknown as ts.TypeNode, mode)); } - return [getParameterBaseType(type)]; + return [getParameterBaseType(type, mode)]; } function paramsNodeToArguments(parameter: ts.ParameterDeclaration): FunctionArguments { @@ -178,7 +187,9 @@ function walkProgram(statement: ts.Statement) { }); obj.methods.push(f); if (m.type) { - f.returnType = getParameterType(m.type); + let mode = new ParameterMode(); + f.returnType = getParameterType(m.type, mode); + f.returnTypeMode = mode.newObject ? 'newObject' : 'normal'; } break; } diff --git a/bridge/scripts/code_generator/src/idl/declaration.ts b/bridge/scripts/code_generator/src/idl/declaration.ts index 23be264e71..852430a08f 100644 --- a/bridge/scripts/code_generator/src/idl/declaration.ts +++ b/bridge/scripts/code_generator/src/idl/declaration.ts @@ -28,9 +28,12 @@ export class PropsDeclaration { readonly: boolean; } +type FunctionReturnTypeMode = 'normal' | 'newObject'; + export class FunctionDeclaration extends PropsDeclaration { args: FunctionArguments[] = []; returnType: ParameterType[] = []; + returnTypeMode: FunctionReturnTypeMode; } export enum ClassObjectKind { diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index f71794a328..c0e3f21c40 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -236,17 +236,19 @@ function generateReturnValueInit(blob: IDLBlob, type: ParameterType[], options: return `Converter<${generateTypeConverter(type)}>::ImplType return_value;`; } -function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}): string { +function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], mode: string, options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}): string { if (type[0] == FunctionArgumentType.void) return 'JS_NULL'; + let method = mode === 'newObject' ? 'ToQuickJSUnsafe' : 'ToQuickJS'; + if (options.isConstructor) { - return `return_value->ToQuickJS()`; + return `return_value->${method}()`; } if (typeof type[0] === 'string') { if (type[0] === 'Promise') { - return 'return_value.ToQuickJS()'; + return `return_value.${method}()`; } else { - return `return_value->ToQuickJS()`; + return `return_value->${method}()`; } } @@ -259,7 +261,7 @@ function generateFunctionBody(blob: IDLBlob, declare: FunctionDeclaration, optio let paramCheck = generateMethodArgumentsCheck(declare); let callBody = generateFunctionCallBody(blob, declare, options); let returnValueInit = generateReturnValueInit(blob, declare.returnType, options); - let returnValueResult = generateReturnValueResult(blob, declare.returnType, options); + let returnValueResult = generateReturnValueResult(blob, declare.returnType, declare.returnTypeMode, options); return `${paramCheck} From 007141ac3ff26298e53dbf3a3abcd4548bd88026 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Sun, 24 Apr 2022 18:45:02 +0800 Subject: [PATCH 093/498] fix: fix members freed by run out of body scope. --- bridge/bindings/qjs/cppgc/member.h | 18 +++- bridge/bindings/qjs/qjs_engine_patch.cc | 10 +- bridge/bindings/qjs/qjs_engine_patch.h | 8 ++ bridge/bindings/qjs/script_wrappable.cc | 36 ++++--- bridge/bindings/qjs/script_wrappable.h | 2 +- bridge/core/dom/document_test.cc | 134 ++++++++++++++++-------- 6 files changed, 138 insertions(+), 70 deletions(-) diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index dd704a4a75..ffec68d11c 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -8,6 +8,7 @@ #include <type_traits> #include "bindings/qjs/script_value.h" #include "bindings/qjs/script_wrappable.h" +#include "bindings/qjs/qjs_engine_patch.h" #include "foundation/casting.h" namespace kraken { @@ -18,7 +19,20 @@ template <typename T, typename = std::is_base_of<ScriptWrappable, T>> class Member { public: Member() = default; - Member(T* ptr): raw_(ptr) {} + Member(T* ptr): raw_(ptr), runtime_(ptr != nullptr ? ptr->runtime() : nullptr) {} + ~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() { @@ -60,12 +74,14 @@ class Member { void SetRaw(T* p) { if (p != nullptr) { auto* wrappable = To<ScriptWrappable>(p); + runtime_ = wrappable->runtime(); JS_DupValue(wrappable->ctx(), wrappable->ToValue().QJSValue()); } raw_ = p; } T* raw_{nullptr}; + JSRuntime* runtime_{nullptr}; }; } // namespace kraken diff --git a/bridge/bindings/qjs/qjs_engine_patch.cc b/bridge/bindings/qjs/qjs_engine_patch.cc index b52474c2c6..c484c535d7 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.cc +++ b/bridge/bindings/qjs/qjs_engine_patch.cc @@ -8,12 +8,6 @@ #include <quickjs/list.h> #include <cstring> -typedef enum { - JS_GC_PHASE_NONE, - JS_GC_PHASE_DECREF, - JS_GC_PHASE_REMOVE_CYCLES, -} JSGCPhaseEnum; - typedef struct JSProxyData { JSValue target; JSValue handler; @@ -354,3 +348,7 @@ JSValue JS_GetProxyTarget(JSValue value) { JSObject* p = JS_VALUE_GET_OBJ(value); return p->u.proxy_data->target; } + +JSGCPhaseEnum JS_GetEnginePhase(JSRuntime* runtime) { + return runtime->gc_phase; +} diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h index 79d7a72a16..9b1591dca2 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -28,6 +28,13 @@ struct JSString { } u; }; + +typedef enum { + JS_GC_PHASE_NONE, + JS_GC_PHASE_DECREF, + JS_GC_PHASE_REMOVE_CYCLES, +} JSGCPhaseEnum; + enum { /* classid tag */ /* union usage | properties */ JS_CLASS_OBJECT = 1, /* must be first */ @@ -116,6 +123,7 @@ 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); #ifdef __cplusplus } diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index bd18bd47dd..b1500371ec 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -15,7 +15,7 @@ JSValue ScriptWrappable::ToQuickJS() { return JS_DupValue(ctx_, jsObject_); } -JSValue ScriptWrappable::ToQuickJSUnsafe() { +JSValue ScriptWrappable::ToQuickJSUnsafe() const { return jsObject_; } @@ -23,6 +23,23 @@ 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<ScriptWrappable*>(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<ScriptWrappable*>(JS_GetOpaque(val, JSValueGetClassId(val))); + delete object; +} + void ScriptWrappable::InitializeQuickJSObject() { auto* wrapperTypeInfo = GetWrapperTypeInfo(); JSRuntime* runtime = runtime_; @@ -34,27 +51,14 @@ void ScriptWrappable::InitializeQuickJSObject() { def.class_name = wrapperTypeInfo->className; - /// 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<ScriptWrappable*>(JS_GetOpaque(val, JSValueGetClassId(val))); - GCVisitor visitor{rt, mark_func}; - object->Trace(&visitor); - }; + def.gc_mark = HandleJSObjectGCMark; /// Define custom behavior when call GetProperty, SetProperty on object. if (wrapperTypeInfo->exoticMethods != nullptr) { def.exotic = wrapperTypeInfo->exoticMethods; } - /// 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<ScriptWrappable*>(JS_GetOpaque(val, JSValueGetClassId(val))); - delete object; - }; + def.finalizer = HandleJSObjectFinalized; JS_NewClass(runtime, wrapperTypeInfo->classId, &def); } diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 2b3ae3742c..8a412c462d 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -47,7 +47,7 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { void Trace(GCVisitor* visitor) const override{}; JSValue ToQuickJS(); - JSValue ToQuickJSUnsafe(); + JSValue ToQuickJSUnsafe() const; ScriptValue ToValue(); FORCE_INLINE ExecutingContext* GetExecutingContext() const { diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 0c29e8d989..ca55dca748 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -77,54 +77,96 @@ TEST(Document, createTextNode) { "div.setAttribute('hello', 1234);" "document.body.appendChild(div);" "let text = document.createTextNode('1234');" -// "div.appendChild(text);" + "div.appendChild(text);" "console.log(div);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } -//// -// TEST(Document, instanceofNode) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "true true true"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// auto context = bridge->getContext(); -// const char* code = -// "console.log(document instanceof Node, document instanceof Document, document instanceof EventTarget)"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -// } -// -// TEST(Document, createElementShouldWorkWithMultipleContext) { -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; -// -// kraken::KrakenPage* bridge1; -// -// const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; -// -// { -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); -// auto context = bridge->getContext(); -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// bridge1 = bridge.release(); -// } -// -// { -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); -// auto context = bridge->getContext(); -// const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// } -// -// bridge1->evaluateScript(code, strlen(code), "vm://", 0); -// -// delete bridge1; -// } + +TEST(Document, createComment) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "<div>"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->getContext(); + const char* code = + "let div = document.createElement('div');" + "div.setAttribute('hello', 1234);" + "document.body.appendChild(div);" + "let comment = document.createComment();" + "div.appendChild(comment);" + "console.log(div);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Document, instanceofNode) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "true true true"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->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, FreedByOutOfScope) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = false; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->getContext(); + const char* code = + "(() => { let img = document.createElement('div'); })();"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, false); +} + +TEST(Document, createElementShouldWorkWithMultipleContext) { + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + kraken::KrakenPage* bridge1; + + const char* code = "(() => { let img = document.createElement('div'); })();"; + + { + 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; +} From 24fc5610e80c94a43d76c97abd9e17f79e6cda8e Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Sun, 24 Apr 2022 10:46:03 +0000 Subject: [PATCH 094/498] Committing clang-format changes --- bridge/bindings/qjs/converter_impl.h | 12 +++++------- bridge/bindings/qjs/cppgc/gc_visitor.h | 4 ++-- bridge/bindings/qjs/cppgc/member.h | 7 ++++--- bridge/bindings/qjs/qjs_engine_patch.h | 1 - bridge/bindings/qjs/script_value.cc | 2 +- bridge/bindings/qjs/wrapper_type_info.h | 2 +- bridge/core/dom/container_node.cc | 6 +++--- bridge/core/dom/document.h | 2 +- bridge/core/dom/document_test.cc | 3 +-- bridge/core/dom/events/event_target.h | 2 +- bridge/core/executing_context.cc | 1 - bridge/core/html/html_body_element.h | 2 +- bridge/core/html/html_div_element.h | 2 +- bridge/core/html/html_head_element.h | 2 +- bridge/core/html/html_html_element.h | 2 +- 15 files changed, 23 insertions(+), 27 deletions(-) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 0be80b7da6..86d0feba09 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -382,7 +382,7 @@ struct Converter<IDLNullable<JSEventListener>> : public ConverterBase<JSEventLis }; // DictionaryBase and Derived class. -template<typename T> +template <typename T> struct Converter<T, typename std::enable_if_t<std::is_base_of<DictionaryBase, T>::value>> : public ConverterBase<T> { static typename T::ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); @@ -398,23 +398,21 @@ struct Converter<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T return toScriptWrappable<T>(value); } static T* ArgumentsValue(ExecutingContext* context, - JSValue value, - uint32_t argv_index, - ExceptionState& exception_state) { + 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)); + ExceptionMessage::ArgumentNotOfType(argv_index, wrapper_type_info->className)); return nullptr; } static JSValue ToValue(JSContext* ctx, T* value) { return value->ToQuickJS(); } }; - }; // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.h b/bridge/bindings/qjs/cppgc/gc_visitor.h index a0a9b0dbaf..230e6a736e 100644 --- a/bridge/bindings/qjs/cppgc/gc_visitor.h +++ b/bridge/bindings/qjs/cppgc/gc_visitor.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_GC_VISITOR_H #define KRAKENBRIDGE_GC_VISITOR_H -#include <quickjs/quickjs.h> #include <bindings/qjs/script_wrappable.h> +#include <quickjs/quickjs.h> #include "foundation/macros.h" #include "member.h" @@ -24,7 +24,7 @@ class GCVisitor final { public: explicit GCVisitor(JSRuntime* rt, JS_MarkFunc* markFunc) : runtime_(rt), markFunc_(markFunc){}; - template<typename T> + template <typename T> void Trace(const Member<T>& target) { if (target.Get() != nullptr) { JS_MarkValue(runtime_, target.Get()->jsObject_, markFunc_); diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index ffec68d11c..ab6fd67e4b 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -6,9 +6,9 @@ #define KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ #include <type_traits> +#include "bindings/qjs/qjs_engine_patch.h" #include "bindings/qjs/script_value.h" #include "bindings/qjs/script_wrappable.h" -#include "bindings/qjs/qjs_engine_patch.h" #include "foundation/casting.h" namespace kraken { @@ -19,7 +19,7 @@ template <typename T, typename = std::is_base_of<ScriptWrappable, T>> class Member { public: Member() = default; - Member(T* ptr): raw_(ptr), runtime_(ptr != nullptr ? ptr->runtime() : nullptr) {} + Member(T* ptr) : raw_(ptr), runtime_(ptr != nullptr ? ptr->runtime() : nullptr) {} ~Member() { if (raw_ != nullptr) { assert(runtime_ != nullptr); @@ -36,7 +36,8 @@ class Member { T* Get() const { return raw_; } void Clear() { - if (raw_ == nullptr) return; + if (raw_ == nullptr) + return; auto* wrappable = To<ScriptWrappable>(raw_); JS_FreeValue(wrappable->ctx(), wrappable->ToValue().QJSValue()); raw_ = nullptr; diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h index 9b1591dca2..530a056ee4 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -28,7 +28,6 @@ struct JSString { } u; }; - typedef enum { JS_GC_PHASE_NONE, JS_GC_PHASE_DECREF, diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index d35c2aab53..903ff2d539 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -5,8 +5,8 @@ #include "script_value.h" #include <vector> -#include "cppgc/gc_visitor.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" diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 003f1fdd75..c07073bb02 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_WRAPPER_TYPE_INFO_H #define KRAKENBRIDGE_WRAPPER_TYPE_INFO_H -#include <cassert> #include <quickjs/quickjs.h> +#include <cassert> #include "bindings/qjs/qjs_engine_patch.h" namespace kraken { diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 161661bcb4..c4de6db100 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -419,9 +419,9 @@ void ContainerNode::NotifyNodeRemoved(Node& root) { } void ContainerNode::Trace(GCVisitor* visitor) const { -// for (Node& node : NodeTraversal::ChildrenOf(*this)) { -// visitor->Trace(&node); -// } + // for (Node& node : NodeTraversal::ChildrenOf(*this)) { + // visitor->Trace(&node); + // } visitor->Trace(first_child_); visitor->Trace(last_child_); diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index eee1bc6c0b..df4241526a 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -10,8 +10,8 @@ #include "core/dom/comment.h" #include "core/dom/document_fragment.h" #include "core/dom/text.h" -#include "tree_scope.h" #include "html_element_type_helper.h" +#include "tree_scope.h" namespace kraken { diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index ca55dca748..a6871220fd 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -138,8 +138,7 @@ TEST(Document, FreedByOutOfScope) { errorCalled = true; }); auto context = bridge->getContext(); - const char* code = - "(() => { let img = document.createElement('div'); })();"; + const char* code = "(() => { let img = document.createElement('div'); })();"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, false); diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index be59fda337..0d94ef5742 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -6,10 +6,10 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H +#include "bindings/qjs/cppgc/member.h" #include "bindings/qjs/js_event_listener.h" #include "bindings/qjs/qjs_function.h" #include "bindings/qjs/script_wrappable.h" -#include "bindings/qjs/cppgc/member.h" #include "core/dom/binding_object.h" #include "event_listener_map.h" #include "foundation/native_string.h" diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 2cb9a8f4d8..7ecd5a9e06 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -67,7 +67,6 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& // Install document. InstallDocument(); - #if ENABLE_PROFILE nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index 71a8d0ffe2..da08da4383 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -14,7 +14,7 @@ class HTMLBodyElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: - using ImplType = HTMLBodyElement*; + using ImplType = HTMLBodyElement*; explicit HTMLBodyElement(Document&); }; diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h index 521baa073e..f09ffc7629 100644 --- a/bridge/core/html/html_div_element.h +++ b/bridge/core/html/html_div_element.h @@ -13,7 +13,7 @@ class HTMLDivElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: - using ImplType = HTMLDivElement*; + using ImplType = HTMLDivElement*; explicit HTMLDivElement(Document&); private: diff --git a/bridge/core/html/html_head_element.h b/bridge/core/html/html_head_element.h index 877b0c5b0c..a1f6ae6e52 100644 --- a/bridge/core/html/html_head_element.h +++ b/bridge/core/html/html_head_element.h @@ -14,7 +14,7 @@ class HTMLHeadElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: - using ImplType = HTMLHeadElement*; + using ImplType = HTMLHeadElement*; explicit HTMLHeadElement(Document&); private: diff --git a/bridge/core/html/html_html_element.h b/bridge/core/html/html_html_element.h index 373eeace57..41ca13ab2a 100644 --- a/bridge/core/html/html_html_element.h +++ b/bridge/core/html/html_html_element.h @@ -14,7 +14,7 @@ class HTMLHtmlElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: - using ImplType = HTMLHtmlElement*; + using ImplType = HTMLHtmlElement*; explicit HTMLHtmlElement(Document&); private: From 9cd7974e25e969cce8681986581f315977f04f9d Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Mon, 25 Apr 2022 16:30:57 +0800 Subject: [PATCH 095/498] fix: fix childNodes on text. --- bridge/bindings/qjs/binding_initializer.cc | 2 + bridge/bindings/qjs/cppgc/garbage_collected.h | 2 +- bridge/bindings/qjs/cppgc/member.h | 23 ++++++++-- bridge/bindings/qjs/wrapper_type_info.h | 7 ++-- bridge/core/dom/container_node.cc | 4 +- bridge/core/dom/container_node.h | 1 + bridge/core/dom/ng/node_list.d.ts | 2 + bridge/core/dom/ng/node_list.h | 8 +++- bridge/core/dom/node.cc | 42 ++++++++++++++++++- bridge/core/dom/node.d.ts | 9 ++-- bridge/core/dom/node.h | 1 + bridge/core/dom/node_data.cc | 13 +++--- bridge/core/dom/node_data.h | 2 +- bridge/core/dom/node_test.cc | 21 +++++++++- bridge/core/executing_context_data.cc | 2 +- bridge/core/script_state.cc | 5 +++ .../code_generator/bin/code_generator.js | 2 - .../json_templates/element_factory.cc.tpl | 1 + bridge/test/test.cmake | 1 + 19 files changed, 120 insertions(+), 28 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index e32c9f923a..8b5b6cadfa 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -23,6 +23,7 @@ #include "qjs_node.h" #include "qjs_text.h" #include "qjs_window.h" +#include "qjs_node_list.h" namespace kraken { @@ -35,6 +36,7 @@ void InstallBindings(ExecutingContext* context) { QJSEventTarget::Install(context); QJSEvent::Install(context); QJSNode::Install(context); + QJSNodeList::Install(context); QJSDocument::Install(context); QJSCharacterData::Install(context); QJSText::Install(context); diff --git a/bridge/bindings/qjs/cppgc/garbage_collected.h b/bridge/bindings/qjs/cppgc/garbage_collected.h index 32b396a093..5fc623ce59 100644 --- a/bridge/bindings/qjs/cppgc/garbage_collected.h +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -45,7 +45,7 @@ class GarbageCollected { */ virtual void Trace(GCVisitor* visitor) const = 0; - virtual void InitializeQuickJSObject() = 0; + virtual void InitializeQuickJSObject() {}; protected: GarbageCollected(){}; diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index ab6fd67e4b..e2dd607d87 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -15,11 +15,18 @@ namespace kraken { class ScriptWrappable; +/** + * Members are used in classes to contain strong pointers to other garbage + * collected objects. All Member fields of a class must be traced in the class' + * trace method. + */ template <typename T, typename = std::is_base_of<ScriptWrappable, T>> class Member { public: Member() = default; - Member(T* ptr) : raw_(ptr), runtime_(ptr != nullptr ? ptr->runtime() : nullptr) {} + Member(T* ptr) { + Initialize(ptr); + } ~Member() { if (raw_ != nullptr) { assert(runtime_ != nullptr); @@ -39,10 +46,17 @@ class Member { if (raw_ == nullptr) return; auto* wrappable = To<ScriptWrappable>(raw_); - JS_FreeValue(wrappable->ctx(), wrappable->ToValue().QJSValue()); + JS_FreeValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); raw_ = nullptr; } + void Initialize(T* p) { + inited_ = true; + if (p == nullptr) return; + raw_ = p; + runtime_ = p->runtime(); + } + // Copy assignment. Member& operator=(const Member& other) { operator=(other.Get()); @@ -73,18 +87,21 @@ class Member { private: void SetRaw(T* p) { + assert(inited_); if (p != nullptr) { auto* wrappable = To<ScriptWrappable>(p); runtime_ = wrappable->runtime(); - JS_DupValue(wrappable->ctx(), wrappable->ToValue().QJSValue()); + JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); } raw_ = p; } T* raw_{nullptr}; JSRuntime* runtime_{nullptr}; + bool inited_{false}; }; + } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index c07073bb02..54061836da 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -14,8 +14,7 @@ namespace kraken { // Define all built-in wrapper class id. enum { - // We assume there will no other class id could overwrite by JS_NewClassID, at least 200. - JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 200, + JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, JS_CLASS_BLOB, JS_CLASS_EVENT, JS_CLASS_ERROR_EVENT, @@ -35,7 +34,9 @@ enum { JS_CLASS_HTML_BODY_ELEMENT, JS_CLASS_HTML_HEAD_ELEMENT, JS_CLASS_HTML_HTML_ELEMENT, - JS_CLASS_HTML_UNKNOWN_ELEMENT + JS_CLASS_HTML_UNKNOWN_ELEMENT, + + JS_CLASS_CUSTOM_CLASS_INIT_COUNT /* last entry for predefined classes */ }; // This struct provides a way to store a bunch of information that is helpful diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index c4de6db100..e0e399ef62 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -323,9 +323,9 @@ std::string ContainerNode::nodeValue() const { } ContainerNode::ContainerNode(TreeScope* tree_scope, ConstructionType type) - : Node(tree_scope->GetDocument().GetExecutingContext(), tree_scope, type) {} + : ContainerNode(tree_scope->GetDocument().GetExecutingContext(), &tree_scope->GetDocument(), type) {} ContainerNode::ContainerNode(ExecutingContext* context, Document* document, ConstructionType type) - : Node(context, document, 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); diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index 2fb6925a67..a8d787bdf4 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -6,6 +6,7 @@ #define KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ #include <vector> +#include "ng/node_list.h" #include "bindings/qjs/cppgc/gc_visitor.h" #include "node.h" diff --git a/bridge/core/dom/ng/node_list.d.ts b/bridge/core/dom/ng/node_list.d.ts index 019ef9365b..20ee314d0b 100644 --- a/bridge/core/dom/ng/node_list.d.ts +++ b/bridge/core/dom/ng/node_list.d.ts @@ -3,4 +3,6 @@ import {Node} from "../node"; export interface NodeList { readonly length: int64; item(index: number): Node; + [index: number]: Node; + new(): void; } diff --git a/bridge/core/dom/ng/node_list.h b/bridge/core/dom/ng/node_list.h index 40a7964426..2ffeba58da 100644 --- a/bridge/core/dom/ng/node_list.h +++ b/bridge/core/dom/ng/node_list.h @@ -14,9 +14,13 @@ class ExceptionState; class NodeList : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); - public: using ImplType = NodeList*; + + static NodeList* Create(ExecutingContext* context, ExceptionState& exception_state) { + return nullptr; + }; + NodeList(JSContext* ctx) : ScriptWrappable(ctx){}; ~NodeList() override = default; @@ -30,6 +34,8 @@ class NodeList : public ScriptWrappable { virtual Node* VirtualOwnerNode() const { return nullptr; } + void Trace(GCVisitor *visitor) const override {}; + protected: }; diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 99cb9adc6c..fa5a171111 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -3,6 +3,7 @@ */ #include "node.h" +#include <unordered_map> #include "character_data.h" #include "document.h" #include "document_fragment.h" @@ -38,10 +39,41 @@ NodeList* Node::childNodes() { return EnsureData().EnsureEmptyChildNodeList(*this); } +namespace { + +//// Helper object to allocate EventTargetData which is otherwise only used +//// through EventTargetWithInlineData. +class EventTargetDataObject final : public GarbageCollected<EventTargetDataObject> { + public: + void Trace(GCVisitor* visitor) const { data_.Trace(visitor); } + + EventTargetData& GetEventTargetData() { return data_; } + + private: + EventTargetData data_; +}; + +} // namespace + +using EventTargetDataMap = std::unordered_map<Node*, EventTargetDataObject*>; +static EventTargetDataMap& GetEventTargetDataMap() { + static thread_local EventTargetDataMap map; + return map; +} + EventTargetData* Node::GetEventTargetData() { - return nullptr; + return HasEventTargetData() ? &GetEventTargetDataMap().at(this)->GetEventTargetData() : nullptr; +} + +EventTargetData& Node::EnsureEventTargetData() { + if (HasEventTargetData()) + return GetEventTargetDataMap().at(this)->GetEventTargetData(); + assert(GetEventTargetDataMap().count(this) == 0); + auto* data = MakeGarbageCollected<EventTargetDataObject>(); + GetEventTargetDataMap().insert(std::make_pair(this, data)); + SetHasEventTargetData(true); + return data->GetEventTargetData(); } -EventTargetData& Node::EnsureEventTargetData() {} NodeData& Node::CreateData() { data_ = std::make_unique<NodeData>(); @@ -394,10 +426,16 @@ Node::Node(ExecutingContext* context, TreeScope* tree_scope, ConstructionType ty next_(nullptr), data_(nullptr) {} +Node::~Node() { + GetEventTargetDataMap().erase(this); +} + void Node::Trace(GCVisitor* visitor) const { visitor->Trace(previous_); visitor->Trace(next_); visitor->Trace(parent_or_shadow_host_node_); + if (data_ != nullptr) + data_->Trace(visitor); EventTarget::Trace(visitor); } diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts index 6fc41dec98..66266de86d 100644 --- a/bridge/core/dom/node.d.ts +++ b/bridge/core/dom/node.d.ts @@ -1,13 +1,14 @@ import { EventTarget } from './events/event_target'; import { Document } from './document'; import {Element} from "./element"; +import {NodeList} from "./ng/node_list"; /** Node is an interface from which a number of DOM API object types inherit. It allows those types to be treated similarly; for example, inheriting the same set of methods, or being tested in the same way. */ interface Node extends EventTarget { - // /** - // * Returns the children. - // */ - // readonly childNodes: NodeList; + /** + * Returns the children. + */ + readonly childNodes: NewObject<NodeList>; /** * Returns the first child. */ diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index c9d1da61a3..c8f4d60b3a 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -288,6 +288,7 @@ class Node : public EventTarget { Node(ExecutingContext* context, TreeScope*, ConstructionType); Node() = delete; + ~Node(); private: uint32_t node_flags_; diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index fd3b9eabf3..b28cd72c97 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -5,6 +5,7 @@ #include "node_data.h" #include "bindings/qjs/cppgc/garbage_collected.h" #include "container_node.h" +#include "ng/node_list.h" #include "ng/child_node_list.h" #include "ng/empty_node_list.h" @@ -12,27 +13,27 @@ namespace kraken { ChildNodeList* NodeData::GetChildNodeList(ContainerNode& node) { assert(!child_node_list_ || &node == child_node_list_->VirtualOwnerNode()); - return To<ChildNodeList>(child_node_list_); + return To<ChildNodeList>(child_node_list_.Get()); } ChildNodeList* NodeData::EnsureChildNodeList(ContainerNode& node) { if (child_node_list_) - return To<ChildNodeList>(child_node_list_); + return To<ChildNodeList>(child_node_list_.Get()); auto* list = MakeGarbageCollected<ChildNodeList>(&node); - child_node_list_ = list; + child_node_list_.Initialize(list); return list; } EmptyNodeList* NodeData::EnsureEmptyChildNodeList(Node& node) { if (child_node_list_) - return To<EmptyNodeList>(child_node_list_); + return To<EmptyNodeList>(child_node_list_.Get()); auto* list = MakeGarbageCollected<EmptyNodeList>(&node); - child_node_list_ = list; + child_node_list_.Initialize(list); return list; } void NodeData::Trace(GCVisitor* visitor) const { - child_node_list_->Trace(visitor); + visitor->Trace(child_node_list_->ToQuickJSUnsafe()); } } // namespace kraken diff --git a/bridge/core/dom/node_data.h b/bridge/core/dom/node_data.h index 8ffb61e736..a1a4864a2a 100644 --- a/bridge/core/dom/node_data.h +++ b/bridge/core/dom/node_data.h @@ -33,7 +33,7 @@ class NodeData { void Trace(GCVisitor* visitor) const; private: - NodeList* child_node_list_; + Member<NodeList> child_node_list_; }; } // namespace kraken diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index e8f8b6cd52..0af7a7c9a9 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -3,10 +3,10 @@ * Author: Kraken Team. */ -#include "event_target.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" + +using namespace kraken; TEST(Node, appendChild) { bool static errorCalled = false; @@ -53,6 +53,23 @@ TEST(Node, childNodes) { EXPECT_EQ(logCalled, true); } +TEST(Node, textNodeHaveEmptyChildNodes) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto context = bridge->getContext(); + 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; diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc index b74903387a..c968f2bd09 100644 --- a/bridge/core/executing_context_data.cc +++ b/bridge/core/executing_context_data.cc @@ -32,7 +32,7 @@ JSValue ExecutionContextData::constructorForIdSlowCase(const WrapperTypeInfo* ty // Allocate a new unique classID from QuickJS. JS_NewClassID(&class_id); - assert(class_id < JS_CLASS_GC_TRACKER); + assert(class_id > JS_CLASS_CUSTOM_CLASS_INIT_COUNT); // Create class template for behavior. JSClassDef def{}; diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index d458944605..67fb2dd43b 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -31,6 +31,11 @@ ScriptState::ScriptState() { event_type_names::Init(ctx_); html_names::Init(ctx_); binding_call_methods::Init(ctx_); + // Bump up the built-in classId. To make sure the created classId are larger than JS_CLASS_CUSTOM_CLASS_INIT_COUNT. + for(int i = 0; i < JS_CLASS_CUSTOM_CLASS_INIT_COUNT - JS_CLASS_GC_TRACKER + 2; i ++) { + JSClassID id{0}; + JS_NewClassID(&id); + } } } diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 551afe27a1..a9ab44b832 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -34,8 +34,6 @@ function genCodeFromTypeDefine() { cwd: source, }); - typeFiles = ['dom/document.d.ts']; - 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', ''); diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl index a229afc24c..7705df96da 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl @@ -96,6 +96,7 @@ HTMLElement* HTMLElementFactory::Create(const AtomicString& name, Document& docu void HTMLElementFactory::Dispose() { delete g_html_constructors; + g_html_constructors = nullptr; } } // namespace kraken diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 1845cd21f5..15ecd9e872 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -23,6 +23,7 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./core/frame/module_manager_test.cc ./core/dom/events/event_target_test.cc ./core/dom/document_test.cc + ./core/dom/node_test.cc # ./bindings/qjs/bom/timer_test.cc # ./bindings/qjs/qjs_patch_test.cc # ./bindings/qjs/garbage_collected_test.cc From 616752554b29b90ca4f2fb5ae5eda268a8a46562 Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Mon, 25 Apr 2022 09:32:31 +0000 Subject: [PATCH 096/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 2 +- bridge/bindings/qjs/cppgc/garbage_collected.h | 2 +- bridge/bindings/qjs/cppgc/member.h | 8 +++----- bridge/bindings/qjs/wrapper_type_info.h | 2 +- bridge/core/dom/container_node.h | 2 +- bridge/core/dom/ng/node_list.h | 7 +++---- bridge/core/dom/node_data.cc | 2 +- bridge/core/script_state.cc | 2 +- 8 files changed, 12 insertions(+), 15 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 8b5b6cadfa..d2992a9c28 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -21,9 +21,9 @@ #include "qjs_html_html_element.h" #include "qjs_module_manager.h" #include "qjs_node.h" +#include "qjs_node_list.h" #include "qjs_text.h" #include "qjs_window.h" -#include "qjs_node_list.h" namespace kraken { diff --git a/bridge/bindings/qjs/cppgc/garbage_collected.h b/bridge/bindings/qjs/cppgc/garbage_collected.h index 5fc623ce59..c5c9687117 100644 --- a/bridge/bindings/qjs/cppgc/garbage_collected.h +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -45,7 +45,7 @@ class GarbageCollected { */ virtual void Trace(GCVisitor* visitor) const = 0; - virtual void InitializeQuickJSObject() {}; + virtual void InitializeQuickJSObject(){}; protected: GarbageCollected(){}; diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index e2dd607d87..2873f4c6fa 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -24,9 +24,7 @@ template <typename T, typename = std::is_base_of<ScriptWrappable, T>> class Member { public: Member() = default; - Member(T* ptr) { - Initialize(ptr); - } + Member(T* ptr) { Initialize(ptr); } ~Member() { if (raw_ != nullptr) { assert(runtime_ != nullptr); @@ -52,7 +50,8 @@ class Member { void Initialize(T* p) { inited_ = true; - if (p == nullptr) return; + if (p == nullptr) + return; raw_ = p; runtime_ = p->runtime(); } @@ -101,7 +100,6 @@ class Member { bool inited_{false}; }; - } // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 54061836da..4d892f67f1 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -36,7 +36,7 @@ enum { JS_CLASS_HTML_HTML_ELEMENT, JS_CLASS_HTML_UNKNOWN_ELEMENT, - JS_CLASS_CUSTOM_CLASS_INIT_COUNT /* last entry for predefined classes */ + JS_CLASS_CUSTOM_CLASS_INIT_COUNT /* last entry for predefined classes */ }; // This struct provides a way to store a bunch of information that is helpful diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index a8d787bdf4..9389f8438e 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -6,8 +6,8 @@ #define KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ #include <vector> -#include "ng/node_list.h" #include "bindings/qjs/cppgc/gc_visitor.h" +#include "ng/node_list.h" #include "node.h" namespace kraken { diff --git a/bridge/core/dom/ng/node_list.h b/bridge/core/dom/ng/node_list.h index 2ffeba58da..10133a6687 100644 --- a/bridge/core/dom/ng/node_list.h +++ b/bridge/core/dom/ng/node_list.h @@ -14,12 +14,11 @@ class ExceptionState; class NodeList : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = NodeList*; - static NodeList* Create(ExecutingContext* context, ExceptionState& exception_state) { - return nullptr; - }; + static NodeList* Create(ExecutingContext* context, ExceptionState& exception_state) { return nullptr; }; NodeList(JSContext* ctx) : ScriptWrappable(ctx){}; ~NodeList() override = default; @@ -34,7 +33,7 @@ class NodeList : public ScriptWrappable { virtual Node* VirtualOwnerNode() const { return nullptr; } - void Trace(GCVisitor *visitor) const override {}; + void Trace(GCVisitor* visitor) const override{}; protected: }; diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index b28cd72c97..5642771146 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -5,9 +5,9 @@ #include "node_data.h" #include "bindings/qjs/cppgc/garbage_collected.h" #include "container_node.h" -#include "ng/node_list.h" #include "ng/child_node_list.h" #include "ng/empty_node_list.h" +#include "ng/node_list.h" namespace kraken { diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 67fb2dd43b..717f0e841c 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -32,7 +32,7 @@ ScriptState::ScriptState() { html_names::Init(ctx_); binding_call_methods::Init(ctx_); // Bump up the built-in classId. To make sure the created classId are larger than JS_CLASS_CUSTOM_CLASS_INIT_COUNT. - for(int i = 0; i < JS_CLASS_CUSTOM_CLASS_INIT_COUNT - JS_CLASS_GC_TRACKER + 2; i ++) { + for (int i = 0; i < JS_CLASS_CUSTOM_CLASS_INIT_COUNT - JS_CLASS_GC_TRACKER + 2; i++) { JSClassID id{0}; JS_NewClassID(&id); } From 7950338c5e632ae37eea9a7f071ea18b99a8dd61 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Mon, 25 Apr 2022 19:37:00 +0800 Subject: [PATCH 097/498] fix: fix event target addEventListener. --- bridge/core/dom/events/event_target_test.cc | 36 +++++++-------- bridge/core/dom/node.cc | 46 ++++++++----------- bridge/core/dom/node.h | 10 ++-- .../code_generator/src/idl/generateSource.ts | 2 +- 4 files changed, 43 insertions(+), 51 deletions(-) diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index 13c82475cd..f9bd0ac5c5 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -29,24 +29,24 @@ TEST(EventTarget, addEventListener) { EXPECT_EQ(errorCalled, false); } -// TEST(EventTarget, removeEventListener) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// auto context = bridge->getContext(); -// const char* code = -// "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f); -// " "div.removeEventListener('click', f); div.dispatchEvent(new Event('click'));"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(logCalled, false); -//} + TEST(EventTarget, removeEventListener) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->getContext(); + const char* code = + "let div = document.createElement('div'); function f(){ console.log(1234); }; div.addEventListener('click', f);" + "div.removeEventListener('click', f); div.dispatchEvent(new Event('click'));"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(logCalled, false); +} // // TEST(EventTarget, setNoEventTargetProperties) { // bool static errorCalled = false; diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index fa5a171111..a2c4ceb90e 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -35,55 +35,43 @@ Element* Node::parentElement() const { NodeList* Node::childNodes() { auto* this_node = DynamicTo<ContainerNode>(this); if (this_node) - return EnsureData().EnsureChildNodeList(*this_node); - return EnsureData().EnsureEmptyChildNodeList(*this); + return EnsureNodeData().EnsureChildNodeList(*this_node); + return EnsureNodeData().EnsureEmptyChildNodeList(*this); } -namespace { - //// Helper object to allocate EventTargetData which is otherwise only used //// through EventTargetWithInlineData. -class EventTargetDataObject final : public GarbageCollected<EventTargetDataObject> { +class EventTargetDataObject final { public: void Trace(GCVisitor* visitor) const { data_.Trace(visitor); } - EventTargetData& GetEventTargetData() { return data_; } private: EventTargetData data_; }; -} // namespace - -using EventTargetDataMap = std::unordered_map<Node*, EventTargetDataObject*>; -static EventTargetDataMap& GetEventTargetDataMap() { - static thread_local EventTargetDataMap map; - return map; -} - EventTargetData* Node::GetEventTargetData() { - return HasEventTargetData() ? &GetEventTargetDataMap().at(this)->GetEventTargetData() : nullptr; + return HasEventTargetData() ? &event_target_data_->GetEventTargetData() : nullptr; } EventTargetData& Node::EnsureEventTargetData() { if (HasEventTargetData()) - return GetEventTargetDataMap().at(this)->GetEventTargetData(); - assert(GetEventTargetDataMap().count(this) == 0); - auto* data = MakeGarbageCollected<EventTargetDataObject>(); - GetEventTargetDataMap().insert(std::make_pair(this, data)); + return event_target_data_->GetEventTargetData(); + assert(event_target_data_ == nullptr); + event_target_data_ = std::make_unique<EventTargetDataObject>(); SetHasEventTargetData(true); - return data->GetEventTargetData(); + return event_target_data_->GetEventTargetData(); } -NodeData& Node::CreateData() { - data_ = std::make_unique<NodeData>(); +NodeData& Node::CreateNodeData() { + node_data_ = std::make_unique<NodeData>(); return *Data(); } -NodeData& Node::EnsureData() { +NodeData& Node::EnsureNodeData() { if (HasData()) return *Data(); - return CreateData(); + return CreateNodeData(); } Node& Node::TreeRoot() const { @@ -424,18 +412,20 @@ Node::Node(ExecutingContext* context, TreeScope* tree_scope, ConstructionType ty previous_(nullptr), tree_scope_(tree_scope), next_(nullptr), - data_(nullptr) {} + node_data_(nullptr) {} Node::~Node() { - GetEventTargetDataMap().erase(this); } void Node::Trace(GCVisitor* visitor) const { visitor->Trace(previous_); visitor->Trace(next_); visitor->Trace(parent_or_shadow_host_node_); - if (data_ != nullptr) - data_->Trace(visitor); + if (node_data_ != nullptr) + node_data_->Trace(visitor); + if (event_target_data_ != nullptr) { + event_target_data_->Trace(visitor); + } EventTarget::Trace(visitor); } diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index c8f4d60b3a..f545c1f847 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -25,6 +25,7 @@ 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 @@ -203,11 +204,11 @@ class Node : public EventTarget { void SetSelfOrAncestorHasDirAutoAttribute() { SetFlag(kSelfOrAncestorHasDirAutoAttribute); } void ClearSelfOrAncestorHasDirAutoAttribute() { ClearFlag(kSelfOrAncestorHasDirAutoAttribute); } - NodeData& CreateData(); + NodeData& CreateNodeData(); bool HasData() const { return GetFlag(kHasDataFlag); } // |RareData| cannot be replaced or removed once assigned. - NodeData* Data() const { return data_.get(); } - NodeData& EnsureData(); + NodeData* Data() const { return node_data_.get(); } + NodeData& EnsureNodeData(); void Trace(GCVisitor*) const override; @@ -296,7 +297,8 @@ class Node : public EventTarget { Member<Node> previous_; Member<Node> next_; TreeScope* tree_scope_; - std::unique_ptr<NodeData> data_; + std::unique_ptr<EventTargetDataObject> event_target_data_; + std::unique_ptr<NodeData> node_data_; }; inline ContainerNode* Node::ParentOrShadowHostNode() const { diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index c0e3f21c40..c9e2191595 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -238,7 +238,7 @@ function generateReturnValueInit(blob: IDLBlob, type: ParameterType[], options: function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], mode: string, options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}): string { if (type[0] == FunctionArgumentType.void) return 'JS_NULL'; - let method = mode === 'newObject' ? 'ToQuickJSUnsafe' : 'ToQuickJS'; + let method = (mode === 'newObject' || options.isConstructor) ? 'ToQuickJSUnsafe' : 'ToQuickJS'; if (options.isConstructor) { return `return_value->${method}()`; From 162cf0108751cae85ffd9e6b43b68d829efd54a9 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Mon, 25 Apr 2022 20:34:49 +0800 Subject: [PATCH 098/498] feat: support Node.childNodes. --- bridge/bindings/qjs/cppgc/member.h | 5 +- bridge/core/dom/document.cc | 4 +- bridge/core/dom/document.h | 2 +- bridge/core/dom/document_test.cc | 2 +- bridge/core/dom/element.cc | 29 ++- bridge/core/dom/element.h | 7 +- bridge/core/dom/events/event_target.d.ts | 1 + bridge/core/dom/node.cc | 1 + bridge/core/dom/node_test.cc | 254 +++++++++++----------- bridge/scripts/code_generator/global.d.ts | 6 +- 10 files changed, 165 insertions(+), 146 deletions(-) diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index 2873f4c6fa..81b2d36bd2 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -24,7 +24,10 @@ template <typename T, typename = std::is_base_of<ScriptWrappable, T>> class Member { public: Member() = default; - Member(T* ptr) { Initialize(ptr); } + Member(T* ptr) { + inited_ = true; + SetRaw(ptr); + } ~Member() { if (raw_ != nullptr) { assert(runtime_ != nullptr); diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index fd9bba5980..5009ef799f 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -21,7 +21,9 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ } Document::Document(ExecutingContext* context) - : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) {} + : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) { + document_element_.Initialize(MakeGarbageCollected<HTMLHtmlElement>(*this)); +} Element* Document::createElement(const AtomicString& name, ExceptionState& exception_state) { if (!IsValidName(name)) { diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index df4241526a..8058201437 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -67,7 +67,7 @@ class Document : public ContainerNode, public TreeScope { private: int node_count_; - Member<Element> document_element_{MakeGarbageCollected<HTMLHtmlElement>(*this)}; + Member<Element> document_element_; }; } // namespace kraken diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index a6871220fd..e65337e8f8 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -13,7 +13,7 @@ TEST(Document, createElement) { bool static logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "<div>"); + EXPECT_STREQ(message.c_str(), "<div/>"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 17af6e0efa..5be997a454 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -15,14 +15,21 @@ namespace kraken { Element::Element(const AtomicString& tag_name, Document* document, Node::ConstructionType construction_type) - : ContainerNode(document, construction_type), tag_name_(tag_name), attributes_(ElementAttributes::Create(this)) {} + : ContainerNode(document, construction_type), tag_name_(tag_name) {} -bool Element::hasAttribute(const AtomicString& name, ExceptionState& exception_state) const { - return attributes_->hasAttribute(name, exception_state); +ElementAttributes & Element::EnsureElementAttributes() { + if (attributes_ == nullptr) { + attributes_.Initialize(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) const { - return attributes_->GetAttribute(name); +AtomicString Element::getAttribute(const AtomicString& name, ExceptionState& exception_state) { + return EnsureElementAttributes().GetAttribute(name); } void Element::setAttribute(const AtomicString& name, const AtomicString& value) { @@ -31,14 +38,14 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value) } void Element::setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { - if (attributes_->hasAttribute(name, exception_state)) { - AtomicString&& oldAttribute = attributes_->GetAttribute(name); - if (!attributes_->setAttribute(name, value, exception_state)) { + if (EnsureElementAttributes().hasAttribute(name, exception_state)) { + AtomicString&& oldAttribute = EnsureElementAttributes().GetAttribute(name); + if (!EnsureElementAttributes().setAttribute(name, value, exception_state)) { return; }; _didModifyAttribute(name, oldAttribute, value); } else { - if (!attributes_->setAttribute(name, value, exception_state)) { + if (!EnsureElementAttributes().setAttribute(name, value, exception_state)) { return; }; _didModifyAttribute(name, AtomicString::Empty(ctx()), value); @@ -52,7 +59,7 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value, } void Element::removeAttribute(const AtomicString& name, ExceptionState& exception_state) { - attributes_->removeAttribute(name, exception_state); + EnsureElementAttributes().removeAttribute(name, exception_state); } BoundingClientRect* Element::getBoundingClientRect(ExceptionState& exception_state) { @@ -130,7 +137,7 @@ std::string Element::nodeName() const { } bool Element::HasEquivalentAttributes(const Element& other) const { - return other.attributes_->IsEquivalent(*attributes_); + return attributes_ != nullptr && other.attributes_ != nullptr && other.attributes_->IsEquivalent(*attributes_); } void Element::Trace(GCVisitor* visitor) const { diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index fb8004af95..de3a2166b3 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -21,10 +21,11 @@ class Element : public ContainerNode { using ImplType = Element*; Element(const AtomicString& tag_name, Document* document, ConstructionType = kCreateElement); - ElementAttributes* attributes() const { return attributes_; } + ElementAttributes* attributes() { return &EnsureElementAttributes(); } + ElementAttributes& EnsureElementAttributes(); - bool hasAttribute(const AtomicString&, ExceptionState& exception_state) const; - AtomicString getAttribute(const AtomicString&, ExceptionState& exception_state) const; + 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. diff --git a/bridge/core/dom/events/event_target.d.ts b/bridge/core/dom/events/event_target.d.ts index f60631ffe8..63d0599ee0 100644 --- a/bridge/core/dom/events/event_target.d.ts +++ b/bridge/core/dom/events/event_target.d.ts @@ -2,6 +2,7 @@ // 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; diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index a2c4ceb90e..c538b09887 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -65,6 +65,7 @@ EventTargetData& Node::EnsureEventTargetData() { NodeData& Node::CreateNodeData() { node_data_ = std::make_unique<NodeData>(); + SetFlag(kHasDataFlag); return *Data(); } diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 0af7a7c9a9..7deae2ad48 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -43,8 +43,8 @@ TEST(Node, childNodes) { "document.body.appendChild(div1);" "document.body.appendChild(div2);" "console.log(" - "document.body.childNodes[0] === div1," - "document.body.childNodes[1] === div2," + "document.body.childNodes.item(0) === div1," + "document.body.childNodes.item(1) === div2," "div1.nextSibling === div2," "div2.previousSibling === div1)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); @@ -91,48 +91,48 @@ TEST(Node, textContent) { EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } - -TEST(Node, setTextContent) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - EXPECT_STREQ(message.c_str(), "1234"); - logCalled = true; - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div');" - "div.textContent = '1234';" - "console.log(div.textContent);"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -TEST(Node, ensureDetached) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - EXPECT_STREQ(message.c_str(), "true true"); - logCalled = true; - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); - const char* code = - "let div = document.createElement('div');" - "document.body.appendChild(div);" - "let container = document.createElement('div');" - "container.appendChild(div);" - "document.body.appendChild(container);" - "console.log(document.body.firstChild === container, container.firstChild === div);"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - +// +//TEST(Node, setTextContent) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// EXPECT_STREQ(message.c_str(), "1234"); +// logCalled = true; +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); +// auto context = bridge->getContext(); +// const char* code = +// "let div = document.createElement('div');" +// "div.textContent = '1234';" +// "console.log(div.textContent);"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//TEST(Node, ensureDetached) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// EXPECT_STREQ(message.c_str(), "true true"); +// logCalled = true; +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); +// auto context = bridge->getContext(); +// const char* code = +// "let div = document.createElement('div');" +// "document.body.appendChild(div);" +// "let container = document.createElement('div');" +// "container.appendChild(div);" +// "document.body.appendChild(container);" +// "console.log(document.body.firstChild === container, container.firstChild === div);"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// TEST(Node, replaceBody) { bool static errorCalled = false; bool static logCalled = false; @@ -149,86 +149,86 @@ TEST(Node, replaceBody) { EXPECT_EQ(errorCalled, false); } - -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 div2 = div.cloneNode(true); -document.body.appendChild(div2); - -div2.setAttribute('id', '456'); - -console.log(div.style.width == div2.style.height, div.getAttribute('id') == '1234', div2.getAttribute('id') == '456'); -)"; - - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "true true true"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} - -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' -); -)"; - - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "true true true"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->getContext(); - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} +// +//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 div2 = div.cloneNode(true); +//document.body.appendChild(div2); +// +//div2.setAttribute('id', '456'); +// +//console.log(div.style.width == div2.style.height, div.getAttribute('id') == '1234', div2.getAttribute('id') == '456'); +//)"; +// +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "true true true"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->getContext(); +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} +// +//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' +//); +//)"; +// +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "true true true"); +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { +// KRAKEN_LOG(VERBOSE) << errmsg; +// errorCalled = true; +// }); +// auto context = bridge->getContext(); +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} diff --git a/bridge/scripts/code_generator/global.d.ts b/bridge/scripts/code_generator/global.d.ts index ac7d1555db..cdf9919223 100644 --- a/bridge/scripts/code_generator/global.d.ts +++ b/bridge/scripts/code_generator/global.d.ts @@ -8,4 +8,8 @@ declare interface BlobPropertyBag {} declare function Dictionary() : any; declare type JSEventListener = void; -type NewObject<T> = void; +// This property will return new created value. +type NewObject<T> = T; + +// This property is implemented by Dart side +type DartImpl<T> = T; From d5754e6841f2c694fadefb8d7adeb91493e9864a Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Mon, 25 Apr 2022 21:36:40 +0800 Subject: [PATCH 099/498] feat: add custom behavior in wrapper_type_info. --- bridge/bindings/qjs/qjs_engine_patch.h | 15 +++++++ bridge/bindings/qjs/script_wrappable.cc | 59 ++++++++++++++++++++----- bridge/bindings/qjs/wrapper_type_info.h | 21 ++++++++- bridge/third_party/quickjs/quickjs.c | 6 --- bridge/third_party/quickjs/quickjs.h | 3 ++ 5 files changed, 87 insertions(+), 17 deletions(-) diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h index 530a056ee4..2c666b13bc 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -124,6 +124,21 @@ 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 diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index b1500371ec..89a150b087 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -40,38 +40,77 @@ static void HandleJSObjectFinalized(JSRuntime* rt, JSValue 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) { + auto* object = static_cast<ScriptWrappable*>(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* wrapper_type_info = object->GetWrapperTypeInfo(); + + 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)); + } + return wrapper_type_info->string_property_getter_handler_(ctx, obj, atom); +} + +/// This callback will be callback 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<ScriptWrappable*>(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* wrapper_type_info = object->GetWrapperTypeInfo(); + + if (wrapper_type_info->indexed_property_setter_handler_ != nullptr && JS_AtomIsTaggedInt(atom)) { + return wrapper_type_info->indexed_property_setter_handler_(ctx, obj, JS_AtomToUInt32(atom), value); + } + + return wrapper_type_info->string_property_setter_handler_(ctx, obj, atom, value); +} + void ScriptWrappable::InitializeQuickJSObject() { - auto* wrapperTypeInfo = GetWrapperTypeInfo(); + 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, wrapperTypeInfo->classId)) { + if (!JS_HasClassId(runtime, wrapper_type_info->classId)) { /// Basic template to describe the behavior about this class. JSClassDef def{}; - def.class_name = wrapperTypeInfo->className; + // 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 custom behavior when call GetProperty, SetProperty on object. - if (wrapperTypeInfo->exoticMethods != nullptr) { - def.exotic = wrapperTypeInfo->exoticMethods; + // 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; } + def.exotic = exotic_methods; def.finalizer = HandleJSObjectFinalized; - JS_NewClass(runtime, wrapperTypeInfo->classId, &def); + 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_, wrapperTypeInfo->classId); + jsObject_ = JS_NewObjectClass(ctx_, wrapper_type_info->classId); JS_SetOpaque(jsObject_, this); - // Let instance inherit EventTarget prototype methods. - JSValue prototype = GetExecutingContext()->contextData()->prototypeForType(wrapperTypeInfo); + // Let our instance into inherit prototype methods. + JSValue prototype = GetExecutingContext()->contextData()->prototypeForType(wrapper_type_info); JS_SetPrototype(ctx_, jsObject_, prototype); wrapped_ = true; diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 4d892f67f1..de47d78677 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -39,6 +39,22 @@ enum { 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); + // 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 @@ -59,7 +75,10 @@ class WrapperTypeInfo final { const char* className{nullptr}; const WrapperTypeInfo* parent_class{nullptr}; JSClassCall* callFunc{nullptr}; - JSClassExoticMethods* exoticMethods{nullptr}; + IndexedPropertyGetterHandler indexed_property_getter_handler_{nullptr}; + StringPropertyGetterHandler string_property_getter_handler_{nullptr}; + IndexedPropertySetterHandler indexed_property_setter_handler_{nullptr}; + StringPropertySetterHandler string_property_setter_handler_{nullptr}; }; } // namespace kraken diff --git a/bridge/third_party/quickjs/quickjs.c b/bridge/third_party/quickjs/quickjs.c index e9492219f5..1b35569d3b 100644 --- a/bridge/third_party/quickjs/quickjs.c +++ b/bridge/third_party/quickjs/quickjs.c @@ -2357,12 +2357,6 @@ static inline BOOL is_math_mode(JSContext *ctx) } #endif -/* JSAtom support */ - -#define JS_ATOM_TAG_INT (1U << 31) -#define JS_ATOM_MAX_INT (JS_ATOM_TAG_INT - 1) -#define JS_ATOM_MAX ((1U << 30) - 1) - /* return the max count from the hash size */ #define JS_ATOM_COUNT_RESIZE(n) ((n) * 2) diff --git a/bridge/third_party/quickjs/quickjs.h b/bridge/third_party/quickjs/quickjs.h index 6481dcf9ed..c7b264ef27 100644 --- a/bridge/third_party/quickjs/quickjs.h +++ b/bridge/third_party/quickjs/quickjs.h @@ -423,6 +423,9 @@ 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, From ab98ff39080889c19e8fbf9d95f82ce90071347e Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Tue, 26 Apr 2022 19:11:53 +0800 Subject: [PATCH 100/498] feat: support generate bindings for indexed property. --- bridge/bindings/qjs/script_wrappable.cc | 23 +++++- bridge/bindings/qjs/wrapper_type_info.h | 7 +- bridge/core/dom/ng/node_list.d.ts | 2 +- bridge/core/dom/node_test.cc | 80 +++++++++---------- .../code_generator/src/idl/analyzer.ts | 28 +++++-- .../code_generator/src/idl/declaration.ts | 13 ++- .../code_generator/src/idl/generateHeader.ts | 4 +- .../code_generator/src/idl/generateSource.ts | 79 ++++++++++++++---- .../code_generator/src/idl/generator.ts | 3 + .../static/idl_templates/interface.cc.tpl | 51 ++++++++++++ .../static/idl_templates/interface.h.tpl | 16 ++++ 11 files changed, 240 insertions(+), 66 deletions(-) diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 89a150b087..aef29d54ea 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -45,16 +45,23 @@ static void HandleJSObjectFinalized(JSRuntime* rt, JSValue val) { /// 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<ScriptWrappable*>(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)); } return wrapper_type_info->string_property_getter_handler_(ctx, obj, atom); } -/// This callback will be callback when JS code set property on this object using [] or `.` operator. +/// 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) { @@ -68,6 +75,15 @@ static int HandleJSPropertySetterCallback(JSContext *ctx, JSValueConst obj, JSAt return wrapper_type_info->string_property_setter_handler_(ctx, obj, atom, value); } +/// 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<ScriptWrappable*>(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* wrapper_type_info = object->GetWrapperTypeInfo(); + + return wrapper_type_info->string_property_checker_handler_(ctx, obj, atom); +} + void ScriptWrappable::InitializeQuickJSObject() { auto* wrapper_type_info = GetWrapperTypeInfo(); JSRuntime* runtime = runtime_; @@ -96,6 +112,11 @@ void ScriptWrappable::InitializeQuickJSObject() { exotic_methods->set_property = HandleJSPropertySetterCallback; } + // Define the callback when check object property exist. + if (UNLIKELY(wrapper_type_info->string_property_checker_handler_ != nullptr)) { + exotic_methods->has_property = HandleJSPropertyCheckerCallback; + } + def.exotic = exotic_methods; def.finalizer = HandleJSObjectFinalized; diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index de47d78677..7968606519 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -55,6 +55,10 @@ using IndexedPropertySetterHandler = bool (*)(JSContext* ctx, JSValueConst obj, // exp: obj['hello'] = value; using StringPropertySetterHandler = bool (*)(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value); +// Callback when check property exist on object. +// exp: 'hello' in obj; +using StringPropertyCheckerHandler = bool (*)(JSContext *ctx, JSValueConst obj, JSAtom atom); + // This struct provides a way to store a bunch of information that is helpful // when creating quickjs objects. Each quickjs bindings class has exactly one static // WrapperTypeInfo member, so comparing pointers is a safe way to determine if @@ -76,9 +80,10 @@ class WrapperTypeInfo final { const WrapperTypeInfo* parent_class{nullptr}; JSClassCall* callFunc{nullptr}; IndexedPropertyGetterHandler indexed_property_getter_handler_{nullptr}; - StringPropertyGetterHandler string_property_getter_handler_{nullptr}; IndexedPropertySetterHandler indexed_property_setter_handler_{nullptr}; + StringPropertyGetterHandler string_property_getter_handler_{nullptr}; StringPropertySetterHandler string_property_setter_handler_{nullptr}; + StringPropertyCheckerHandler string_property_checker_handler_{nullptr}; }; } // namespace kraken diff --git a/bridge/core/dom/ng/node_list.d.ts b/bridge/core/dom/ng/node_list.d.ts index 20ee314d0b..f5fddcf290 100644 --- a/bridge/core/dom/ng/node_list.d.ts +++ b/bridge/core/dom/ng/node_list.d.ts @@ -3,6 +3,6 @@ import {Node} from "../node"; export interface NodeList { readonly length: int64; item(index: number): Node; - [index: number]: Node; + readonly [index: number]: Node; new(): void; } diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 7deae2ad48..1c13d7af3b 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -43,8 +43,8 @@ TEST(Node, childNodes) { "document.body.appendChild(div1);" "document.body.appendChild(div2);" "console.log(" - "document.body.childNodes.item(0) === div1," - "document.body.childNodes.item(1) === div2," + "document.body.childNodes[0] === div1," + "document.body.childNodes[1] === div2," "div1.nextSibling === div2," "div2.previousSibling === div1)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); @@ -53,44 +53,44 @@ TEST(Node, childNodes) { EXPECT_EQ(logCalled, true); } -TEST(Node, textNodeHaveEmptyChildNodes) { - bool static errorCalled = false; - bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); - 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; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - EXPECT_STREQ(message.c_str(), "1234helloworld"); - logCalled = true; - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); - const char* code = - "let text1 = document.createTextNode('1234');" - "let text2 = document.createTextNode('helloworld');" - "let div = document.createElement('div');" - "div.appendChild(text1);" - "div.appendChild(text2);" - "console.log(div.textContent)"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} +//TEST(Node, textNodeHaveEmptyChildNodes) { +// bool static errorCalled = false; +// bool static logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); +// auto context = bridge->getContext(); +// 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; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// EXPECT_STREQ(message.c_str(), "1234helloworld"); +// logCalled = true; +// }; +// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); +// auto context = bridge->getContext(); +// const char* code = +// "let text1 = document.createTextNode('1234');" +// "let text2 = document.createTextNode('helloworld');" +// "let div = document.createElement('div');" +// "div.appendChild(text1);" +// "div.appendChild(text2);" +// "console.log(div.textContent)"; +// bridge->evaluateScript(code, strlen(code), "vm://", 0); +// +// EXPECT_EQ(errorCalled, false); +// EXPECT_EQ(logCalled, true); +//} // //TEST(Node, setTextContent) { // bool static errorCalled = false; diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 51763fd2a5..4a60ea37e1 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -6,7 +6,7 @@ import { FunctionArguments, FunctionArgumentType, FunctionDeclaration, - FunctionObject, + FunctionObject, IndexedPropertyDeclaration, ParameterMode, PropsDeclaration, } from './declaration'; import {generatorSource} from './generator'; @@ -52,10 +52,6 @@ function getParameterName(name: ts.BindingName) : string { export type ParameterType = FunctionArgumentType | string; -class ParameterMode { - newObject?: boolean; -} - function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): ParameterType { if (type.kind === ts.SyntaxKind.StringKeyword) { return FunctionArgumentType.dom_string; @@ -91,6 +87,11 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete 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 argument.typeName.text; } return identifier; @@ -189,10 +190,25 @@ function walkProgram(statement: ts.Statement) { if (m.type) { let mode = new ParameterMode(); f.returnType = getParameterType(m.type, mode); - f.returnTypeMode = mode.newObject ? 'newObject' : 'normal'; + 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(); diff --git a/bridge/scripts/code_generator/src/idl/declaration.ts b/bridge/scripts/code_generator/src/idl/declaration.ts index 852430a08f..c0e97ecb82 100644 --- a/bridge/scripts/code_generator/src/idl/declaration.ts +++ b/bridge/scripts/code_generator/src/idl/declaration.ts @@ -22,18 +22,26 @@ export class FunctionArguments { required: boolean; } +export class ParameterMode { + newObject?: boolean; + dartImpl?: boolean; +} + export class PropsDeclaration { type: ParameterType[] = []; + typeMode: ParameterMode; name: string; readonly: boolean; } -type FunctionReturnTypeMode = 'normal' | 'newObject'; +export class IndexedPropertyDeclaration extends PropsDeclaration { + indexKeyType: 'string' | 'number'; +} export class FunctionDeclaration extends PropsDeclaration { args: FunctionArguments[] = []; returnType: ParameterType[] = []; - returnTypeMode: FunctionReturnTypeMode; + returnTypeMode?: ParameterMode; } export enum ClassObjectKind { @@ -45,6 +53,7 @@ export class ClassObject { name: string; parent: string; props: PropsDeclaration[] = []; + indexedProp?: IndexedPropertyDeclaration; methods: FunctionDeclaration[] = []; construct?: FunctionDeclaration; kind: ClassObjectKind = ClassObjectKind.interface diff --git a/bridge/scripts/code_generator/src/idl/generateHeader.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts index 500a0c83ad..e8d257dfbe 100644 --- a/bridge/scripts/code_generator/src/idl/generateHeader.ts +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -84,5 +84,7 @@ export function generateCppHeader(blob: IDLBlob, options: GenerateOptions) { 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 index c9e2191595..e711cfaf35 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -5,6 +5,7 @@ import { FunctionArgumentType, FunctionDeclaration, FunctionObject, + ParameterMode, } from "./declaration"; import {addIndent, getClassName} from "./utils"; import {ParameterType} from "./analyzer"; @@ -35,7 +36,7 @@ function generateMethodArgumentsCheck(m: FunctionDeclaration) { } export function generateTypeValue(type: ParameterType[]): string { - switch(type[0]) { + switch (type[0]) { case FunctionArgumentType.int64: { return 'int64_t'; } @@ -58,6 +59,11 @@ export function generateTypeValue(type: ParameterType[]): string { return 'ScriptValue'; } } + + if (typeof type[0] == 'string') { + return type[0] + '*'; + } + return ''; } @@ -70,7 +76,7 @@ export function generateTypeConverter(type: ParameterType[]): string { } else if (typeof type[0] === 'string') { returnValue = type[0]; } else { - switch(type[0]) { + switch (type[0]) { case FunctionArgumentType.int32: returnValue = `IDLInt32`; break; @@ -81,7 +87,7 @@ export function generateTypeConverter(type: ParameterType[]): string { returnValue = `IDLDouble`; break; case FunctionArgumentType.function: - returnValue = `IDLCallback`; + returnValue = `IDLCallback`; break; case FunctionArgumentType.boolean: returnValue = `IDLBoolean`; @@ -109,7 +115,7 @@ export function generateTypeConverter(type: ParameterType[]): string { function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { let type = generateTypeConverter(argument.type); - let hasArgumentCheck = type.indexOf('Element') >= 0 || type.indexOf('Node') >=0 || type === 'EventTarget'; + let hasArgumentCheck = type.indexOf('Element') >= 0 || type.indexOf('Node') >= 0 || type === 'EventTarget'; let body = ''; if (hasArgumentCheck) { @@ -154,7 +160,10 @@ if (argc <= ${argsIndex + 1}) { }`; } -function generateFunctionCallBody(blob: IDLBlob, declaration: FunctionDeclaration, options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { +function generateFunctionCallBody(blob: IDLBlob, declaration: FunctionDeclaration, options: GenFunctionBodyOptions = { + isConstructor: false, + isInstanceMethod: false +}) { let minimalRequiredArgc = 0; declaration.args.forEach(m => { if (m.required) minimalRequiredArgc++; @@ -172,7 +181,7 @@ function generateFunctionCallBody(blob: IDLBlob, declaration: FunctionDeclaratio let optionalArgumentsInit: string[] = []; let totalArguments: string[] = requiredArguments.slice(); - for (let i = minimalRequiredArgc; i < declaration.args.length; i ++) { + 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}`); } @@ -220,7 +229,10 @@ return ${overloadMethods[0].name}_overload_${0}(ctx, this_val, argc, argv); `; } -function generateReturnValueInit(blob: IDLBlob, type: ParameterType[], options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}) { +function generateReturnValueInit(blob: IDLBlob, type: ParameterType[], options: GenFunctionBodyOptions = { + isConstructor: false, + isInstanceMethod: false +}) { if (type[0] == FunctionArgumentType.void) return ''; if (options.isConstructor) { @@ -236,9 +248,12 @@ function generateReturnValueInit(blob: IDLBlob, type: ParameterType[], options: return `Converter<${generateTypeConverter(type)}>::ImplType return_value;`; } -function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], mode: string, options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod: false}): string { +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 = (mode === 'newObject' || options.isConstructor) ? 'ToQuickJSUnsafe' : 'ToQuickJS'; + let method = (mode && mode.newObject || options.isConstructor) ? 'ToQuickJSUnsafe' : 'ToQuickJS'; if (options.isConstructor) { return `return_value->${method}()`; @@ -255,9 +270,16 @@ function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], mode: s return `Converter<${generateTypeConverter(type)}>::ToValue(ctx, std::move(return_value))`; } -type GenFunctionBodyOptions = {isConstructor?: boolean, isInstanceMethod?: boolean}; +type GenFunctionBodyOptions = { isConstructor?: boolean, isInstanceMethod?: boolean }; + +function generateIndexedPropertyBody() { -function generateFunctionBody(blob: IDLBlob, declare: FunctionDeclaration, options: GenFunctionBodyOptions = {isConstructor: false, isInstanceMethod : false}) { +} + +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); @@ -291,7 +313,7 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { const templateKind = getTemplateKind(object); if (templateKind === TemplateKind.null) return ''; - switch(templateKind) { + switch (templateKind) { case TemplateKind.Interface: { object = object as ClassObject; object.props.forEach(prop => { @@ -309,18 +331,45 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { options.classPropsInstallList.push(`{"${method.name}", ${method.name}, ${method.args.length}}`) } }); + if (object.construct) { options.constructorInstallList.push(`{"${getClassName(blob)}", nullptr, nullptr, constructor}`) } + let wrapperTypeRegisterList = [ + `JS_CLASS_${_.snakeCase(getClassName(blob)).toUpperCase()}`, // ClassId + `"${getClassName(blob)}"`, // ClassName + object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr', // parentClassWrapper + object.construct ? `QJS${getClassName(blob)}::ConstructorCallback` : 'nullptr', // ConstructorCallback + ]; + + // Generate indexed property callback. + if (object.indexedProp) { + if (object.indexedProp.indexKeyType == 'number') { + wrapperTypeRegisterList.push(`IndexedPropertyGetterCallback`); + if (!object.indexedProp.readonly) { + wrapperTypeRegisterList.push(`IndexedPropertySetterCallback`); + } + } else { + wrapperTypeRegisterList.push('nullptr'); + wrapperTypeRegisterList.push('nullptr'); + + wrapperTypeRegisterList.push(`StringPropertyGetterCallback`); + if (!object.indexedProp.readonly) { + wrapperTypeRegisterList.push(`StringPropertySetterCallback`); + } + } + } + options.wrapperTypeInfoInit = ` -const WrapperTypeInfo QJS${getClassName(blob)}::wrapper_type_info_ {JS_CLASS_${_.snakeCase(getClassName(blob)).toUpperCase()}, "${getClassName(blob)}", ${object.parent != null ? `${object.parent}::GetStaticWrapperTypeInfo()` : 'nullptr'}, ${object.construct ? `QJS${getClassName(blob)}::ConstructorCallback` : 'nullptr'}}; +const WrapperTypeInfo QJS${getClassName(blob)}::wrapper_type_info_ {${wrapperTypeRegisterList.join(', ')}}; const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClassName(blob)}::wrapper_type_info_;`; return _.template(readTemplate('interface'))({ className: getClassName(blob), blob: blob, object: object, generateFunctionBody, + generateTypeValue, generateOverLoadSwitchBody, overloadMethods, filtedMethods, @@ -356,5 +405,7 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass 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 index 2a712dc1ce..576f63c91f 100644 --- a/bridge/scripts/code_generator/src/idl/generator.ts +++ b/bridge/scripts/code_generator/src/idl/generator.ts @@ -7,6 +7,7 @@ function generateSupportedOptions(): GenerateOptions { let classMethodsInstallList: string[] = []; let constructorInstallList: string[] = []; let classPropsInstallList: string[] = []; + let indexedProperty: string = ''; let wrapperTypeInfoInit = ''; return { @@ -14,6 +15,7 @@ function generateSupportedOptions(): GenerateOptions { classPropsInstallList, classMethodsInstallList, constructorInstallList, + indexedProperty, wrapperTypeInfoInit }; } @@ -24,6 +26,7 @@ export type GenerateOptions = { constructorInstallList: string[]; classPropsInstallList: string[]; wrapperTypeInfoInit: string; + indexedProperty: string; }; export function generatorSource(blob: IDLBlob) { diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index ecd64e9130..8a6ab68986 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -4,6 +4,57 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob } <% } %> +<% if (object.indexedProp) { %> + <% if (object.indexedProp.indexKeyType == 'number') { %> + JSValue QJS<%= className %>::IndexedPropertyGetterCallback(JSContext* ctx, JSValue obj, uint32_t index) { + auto* self = toScriptWrappable<NodeList>(obj); + if (index >= self->length()) { + return JS_UNDEFINED; + } + ExceptionState exception_state; + <%= generateTypeValue(object.indexedProp.type) %> result = self->item(index, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + return result->ToQuickJS(); + }; + <% } else { %> + JSValue QJS<%= className %>::StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key) { + auto* self = toScriptWrappable<NodeList>(obj); + ExceptionState exception_state; + ${generateTypeValue(object.indexedProp.type)} result = self->item(key, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + return result->ToQuickJS(); + }; + <% } %> + <% 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<NodeList>(obj); + ExceptionState exception_state; + bool success = self->SetItem(index, value, 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<NodeList>(obj); + ExceptionState exception_state; + bool success = self->SetItem(key, value, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return false; + } + return success; + }; + <% } %> + <% } %> + <% } %> + + <% _.forEach(filtedMethods, function(method, index) { %> <% if (overloadMethods[method.name] && overloadMethods[method.name].length > 1) { %> diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl index 71c0dd824f..63e4b3246d 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl @@ -17,6 +17,22 @@ class QJS<%= className %> : public QJSInterfaceBridge<QJS<%= className %>, <%= c <% if (classMethodsInstallList.length > 0) { %> static void InstallPrototypeMethods(ExecutingContext* context); <% } %> <% if (classPropsInstallList.length > 0) { %> static void InstallPrototypeProperties(ExecutingContext* context); <% } %> <% if (object.construct) { %> static void InstallConstructor(ExecutingContext* context); <% } %> + + <% if (object.indexedProp) { %> + <% if (object.indexedProp.indexKeyType == 'number') { %> + static JSValue IndexedPropertyGetterCallback(JSContext* ctx, JSValue obj, uint32_t index); + <% } else { %> + static JSValue StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key); + <% } %> + <% if (!object.indexedProp.readonly) { %> + + <% if (object.indexedProp.indexKeyType == 'number') { %> + static bool IndexedPropertySetterCallback(JSContext* ctx, JSValueConst obj, uint32_t index, JSValueConst value); + <% } else { %> + static bool StringPropertySetterCallback(JSContext* ctx, JSValueConst obj, JSAtom key, JSValueConst value); + <% } %> + <% } %> + <% } %> }; From 378da44b7497991c58ebd6a3e5f690917695a2a1 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Tue, 26 Apr 2022 19:38:59 +0800 Subject: [PATCH 101/498] fix: fix custom property. --- bridge/bindings/qjs/script_wrappable.cc | 9 +++++-- bridge/core/dom/node_test.cc | 32 ++++++++++++------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index aef29d54ea..097e496b70 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -57,8 +57,11 @@ static JSValue HandleJSPropertyGetterCallback(JSContext *ctx, JSValueConst obj, 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 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. @@ -70,9 +73,11 @@ static int HandleJSPropertySetterCallback(JSContext *ctx, JSValueConst obj, JSAt 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 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. diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 1c13d7af3b..0f59608aad 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -53,22 +53,22 @@ TEST(Node, childNodes) { EXPECT_EQ(logCalled, true); } -//TEST(Node, textNodeHaveEmptyChildNodes) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); -// auto context = bridge->getContext(); -// 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, textNodeHaveEmptyChildNodes) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto context = bridge->getContext(); + 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; From e4dbf4b74d0262f99cf0e9df4a085a200024aa40 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Wed, 27 Apr 2022 11:23:57 +0800 Subject: [PATCH 102/498] chore: remove unused code. --- bridge/CMakeLists.txt | 10 +- bridge/bindings/qjs/converter_impl.h | 2 +- bridge/core/dom/{ng => }/child_node_list.cc | 0 bridge/core/dom/{ng => }/child_node_list.h | 2 +- bridge/core/dom/container_node.h | 2 +- bridge/core/dom/{ng => }/empty_node_list.cc | 0 bridge/core/dom/{ng => }/empty_node_list.h | 0 bridge/core/dom/ng/attr.cc | 13 -- bridge/core/dom/ng/attr.h | 64 ------- bridge/core/dom/ng/attribute.h | 60 ------- bridge/core/dom/ng/attribute_collection.h | 112 ------------ bridge/core/dom/ng/element_data.cc | 39 ----- bridge/core/dom/ng/element_data.h | 94 ---------- bridge/core/dom/ng/space_split_string.cc | 185 -------------------- bridge/core/dom/ng/space_split_string.h | 94 ---------- bridge/core/dom/node.cc | 4 +- bridge/core/dom/node.d.ts | 2 +- bridge/core/dom/node_data.cc | 6 +- bridge/core/dom/{ng => }/node_list.d.ts | 2 +- bridge/core/dom/{ng => }/node_list.h | 0 20 files changed, 15 insertions(+), 676 deletions(-) rename bridge/core/dom/{ng => }/child_node_list.cc (100%) rename bridge/core/dom/{ng => }/child_node_list.h (98%) rename bridge/core/dom/{ng => }/empty_node_list.cc (100%) rename bridge/core/dom/{ng => }/empty_node_list.h (100%) delete mode 100644 bridge/core/dom/ng/attr.cc delete mode 100644 bridge/core/dom/ng/attr.h delete mode 100644 bridge/core/dom/ng/attribute.h delete mode 100644 bridge/core/dom/ng/attribute_collection.h delete mode 100644 bridge/core/dom/ng/element_data.cc delete mode 100644 bridge/core/dom/ng/element_data.h delete mode 100644 bridge/core/dom/ng/space_split_string.cc delete mode 100644 bridge/core/dom/ng/space_split_string.h rename bridge/core/dom/{ng => }/node_list.d.ts (81%) rename bridge/core/dom/{ng => }/node_list.h (100%) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index e4879eb873..4466a540bc 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -316,11 +316,11 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/document_fragment.h core/dom/document_fragment.cc core/dom/collection_index_cache.h - core/dom/ng/child_node_list.cc - core/dom/ng/child_node_list.h - core/dom/ng/empty_node_list.cc - core/dom/ng/empty_node_list.h - core/dom/ng/node_list.h + core/dom/child_node_list.cc + core/dom/child_node_list.h + core/dom/empty_node_list.cc + core/dom/empty_node_list.h + core/dom/node_list.h core/dom/container_node.cc core/dom/container_node.h core/events/error_event.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 86d0feba09..3ad673e078 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -12,7 +12,7 @@ #include "core/dom/document.h" #include "core/dom/events/event.h" #include "core/dom/events/event_target.h" -#include "core/dom/ng/node_list.h" +#include "core/dom/node_list.h" #include "core/fileapi/blob_part.h" #include "core/fileapi/blob_property_bag.h" #include "core/html/html_body_element.h" diff --git a/bridge/core/dom/ng/child_node_list.cc b/bridge/core/dom/child_node_list.cc similarity index 100% rename from bridge/core/dom/ng/child_node_list.cc rename to bridge/core/dom/child_node_list.cc diff --git a/bridge/core/dom/ng/child_node_list.h b/bridge/core/dom/child_node_list.h similarity index 98% rename from bridge/core/dom/ng/child_node_list.h rename to bridge/core/dom/child_node_list.h index 6beb1f4333..8abed0ddad 100644 --- a/bridge/core/dom/ng/child_node_list.h +++ b/bridge/core/dom/child_node_list.h @@ -8,7 +8,7 @@ #include "bindings/qjs/cppgc/gc_visitor.h" #include "core/dom/collection_index_cache.h" #include "core/dom/container_node.h" -#include "node_list.h" +#include "core/dom/node_list.h" namespace kraken { diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index 9389f8438e..a4cd848319 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -7,8 +7,8 @@ #include <vector> #include "bindings/qjs/cppgc/gc_visitor.h" -#include "ng/node_list.h" #include "node.h" +#include "node_list.h" namespace kraken { diff --git a/bridge/core/dom/ng/empty_node_list.cc b/bridge/core/dom/empty_node_list.cc similarity index 100% rename from bridge/core/dom/ng/empty_node_list.cc rename to bridge/core/dom/empty_node_list.cc diff --git a/bridge/core/dom/ng/empty_node_list.h b/bridge/core/dom/empty_node_list.h similarity index 100% rename from bridge/core/dom/ng/empty_node_list.h rename to bridge/core/dom/empty_node_list.h diff --git a/bridge/core/dom/ng/attr.cc b/bridge/core/dom/ng/attr.cc deleted file mode 100644 index 5d02dca8ed..0000000000 --- a/bridge/core/dom/ng/attr.cc +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "attr.h" -#include "element.h" - -namespace kraken { - -Attr::Attr(Element& element, const AtomicString& name) - : Node(&element.GetDocument(), kCreateOther), element_(&element), name_(name) {} - -} // namespace kraken diff --git a/bridge/core/dom/ng/attr.h b/bridge/core/dom/ng/attr.h deleted file mode 100644 index aefc96d47a..0000000000 --- a/bridge/core/dom/ng/attr.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_CORE_DOM_ATTR_H_ -#define KRAKENBRIDGE_CORE_DOM_ATTR_H_ - -#include "bindings/qjs/atomic_string.h" -#include "node.h" - -namespace kraken { - -class Element; -class Document; - -class Attr : public Node { - DEFINE_WRAPPERTYPEINFO(); - - public: - Attr(Element& element, const AtomicString& name); - Attr(Document& document, const AtomicString& name, const AtomicString& value); - - ~Attr() override; - - std::string name() const { return name_.ToStdString(); } - bool specified() const { return true; } - Element* ownerElement() const { return element_; } - - const AtomicString& value() const; - void setValue(const AtomicString&, ExceptionState&); - - const QualifiedName GetQualifiedName() const; - - void AttachToElement(Element*, const AtomicString&); - void DetachFromElementWithValue(const AtomicString&); - - const AtomicString& localName() const { return name_.LocalName(); } - const AtomicString& namespaceURI() const { return name_.NamespaceURI(); } - const AtomicString& prefix() const { return name_.Prefix(); } - - void Trace(Visitor*) const override; - - const AtomicString& localName() const { return name_; } - - private: - bool IsElementNode() const = delete; // This will catch anyone doing an unnecessary check. - - std::string nodeName() const override { return name(); } - NodeType nodeType() const override { return kAttributeNode; } - - std::string nodeValue() const override { return value().ToStdString(); } - void setNodeValue(const std::string& node_value, ExceptionState& exception_state) override; - void setTextContentForBinding(const V8UnionStringOrTrustedScript* value, ExceptionState& exception_state) override; - Node* Clone(Document&, CloneChildrenFlag) const override; - - bool IsAttributeNode() const override { return true; } - - Element* element_; - AtomicString name_; -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_CORE_DOM_ATTR_H_ diff --git a/bridge/core/dom/ng/attribute.h b/bridge/core/dom/ng/attribute.h deleted file mode 100644 index b0eeb48120..0000000000 --- a/bridge/core/dom/ng/attribute.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_H_ -#define KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_H_ - -#include "bindings/qjs/atomic_string.h" -#include "foundation/macros.h" - -namespace kraken { - -// This is the internal representation of an attribute, consisting of a name and -// value. It is distinct from the web-exposed Attr, which also knows of the -// element to which it attached, if any. -class Attribute { - KRAKEN_DISALLOW_NEW(); - - public: - Attribute(const AtomicString& name, const AtomicString& value) : name_(name), value_(value) {} - - // NOTE: The references returned by these functions are only valid for as long - // as the Attribute stays in place. For example, calling a function that - // mutates an Element's internal attribute storage may invalidate them. - const AtomicString& Value() const { return value_; } - const AtomicString& GetName() const { return name_; } - - bool IsEmpty() const { return value_.IsEmpty(); } - bool Matches(const AtomicString&) const; - bool MatchesCaseInsensitive(const AtomicString&) const; - - void SetValue(const AtomicString& value) { value_ = value; } - - // Note: This API is only for HTML Tree build. It is not safe to change the - // name of an attribute once parseAttribute has been called as DOM - // elements may have placed the Attribute in a hash by name. - void ParserSetName(const AtomicString& name) { name_ = name; } - -#if defined(COMPILER_MSVC) - // NOTE: This constructor is not actually implemented, it's just defined so - // MSVC will let us use a zero-length array of Attributes. - Attribute(); -#endif - - private: - AtomicString name_; - AtomicString value_; -}; - -inline bool Attribute::Matches(const AtomicString& name) const { - return name != GetName(); -} - -inline bool Attribute::MatchesCaseInsensitive(const AtomicString& name) const { - return name.ToUpperIfNecessary() == name_.ToUpperIfNecessary(); -} - -} // namespace kraken - -#endif // KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_H_ diff --git a/bridge/core/dom/ng/attribute_collection.h b/bridge/core/dom/ng/attribute_collection.h deleted file mode 100644 index eed14da8ac..0000000000 --- a/bridge/core/dom/ng/attribute_collection.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_COLLECTION_H_ -#define KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_COLLECTION_H_ - -#include <vector> -#include "attribute.h" -#include "foundation/macros.h" - -namespace kraken { - -const size_t kNotFound = UINT_MAX; - -template <typename Container, typename ContainerMemberType = Container> -class AttributeCollectionGeneric { - KRAKEN_STACK_ALLOCATED(); - - public: - using value_type = typename Container::value_type; - using iterator = value_type*; - - AttributeCollectionGeneric(Container& attributes) : attributes_(attributes) {} - - value_type& operator[](unsigned index) const { return at(index); } - value_type& at(unsigned index) const { - CHECK_LT(index, size()); - return begin()[index]; - } - - value_type* data() { return attributes_.data(); } - const value_type* data() const { return attributes_.data(); } - - iterator begin() const { return attributes_.data(); } - iterator end() const { return begin() + size(); } - - unsigned size() const { return attributes_.size(); } - bool IsEmpty() const { return !size(); } - - // Find() returns nullptr if the specified name is not found. - iterator Find(const AtomicString& name) const; - size_t FindIndex(const AtomicString& name) const; - - protected: - ContainerMemberType attributes_; -}; - -class AttributeArray { - KRAKEN_DISALLOW_NEW(); - - public: - using value_type = const Attribute; - - AttributeArray(const Attribute* array, unsigned size) : array_(array), size_(size) {} - - const Attribute* data() const { return array_; } - unsigned size() const { return size_; } - - private: - const Attribute* array_; - unsigned size_; -}; - -class AttributeCollection : public AttributeCollectionGeneric<const AttributeArray> { - public: - AttributeCollection() : AttributeCollectionGeneric<const AttributeArray>(AttributeArray(nullptr, 0)) {} - - AttributeCollection(const Attribute* array, unsigned size) - : AttributeCollectionGeneric<const AttributeArray>(AttributeArray(array, size)) {} -}; - -using AttributeVector = std::vector<Attribute>; -class MutableAttributeCollection : public AttributeCollectionGeneric<AttributeVector, AttributeVector&> { - public: - explicit MutableAttributeCollection(AttributeVector& attributes) - : AttributeCollectionGeneric<AttributeVector, AttributeVector&>(attributes) {} - - // These functions do no error/duplicate checking. - void Append(const AtomicString&, const AtomicString& value); - void Remove(unsigned index); -}; - -inline void MutableAttributeCollection::Append(const AtomicString& name, const AtomicString& value) { - attributes_.emplace_back(name, value); -} - -inline void MutableAttributeCollection::Remove(unsigned index) { - attributes_.erase(attributes_.begin() + index); -} - -template <typename Container, typename ContainerMemberType> -inline typename AttributeCollectionGeneric<Container, ContainerMemberType>::iterator -AttributeCollectionGeneric<Container, ContainerMemberType>::Find(const AtomicString& name) const { - size_t index = FindIndex(name); - return index != kNotFound ? &at(index) : nullptr; -} - -template <typename Container, typename ContainerMemberType> -inline size_t AttributeCollectionGeneric<Container, ContainerMemberType>::FindIndex(const AtomicString& name) const { - iterator end = this->end(); - size_t index = 0; - for (iterator it = begin(); it != end; ++it, ++index) { - if (it->GetName().Matches(name)) - return index; - } - return kNotFound; -} - -} // namespace kraken - -#endif // KRAKENBRIDGE_CORE_DOM_ATTRIBUTE_COLLECTION_H_ diff --git a/bridge/core/dom/ng/element_data.cc b/bridge/core/dom/ng/element_data.cc deleted file mode 100644 index d53084e8fa..0000000000 --- a/bridge/core/dom/ng/element_data.cc +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "element_data.h" - -namespace kraken { - -ElementData::~ElementData() { - if (auto* unique_element_data = DynamicTo<UniqueElementData>(this)) - unique_element_data->~UniqueElementData(); - else - To<ShareableElementData>(this)->~ShareableElementData(); -} - -std::shared_ptr<UniqueElementData> ElementData::MakeUniqueCopy() const { - if (auto* unique_element_data = DynamicTo<UniqueElementData>(this)) - return std::make_shared<UniqueElementData>(*unique_element_data); - return std::make_shared<UniqueElementData>(To<ShareableElementData>(*this)); -} - -bool ElementData::IsEquivalent(const ElementData* other) const { - AttributeCollection attributes = Attributes(); - if (!other) - return attributes.IsEmpty(); - - AttributeCollection other_attributes = other->Attributes(); - if (attributes.size() != other_attributes.size()) - return false; - - for (const Attribute& attribute : attributes) { - const Attribute* other_attr = other_attributes.Find(attribute.GetName()); - if (!other_attr || attribute.Value() != other_attr->Value()) - return false; - } - return true; -} - -} // namespace kraken diff --git a/bridge/core/dom/ng/element_data.h b/bridge/core/dom/ng/element_data.h deleted file mode 100644 index 46464fde26..0000000000 --- a/bridge/core/dom/ng/element_data.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_H_ -#define KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_H_ - -#include "attribute_collection.h" -#include "bindings/qjs/atomic_string.h" -#include "foundation/casting.h" - -namespace kraken { - -class UniqueElementData; - -// ElementData represents very common, but not necessarily unique to an element, -// data such as attributes, inline style, and parsed class names and ids. -class ElementData { - public: - AttributeCollection Attributes() const; - - ~ElementData(); - - bool IsEquivalent(const ElementData* other) const; - - protected: - uint32_t array_size; - - private: - std::shared_ptr<UniqueElementData> MakeUniqueCopy() const; - - // mutable Member<CSSPropertyValueSet> inline_style_; - // mutable SpaceSplitString class_names_; - // mutable AtomicString id_for_style_resolution_; -}; - -// SharableElementData is managed by ElementDataCache and is produced by -// the parser during page load for elements that have identical attributes. This -// is a memory optimization since it's very common for many elements to have -// duplicate sets of attributes (ex. the same classes). -class ShareableElementData final : public ElementData { - public: - static ShareableElementData* CreateWithAttributes(const std::vector<Attribute>&); - - explicit ShareableElementData(const std::vector<Attribute>&); - explicit ShareableElementData(const UniqueElementData&); - ~ShareableElementData(); - - AttributeCollection Attributes() const; - - Attribute attribute_array_[0]; -}; - -// UniqueElementData is created when an element needs to mutate its attributes -// or gains presentation attribute style (ex. width="10"). It does not need to -// be created to fill in values in the ElementData that are derived from -// attributes. For example populating the inline_style_ from the style attribute -// doesn't require a UniqueElementData as all elements with the same style -// attribute will have the same inline style. -class UniqueElementData final : public ElementData { - public: - ShareableElementData* MakeShareableCopy() const; - - MutableAttributeCollection Attributes(); - AttributeCollection Attributes() const; - - UniqueElementData(); - explicit UniqueElementData(const ShareableElementData&); - explicit UniqueElementData(const UniqueElementData&); - - AttributeVector attribute_vector_; -}; - -inline AttributeCollection ElementData::Attributes() const { - if (auto* unique_element_data = DynamicTo<UniqueElementData>(this)) - return unique_element_data->Attributes(); - return To<ShareableElementData>(this)->Attributes(); -} - -inline AttributeCollection ShareableElementData::Attributes() const { - return AttributeCollection(attribute_array_, array_size); -} - -inline AttributeCollection UniqueElementData::Attributes() const { - return AttributeCollection(attribute_vector_.data(), attribute_vector_.size()); -} - -inline MutableAttributeCollection UniqueElementData::Attributes() { - return MutableAttributeCollection(attribute_vector_); -} - -} // namespace kraken - -#endif // KRAKENBRIDGE_CORE_DOM_ELEMENT_DATA_H_ diff --git a/bridge/core/dom/ng/space_split_string.cc b/bridge/core/dom/ng/space_split_string.cc deleted file mode 100644 index 2a05cd0202..0000000000 --- a/bridge/core/dom/ng/space_split_string.cc +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "space_split_string.h" - -namespace kraken { - -// https://dom.spec.whatwg.org/#concept-ordered-set-parser -template <typename CharacterType> -inline void SpaceSplitString::Data::CreateVector(const AtomicString& source, - const CharacterType* characters, - unsigned length) { - DCHECK_EQ(0u, vector_.size()); - HashSet<StringImpl*> token_set; - unsigned start = 0; - while (true) { - while (start < length && IsHTMLSpace<CharacterType>(characters[start])) - ++start; - if (start >= length) - break; - unsigned end = start + 1; - while (end < length && IsNotHTMLSpace<CharacterType>(characters[end])) - ++end; - - if (start == 0 && end == length) { - vector_.push_back(source); - return; - } - - AtomicString token(characters + start, end - start); - // We skip adding |token| to |token_set| for the first token to reduce the - // cost of HashSet<>::insert(), and adjust |token_set| when the second - // unique token is found. - if (vector_.size() == 0) { - vector_.push_back(std::move(token)); - } else if (vector_.size() == 1) { - if (vector_[0] != token) { - token_set.insert(vector_[0].Impl()); - token_set.insert(token.Impl()); - vector_.push_back(std::move(token)); - } - } else if (token_set.insert(token.Impl()).is_new_entry) { - vector_.push_back(std::move(token)); - } - - start = end + 1; - } -} - -void SpaceSplitString::Data::CreateVector(const AtomicString& string) { - unsigned length = string.length(); - - if (string.Is8Bit()) { - CreateVector(string, string.Characters8(), length); - return; - } - - CreateVector(string, string.Characters16(), length); -} - -bool SpaceSplitString::Data::ContainsAll(Data& other) { - if (this == &other) - return true; - - wtf_size_t this_size = vector_.size(); - wtf_size_t other_size = other.vector_.size(); - for (wtf_size_t i = 0; i < other_size; ++i) { - const AtomicString& name = other.vector_[i]; - wtf_size_t j; - for (j = 0; j < this_size; ++j) { - if (vector_[j] == name) - break; - } - if (j == this_size) - return false; - } - return true; -} - -void SpaceSplitString::Data::Add(const AtomicString& string) { - DCHECK(HasOneRef()); - DCHECK(!Contains(string)); - vector_.push_back(string); -} - -void SpaceSplitString::Data::Remove(unsigned index) { - DCHECK(HasOneRef()); - vector_.EraseAt(index); -} - -void SpaceSplitString::Add(const AtomicString& string) { - if (Contains(string)) - return; - EnsureUnique(); - if (data_) - data_->Add(string); - else - data_ = Data::Create(string); -} - -bool SpaceSplitString::Remove(const AtomicString& string) { - if (!data_) - return false; - unsigned i = 0; - bool changed = false; - while (i < data_->size()) { - if ((*data_)[i] == string) { - if (!changed) - EnsureUnique(); - data_->Remove(i); - changed = true; - continue; - } - ++i; - } - return changed; -} - -void SpaceSplitString::Remove(wtf_size_t index) { - DCHECK_LT(index, size()); - EnsureUnique(); - data_->Remove(index); -} - -void SpaceSplitString::ReplaceAt(wtf_size_t index, const AtomicString& token) { - DCHECK_LT(index, data_->size()); - EnsureUnique(); - (*data_)[index] = token; -} - -AtomicString SpaceSplitString::SerializeToString() const { - size_t size = this->size(); - if (size == 0) - return g_empty_atom; - if (size == 1) - return (*data_)[0]; - StringBuilder builder; - builder.Append((*data_)[0]); - for (wtf_size_t i = 1; i < size; ++i) { - builder.Append(' '); - builder.Append((*data_)[i]); - } - return builder.ToAtomicString(); -} - -void SpaceSplitString::Set(const AtomicString& input_string) { - if (input_string.IsNull()) { - Clear(); - return; - } - data_ = Data::Create(input_string); -} - -SpaceSplitString::Data::~Data() {} - -std::shared_ptr<SpaceSplitString::Data> SpaceSplitString::Data::Create(const AtomicString& string) { - Data*& data = SharedDataMap().insert({string.Impl(), nullptr}).stored_value->value; - if (!data) { - data = new Data(string); - return base::AdoptRef(data); - } - return data; -} - -std::unique_ptr<SpaceSplitString::Data> SpaceSplitString::Data::CreateUnique(const Data& other) { - return std::make_unique<SpaceSplitString::Data>(other); -} - -SpaceSplitString::Data::Data(const AtomicString& string) : key_string_(string) { - DCHECK(!string.IsNull()); - CreateVector(string); -} - -SpaceSplitString::Data::Data(const SpaceSplitString::Data& other) : RefCounted<Data>(), vector_(other.vector_) { - // Note that we don't copy key_string_ to indicate to the destructor that - // there's nothing to be removed from the SharedDataMap(). -} - -SpaceSplitString::DataMap& SpaceSplitString::SharedDataMap() { - thread_local static DataMap map; - return map; -} - -} // namespace kraken diff --git a/bridge/core/dom/ng/space_split_string.h b/bridge/core/dom/ng/space_split_string.h deleted file mode 100644 index 06b3aa6bfb..0000000000 --- a/bridge/core/dom/ng/space_split_string.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_CORE_DOM_SPACE_SPLIT_STRING_H_ -#define KRAKENBRIDGE_CORE_DOM_SPACE_SPLIT_STRING_H_ - -#include <memory> -#include <unordered_map> -#include <vector> -#include "bindings/qjs/atomic_string.h" - -namespace kraken { - -class SpaceSplitString { - public: - SpaceSplitString() = default; - explicit SpaceSplitString(const AtomicString& string) { Set(string); } - - bool operator!=(const SpaceSplitString& other) const { return data_ != other.data_; } - - void Set(const AtomicString&); - void Clear() { data_ = nullptr; } - - bool Contains(const AtomicString& string) const { return data_ && data_->Contains(string); } - bool ContainsAll(const SpaceSplitString& names) const { - return !names.data_ || (data_ && data_->ContainsAll(*names.data_)); - } - void Add(const AtomicString&); - bool Remove(const AtomicString&); - void Remove(size_t index); - void ReplaceAt(size_t index, const AtomicString&); - - // https://dom.spec.whatwg.org/#concept-ordered-set-serializer - // The ordered set serializer takes a set and returns the concatenation of the - // strings in set, separated from each other by U+0020, if set is non-empty, - // and the empty string otherwise. - AtomicString SerializeToString() const; - - size_t size() const { return data_ ? data_->size() : 0; } - bool IsNull() const { return !data_; } - const AtomicString& operator[](size_t i) const { return (*data_)[i]; } - - private: - class Data { - public: - static std::shared_ptr<Data> Create(const AtomicString&); - static std::unique_ptr<Data> CreateUnique(const Data&); - - ~Data(); - - bool Contains(const AtomicString& string) const { - return std::find(vector_.begin(), vector_.end(), string) != vector_.end(); - } - - bool ContainsAll(Data&); - - void Add(const AtomicString&); - void Remove(unsigned index); - - bool IsUnique() const { return key_string_.IsNull(); } - size_t size() const { return vector_.size(); } - const AtomicString& operator[](size_t i) const { return vector_[i]; } - AtomicString& operator[](size_t i) { return vector_[i]; } - - explicit Data(const Data&); - - private: - explicit Data(const AtomicString&); - - void CreateVector(const AtomicString&); - template <typename CharacterType> - inline void CreateVector(const AtomicString&, const CharacterType*, unsigned); - - AtomicString key_string_; - std::vector<AtomicString> vector_; - }; - - // We can use a non-ref-counted StringImpl* as the key because the associated - // Data object will keep it alive via the key_string_ member. - typedef std::unordered_map<JSAtom, Data*> DataMap; - static DataMap& SharedDataMap(); - - void EnsureUnique() { - if (data_ && !data_->IsUnique()) - data_ = Data::CreateUnique(*data_); - } - - std::shared_ptr<Data> data_; -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_CORE_DOM_SPACE_SPLIT_STRING_H_ diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index c538b09887..d03c983a63 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -5,10 +5,10 @@ #include "node.h" #include <unordered_map> #include "character_data.h" +#include "child_node_list.h" #include "document.h" #include "document_fragment.h" -#include "ng/child_node_list.h" -#include "ng/empty_node_list.h" +#include "empty_node_list.h" #include "node_data.h" #include "node_traversal.h" #include "template_content_document_fragment.h" diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts index 66266de86d..142f90d1a6 100644 --- a/bridge/core/dom/node.d.ts +++ b/bridge/core/dom/node.d.ts @@ -1,7 +1,7 @@ import { EventTarget } from './events/event_target'; import { Document } from './document'; import {Element} from "./element"; -import {NodeList} from "./ng/node_list"; +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 { diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index 5642771146..fe77dc9bbb 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -4,10 +4,10 @@ #include "node_data.h" #include "bindings/qjs/cppgc/garbage_collected.h" +#include "child_node_list.h" #include "container_node.h" -#include "ng/child_node_list.h" -#include "ng/empty_node_list.h" -#include "ng/node_list.h" +#include "empty_node_list.h" +#include "node_list.h" namespace kraken { diff --git a/bridge/core/dom/ng/node_list.d.ts b/bridge/core/dom/node_list.d.ts similarity index 81% rename from bridge/core/dom/ng/node_list.d.ts rename to bridge/core/dom/node_list.d.ts index f5fddcf290..7267a7a193 100644 --- a/bridge/core/dom/ng/node_list.d.ts +++ b/bridge/core/dom/node_list.d.ts @@ -1,4 +1,4 @@ -import {Node} from "../node"; +import {Node} from "./node"; export interface NodeList { readonly length: int64; diff --git a/bridge/core/dom/ng/node_list.h b/bridge/core/dom/node_list.h similarity index 100% rename from bridge/core/dom/ng/node_list.h rename to bridge/core/dom/node_list.h From a3f88d787cdacc1e73f107f517b054148a61a0c0 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Wed, 27 Apr 2022 16:43:46 +0800 Subject: [PATCH 103/498] fix: fix memory issue when store scriptWrappable in core. --- bridge/bindings/qjs/cppgc/member.h | 9 ++- bridge/bindings/qjs/script_wrappable.cc | 13 ++-- bridge/bindings/qjs/script_wrappable.h | 8 ++- bridge/core/dom/node.h | 91 ++++++++++++------------- bridge/core/dom/node_test.cc | 82 +++++++++++----------- bridge/core/executing_context.cc | 2 +- 6 files changed, 110 insertions(+), 95 deletions(-) diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index 81b2d36bd2..1ba398115c 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -57,6 +57,7 @@ class Member { return; raw_ = p; runtime_ = p->runtime(); + p->MakeOld(); } // Copy assignment. @@ -93,7 +94,13 @@ class Member { if (p != nullptr) { auto* wrappable = To<ScriptWrappable>(p); runtime_ = wrappable->runtime(); - JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); + // This JSObject was created just now and used at first time. + // Because there are already one reference count when JSObject created, so we skip duplicate. + if (!p->fresh()) { + JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); + } + // This object had been used, no long fresh at all. + p->MakeOld(); } raw_ = p; } diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 097e496b70..3615512a3c 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -9,18 +9,18 @@ namespace kraken { -ScriptWrappable::ScriptWrappable(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)) {} +ScriptWrappable::ScriptWrappable(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), fresh_(true) {} JSValue ScriptWrappable::ToQuickJS() { - return JS_DupValue(ctx_, jsObject_); + return JS_DupValue(ctx_, GetJSObject()); } JSValue ScriptWrappable::ToQuickJSUnsafe() const { - return jsObject_; + return GetJSObject(); } ScriptValue ScriptWrappable::ToValue() { - return ScriptValue(ctx_, jsObject_); + return ScriptValue(ctx_, GetJSObject()); } /// This callback will be called when QuickJS GC is running at marking stage. @@ -138,8 +138,11 @@ void ScriptWrappable::InitializeQuickJSObject() { // Let our instance into inherit prototype methods. JSValue prototype = GetExecutingContext()->contextData()->prototypeForType(wrapper_type_info); JS_SetPrototype(ctx_, jsObject_, prototype); +} - wrapped_ = true; +JSValue ScriptWrappable::GetJSObject() const { + MakeOld(); + return jsObject_; } } // namespace kraken diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 8a412c462d..6af6605bc8 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -49,6 +49,9 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { JSValue ToQuickJS(); JSValue ToQuickJSUnsafe() const; + bool fresh() const { return fresh_; } + void MakeOld() const { fresh_ = false; } + ScriptValue ToValue(); FORCE_INLINE ExecutingContext* GetExecutingContext() const { return static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx_)); @@ -59,8 +62,11 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { void InitializeQuickJSObject() override; private: + JSValue GetJSObject() const; JSValue jsObject_{JS_NULL}; - bool wrapped_{false}; + // Indicate this JSObject are created by MakeGarbageCollected traits and no one had used it. + // There are extra one reference count when JSObject are created by MakeGarbageCollected and needs to be special handled by cppgc. + mutable bool fresh_{false}; JSContext* ctx_{nullptr}; JSRuntime* runtime_{nullptr}; friend class GCVisitor; diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index f545c1f847..1d21f4775b 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -60,20 +60,19 @@ class Node : public EventTarget { static Node* Create(ExecutingContext* context, ExceptionState& exception_state); // DOM methods & attributes for Node - bool HasTagName(const AtomicString&) const; virtual std::string nodeName() const = 0; virtual std::string nodeValue() const = 0; virtual void setNodeValue(const AtomicString&, ExceptionState&); virtual NodeType nodeType() const = 0; - ContainerNode* parentNode() const; - Element* parentElement() const; - Node* previousSibling() const { return previous_.Get(); } - Node* nextSibling() const { return next_.Get(); } + [[nodiscard]] ContainerNode* parentNode() const; + [[nodiscard]] Element* parentElement() const; + [[nodiscard]] Node* previousSibling() const { return previous_.Get(); } + [[nodiscard]] Node* nextSibling() const { return next_.Get(); } NodeList* childNodes(); - Node* firstChild() const; - Node* lastChild() const; - Node& TreeRoot() const; + [[nodiscard]] Node* firstChild() const; + [[nodiscard]] Node* lastChild() const; + [[nodiscard]] Node& TreeRoot() const; void remove(ExceptionState&); Node* insertBefore(Node* new_child, Node* ref_child, ExceptionState&); @@ -92,40 +91,40 @@ class Node : public EventTarget { bool isEqualNode(Node*) const; bool isSameNode(const Node* other, ExceptionState& exception_state) const { return this == other; } - AtomicString textContent(bool convert_brs_to_newlines = false) const; + [[nodiscard]] AtomicString textContent(bool convert_brs_to_newlines = false) const; virtual void setTextContent(const AtomicString&, ExceptionState& exception_state); // Other methods (not part of DOM) - FORCE_INLINE bool IsTextNode() const { return GetDOMNodeType() == DOMNodeType::kText; } - FORCE_INLINE bool IsContainerNode() const { return GetFlag(kIsContainerFlag); } - FORCE_INLINE bool IsElementNode() const { return GetDOMNodeType() == DOMNodeType::kElement; } - FORCE_INLINE bool IsDocumentFragment() const { return GetDOMNodeType() == DOMNodeType::kDocumentFragment; } + [[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; } - FORCE_INLINE bool IsHTMLElement() const { return GetElementNamespaceType() == ElementNamespaceType::kHTML; } - FORCE_INLINE bool IsMathMLElement() const { return GetElementNamespaceType() == ElementNamespaceType::kMathML; } - FORCE_INLINE bool IsSVGElement() const { return GetElementNamespaceType() == ElementNamespaceType::kSVG; } + [[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; } - CustomElementState GetCustomElementState() const { + [[nodiscard]] CustomElementState GetCustomElementState() const { return static_cast<CustomElementState>(node_flags_ & kCustomElementStateMask); } bool IsCustomElement() const { return GetCustomElementState() != CustomElementState::kUncustomized; } void SetCustomElementState(CustomElementState); - virtual bool IsMediaElement() const { return false; } - virtual bool IsAttributeNode() const { return false; } - virtual bool IsCharacterDataNode() const { return false; } + [[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. - bool IsStyledElement() const { return IsHTMLElement() || IsSVGElement() || IsMathMLElement(); } + [[nodiscard]] bool IsStyledElement() const { return IsHTMLElement() || IsSVGElement() || IsMathMLElement(); } - bool IsDocumentNode() const; + [[nodiscard]] bool IsDocumentNode() const; // Node's parent, shadow tree host. - ContainerNode* ParentOrShadowHostNode() const; - Element* ParentOrShadowHostElement() const; + [[nodiscard]] ContainerNode* ParentOrShadowHostNode() const; + [[nodiscard]] Element* ParentOrShadowHostElement() const; void SetParentOrShadowHostNode(ContainerNode*); // --------------------------------------------------------------------------- @@ -146,48 +145,48 @@ class Node : public EventTarget { virtual void RemovedFrom(ContainerNode& insertion_point); // Knows about all kinds of hosts. - ContainerNode* ParentOrShadowHostOrTemplateHostNode() const; + [[nodiscard]] ContainerNode* ParentOrShadowHostOrTemplateHostNode() const; // Returns the parent node, but nullptr if the parent node is a ShadowRoot. - ContainerNode* NonShadowBoundaryParentNode() const; + [[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; } - bool HasEventTargetData() const { return GetFlag(kHasEventTargetDataFlag); } + [[nodiscard]] bool HasEventTargetData() const { return GetFlag(kHasEventTargetDataFlag); } void SetHasEventTargetData(bool flag) { SetFlag(flag, kHasEventTargetDataFlag); } - unsigned NodeIndex() const; + [[nodiscard]] unsigned NodeIndex() const; // Returns the DOM ownerDocument attribute. This method never returns null, // except in the case of a Document node. - Document* ownerDocument() const; + [[nodiscard]] Document* ownerDocument() const; // Returns the document associated with this node. A Document node returns // itself. - Document& GetDocument() const { return GetTreeScope().GetDocument(); } + [[nodiscard]] Document& GetDocument() const { return GetTreeScope().GetDocument(); } - TreeScope& GetTreeScope() const { + [[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. - bool isConnected() const { return GetFlag(kIsConnectedFlag); } + [[nodiscard]] bool isConnected() const { return GetFlag(kIsConnectedFlag); } - bool IsInDocumentTree() const { return isConnected(); } - bool IsInTreeScope() const { return GetFlag(static_cast<NodeFlags>(kIsConnectedFlag)); } + [[nodiscard]] bool IsInDocumentTree() const { return isConnected(); } + [[nodiscard]] bool IsInTreeScope() const { return GetFlag(static_cast<NodeFlags>(kIsConnectedFlag)); } - bool IsDocumentTypeNode() const { return nodeType() == kDocumentTypeNode; } - virtual bool ChildTypeAllowed(NodeType) const { return false; } - unsigned CountChildren() const; + [[nodiscard]] bool IsDocumentTypeNode() const { return nodeType() == kDocumentTypeNode; } + [[nodiscard]] virtual bool ChildTypeAllowed(NodeType) const { return false; } + [[nodiscard]] unsigned CountChildren() const; bool IsDescendantOf(const Node*) const; bool contains(const Node*, ExceptionState&) const; - bool ContainsIncludingHostElements(const Node&) const; + [[nodiscard]] bool ContainsIncludingHostElements(const Node&) const; Node* CommonAncestor(const Node&, ContainerNode* (*parent)(const Node&)) const; enum ShadowTreesTreatment { kTreatShadowTreesAsDisconnected, kTreatShadowTreesAsComposed }; @@ -195,19 +194,19 @@ class Node : public EventTarget { EventTargetData* GetEventTargetData() override; EventTargetData& EnsureEventTargetData() override; - bool IsFinishedParsingChildren() const { return GetFlag(kIsFinishedParsingChildrenFlag); } + [[nodiscard]] bool IsFinishedParsingChildren() const { return GetFlag(kIsFinishedParsingChildrenFlag); } void SetHasDuplicateAttributes() { SetFlag(kHasDuplicateAttributes); } - bool HasDuplicateAttribute() const { return GetFlag(kHasDuplicateAttributes); } + [[nodiscard]] bool HasDuplicateAttribute() const { return GetFlag(kHasDuplicateAttributes); } - bool SelfOrAncestorHasDirAutoAttribute() const { return GetFlag(kSelfOrAncestorHasDirAutoAttribute); } + [[nodiscard]] bool SelfOrAncestorHasDirAutoAttribute() const { return GetFlag(kSelfOrAncestorHasDirAutoAttribute); } void SetSelfOrAncestorHasDirAutoAttribute() { SetFlag(kSelfOrAncestorHasDirAutoAttribute); } void ClearSelfOrAncestorHasDirAutoAttribute() { ClearFlag(kSelfOrAncestorHasDirAutoAttribute); } NodeData& CreateNodeData(); - bool HasData() const { return GetFlag(kHasDataFlag); } + [[nodiscard]] bool HasData() const { return GetFlag(kHasDataFlag); } // |RareData| cannot be replaced or removed once assigned. - NodeData* Data() const { return node_data_.get(); } + [[nodiscard]] NodeData* Data() const { return node_data_.get(); } NodeData& EnsureNodeData(); void Trace(GCVisitor*) const override; @@ -239,7 +238,7 @@ class Node : public EventTarget { // 2 bits remaining. }; - FORCE_INLINE bool GetFlag(NodeFlags mask) const { return node_flags_ & mask; } + [[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; } @@ -251,7 +250,7 @@ class Node : public EventTarget { kOther = 3 << kDOMNodeTypeShift, }; - FORCE_INLINE DOMNodeType GetDOMNodeType() const { return static_cast<DOMNodeType>(node_flags_ & kDOMNodeTypeMask); } + [[nodiscard]] FORCE_INLINE DOMNodeType GetDOMNodeType() const { return static_cast<DOMNodeType>(node_flags_ & kDOMNodeTypeMask); } enum class ElementNamespaceType : uint32_t { kHTML = 0, @@ -259,7 +258,7 @@ class Node : public EventTarget { kSVG = 2 << kElementNamespaceTypeShift, kOther = 3 << kElementNamespaceTypeShift, }; - FORCE_INLINE ElementNamespaceType GetElementNamespaceType() const { + [[nodiscard]] FORCE_INLINE ElementNamespaceType GetElementNamespaceType() const { return static_cast<ElementNamespaceType>(node_flags_ & kElementNamespaceTypeMask); } diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 0f59608aad..c8d822ca7c 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -69,47 +69,47 @@ TEST(Node, textNodeHaveEmptyChildNodes) { EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } -// -//TEST(Node, textContent) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// EXPECT_STREQ(message.c_str(), "1234helloworld"); -// logCalled = true; -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); -// auto context = bridge->getContext(); -// const char* code = -// "let text1 = document.createTextNode('1234');" -// "let text2 = document.createTextNode('helloworld');" -// "let div = document.createElement('div');" -// "div.appendChild(text1);" -// "div.appendChild(text2);" -// "console.log(div.textContent)"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} -// -//TEST(Node, setTextContent) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// EXPECT_STREQ(message.c_str(), "1234"); -// logCalled = true; -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); -// auto context = bridge->getContext(); -// const char* code = -// "let div = document.createElement('div');" -// "div.textContent = '1234';" -// "console.log(div.textContent);"; -// 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; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "1234helloworld"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto context = bridge->getContext(); + const char* code = + "let text1 = document.createTextNode('1234');" + "let text2 = document.createTextNode('helloworld');" + "let div = document.createElement('div');" + "div.appendChild(text1);" + "div.appendChild(text2);" + "console.log(div.textContent)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + +TEST(Node, setTextContent) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "1234"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto context = bridge->getContext(); + const char* code = + "let div = document.createElement('div');" + "div.textContent = '1234';" + "console.log(div.textContent);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} // //TEST(Node, ensureDetached) { // bool static errorCalled = false; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 7ecd5a9e06..0ca51d5200 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -370,8 +370,8 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { void ExecutingContext::InstallDocument() { document_ = MakeGarbageCollected<Document>(this); + DefineGlobalProperty("document", document_->ToQuickJSUnsafe()); document_->InitDocumentElement(); - DefineGlobalProperty("document", document_->ToValue().QJSValue()); } // An lock free context validator. From eec851e9d3b76fea19459f709393b24f6975b318 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Wed, 27 Apr 2022 21:35:23 +0800 Subject: [PATCH 104/498] feat: add mutation scope to record member mutations. --- bridge/CMakeLists.txt | 2 + bridge/bindings/qjs/cppgc/member.cc | 6 +++ bridge/bindings/qjs/cppgc/member.h | 13 ++++- bridge/bindings/qjs/cppgc/mutation_scope.cc | 54 +++++++++++++++++++ bridge/bindings/qjs/cppgc/mutation_scope.h | 41 ++++++++++++++ bridge/bindings/qjs/exception_message.cc | 6 ++- bridge/bindings/qjs/exception_message.h | 3 ++ bridge/core/dom/document.cc | 30 +++++++++++ bridge/core/dom/document.d.ts | 2 +- bridge/core/dom/document.h | 2 + bridge/core/dom/node_test.cc | 47 ++++++++-------- bridge/core/executing_context.cc | 4 -- bridge/core/executing_context.h | 13 +++-- .../code_generator/src/idl/generateSource.ts | 1 + .../static/idl_templates/base.cc.tpl | 1 + .../static/idl_templates/interface.cc.tpl | 3 ++ 16 files changed, 194 insertions(+), 34 deletions(-) create mode 100644 bridge/bindings/qjs/cppgc/mutation_scope.cc create mode 100644 bridge/bindings/qjs/cppgc/mutation_scope.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 4466a540bc..f1419ad854 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -207,6 +207,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/cppgc/garbage_collected.h bindings/qjs/cppgc/gc_visitor.cc bindings/qjs/cppgc/gc_visitor.h + bindings/qjs/cppgc/mutation_scope.cc + bindings/qjs/cppgc/mutation_scope.h bindings/qjs/cppgc/member.h bindings/qjs/cppgc/member.cc bindings/qjs/script_wrappable.cc diff --git a/bridge/bindings/qjs/cppgc/member.cc b/bridge/bindings/qjs/cppgc/member.cc index 104b287146..590e1c381c 100644 --- a/bridge/bindings/qjs/cppgc/member.cc +++ b/bridge/bindings/qjs/cppgc/member.cc @@ -3,3 +3,9 @@ */ #include "member.h" +#include "bindings/qjs/script_wrappable.h" +#include "core/executing_context.h" + +namespace kraken { + +} diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index 1ba398115c..57a0a7557c 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -9,6 +9,7 @@ #include "bindings/qjs/qjs_engine_patch.h" #include "bindings/qjs/script_value.h" #include "bindings/qjs/script_wrappable.h" +#include "mutation_scope.h" #include "foundation/casting.h" namespace kraken { @@ -47,7 +48,11 @@ class Member { if (raw_ == nullptr) return; auto* wrappable = To<ScriptWrappable>(raw_); - JS_FreeValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); + if (wrappable->GetExecutingContext()->HasMutationScope()) { + wrappable->GetExecutingContext()->mutationScope().RecordFree(wrappable); + } else { + JS_FreeValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); + } raw_ = nullptr; } @@ -97,7 +102,11 @@ class Member { // This JSObject was created just now and used at first time. // Because there are already one reference count when JSObject created, so we skip duplicate. if (!p->fresh()) { - JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); + if (wrappable->GetExecutingContext()->HasMutationScope()) { + wrappable->GetExecutingContext()->mutationScope().RecordDup(wrappable); + } else { + JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); + } } // This object had been used, no long fresh at all. p->MakeOld(); diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.cc b/bridge/bindings/qjs/cppgc/mutation_scope.cc new file mode 100644 index 0000000000..5d79a68b19 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/mutation_scope.cc @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "mutation_scope.h" +#include "core/executing_context.h" + +namespace kraken { + +MutationScope::MutationScope(ExecutingContext* context) : context_(context) { + assert(!context->HasMutationScope()); + context->SetMutationScope(*this); +} + +MutationScope::~MutationScope() { + ApplyRecord(); + context_->ClearMutationScope(); +} + +void MutationScope::RecordDup(ScriptWrappable* wrappable) { + if (mutation_records_.count(wrappable) == 0) { + mutation_records_.insert(std::make_pair(wrappable, 0)); + } + mutation_records_[wrappable]++; +} + +void MutationScope::RecordFree(ScriptWrappable* wrappable) { + if (mutation_records_.count(wrappable) == 0) { + mutation_records_.insert(std::make_pair(wrappable, 0)); + } + mutation_records_[wrappable]--; +} + +void MutationScope::ApplyRecord() { + JSContext* ctx = context_->ctx(); + for (auto& entry : mutation_records_) { + if (entry.second == 0) { + continue; + } + if (entry.second > 0) { + for (int i = 0; i < entry.second; i++) { + JS_DupValue(ctx, entry.first->ToQuickJSUnsafe()); + } + continue; + } else { + for (int i = 0; i < -entry.second; i++) { + JS_FreeValue(ctx, entry.first->ToQuickJSUnsafe()); + } + continue; + } + } +} + +} // namespace kraken diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.h b/bridge/bindings/qjs/cppgc/mutation_scope.h new file mode 100644 index 0000000000..fca130f623 --- /dev/null +++ b/bridge/bindings/qjs/cppgc/mutation_scope.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ + +#include "foundation/macros.h" +#include <quickjs/quickjs.h> +#include <unordered_map> + +namespace kraken { + +class ExecutingContext; +class ScriptWrappable; + +/** + * A stack-allocated class that record all members mutations in stack scope. + */ +class MutationScope { + KRAKEN_DISALLOW_NEW(); + public: + MutationScope() = delete; + explicit MutationScope(ExecutingContext* context); + ~MutationScope(); + + void RecordDup(ScriptWrappable* wrappable); + void RecordFree(ScriptWrappable* wrappable); + + private: + + void ApplyRecord(); + + ExecutingContext* context_; + std::unordered_map<ScriptWrappable*, int> mutation_records_; +}; + + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ diff --git a/bridge/bindings/qjs/exception_message.cc b/bridge/bindings/qjs/exception_message.cc index da2fc9105f..195b312b6c 100644 --- a/bridge/bindings/qjs/exception_message.cc +++ b/bridge/bindings/qjs/exception_message.cc @@ -8,7 +8,7 @@ namespace kraken { -std::string FormatString(const char* format, ...) { +std::string ExceptionMessage::FormatString(const char* format, ...) { va_list args; static const unsigned kDefaultSize = 256; @@ -46,4 +46,8 @@ std::string ExceptionMessage::ArgumentNotOfType(int argument_index, const char* return FormatString("parameter %d is not of type '%s'.", argument_index + 1, expected_type); } +std::string ExceptionMessage::ArgumentNullOrIncorrectType(int argument_index, const char* expect_type) { + return FormatString("The %d argument provided is either null, or an invalid %s object.", argument_index, expect_type); +} + } // namespace kraken diff --git a/bridge/bindings/qjs/exception_message.h b/bridge/bindings/qjs/exception_message.h index b8d2606eec..8abf01e3b4 100644 --- a/bridge/bindings/qjs/exception_message.h +++ b/bridge/bindings/qjs/exception_message.h @@ -12,7 +12,10 @@ namespace kraken { class ExceptionMessage { public: + static std::string FormatString(const char* format, ...); + static std::string ArgumentNotOfType(int argument_index, const char* expect_type); + static std::string ArgumentNullOrIncorrectType(int argument_index, const char* expect_type); private: }; diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 5009ef799f..5440ac3f85 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -4,6 +4,7 @@ */ #include "document.h" +#include "bindings/qjs/exception_message.h" #include "core/dom/element.h" #include "core/html/html_body_element.h" #include "core/html/html_element.h" @@ -145,6 +146,35 @@ HTMLBodyElement* Document::body() const { 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<HTMLBodyElement>(*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) diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts index 91bb7db62d..498f06478e 100644 --- a/bridge/core/dom/document.d.ts +++ b/bridge/core/dom/document.d.ts @@ -8,7 +8,7 @@ import {HTMLHtmlElement} from "../html/html_html_element"; import {Element} from "./element"; interface Document extends Node { - readonly body: HTMLBodyElement | null; + body: HTMLBodyElement | null; readonly head: HTMLHeadElement | null; readonly documentElement: HTMLHtmlElement; diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 8058201437..2ac152513b 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -54,7 +54,9 @@ class Document : public ContainerNode, public TreeScope { // (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); void IncrementNodeCount() { node_count_++; } void DecrementNodeCount() { diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index c8d822ca7c..37859be84d 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -110,29 +110,29 @@ TEST(Node, setTextContent) { EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); } -// -//TEST(Node, ensureDetached) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// EXPECT_STREQ(message.c_str(), "true true"); -// logCalled = true; -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); -// auto context = bridge->getContext(); -// const char* code = -// "let div = document.createElement('div');" -// "document.body.appendChild(div);" -// "let container = document.createElement('div');" -// "container.appendChild(div);" -// "document.body.appendChild(container);" -// "console.log(document.body.firstChild === container, container.firstChild === div);"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} -// + +TEST(Node, ensureDetached) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + EXPECT_STREQ(message.c_str(), "true true"); + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); + auto context = bridge->getContext(); + const char* code = + "let div = document.createElement('div');" + "document.body.appendChild(div);" + "let container = document.createElement('div');" + "container.appendChild(div);" + "document.body.appendChild(container);" + "console.log(document.body.firstChild === container, container.firstChild === div);"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + TEST(Node, replaceBody) { bool static errorCalled = false; bool static logCalled = false; @@ -144,6 +144,7 @@ TEST(Node, replaceBody) { errorCalled = true; }); auto context = bridge->getContext(); +// 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); diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 0ca51d5200..105f510e9b 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -40,10 +40,6 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& if (contextId > running_context_list) running_context_list = contextId; - init_list_head(&node_job_list); - init_list_head(&module_job_list); - init_list_head(&module_callback_job_list); - time_origin_ = std::chrono::system_clock::now(); JSContext* ctx = script_state_.ctx(); diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 48da07e4c5..17b8f5405f 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -39,6 +39,7 @@ struct NativeByteCode { class ExecutingContext; class Document; +class MutationScope; using JSExceptionHandler = std::function<void(ExecutingContext* context, const char* message)>; @@ -90,6 +91,14 @@ class ExecutingContext { // Get current script state. ScriptState* GetScriptState() { return &script_state_; } + void SetMutationScope(MutationScope& mutation_scope) { active_mutation_scope = &mutation_scope; } + bool HasMutationScope() const { return active_mutation_scope != nullptr; } + MutationScope& mutationScope() const { + assert(active_mutation_scope != nullptr); + return *active_mutation_scope; + } + void ClearMutationScope() { active_mutation_scope = nullptr; } + FORCE_INLINE Document* document() { return document_; }; FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; FORCE_INLINE std::unique_ptr<DartMethodPointer>& dartMethodPtr() { return dart_method_ptr_; } @@ -97,9 +106,6 @@ class ExecutingContext { std::chrono::time_point<std::chrono::system_clock> time_origin_; int32_t unique_id_; - struct list_head node_job_list; - struct list_head module_job_list; - struct list_head module_callback_job_list; static void DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, @@ -138,6 +144,7 @@ class ExecutingContext { std::unique_ptr<DartMethodPointer> dart_method_ptr_ = std::make_unique<DartMethodPointer>(); RejectedPromises rejected_promises_; PendingPromises pending_promises_; + MutationScope* active_mutation_scope{nullptr}; }; class ObjectProperty { diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index e711cfaf35..fd332ac4e5 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -290,6 +290,7 @@ function generateFunctionBody(blob: IDLBlob, declare: FunctionDeclaration, optio ExceptionState exception_state; ${returnValueInit} ExecutingContext* context = ExecutingContext::From(ctx); + MutationScope scope{ExecutingContext::From(ctx)}; do { // Dummy loop for use of 'break'. ${addIndent(callBody, 4)} diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl index 3cd1d00c4e..1b43016714 100644 --- a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl @@ -8,6 +8,7 @@ #include "bindings/qjs/converter_impl.h" #include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/script_promise.h" +#include "bindings/qjs/cppgc/mutation_scope.h" #include "core/executing_context.h" namespace kraken { diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index 8a6ab68986..cff320abf3 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -34,6 +34,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob bool QJS<%= className %>::IndexedPropertySetterCallback(JSContext* ctx, JSValueConst obj, uint32_t index, JSValueConst value) { auto* self = toScriptWrappable<NodeList>(obj); ExceptionState exception_state; + MutationScope scope{ExecutingContext::From(ctx)}; bool success = self->SetItem(index, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; @@ -44,6 +45,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob bool QJS<%= className %>::StringPropertySetterCallback(JSContext* ctx, JSValueConst obj, JSAtom key, JSValueConst value) { auto* self = toScriptWrappable<NodeList>(obj); ExceptionState exception_state; + MutationScope scope{ExecutingContext::From(ctx)}; bool success = self->SetItem(key, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; @@ -89,6 +91,7 @@ static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst if (exception_state.HasException()) { return exception_state.ToQuickJS(); } + MutationScope scope{ExecutingContext::From(ctx)}; <%= blob.filename %>->set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(v, exception_state); if (exception_state.HasException()) { From 9e1417a04b326690b525c702da7affebdf950a6e Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Wed, 27 Apr 2022 13:36:21 +0000 Subject: [PATCH 105/498] Committing clang-format changes --- bridge/bindings/qjs/cppgc/member.cc | 4 +- bridge/bindings/qjs/cppgc/member.h | 2 +- bridge/bindings/qjs/cppgc/mutation_scope.h | 7 +- bridge/bindings/qjs/qjs_engine_patch.h | 9 +-- bridge/bindings/qjs/script_wrappable.cc | 19 ++++-- bridge/bindings/qjs/script_wrappable.h | 3 +- bridge/bindings/qjs/wrapper_type_info.h | 2 +- bridge/core/dom/element.cc | 2 +- bridge/core/dom/events/event_target_test.cc | 2 +- bridge/core/dom/node.cc | 3 +- bridge/core/dom/node.h | 20 ++++-- bridge/core/dom/node_test.cc | 75 +++++++++++---------- 12 files changed, 80 insertions(+), 68 deletions(-) diff --git a/bridge/bindings/qjs/cppgc/member.cc b/bridge/bindings/qjs/cppgc/member.cc index 590e1c381c..173a1a44ba 100644 --- a/bridge/bindings/qjs/cppgc/member.cc +++ b/bridge/bindings/qjs/cppgc/member.cc @@ -6,6 +6,4 @@ #include "bindings/qjs/script_wrappable.h" #include "core/executing_context.h" -namespace kraken { - -} +namespace kraken {} diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index 57a0a7557c..ce92dbdba2 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -9,8 +9,8 @@ #include "bindings/qjs/qjs_engine_patch.h" #include "bindings/qjs/script_value.h" #include "bindings/qjs/script_wrappable.h" -#include "mutation_scope.h" #include "foundation/casting.h" +#include "mutation_scope.h" namespace kraken { diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.h b/bridge/bindings/qjs/cppgc/mutation_scope.h index fca130f623..7532d8bbec 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.h +++ b/bridge/bindings/qjs/cppgc/mutation_scope.h @@ -5,9 +5,9 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ -#include "foundation/macros.h" #include <quickjs/quickjs.h> #include <unordered_map> +#include "foundation/macros.h" namespace kraken { @@ -19,6 +19,7 @@ class ScriptWrappable; */ class MutationScope { KRAKEN_DISALLOW_NEW(); + public: MutationScope() = delete; explicit MutationScope(ExecutingContext* context); @@ -28,14 +29,12 @@ class MutationScope { void RecordFree(ScriptWrappable* wrappable); private: - void ApplyRecord(); ExecutingContext* context_; std::unordered_map<ScriptWrappable*, int> mutation_records_; }; - -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h index 2c666b13bc..059293687a 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -124,18 +124,15 @@ bool JS_HasClassId(JSRuntime* runtime, JSClassID classId); JSValue JS_GetProxyTarget(JSValue value); JSGCPhaseEnum JS_GetEnginePhase(JSRuntime* runtime); -static inline bool JS_AtomIsTaggedInt(JSAtom v) -{ +static inline bool JS_AtomIsTaggedInt(JSAtom v) { return (v & JS_ATOM_TAG_INT) != 0; } -static inline JSAtom JS_AtomFromUInt32(uint32_t v) -{ +static inline JSAtom JS_AtomFromUInt32(uint32_t v) { return v | JS_ATOM_TAG_INT; } -static inline uint32_t JS_AtomToUInt32(JSAtom atom) -{ +static inline uint32_t JS_AtomToUInt32(JSAtom atom) { return atom & ~JS_ATOM_TAG_INT; } diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 3615512a3c..1ab8705c46 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -43,8 +43,7 @@ static void HandleJSObjectFinalized(JSRuntime* rt, JSValue val) { /// 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) { +static JSValue HandleJSPropertyGetterCallback(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { ExecutingContext* context = ExecutingContext::From(ctx); auto* object = static_cast<ScriptWrappable*>(JS_GetOpaque(obj, JSValueGetClassId(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); @@ -66,8 +65,12 @@ static JSValue HandleJSPropertyGetterCallback(JSContext *ctx, JSValueConst obj, /// 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) { +static int HandleJSPropertySetterCallback(JSContext* ctx, + JSValueConst obj, + JSAtom atom, + JSValueConst value, + JSValueConst receiver, + int flags) { auto* object = static_cast<ScriptWrappable*>(JS_GetOpaque(obj, JSValueGetClassId(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); @@ -82,7 +85,7 @@ static int HandleJSPropertySetterCallback(JSContext *ctx, JSValueConst obj, JSAt /// 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) { +static int HandleJSPropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSAtom atom) { auto* object = static_cast<ScriptWrappable*>(JS_GetOpaque(obj, JSValueGetClassId(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); @@ -108,12 +111,14 @@ void ScriptWrappable::InitializeQuickJSObject() { 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)) { + 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)) { + if (UNLIKELY(wrapper_type_info->indexed_property_getter_handler_ != nullptr || + wrapper_type_info->string_property_setter_handler_ != nullptr)) { exotic_methods->set_property = HandleJSPropertySetterCallback; } diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 6af6605bc8..59d738865b 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -65,7 +65,8 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { JSValue GetJSObject() const; JSValue jsObject_{JS_NULL}; // Indicate this JSObject are created by MakeGarbageCollected traits and no one had used it. - // There are extra one reference count when JSObject are created by MakeGarbageCollected and needs to be special handled by cppgc. + // There are extra one reference count when JSObject are created by MakeGarbageCollected and needs to be special + // handled by cppgc. mutable bool fresh_{false}; JSContext* ctx_{nullptr}; JSRuntime* runtime_{nullptr}; diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 7968606519..e8bb62d88f 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -57,7 +57,7 @@ using StringPropertySetterHandler = bool (*)(JSContext* ctx, JSValueConst obj, J // Callback when check property exist on object. // exp: 'hello' in obj; -using StringPropertyCheckerHandler = bool (*)(JSContext *ctx, JSValueConst obj, JSAtom atom); +using StringPropertyCheckerHandler = bool (*)(JSContext* ctx, JSValueConst obj, JSAtom atom); // This struct provides a way to store a bunch of information that is helpful // when creating quickjs objects. Each quickjs bindings class has exactly one static diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 5be997a454..fd7c34e113 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -17,7 +17,7 @@ namespace kraken { Element::Element(const AtomicString& tag_name, Document* document, Node::ConstructionType construction_type) : ContainerNode(document, construction_type), tag_name_(tag_name) {} -ElementAttributes & Element::EnsureElementAttributes() { +ElementAttributes& Element::EnsureElementAttributes() { if (attributes_ == nullptr) { attributes_.Initialize(ElementAttributes::Create(this)); } diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index f9bd0ac5c5..e62eacab16 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -29,7 +29,7 @@ TEST(EventTarget, addEventListener) { EXPECT_EQ(errorCalled, false); } - TEST(EventTarget, removeEventListener) { +TEST(EventTarget, removeEventListener) { bool static errorCalled = false; bool static logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index d03c983a63..dc5f73258c 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -415,8 +415,7 @@ Node::Node(ExecutingContext* context, TreeScope* tree_scope, ConstructionType ty next_(nullptr), node_data_(nullptr) {} -Node::~Node() { -} +Node::~Node() {} void Node::Trace(GCVisitor* visitor) const { visitor->Trace(previous_); diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 1d21f4775b..efd593bf6b 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -98,11 +98,19 @@ class Node : public EventTarget { [[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 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]] 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); @@ -250,7 +258,9 @@ class Node : public EventTarget { kOther = 3 << kDOMNodeTypeShift, }; - [[nodiscard]] FORCE_INLINE DOMNodeType GetDOMNodeType() const { return static_cast<DOMNodeType>(node_flags_ & kDOMNodeTypeMask); } + [[nodiscard]] FORCE_INLINE DOMNodeType GetDOMNodeType() const { + return static_cast<DOMNodeType>(node_flags_ & kDOMNodeTypeMask); + } enum class ElementNamespaceType : uint32_t { kHTML = 0, diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 37859be84d..bd38c62fa9 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -144,29 +144,31 @@ TEST(Node, replaceBody) { errorCalled = true; }); auto context = bridge->getContext(); -// const char* code = "let newbody = document.createElement('body'); document.documentElement.replaceChild(newbody, document.body)"; + // 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); EXPECT_EQ(errorCalled, false); } // -//TEST(Node, cloneNode) { +// 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; @@ -186,34 +188,35 @@ TEST(Node, replaceBody) { // EXPECT_EQ(logCalled, true); //} // -//TEST(Node, nestedNode) { +// 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 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 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 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); +// 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' +// console.log( +// div2.firstChild.getAttribute('id') === 'child123', div2.firstChild.style.width === '10px', +// div2.firstChild.style.height === '10px' //); //)"; // From ba4cf4cae5ffd5b72fb10860e679338640a7fb57 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Thu, 28 Apr 2022 00:49:17 +0800 Subject: [PATCH 106/498] fix: fix member mutation scope may be nested. --- bridge/bindings/qjs/atomic_string_test.cc | 2 ++ bridge/bindings/qjs/cppgc/member.h | 11 +++--- bridge/bindings/qjs/cppgc/mutation_scope.cc | 36 +++++++------------ bridge/bindings/qjs/cppgc/mutation_scope.h | 13 ++++--- bridge/bindings/qjs/script_value.cc | 4 ++- bridge/core/dom/document_test.cc | 2 +- bridge/core/executing_context.cc | 13 +++++++ bridge/core/executing_context.h | 13 ++++--- .../code_generator/src/idl/generateSource.ts | 2 +- .../static/idl_templates/interface.cc.tpl | 6 ++-- 10 files changed, 54 insertions(+), 48 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string_test.cc b/bridge/bindings/qjs/atomic_string_test.cc index 997f53ba4b..e9ba3e389a 100644 --- a/bridge/bindings/qjs/atomic_string_test.cc +++ b/bridge/bindings/qjs/atomic_string_test.cc @@ -67,6 +67,8 @@ TEST(AtomicString, ToQuickJS) { 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); }); } diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index ce92dbdba2..ec58ba32f9 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -48,8 +48,9 @@ class Member { if (raw_ == nullptr) return; auto* wrappable = To<ScriptWrappable>(raw_); - if (wrappable->GetExecutingContext()->HasMutationScope()) { - wrappable->GetExecutingContext()->mutationScope().RecordFree(wrappable); + // Record the free operation to avoid JSObject had been freed immediately. + if (LIKELY(wrappable->GetExecutingContext()->HasMutationScope())) { + wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); } else { JS_FreeValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); } @@ -102,11 +103,7 @@ class Member { // This JSObject was created just now and used at first time. // Because there are already one reference count when JSObject created, so we skip duplicate. if (!p->fresh()) { - if (wrappable->GetExecutingContext()->HasMutationScope()) { - wrappable->GetExecutingContext()->mutationScope().RecordDup(wrappable); - } else { - JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); - } + JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); } // This object had been used, no long fresh at all. p->MakeOld(); diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.cc b/bridge/bindings/qjs/cppgc/mutation_scope.cc index 5d79a68b19..1679eff203 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.cc +++ b/bridge/bindings/qjs/cppgc/mutation_scope.cc @@ -7,46 +7,36 @@ namespace kraken { -MutationScope::MutationScope(ExecutingContext* context) : context_(context) { - assert(!context->HasMutationScope()); +MemberMutationScope::MemberMutationScope(ExecutingContext* context) : context_(context) { context->SetMutationScope(*this); } -MutationScope::~MutationScope() { +MemberMutationScope::~MemberMutationScope() { ApplyRecord(); context_->ClearMutationScope(); } -void MutationScope::RecordDup(ScriptWrappable* wrappable) { - if (mutation_records_.count(wrappable) == 0) { - mutation_records_.insert(std::make_pair(wrappable, 0)); - } - mutation_records_[wrappable]++; +void MemberMutationScope::SetParent(MemberMutationScope* parent_scope) { + assert(parent_scope_ == nullptr); + parent_scope_ = parent_scope; } -void MutationScope::RecordFree(ScriptWrappable* wrappable) { +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 MutationScope::ApplyRecord() { +void MemberMutationScope::ApplyRecord() { JSContext* ctx = context_->ctx(); for (auto& entry : mutation_records_) { - if (entry.second == 0) { - continue; - } - if (entry.second > 0) { - for (int i = 0; i < entry.second; i++) { - JS_DupValue(ctx, entry.first->ToQuickJSUnsafe()); - } - continue; - } else { - for (int i = 0; i < -entry.second; i++) { - JS_FreeValue(ctx, entry.first->ToQuickJSUnsafe()); - } - continue; + for (int i = 0; i < -entry.second; i++) { + JS_FreeValue(ctx, entry.first->ToQuickJSUnsafe()); } } } diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.h b/bridge/bindings/qjs/cppgc/mutation_scope.h index 7532d8bbec..14fefbc593 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.h +++ b/bridge/bindings/qjs/cppgc/mutation_scope.h @@ -17,20 +17,23 @@ class ScriptWrappable; /** * A stack-allocated class that record all members mutations in stack scope. */ -class MutationScope { +class MemberMutationScope { KRAKEN_DISALLOW_NEW(); public: - MutationScope() = delete; - explicit MutationScope(ExecutingContext* context); - ~MutationScope(); + MemberMutationScope() = delete; + explicit MemberMutationScope(ExecutingContext* context); + ~MemberMutationScope(); + + void SetParent(MemberMutationScope* parent_scope); + [[nodiscard]] MemberMutationScope* Parent() const; - void RecordDup(ScriptWrappable* wrappable); void RecordFree(ScriptWrappable* wrappable); private: void ApplyRecord(); + MemberMutationScope* parent_scope_{nullptr}; ExecutingContext* context_; std::unordered_map<ScriptWrappable*, int> mutation_records_; }; diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 903ff2d539..170b397006 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -66,12 +66,14 @@ JSValue ScriptValue::QJSValue() const { } ScriptValue ScriptValue::ToJSONStringify(ExceptionState* exception) const { - ScriptValue result = ScriptValue(ctx_, JS_JSONStringify(ctx_, value_, JS_NULL, JS_NULL)); + 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; } diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index e65337e8f8..00b37833bb 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -33,7 +33,7 @@ TEST(Document, body) { bool static logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "<body>"); + EXPECT_STREQ(message.c_str(), "<body/>"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 105f510e9b..2b575645bb 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -360,6 +360,19 @@ ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { return &module_callbacks_; } +void ExecutingContext::SetMutationScope(MemberMutationScope& mutation_scope) { + // MemberMutationScope may be called by other MemberMutationScope in the call stack. + // Should save the tree corresponding to the call stack. + if (active_mutation_scope != nullptr) { + mutation_scope.SetParent(active_mutation_scope); + } + active_mutation_scope = &mutation_scope; +} + +void ExecutingContext::ClearMutationScope() { + active_mutation_scope = active_mutation_scope->Parent(); +} + // PendingPromises* ExecutingContext::PendingPromises() { // return &pending_promises_; //} diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 17b8f5405f..97f7efba27 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -39,7 +39,7 @@ struct NativeByteCode { class ExecutingContext; class Document; -class MutationScope; +class MemberMutationScope; using JSExceptionHandler = std::function<void(ExecutingContext* context, const char* message)>; @@ -91,13 +91,12 @@ class ExecutingContext { // Get current script state. ScriptState* GetScriptState() { return &script_state_; } - void SetMutationScope(MutationScope& mutation_scope) { active_mutation_scope = &mutation_scope; } + void SetMutationScope(MemberMutationScope& mutation_scope); bool HasMutationScope() const { return active_mutation_scope != nullptr; } - MutationScope& mutationScope() const { - assert(active_mutation_scope != nullptr); - return *active_mutation_scope; + MemberMutationScope* mutationScope() const { + return active_mutation_scope; } - void ClearMutationScope() { active_mutation_scope = nullptr; } + void ClearMutationScope(); FORCE_INLINE Document* document() { return document_; }; FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; @@ -144,7 +143,7 @@ class ExecutingContext { std::unique_ptr<DartMethodPointer> dart_method_ptr_ = std::make_unique<DartMethodPointer>(); RejectedPromises rejected_promises_; PendingPromises pending_promises_; - MutationScope* active_mutation_scope{nullptr}; + MemberMutationScope* active_mutation_scope{nullptr}; }; class ObjectProperty { diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index fd332ac4e5..e8aa0e2849 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -290,7 +290,7 @@ function generateFunctionBody(blob: IDLBlob, declare: FunctionDeclaration, optio ExceptionState exception_state; ${returnValueInit} ExecutingContext* context = ExecutingContext::From(ctx); - MutationScope scope{ExecutingContext::From(ctx)}; + MemberMutationScope scope{ExecutingContext::From(ctx)}; do { // Dummy loop for use of 'break'. ${addIndent(callBody, 4)} diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index cff320abf3..12d996249d 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -34,7 +34,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob bool QJS<%= className %>::IndexedPropertySetterCallback(JSContext* ctx, JSValueConst obj, uint32_t index, JSValueConst value) { auto* self = toScriptWrappable<NodeList>(obj); ExceptionState exception_state; - MutationScope scope{ExecutingContext::From(ctx)}; + MemberMutationScope scope{ExecutingContext::From(ctx)}; bool success = self->SetItem(index, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; @@ -45,7 +45,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob bool QJS<%= className %>::StringPropertySetterCallback(JSContext* ctx, JSValueConst obj, JSAtom key, JSValueConst value) { auto* self = toScriptWrappable<NodeList>(obj); ExceptionState exception_state; - MutationScope scope{ExecutingContext::From(ctx)}; + MemberMutationScope scope{ExecutingContext::From(ctx)}; bool success = self->SetItem(key, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; @@ -91,7 +91,7 @@ static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst if (exception_state.HasException()) { return exception_state.ToQuickJS(); } - MutationScope scope{ExecutingContext::From(ctx)}; + MemberMutationScope scope{ExecutingContext::From(ctx)}; <%= blob.filename %>->set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(v, exception_state); if (exception_state.HasException()) { From eb1c38a9569ca218bd2c3146130a901abfcc8da9 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Thu, 28 Apr 2022 08:14:07 +0800 Subject: [PATCH 107/498] fix: fix HTMLUnknownElement constructor not register. --- bridge/bindings/qjs/binding_initializer.cc | 2 ++ bridge/core/dom/document_test.cc | 16 ++++++++-------- bridge/core/html/html_unknown_element.d.ts | 4 +++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index d2992a9c28..ceea19fbf2 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -19,6 +19,7 @@ #include "qjs_html_element.h" #include "qjs_html_head_element.h" #include "qjs_html_html_element.h" +#include "qjs_html_unknown_element.h" #include "qjs_module_manager.h" #include "qjs_node.h" #include "qjs_node_list.h" @@ -47,6 +48,7 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLHeadElement::Install(context); QJSHTMLBodyElement::Install(context); QJSHTMLHtmlElement::Install(context); + QJSHTMLUnknownElement::Install(context); // Legacy bindings, not standard. QJSElementAttributes::Install(context); diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 00b37833bb..79771fff05 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -149,7 +149,7 @@ TEST(Document, createElementShouldWorkWithMultipleContext) { kraken::KrakenPage* bridge1; - const char* code = "(() => { let img = document.createElement('div'); })();"; + const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); @@ -157,13 +157,13 @@ TEST(Document, createElementShouldWorkWithMultipleContext) { 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); - // } + + { + 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); diff --git a/bridge/core/html/html_unknown_element.d.ts b/bridge/core/html/html_unknown_element.d.ts index c2659304b2..f80b4a9947 100644 --- a/bridge/core/html/html_unknown_element.d.ts +++ b/bridge/core/html/html_unknown_element.d.ts @@ -1,3 +1,5 @@ import {HTMLElement} from "./html_element"; -export interface HTMLUnknownElement extends HTMLElement {} +export interface HTMLUnknownElement extends HTMLElement { + new(): void; +} From 5e70f94589b36eece7d5907021cbaf8cad8572ca Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Thu, 28 Apr 2022 00:15:07 +0000 Subject: [PATCH 108/498] Committing clang-format changes --- bridge/core/executing_context.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 97f7efba27..9c21bd2a1c 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -93,9 +93,7 @@ class ExecutingContext { void SetMutationScope(MemberMutationScope& mutation_scope); bool HasMutationScope() const { return active_mutation_scope != nullptr; } - MemberMutationScope* mutationScope() const { - return active_mutation_scope; - } + MemberMutationScope* mutationScope() const { return active_mutation_scope; } void ClearMutationScope(); FORCE_INLINE Document* document() { return document_; }; From 0795a95e4d077d8c5d8b9bf98cf422e45d8cde8b Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Thu, 28 Apr 2022 17:04:13 +0800 Subject: [PATCH 109/498] feat: support Node.cloneNode. --- bridge/bindings/qjs/atomic_string.cc | 8 +++ bridge/bindings/qjs/exception_state.cc | 4 ++ bridge/bindings/qjs/exception_state.h | 5 ++ bridge/core/dom/container_node.cc | 11 +++++ bridge/core/dom/container_node.h | 3 ++ bridge/core/dom/document.cc | 1 + bridge/core/dom/document_fragment.cc | 9 ++-- bridge/core/dom/element.cc | 29 ++++++++++- bridge/core/dom/element.h | 11 +++++ bridge/core/dom/events/event_target.h | 1 + bridge/core/dom/node.d.ts | 2 +- bridge/core/dom/node_test.cc | 68 +++++++++++++------------- 12 files changed, 111 insertions(+), 41 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index 935e98d5dd..a2adccd7e8 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -97,6 +97,8 @@ AtomicString::AtomicString(const AtomicString& value) { } ctx_ = value.ctx_; runtime_ = value.runtime_; + length_ = value.length_; + kind_ = value.kind_; } AtomicString& AtomicString::operator=(const AtomicString& other) { @@ -105,6 +107,8 @@ AtomicString& AtomicString::operator=(const AtomicString& other) { } runtime_ = other.runtime_; ctx_ = other.ctx_; + length_ = other.length_; + kind_ = other.kind_; return *this; } @@ -114,6 +118,8 @@ AtomicString::AtomicString(AtomicString&& value) noexcept { } ctx_ = value.ctx_; runtime_ = value.runtime_; + length_ = value.length_; + kind_ = value.kind_; } AtomicString& AtomicString::operator=(AtomicString&& value) noexcept { @@ -122,6 +128,8 @@ AtomicString& AtomicString::operator=(AtomicString&& value) noexcept { } ctx_ = value.ctx_; runtime_ = value.runtime_; + length_ = value.length_; + kind_ = value.kind_; return *this; } diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index c62c523e18..c9695df238 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -35,6 +35,10 @@ bool ExceptionState::HasException() { return !JS_IsNull(exception_); } +ExceptionState& ExceptionState::ReturnThis() { + return *this; +} + JSValue ExceptionState::ToQuickJS() { return exception_; } diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index f618de8113..14310f2759 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -10,6 +10,8 @@ #include <string> #include "foundation/macros.h" +#define ASSERT_NO_EXCEPTION() ExceptionState().ReturnThis() + namespace kraken { enum ErrorType { TypeError, InternalError, RangeError, ReferenceError, SyntaxError }; @@ -23,6 +25,9 @@ class ExceptionState { void ThrowException(JSContext* ctx, ErrorType type, const std::string& message); void ThrowException(JSContext* ctx, JSValue exception); bool HasException(); + + ExceptionState& ReturnThis(); + JSValue ToQuickJS(); private: diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index e0e399ef62..dec2b0a5e7 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -257,6 +257,10 @@ Node* ContainerNode::AppendChild(Node* new_child, ExceptionState& exception_stat 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, @@ -318,6 +322,13 @@ void ContainerNode::RemoveChildren() { } } +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 ""; } diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index a4cd848319..eedeffe055 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -38,6 +38,7 @@ class ContainerNode : public Node { 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, @@ -45,6 +46,8 @@ class ContainerNode : public Node { void RemoveChildren(); + void CloneChildNodesFrom(const ContainerNode&, CloneChildrenFlag); + std::string nodeValue() const override; virtual bool ChildrenCanHaveStyle() const { return true; } diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 5440ac3f85..3024eeaeb4 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -125,6 +125,7 @@ bool Document::IsValidName(const AtomicString& name) { } Node* Document::Clone(Document&, CloneChildrenFlag) const { + assert(false); return nullptr; } diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index 5c1d7e102e..3a36472c53 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -32,11 +32,10 @@ std::string DocumentFragment::nodeValue() const { } Node* DocumentFragment::Clone(Document& factory, CloneChildrenFlag flag) const { - // ExceptionState exception_state; - // DocumentFragment* clone = Create(&factory, exception_state); - // if (flag != CloneChildrenFlag::kSkip) - // clone->CloneChildNodesFrom(*this, flag); - // return clone; + DocumentFragment* clone = Create(factory); + if (flag != CloneChildrenFlag::kSkip) + clone->CloneChildNodesFrom(*this, flag); + return clone; } bool DocumentFragment::ChildTypeAllowed(NodeType type) const { diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index fd7c34e113..2358aca2e4 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -136,6 +136,25 @@ std::string Element::nodeName() const { return tag_name_.ToStdString(); } +Element& Element::CloneWithChildren(CloneChildrenFlag flag, Document* document) const { + Element& clone = CloneWithoutAttributesAndChildren(document ? *document : GetDocument()); + // This will catch HTML elements in the wrong namespace that are not correctly + // copied. This is a sanity check as HTML overloads some of the DOM methods. + assert(IsHTMLElement() == clone.IsHTMLElement()); + + clone.CloneAttributesFrom(*this); + clone.CloneNonAttributePropertiesFrom(*this, flag); + clone.CloneChildNodesFrom(*this, flag); + return clone; +} + +Element& Element::CloneWithoutChildren(Document* document) const {} + +void Element::CloneAttributesFrom(const Element& other) { + if (other.attributes_ == nullptr) return; + EnsureElementAttributes().CopyWith(other.attributes_); +} + bool Element::HasEquivalentAttributes(const Element& other) const { return attributes_ != nullptr && other.attributes_ != nullptr && other.attributes_->IsEquivalent(*attributes_); } @@ -146,7 +165,15 @@ void Element::Trace(GCVisitor* visitor) const { } Node* Element::Clone(Document& factory, CloneChildrenFlag flag) const { - return nullptr; + if (flag == CloneChildrenFlag::kSkip) + return &CloneWithoutChildren(&factory); + Element* copy = &CloneWithChildren(flag, &factory); + return copy; +} + +Element& Element::CloneWithoutAttributesAndChildren(Document& factory) const { + KRAKEN_LOG(VERBOSE) << tagName().ToStdString(); + return *factory.createElement(tagName(), ASSERT_NO_EXCEPTION()); } class ElementSnapshotReader { diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index de3a2166b3..3bb31fdfe2 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -67,11 +67,21 @@ class Element : public ContainerNode { AtomicString tagName() const { return tag_name_; } std::string nodeName() const override; + + Element& CloneWithChildren(CloneChildrenFlag flag, Document* = nullptr) const; + Element& CloneWithoutChildren(Document* = nullptr) const; + NodeType nodeType() const override; bool ChildTypeAllowed(NodeType) const override; + // Clones attributes only. + void CloneAttributesFrom(const Element&); bool HasEquivalentAttributes(const Element& other) const; + // Step 5 of https://dom.spec.whatwg.org/#concept-node-clone + virtual void CloneNonAttributePropertiesFrom(const Element&, + CloneChildrenFlag) {} + void Trace(GCVisitor* visitor) const override; protected: @@ -79,6 +89,7 @@ class Element : public ContainerNode { // 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(); diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 0d94ef5742..401368d3fa 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -6,6 +6,7 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H +#include "foundation/logging.h" #include "bindings/qjs/cppgc/member.h" #include "bindings/qjs/js_event_listener.h" #include "bindings/qjs/qjs_function.h" diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts index 142f90d1a6..815faec3b5 100644 --- a/bridge/core/dom/node.d.ts +++ b/bridge/core/dom/node.d.ts @@ -56,7 +56,7 @@ interface Node extends EventTarget { /** * Returns a copy of node. If deep is true, the copy also includes the node's descendants. */ - cloneNode(deep?: boolean): Node; + cloneNode(deep?: boolean): NewObject<Node>; /** * Returns true if other is an inclusive descendant of node, and false otherwise. */ diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index bd38c62fa9..13fde1fa2e 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -151,43 +151,43 @@ TEST(Node, replaceBody) { EXPECT_EQ(errorCalled, false); } -// -// TEST(Node, cloneNode) { -// std::string code = R"( -// const div = document.createElement('div'); + + 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 div2 = div.cloneNode(true); -// document.body.appendChild(div2); -// -// div2.setAttribute('id', '456'); -// -// console.log(div.style.width == div2.style.height, div.getAttribute('id') == '1234', div2.getAttribute('id') == -// '456'); -//)"; -// -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "true true true"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// auto context = bridge->getContext(); -// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} -// + let str = '1234'; + div.setAttribute('id', str); + document.body.appendChild(div); + + const div2 = div.cloneNode(true); + document.body.appendChild(div2); + + div2.setAttribute('id', '456'); + + console.log(div.getAttribute('id') == '1234', div2.getAttribute('id') == + '456'); +)"; + + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "true true true"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->getContext(); + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} + // TEST(Node, nestedNode) { // std::string code = R"( // const div = document.createElement('div'); From 9a681600a3ee746fd591986bc2e64359982f2f99 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Thu, 28 Apr 2022 21:38:19 +0800 Subject: [PATCH 110/498] feat: add css style declaration. --- bridge/CMakeLists.txt | 4 + bridge/bindings/qjs/atomic_string.cc | 8 + bridge/bindings/qjs/atomic_string.h | 1 + bridge/bindings/qjs/binding_initializer.cc | 2 + bridge/bindings/qjs/wrapper_type_info.h | 1 + bridge/core/css/css_style_declaration.cc | 300 ------------------ bridge/core/css/css_style_declaration.h | 61 ---- .../core/css/legacy/css_style_declaration.cc | 126 ++++++++ .../css/legacy/css_style_declaration.d.ts | 14 + .../core/css/legacy/css_style_declaration.h | 45 +++ bridge/core/dom/container_node.cc | 3 - bridge/core/dom/element.cc | 33 +- bridge/core/dom/element.d.ts | 2 + bridge/core/dom/element.h | 4 + bridge/core/dom/events/event_target.cc | 2 +- bridge/core/dom/node_test.cc | 72 ++--- bridge/foundation/ui_command_buffer.cc | 16 +- bridge/foundation/ui_command_buffer.h | 8 +- .../scripts/code_generator/src/idl/utils.ts | 4 + .../static/idl_templates/interface.cc.tpl | 27 +- 20 files changed, 305 insertions(+), 428 deletions(-) delete mode 100644 bridge/core/css/css_style_declaration.cc delete mode 100644 bridge/core/css/css_style_declaration.h create mode 100644 bridge/core/css/legacy/css_style_declaration.cc create mode 100644 bridge/core/css/legacy/css_style_declaration.d.ts create mode 100644 bridge/core/css/legacy/css_style_declaration.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index f1419ad854..85e19b5c34 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -279,6 +279,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/frame/module_callback.h core/frame/module_callback_coordinator.cc core/frame/module_callback_coordinator.h + core/css/legacy/css_style_declaration.cc + core/css/legacy/css_style_declaration.h core/dom/frame_request_callback_collection.cc core/dom/frame_request_callback_collection.h core/dom/events/event_listener.h @@ -415,6 +417,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_document_fragment.h out/qjs_bounding_client_rect.cc out/qjs_bounding_client_rect.h + out/qjs_css_style_declaration.cc + out/qjs_css_style_declaration.h out/qjs_text.cc out/qjs_text.h out/qjs_node_list.cc diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index a2adccd7e8..8b4c117cad 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -52,6 +52,7 @@ AtomicString::AtomicString(JSContext* ctx, const std::string& string) atom_(JS_NewAtom(ctx, string.c_str())), kind_(GetStringKind(string)), length_(string.size()) {} + AtomicString::AtomicString(JSContext* ctx, JSValue value) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_ValueToAtom(ctx, value)) { if (JS_IsString(value)) { @@ -60,6 +61,13 @@ AtomicString::AtomicString(JSContext* ctx, JSValue value) } } +AtomicString::AtomicString(JSContext* ctx, JSAtom atom): runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_DupAtom(ctx, atom)) { + JSValue string = JS_AtomToValue(ctx, atom); + kind_ = GetStringKind(string); + length_ = JS_VALUE_GET_STRING(string)->len; + JS_FreeValue(ctx, string); +} + bool AtomicString::IsNull() const { return atom_ == JS_ATOM_NULL; } diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 741c8ace87..ed7ed1b13b 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -36,6 +36,7 @@ class AtomicString { AtomicString() = default; AtomicString(JSContext* ctx, const std::string& string); AtomicString(JSContext* ctx, JSValue value); + AtomicString(JSContext* ctx, JSAtom atom); ~AtomicString() { JS_FreeAtomRT(runtime_, atom_); }; // Return the undefined string value from atom key. diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index ceea19fbf2..5ed0cc45cd 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -21,6 +21,7 @@ #include "qjs_html_html_element.h" #include "qjs_html_unknown_element.h" #include "qjs_module_manager.h" +#include "qjs_css_style_declaration.h" #include "qjs_node.h" #include "qjs_node_list.h" #include "qjs_text.h" @@ -49,6 +50,7 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLBodyElement::Install(context); QJSHTMLHtmlElement::Install(context); QJSHTMLUnknownElement::Install(context); + QJSCSSStyleDeclaration::Install(context); // Legacy bindings, not standard. QJSElementAttributes::Install(context); diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index e8bb62d88f..02c5f075ad 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -35,6 +35,7 @@ enum { JS_CLASS_HTML_HEAD_ELEMENT, JS_CLASS_HTML_HTML_ELEMENT, JS_CLASS_HTML_UNKNOWN_ELEMENT, + JS_CLASS_CSS_STYLE_DECLARATION, JS_CLASS_CUSTOM_CLASS_INIT_COUNT /* last entry for predefined classes */ }; diff --git a/bridge/core/css/css_style_declaration.cc b/bridge/core/css/css_style_declaration.cc deleted file mode 100644 index d67e9ced4b..0000000000 --- a/bridge/core/css/css_style_declaration.cc +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "css_style_declaration.h" -#include "event_target.h" - -namespace kraken { - -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<std::string, std::string> propertyCache{}; - - if (propertyCache.count(propertyName) > 0) { - return propertyCache[propertyName]; - } - - std::vector<char> 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<EventTargetInstance*>(JS_GetOpaque(eventTargetValue, EventTarget::classId(eventTargetValue))); - auto style = new StyleDeclaration(this, eventTargetInstance); - return style->jsObject; -} - -JSClassID CSSStyleDeclaration::kCSSStyleDeclarationClassId{0}; - -CSSStyleDeclaration::CSSStyleDeclaration(ExecutionContext* context) : HostClass(context, "CSSStyleDeclaration") { - std::call_once(kinitCSSStyleDeclarationFlag, []() { JS_NewClassID(&kCSSStyleDeclarationClassId); }); -} - -IMPL_FUNCTION(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<StyleDeclaration*>(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->setProperty(propertyName, propertyValue); - - JS_FreeCString(ctx, cPropertyName); - - return JS_UNDEFINED; -} - -IMPL_FUNCTION(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<StyleDeclaration*>(JS_GetOpaque(this_val, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - - JSValue propertyNameValue = argv[0]; - - const char* cPropertyName = JS_ToCString(ctx, propertyNameValue); - std::string propertyName = std::string(cPropertyName); - - instance->removeProperty(propertyName); - - JS_FreeCString(ctx, cPropertyName); - - return JS_UNDEFINED; -} - -IMPL_FUNCTION(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<StyleDeclaration*>(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->getPropertyValue(propertyName); - JS_FreeCString(ctx, cPropertyName); - return returnValue; -} - -StyleDeclaration::StyleDeclaration(CSSStyleDeclaration* cssStyleDeclaration, EventTargetInstance* ownerEventTarget) - : Instance(cssStyleDeclaration, - "CSSStyleDeclaration", - &m_exoticMethods, - CSSStyleDeclaration::kCSSStyleDeclarationClassId, - finalize), - ownerEventTarget(ownerEventTarget) { - JS_DupValue(m_ctx, ownerEventTarget->jsObject); -} - -StyleDeclaration::~StyleDeclaration() {} - -bool StyleDeclaration::setProperty(std::string& name, JSValue value) { - name = parseJavaScriptCSSPropertyName(name); - - m_properties[name] = jsValueToStdString(m_ctx, value); - - if (ownerEventTarget != nullptr) { - std::unique_ptr<NativeString> args_01 = stringToNativeString(name); - std::unique_ptr<NativeString> args_02 = jsValueToNativeString(m_ctx, value); - m_context->uiCommandBuffer()->addCommand(ownerEventTarget->eventTargetId(), UICommand::setStyle, *args_01, *args_02, - nullptr); - } - - return true; -} - -void StyleDeclaration::removeProperty(std::string& name) { - name = parseJavaScriptCSSPropertyName(name); - - if (m_properties.count(name) == 0) { - return; - } - - m_properties.erase(name); - - if (ownerEventTarget != nullptr) { - std::unique_ptr<NativeString> args_01 = stringToNativeString(name); - std::unique_ptr<NativeString> args_02 = jsValueToNativeString(m_ctx, JS_NULL); - m_context->uiCommandBuffer()->addCommand(ownerEventTarget->eventTargetId(), UICommand::setStyle, *args_01, *args_02, - nullptr); - } -} - -JSValue StyleDeclaration::getPropertyValue(std::string& name) { - name = parseJavaScriptCSSPropertyName(name); - - if (m_properties.count(name) > 0) { - return JS_NewString(m_ctx, m_properties[name].c_str()); - } - - return JS_NewString(m_ctx, ""); -} - -void parseRules(std::string& source, ParseRuleCallback callback, void* context) { - uint32_t idx = 0; - uint32_t start = idx; - uint32_t end = source.length(); - bool is_in_annotation = false; - bool is_in_search_key = false; - - std::string key; - std::string value; - - while (idx <= end) { - char c = source[idx]; - - if (c == ' ') { - start++; - } else if (c == '/' && source[idx + 1] == '*') { - is_in_annotation = true; - } else if (c == '*' && source[idx + 1] == '/') { - is_in_annotation = false; - } else if (c == ':' && !is_in_annotation && !is_in_search_key) { - key = source.substr(start, idx - start); - start = idx + 1; - is_in_search_key = true; - } else if ((c == ';' || idx == end) && !is_in_annotation) { - value = source.substr(start, idx - start); - start = idx + 1; - callback(context, key, value); - key = ""; - is_in_search_key = false; - } - - idx++; - } -} - -void StyleDeclaration::setCssText(std::string& cssText) { - parseRules( - cssText, - [](void* p, std::string& key, std::string& value) { - auto* style = static_cast<StyleDeclaration*>(p); - style->setProperty(key, value); - }, - this); -} - -// TODO: add support for annotation CSS styleSheets. -std::string StyleDeclaration::toString() { - if (m_properties.empty()) - return ""; - - std::string s; - - for (auto& item : m_properties) { - s += item.first + ": " + item.second + ";"; - } - - s += "\""; - return s; -} - -void StyleDeclaration::copyWith(StyleDeclaration* style) { - for (auto& item : style->m_properties) { - m_properties[item.first] = item.second; - } -} - -bool StyleDeclaration::hasObjectProperty(JSContext* ctx, JSValue obj, JSAtom atom) { - auto* style = static_cast<StyleDeclaration*>(JS_GetOpaque(obj, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - const char* cname = JS_AtomToCString(ctx, atom); - std::string name = std::string(cname); - bool match = style->m_properties.count(name) >= 0; - JS_FreeCString(ctx, cname); - return match; -} - -// Property Accessors -int StyleDeclaration::setObjectProperty(JSContext* ctx, - JSValue obj, - JSAtom atom, - JSValue value, - JSValue receiver, - int flags) { - auto* style = - static_cast<StyleDeclaration*>(JS_GetOpaque(receiver, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - const char* cname = JS_AtomToCString(ctx, atom); - std::string name = std::string(cname); - bool success = style->setProperty(name, value); - JS_FreeCString(ctx, cname); - return success; -} - -JSValue StyleDeclaration::getObjectProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { - auto* styleInstance = static_cast<EventTargetInstance*>(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<StyleDeclaration*>(JS_GetOpaque(receiver, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - const char* cname = JS_AtomToCString(ctx, atom); - std::string name = std::string(cname); - JSValue result = style->getPropertyValue(name); - JS_FreeCString(ctx, cname); - return result; -} - -JSClassExoticMethods StyleDeclaration::m_exoticMethods{ - nullptr, nullptr, nullptr, nullptr, nullptr, getObjectProperty, setObjectProperty, -}; - -void StyleDeclaration::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) { - Instance::trace(rt, val, mark_func); - // We should tel gc style relies on element - JS_MarkValue(rt, ownerEventTarget->jsObject, mark_func); -} - -} // namespace kraken diff --git a/bridge/core/css/css_style_declaration.h b/bridge/core/css/css_style_declaration.h deleted file mode 100644 index a6f25fab68..0000000000 --- a/bridge/core/css/css_style_declaration.h +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_CSS_STYLE_DECLARATION_H -#define KRAKENBRIDGE_CSS_STYLE_DECLARATION_H - -#include "bindings/qjs/cppgc/garbage_collected.h" -#include "bindings/qjs/dom/event_target.h" -#include "bindings/qjs/macros.h" - -namespace kraken { - -void bindCSSStyleDeclaration(std::unique_ptr<ExecutionContext>& context); - -template <typename CharacterType> -inline bool isASCIILower(CharacterType character) { - return character >= 'a' && character <= 'z'; -} - -template <typename CharacterType> -inline CharacterType toASCIIUpper(CharacterType character) { - return character & ~(isASCIILower(character) << 5); -} - -class CSSStyleDeclaration : public GarbageCollected<CSSStyleDeclaration> { - public: - static JSClassID classId; - static CSSStyleDeclaration* create(JSContext* ctx); - static JSValue constructor(ExecutionContext* context); - static JSValue prototype(ExecutionContext* context); - - CSSStyleDeclaration(); - - bool setProperty(std::string& name, JSValue value); - void removeProperty(std::string& name); - JSValue getPropertyValue(std::string& name); - void setCssText(std::string& cssText); - std::string toString(); - void copyWith(CSSStyleDeclaration* style); - - DEFINE_FUNCTION(setProperty); - DEFINE_FUNCTION(removeProperty); - DEFINE_FUNCTION(getPropertyValue); - - private: - static int hasObjectProperty(JSContext* ctx, JSValueConst obj, JSAtom atom); - static int setObjectProperty(JSContext* ctx, - JSValueConst obj, - JSAtom atom, - JSValueConst value, - JSValueConst receiver, - int flags); - static JSValue getObjectProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); - std::unordered_map<std::string, std::string> m_properties; -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_CSS_STYLE_DECLARATION_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..a1cddcf172 --- /dev/null +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "css_style_declaration.h" +#include <vector> +#include "core/dom/element.h" +#include "core/executing_context.h" + +namespace kraken { + +template <typename CharacterType> +inline bool isASCIILower(CharacterType character) { + return character >= 'a' && character <= 'z'; +} + +template <typename CharacterType> +inline CharacterType toASCIIUpper(CharacterType character) { + return character & ~(isASCIILower(character) << 5); +} + +static std::string parseJavaScriptCSSPropertyName(std::string& propertyName) { + static std::unordered_map<std::string, std::string> propertyCache{}; + + if (propertyCache.count(propertyName) > 0) { + return propertyCache[propertyName]; + } + + std::vector<char> 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) { + +} + +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) {} + +AtomicString CSSStyleDeclaration::removeProperty(const AtomicString& key, ExceptionState& exception_state) { + std::string propertyName = key.ToStdString(); + return InternalRemoveProperty(propertyName); +} + +AtomicString CSSStyleDeclaration::InternalGetPropertyValue(std::string& name) { + name = parseJavaScriptCSSPropertyName(name); + + if (LIKELY(properties_.count(name) > 0)) { + return properties_[name]; + } + + return AtomicString::Empty(ctx()); +} + +bool CSSStyleDeclaration::InternalSetProperty(std::string& name, const AtomicString& value) { + name = parseJavaScriptCSSPropertyName(name); + + properties_[name] = value; + + std::unique_ptr<NativeString> args_01 = stringToNativeString(name); + std::unique_ptr<NativeString> args_02 = value.ToNativeString(); + GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::setStyle, args_01.release(), + args_02.release(), nullptr); + + return true; +} + +AtomicString CSSStyleDeclaration::InternalRemoveProperty(std::string& name) { + name = parseJavaScriptCSSPropertyName(name); + + if (UNLIKELY(properties_.count(name) == 0)) { + return AtomicString::Empty(ctx()); + } + + properties_.erase(name); + + std::unique_ptr<NativeString> args_01 = stringToNativeString(name); + std::unique_ptr<NativeString> args_02 = jsValueToNativeString(ctx(), JS_NULL); + GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::setStyle, args_01.release(), + args_02.release(), nullptr); +} + +} // namespace kraken diff --git a/bridge/core/css/legacy/css_style_declaration.d.ts b/bridge/core/css/legacy/css_style_declaration.d.ts new file mode 100644 index 0000000000..25cf36f1d3 --- /dev/null +++ b/bridge/core/css/legacy/css_style_declaration.d.ts @@ -0,0 +1,14 @@ +export interface CSSStyleDeclaration { + // @ts-ignore + readonly length: int64; + // @ts-ignore + getPropertyValue(property: string): string; + // @ts-ignore + setProperty(property: string, value: string): void; + // @ts-ignore + removeProperty(property: string): string; + + [prop: string]: string; + + new(): void; +} diff --git a/bridge/core/css/legacy/css_style_declaration.h b/bridge/core/css/legacy/css_style_declaration.h new file mode 100644 index 0000000000..dccef94c86 --- /dev/null +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_CSS_STYLE_DECLARATION_H +#define KRAKENBRIDGE_CSS_STYLE_DECLARATION_H + +#include <unordered_map> +#include "bindings/qjs/cppgc/member.h" +#include "bindings/qjs/script_wrappable.h" +#include "bindings/qjs/exception_state.h" +#include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/script_value.h" + +namespace kraken { + +class Element; + +class CSSStyleDeclaration : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + public: + using ImplType = CSSStyleDeclaration*; + static CSSStyleDeclaration* Create(ExecutingContext* context, ExceptionState& exception_state); + explicit CSSStyleDeclaration(ExecutingContext* context, int64_t owner_element_target_id); + + AtomicString item(const AtomicString& key, ExceptionState& exception_state); + bool SetItem(const AtomicString& key, const AtomicString& value, ExceptionState& exception_state); + int64_t length() const; + + AtomicString getPropertyValue(const AtomicString& key, ExceptionState& exception_state); + void setProperty(const AtomicString& key, const AtomicString& value, ExceptionState& exception_state); + AtomicString removeProperty(const AtomicString& key, ExceptionState& exception_state); + private: + AtomicString InternalGetPropertyValue(std::string& name); + bool InternalSetProperty(std::string& name, const AtomicString& value); + AtomicString InternalRemoveProperty(std::string& name); + std::unordered_map<std::string, AtomicString> properties_; + int32_t owner_element_target_id_; +}; + + +} // namespace kraken + +#endif // KRAKENBRIDGE_CSS_STYLE_DECLARATION_H diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index dec2b0a5e7..0bfd15cb97 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -430,9 +430,6 @@ void ContainerNode::NotifyNodeRemoved(Node& root) { } void ContainerNode::Trace(GCVisitor* visitor) const { - // for (Node& node : NodeTraversal::ChildrenOf(*this)) { - // visitor->Trace(&node); - // } visitor->Trace(first_child_); visitor->Trace(last_child_); diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 2358aca2e4..d0c48f09a0 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -54,7 +54,7 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value, std::unique_ptr<NativeString> args_01 = name.ToNativeString(); std::unique_ptr<NativeString> args_02 = value.ToNativeString(); - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), static_cast<int32_t>(UICommand::setAttribute), + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::setAttribute, args_01.release(), args_02.release(), nullptr); } @@ -136,10 +136,21 @@ std::string Element::nodeName() const { return tag_name_.ToStdString(); } +CSSStyleDeclaration* Element::style() { + if (!IsStyledElement()) + return nullptr; + return &EnsureCSSStyleDeclaration(); +} + +CSSStyleDeclaration& Element::EnsureCSSStyleDeclaration() { + if (cssom_wrapper_ == nullptr) { + cssom_wrapper_.Initialize(MakeGarbageCollected<CSSStyleDeclaration>(GetExecutingContext(), eventTargetId())); + } + return *cssom_wrapper_; +} + Element& Element::CloneWithChildren(CloneChildrenFlag flag, Document* document) const { Element& clone = CloneWithoutAttributesAndChildren(document ? *document : GetDocument()); - // This will catch HTML elements in the wrong namespace that are not correctly - // copied. This is a sanity check as HTML overloads some of the DOM methods. assert(IsHTMLElement() == clone.IsHTMLElement()); clone.CloneAttributesFrom(*this); @@ -148,10 +159,20 @@ Element& Element::CloneWithChildren(CloneChildrenFlag flag, Document* document) return clone; } -Element& Element::CloneWithoutChildren(Document* document) const {} +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) return; + if (other.attributes_ == nullptr) + return; EnsureElementAttributes().CopyWith(other.attributes_); } @@ -161,6 +182,7 @@ bool Element::HasEquivalentAttributes(const Element& other) const { void Element::Trace(GCVisitor* visitor) const { visitor->Trace(attributes_); + visitor->Trace(cssom_wrapper_); ContainerNode::Trace(visitor); } @@ -172,7 +194,6 @@ Node* Element::Clone(Document& factory, CloneChildrenFlag flag) const { } Element& Element::CloneWithoutAttributesAndChildren(Document& factory) const { - KRAKEN_LOG(VERBOSE) << tagName().ToStdString(); return *factory.createElement(tagName(), ASSERT_NO_EXCEPTION()); } diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index 0d9f38f7d7..a44ec2baf8 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -2,9 +2,11 @@ import {Node} from "./node"; import {Document} from "./document"; import {ScrollToOptions} from "./scroll_to_options"; import { ElementAttributes } from './legacy/element_attributes'; +import {CSSStyleDeclaration} from "../css/legacy/css_style_declaration"; interface Element extends Node { readonly attributes: ElementAttributes; + readonly style: CSSStyleDeclaration; readonly clientHeight: number; readonly clientLeft: number; diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 3bb31fdfe2..325865f8c4 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -10,6 +10,7 @@ #include "container_node.h" #include "legacy/bounding_client_rect.h" #include "legacy/element_attributes.h" +#include "core/css/legacy/css_style_declaration.h" #include "qjs_scroll_to_options.h" namespace kraken { @@ -67,6 +68,8 @@ class Element : public ContainerNode { AtomicString tagName() const { return tag_name_; } std::string nodeName() const override; + CSSStyleDeclaration* style(); + CSSStyleDeclaration& EnsureCSSStyleDeclaration(); Element& CloneWithChildren(CloneChildrenFlag flag, Document* = nullptr) const; Element& CloneWithoutChildren(Document* = nullptr) const; @@ -99,6 +102,7 @@ class Element : public ContainerNode { void _beforeUpdateId(JSValue oldIdValue, JSValue newIdValue); Member<ElementAttributes> attributes_; + Member<CSSStyleDeclaration> cssom_wrapper_; AtomicString tag_name_ = AtomicString::Empty(ctx()); }; diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index b384ba930d..9cf5e7975c 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -42,7 +42,7 @@ EventTarget* EventTarget::Create(ExecutingContext* context, ExceptionState& exce EventTarget::EventTarget(ExecutingContext* context) : BindingObject(context), ScriptWrappable(context->ctx()), - event_target_id_(global_event_target_id.fetch_add(std::memory_order_relaxed)) {} + event_target_id_(global_event_target_id++) {} bool EventTarget::addEventListener(const AtomicString& event_type, const std::shared_ptr<EventListener>& event_listener, diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 13fde1fa2e..19382aa61c 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -155,9 +155,9 @@ 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'; + div.style.width = '100px'; + div.style.height = '100px'; + div.style.backgroundColor = 'yellow'; let str = '1234'; div.setAttribute('id', str); document.body.appendChild(div); @@ -167,7 +167,7 @@ TEST(Node, replaceBody) { div2.setAttribute('id', '456'); - console.log(div.getAttribute('id') == '1234', div2.getAttribute('id') == + console.log(div.style.width == div2.style.height, div.getAttribute('id') == '1234', div2.getAttribute('id') == '456'); )"; @@ -188,51 +188,51 @@ TEST(Node, replaceBody) { EXPECT_EQ(logCalled, true); } -// TEST(Node, nestedNode) { -// std::string code = R"( -// const div = document.createElement('div'); + 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'); + 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'); + 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); -// + 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' //); -//)"; -// -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "true true true"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// auto context = bridge->getContext(); -// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} +)"; + + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "true true true"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->getContext(); + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 8abf6f4f57..2e623ec616 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -11,17 +11,17 @@ namespace kraken { UICommandBuffer::UICommandBuffer(ExecutingContext* context) : m_context(context) {} -void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr, bool batchedUpdate) { +void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr, bool batchedUpdate) { if (batchedUpdate) { m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); update_batched = true; } - UICommandItem item{id, type, nativePtr}; + UICommandItem item{id, static_cast<int32_t>(type), nativePtr}; queue.emplace_back(item); } -void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { +void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); @@ -29,11 +29,11 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, void* nativePtr) { update_batched = true; } - UICommandItem item{id, type, nativePtr}; + UICommandItem item{id, static_cast<int32_t>(type), nativePtr}; queue.emplace_back(item); } -void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString* args_01, void* nativePtr) { +void UICommandBuffer::addCommand(int32_t id, UICommand type, NativeString* args_01, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); @@ -41,12 +41,12 @@ void UICommandBuffer::addCommand(int32_t id, int32_t type, NativeString* args_01 #endif } - UICommandItem item{id, type, args_01, nativePtr}; + UICommandItem item{id, static_cast<int32_t>(type), args_01, nativePtr}; queue.emplace_back(item); } void UICommandBuffer::addCommand(int32_t id, - int32_t type, + UICommand type, NativeString* args_01, NativeString* args_02, void* nativePtr) { @@ -56,7 +56,7 @@ void UICommandBuffer::addCommand(int32_t id, update_batched = true; } #endif - UICommandItem item{id, type, args_01, args_02, nativePtr}; + UICommandItem item{id, static_cast<int32_t>(type), args_01, args_02, nativePtr}; queue.emplace_back(item); } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 7bc4064e9e..30ec5139f1 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -61,10 +61,10 @@ class UICommandBuffer { public: UICommandBuffer() = delete; explicit UICommandBuffer(ExecutingContext* context); - 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); + void addCommand(int32_t id, UICommand type, void* nativePtr, bool batchedUpdate); + void addCommand(int32_t id, UICommand type, void* nativePtr); + void addCommand(int32_t id, UICommand type, NativeString* args_01, NativeString* args_02, void* nativePtr); + void addCommand(int32_t id, UICommand type, NativeString* args_01, void* nativePtr); UICommandItem* data(); int64_t size(); void clear(); diff --git a/bridge/scripts/code_generator/src/idl/utils.ts b/bridge/scripts/code_generator/src/idl/utils.ts index c4eb0ad376..4d7fd6f040 100644 --- a/bridge/scripts/code_generator/src/idl/utils.ts +++ b/bridge/scripts/code_generator/src/idl/utils.ts @@ -19,6 +19,10 @@ export function getClassName(blob: IDLBlob) { return 'HTML' + raw.slice(4); } + if (raw.slice(0, 3) == 'css') { + return 'CSS' + raw.slice(3); + } + return `${raw[0].toUpperCase() + raw.slice(1)}`; } diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index 12d996249d..c5b4233a17 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -7,7 +7,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob <% if (object.indexedProp) { %> <% if (object.indexedProp.indexKeyType == 'number') { %> JSValue QJS<%= className %>::IndexedPropertyGetterCallback(JSContext* ctx, JSValue obj, uint32_t index) { - auto* self = toScriptWrappable<NodeList>(obj); + auto* self = toScriptWrappable<<%= className %>>(obj); if (index >= self->length()) { return JS_UNDEFINED; } @@ -16,26 +16,31 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } - return result->ToQuickJS(); + + return Converter<<%= generateTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); }; <% } else { %> JSValue QJS<%= className %>::StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key) { - auto* self = toScriptWrappable<NodeList>(obj); + auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; - ${generateTypeValue(object.indexedProp.type)} result = self->item(key, exception_state); + ${generateTypeValue(object.indexedProp.type)} result = self->item(AtomicString(ctx, key), exception_state); if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } - return result->ToQuickJS(); + return Converter<<%= generateTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); }; <% } %> <% if (!object.indexedProp.readonly) { %> <% if (object.indexedProp.indexKeyType == 'number') { %> bool QJS<%= className %>::IndexedPropertySetterCallback(JSContext* ctx, JSValueConst obj, uint32_t index, JSValueConst value) { - auto* self = toScriptWrappable<NodeList>(obj); + auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; MemberMutationScope scope{ExecutingContext::From(ctx)}; - bool success = self->SetItem(index, value, exception_state); + auto&& v = Converter<<%= generateTypeConverter(object.indexedProp.type) %>>::FromValue(ctx, value, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return false; + } + bool success = self->SetItem(index, v, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; } @@ -43,10 +48,14 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob }; <% } else { %> bool QJS<%= className %>::StringPropertySetterCallback(JSContext* ctx, JSValueConst obj, JSAtom key, JSValueConst value) { - auto* self = toScriptWrappable<NodeList>(obj); + auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; MemberMutationScope scope{ExecutingContext::From(ctx)}; - bool success = self->SetItem(key, value, exception_state); + auto&& v = Converter<<%= generateTypeConverter(object.indexedProp.type) %>>::FromValue(ctx, value, exception_state); + if (UNLIKELY(exception_state.HasException())) { + return false; + } + bool success = self->SetItem(AtomicString(ctx, key), v, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; } From 57dfe1941a326adac2e94a4fdf9eb5219f1d88a3 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Fri, 29 Apr 2022 17:23:19 +0800 Subject: [PATCH 111/498] refactor: remove bad member design. --- bridge/CMakeLists.txt | 2 +- bridge/bindings/qjs/cppgc/garbage_collected.h | 8 +-- bridge/bindings/qjs/cppgc/local_handle.h | 62 +++++++++++++++++++ bridge/bindings/qjs/cppgc/member.cc | 9 --- bridge/bindings/qjs/cppgc/member.h | 20 +----- bridge/bindings/qjs/script_wrappable.cc | 13 ++-- bridge/bindings/qjs/script_wrappable.h | 21 ++++--- bridge/core/dom/document.cc | 2 +- bridge/core/dom/document.d.ts | 8 +-- bridge/core/dom/document.h | 1 + bridge/core/dom/element.cc | 6 +- bridge/core/dom/node.d.ts | 4 +- bridge/core/dom/node_data.cc | 4 +- bridge/core/dom/node_test.cc | 32 +++++----- bridge/core/executing_context.cc | 3 +- bridge/scripts/code_generator/global.d.ts | 3 - .../code_generator/src/idl/generateSource.ts | 2 +- .../static/idl_templates/interface.cc.tpl | 3 + 18 files changed, 120 insertions(+), 83 deletions(-) create mode 100644 bridge/bindings/qjs/cppgc/local_handle.h delete mode 100644 bridge/bindings/qjs/cppgc/member.cc diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 85e19b5c34..7ac6786740 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -210,7 +210,7 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/cppgc/mutation_scope.cc bindings/qjs/cppgc/mutation_scope.h bindings/qjs/cppgc/member.h - bindings/qjs/cppgc/member.cc + bindings/qjs/cppgc/local_handle.h bindings/qjs/script_wrappable.cc bindings/qjs/script_wrappable.h bindings/qjs/wrapper_type_info.h diff --git a/bridge/bindings/qjs/cppgc/garbage_collected.h b/bridge/bindings/qjs/cppgc/garbage_collected.h index c5c9687117..e2fd9de77e 100644 --- a/bridge/bindings/qjs/cppgc/garbage_collected.h +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -12,6 +12,7 @@ #include "bindings/qjs/qjs_engine_patch.h" #include "foundation/casting.h" #include "foundation/macros.h" +#include "local_handle.h" namespace kraken { @@ -20,6 +21,7 @@ class MakeGarbageCollectedTrait; class ExecutingContext; class GCVisitor; +class ScriptWrappable; /** * This class are mainly designed as base class for ScriptWrappable. If you wants to implement @@ -68,10 +70,8 @@ class MakeGarbageCollectedTrait { template <typename T, typename... Args> T* MakeGarbageCollected(Args&&... args) { - static_assert(std::is_base_of<typename T::ParentMostGarbageCollectedType, T>::value, - "U of GarbageCollected<U> must be a base of T. Check " - "GarbageCollected<T> base class inheritance."); - return MakeGarbageCollectedTrait<T>::Allocate(std::forward<Args>(args)...); + static_assert(std::is_base_of<ScriptWrappable, T>::value, "MakeGarbageCollected T must be Derived from ScriptWrappable."); + return MakeLocal<T>(MakeGarbageCollectedTrait<T>::Allocate(std::forward<Args>(args)...)).Get(); } } // namespace kraken diff --git a/bridge/bindings/qjs/cppgc/local_handle.h b/bridge/bindings/qjs/cppgc/local_handle.h new file mode 100644 index 0000000000..681e74f6ee --- /dev/null +++ b/bridge/bindings/qjs/cppgc/local_handle.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ +#define KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ + +#include "foundation/macros.h" +#include "foundation/casting.h" +#include <type_traits> +#include <quickjs/quickjs.h> +#include "mutation_scope.h" + +namespace kraken { + +template<typename T> +class LocalTrait; +class ScriptWrappable; + +/** + * A stack allocated class which hold object reference temporary. +*/ +template<typename T> +class Local { + KRAKEN_STACK_ALLOCATED(); + public: + static Local<T> Empty() { return Local<T>(nullptr); } + + Local() = delete; + ~Local(); + + inline T* Get() const { return raw_; } + + protected: + explicit Local(T* p) : raw_(p) { + static_assert(std::is_base_of<ScriptWrappable, T>::value, "Local-Handle only accept ScriptWrappble params."); + }; + + private: + T* raw_; + friend class LocalTrait<T>; +}; + +template <typename T> +class LocalTrait { + public: + template <typename... Args> + static Local<T> Allocate(Args&&... args) { + return Local<T>(std::forward<Args>(args)...); + } + + friend class Local<T>; +}; + +template <typename T, typename... Args> +Local<T> MakeLocal(Args&&... args) { + return LocalTrait<T>::Allocate(std::forward<Args>(args)...); +} + +} + +#endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ diff --git a/bridge/bindings/qjs/cppgc/member.cc b/bridge/bindings/qjs/cppgc/member.cc deleted file mode 100644 index 173a1a44ba..0000000000 --- a/bridge/bindings/qjs/cppgc/member.cc +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#include "member.h" -#include "bindings/qjs/script_wrappable.h" -#include "core/executing_context.h" - -namespace kraken {} diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index ec58ba32f9..032167a248 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -26,7 +26,6 @@ class Member { public: Member() = default; Member(T* ptr) { - inited_ = true; SetRaw(ptr); } ~Member() { @@ -57,15 +56,6 @@ class Member { raw_ = nullptr; } - void Initialize(T* p) { - inited_ = true; - if (p == nullptr) - return; - raw_ = p; - runtime_ = p->runtime(); - p->MakeOld(); - } - // Copy assignment. Member& operator=(const Member& other) { operator=(other.Get()); @@ -96,24 +86,16 @@ class Member { private: void SetRaw(T* p) { - assert(inited_); if (p != nullptr) { auto* wrappable = To<ScriptWrappable>(p); runtime_ = wrappable->runtime(); - // This JSObject was created just now and used at first time. - // Because there are already one reference count when JSObject created, so we skip duplicate. - if (!p->fresh()) { - JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); - } - // This object had been used, no long fresh at all. - p->MakeOld(); + JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); } raw_ = p; } T* raw_{nullptr}; JSRuntime* runtime_{nullptr}; - bool inited_{false}; }; } // namespace kraken diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 1ab8705c46..62d71906db 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -9,18 +9,18 @@ namespace kraken { -ScriptWrappable::ScriptWrappable(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), fresh_(true) {} +ScriptWrappable::ScriptWrappable(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)) {} JSValue ScriptWrappable::ToQuickJS() { - return JS_DupValue(ctx_, GetJSObject()); + return JS_DupValue(ctx_, jsObject_); } JSValue ScriptWrappable::ToQuickJSUnsafe() const { - return GetJSObject(); + return jsObject_; } ScriptValue ScriptWrappable::ToValue() { - return ScriptValue(ctx_, GetJSObject()); + return ScriptValue(ctx_, jsObject_); } /// This callback will be called when QuickJS GC is running at marking stage. @@ -145,9 +145,4 @@ void ScriptWrappable::InitializeQuickJSObject() { JS_SetPrototype(ctx_, jsObject_, prototype); } -JSValue ScriptWrappable::GetJSObject() const { - MakeOld(); - return jsObject_; -} - } // namespace kraken diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 59d738865b..00c1c38e5b 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -49,9 +49,6 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { JSValue ToQuickJS(); JSValue ToQuickJSUnsafe() const; - bool fresh() const { return fresh_; } - void MakeOld() const { fresh_ = false; } - ScriptValue ToValue(); FORCE_INLINE ExecutingContext* GetExecutingContext() const { return static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx_)); @@ -62,12 +59,7 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { void InitializeQuickJSObject() override; private: - JSValue GetJSObject() const; JSValue jsObject_{JS_NULL}; - // Indicate this JSObject are created by MakeGarbageCollected traits and no one had used it. - // There are extra one reference count when JSObject are created by MakeGarbageCollected and needs to be special - // handled by cppgc. - mutable bool fresh_{false}; JSContext* ctx_{nullptr}; JSRuntime* runtime_{nullptr}; friend class GCVisitor; @@ -79,6 +71,19 @@ inline ScriptWrappable* toScriptWrappable(JSValue object) { return static_cast<ScriptWrappable*>(JS_GetOpaque(object, JSValueGetClassId(object))); } +template <typename T> +Local<T>::~Local<T>() { + if (raw_ == nullptr) + return; + auto* wrappable = To<ScriptWrappable>(raw_); + // Record the free operation to avoid JSObject had been freed immediately. + if (LIKELY(wrappable->GetExecutingContext()->HasMutationScope())) { + wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); + } else { + JS_FreeValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); + } +} + } // namespace kraken #endif // KRAKENBRIDGE_SCRIPT_WRAPPABLE_H diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 3024eeaeb4..89c4278aa8 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -23,7 +23,7 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ Document::Document(ExecutingContext* context) : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) { - document_element_.Initialize(MakeGarbageCollected<HTMLHtmlElement>(*this)); + document_element_ = MakeGarbageCollected<HTMLHtmlElement>(*this); } Element* Document::createElement(const AtomicString& name, ExceptionState& exception_state) { diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts index 498f06478e..a93c8748c9 100644 --- a/bridge/core/dom/document.d.ts +++ b/bridge/core/dom/document.d.ts @@ -12,10 +12,10 @@ interface Document extends Node { readonly head: HTMLHeadElement | null; readonly documentElement: HTMLHtmlElement; - createElement(tagName: string): NewObject<Element>; - createTextNode(value: string): NewObject<Text>; - createDocumentFragment(): NewObject<DocumentFragment>; - createComment(): NewObject<Comment>; + createElement(tagName: string): Element; + createTextNode(value: string): Text; + createDocumentFragment(): DocumentFragment; + createComment(): Comment; new(): Document; } diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 2ac152513b..5f87295d4b 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -6,6 +6,7 @@ #ifndef KRAKENBRIDGE_DOCUMENT_H #define KRAKENBRIDGE_DOCUMENT_H +#include "bindings/qjs/cppgc/local_handle.h" #include "container_node.h" #include "core/dom/comment.h" #include "core/dom/document_fragment.h" diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index d0c48f09a0..4154c34da4 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -19,7 +19,7 @@ Element::Element(const AtomicString& tag_name, Document* document, Node::Constru ElementAttributes& Element::EnsureElementAttributes() { if (attributes_ == nullptr) { - attributes_.Initialize(ElementAttributes::Create(this)); + attributes_ = ElementAttributes::Create(this); } return *attributes_; } @@ -144,7 +144,7 @@ CSSStyleDeclaration* Element::style() { CSSStyleDeclaration& Element::EnsureCSSStyleDeclaration() { if (cssom_wrapper_ == nullptr) { - cssom_wrapper_.Initialize(MakeGarbageCollected<CSSStyleDeclaration>(GetExecutingContext(), eventTargetId())); + cssom_wrapper_ = MakeGarbageCollected<CSSStyleDeclaration>(GetExecutingContext(), eventTargetId()); } return *cssom_wrapper_; } @@ -194,7 +194,7 @@ Node* Element::Clone(Document& factory, CloneChildrenFlag flag) const { } Element& Element::CloneWithoutAttributesAndChildren(Document& factory) const { - return *factory.createElement(tagName(), ASSERT_NO_EXCEPTION()); + return *(factory.createElement(tagName(), ASSERT_NO_EXCEPTION())); } class ElementSnapshotReader { diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts index 815faec3b5..9a4d804d5b 100644 --- a/bridge/core/dom/node.d.ts +++ b/bridge/core/dom/node.d.ts @@ -8,7 +8,7 @@ interface Node extends EventTarget { /** * Returns the children. */ - readonly childNodes: NewObject<NodeList>; + readonly childNodes: NodeList; /** * Returns the first child. */ @@ -56,7 +56,7 @@ interface Node extends EventTarget { /** * Returns a copy of node. If deep is true, the copy also includes the node's descendants. */ - cloneNode(deep?: boolean): NewObject<Node>; + cloneNode(deep?: boolean): Node; /** * Returns true if other is an inclusive descendant of node, and false otherwise. */ diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index fe77dc9bbb..6029a5a6d1 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -20,7 +20,7 @@ ChildNodeList* NodeData::EnsureChildNodeList(ContainerNode& node) { if (child_node_list_) return To<ChildNodeList>(child_node_list_.Get()); auto* list = MakeGarbageCollected<ChildNodeList>(&node); - child_node_list_.Initialize(list); + child_node_list_ = list; return list; } @@ -28,7 +28,7 @@ EmptyNodeList* NodeData::EnsureEmptyChildNodeList(Node& node) { if (child_node_list_) return To<EmptyNodeList>(child_node_list_.Get()); auto* list = MakeGarbageCollected<EmptyNodeList>(&node); - child_node_list_.Initialize(list); + child_node_list_ = list; return list; } diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 19382aa61c..ff9dd88408 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -191,33 +191,33 @@ TEST(Node, replaceBody) { 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'); + 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'); + 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'); + 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' -//); + console.log( + div2.firstChild.getAttribute('id') === 'child123', div2.firstChild.style.width === '10px', + div2.firstChild.style.height === '10px' +); )"; bool static errorCalled = false; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 2b575645bb..68e50cb1a0 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -378,9 +378,10 @@ void ExecutingContext::ClearMutationScope() { //} void ExecutingContext::InstallDocument() { + MemberMutationScope scope{this}; document_ = MakeGarbageCollected<Document>(this); - DefineGlobalProperty("document", document_->ToQuickJSUnsafe()); document_->InitDocumentElement(); + DefineGlobalProperty("document", document_->ToQuickJS()); } // An lock free context validator. diff --git a/bridge/scripts/code_generator/global.d.ts b/bridge/scripts/code_generator/global.d.ts index cdf9919223..7f631cb160 100644 --- a/bridge/scripts/code_generator/global.d.ts +++ b/bridge/scripts/code_generator/global.d.ts @@ -8,8 +8,5 @@ declare interface BlobPropertyBag {} declare function Dictionary() : any; declare type JSEventListener = void; -// This property will return new created value. -type NewObject<T> = T; - // This property is implemented by Dart side type DartImpl<T> = T; diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index e8aa0e2849..e148f3e987 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -253,7 +253,7 @@ function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], mode?: isInstanceMethod: false }): string { if (type[0] == FunctionArgumentType.void) return 'JS_NULL'; - let method = (mode && mode.newObject || options.isConstructor) ? 'ToQuickJSUnsafe' : 'ToQuickJS'; + let method = 'ToQuickJS'; if (options.isConstructor) { return `return_value->${method}()`; diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index c5b4233a17..aca284f4bc 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -12,6 +12,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob return JS_UNDEFINED; } ExceptionState exception_state; + MemberMutationScope scope{ExecutingContext::From(ctx)}; <%= generateTypeValue(object.indexedProp.type) %> result = self->item(index, exception_state); if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); @@ -23,6 +24,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob JSValue QJS<%= className %>::StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key) { auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; + MemberMutationScope scope{ExecutingContext::From(ctx)}; ${generateTypeValue(object.indexedProp.type)} result = self->item(AtomicString(ctx, key), exception_state); if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); @@ -90,6 +92,7 @@ static JSValue <%= overloadMethod.name %>_overload_<%= index %>(JSContext* ctx, 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<<%= generateTypeConverter(prop.type) %>>::ToValue(ctx, <%= blob.filename %>-><%= prop.name %>()); } <% if (!prop.readonly) { %> From 1cedc29b86deda7a56f7ec8e71993ca05937fdb9 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Fri, 29 Apr 2022 17:38:14 +0800 Subject: [PATCH 112/498] fix: fix style. --- bridge/core/css/legacy/css_style_declaration.cc | 17 +++++++++++++++-- bridge/core/css/legacy/css_style_declaration.h | 3 +++ bridge/core/dom/element.cc | 9 ++++++--- bridge/core/dom/node.cc | 1 + 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index a1cddcf172..14c8b4d98b 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -64,7 +64,8 @@ AtomicString CSSStyleDeclaration::item(const AtomicString& key, ExceptionState& } 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 { @@ -78,13 +79,22 @@ AtomicString CSSStyleDeclaration::getPropertyValue(const AtomicString& key, Exce void CSSStyleDeclaration::setProperty(const AtomicString& key, const AtomicString& value, - ExceptionState& exception_state) {} + 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; + } +} AtomicString CSSStyleDeclaration::InternalGetPropertyValue(std::string& name) { name = parseJavaScriptCSSPropertyName(name); @@ -115,12 +125,15 @@ AtomicString CSSStyleDeclaration::InternalRemoveProperty(std::string& name) { return AtomicString::Empty(ctx()); } + AtomicString return_value = properties_[name]; properties_.erase(name); std::unique_ptr<NativeString> args_01 = stringToNativeString(name); std::unique_ptr<NativeString> args_02 = jsValueToNativeString(ctx(), JS_NULL); GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::setStyle, args_01.release(), args_02.release(), nullptr); + + return return_value; } } // namespace kraken diff --git a/bridge/core/css/legacy/css_style_declaration.h b/bridge/core/css/legacy/css_style_declaration.h index dccef94c86..621cdde50f 100644 --- a/bridge/core/css/legacy/css_style_declaration.h +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -31,6 +31,9 @@ class CSSStyleDeclaration : public ScriptWrappable { 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); + private: AtomicString InternalGetPropertyValue(std::string& name); bool InternalSetProperty(std::string& name, const AtomicString& value); diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 4154c34da4..b4ce511220 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -171,9 +171,12 @@ Element& Element::CloneWithoutChildren(Document* document) const { } void Element::CloneAttributesFrom(const Element& other) { - if (other.attributes_ == nullptr) - return; - EnsureElementAttributes().CopyWith(other.attributes_); + if (other.attributes_ != nullptr) { + EnsureElementAttributes().CopyWith(other.attributes_); + } + if (other.cssom_wrapper_ != nullptr) { + EnsureCSSStyleDeclaration().CopyWith(other.cssom_wrapper_); + } } bool Element::HasEquivalentAttributes(const Element& other) const { diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index dc5f73258c..de8195ae64 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -18,6 +18,7 @@ namespace kraken { Node* Node::Create(ExecutingContext* context, ExceptionState& exception_state) { exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor"); + return nullptr; } void Node::setNodeValue(const AtomicString& value, ExceptionState& exception_state) { From 2d9cf4840cb3481bc609f63d50183b8d8376d406 Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Fri, 29 Apr 2022 09:39:02 +0000 Subject: [PATCH 113/498] Committing clang-format changes --- bridge/bindings/qjs/atomic_string.cc | 3 ++- bridge/bindings/qjs/binding_initializer.cc | 2 +- bridge/bindings/qjs/cppgc/garbage_collected.h | 3 ++- bridge/bindings/qjs/cppgc/local_handle.h | 15 ++++++++------- bridge/bindings/qjs/cppgc/member.h | 4 +--- bridge/bindings/qjs/exception_state.h | 2 +- bridge/core/css/legacy/css_style_declaration.cc | 1 - bridge/core/css/legacy/css_style_declaration.h | 6 +++--- bridge/core/dom/container_node.cc | 2 +- bridge/core/dom/element.cc | 7 +++---- bridge/core/dom/element.h | 5 ++--- bridge/core/dom/events/event_target.cc | 4 +--- bridge/core/dom/events/event_target.h | 2 +- bridge/core/dom/node_test.cc | 4 ++-- 14 files changed, 28 insertions(+), 32 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index 8b4c117cad..dc24878101 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -61,7 +61,8 @@ AtomicString::AtomicString(JSContext* ctx, JSValue value) } } -AtomicString::AtomicString(JSContext* ctx, JSAtom atom): runtime_(JS_GetRuntime(ctx)), ctx_(ctx), atom_(JS_DupAtom(ctx, atom)) { +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; diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 5ed0cc45cd..a6bedd0f40 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -9,6 +9,7 @@ #include "qjs_character_data.h" #include "qjs_comment.h" #include "qjs_console.h" +#include "qjs_css_style_declaration.h" #include "qjs_document.h" #include "qjs_element.h" #include "qjs_element_attributes.h" @@ -21,7 +22,6 @@ #include "qjs_html_html_element.h" #include "qjs_html_unknown_element.h" #include "qjs_module_manager.h" -#include "qjs_css_style_declaration.h" #include "qjs_node.h" #include "qjs_node_list.h" #include "qjs_text.h" diff --git a/bridge/bindings/qjs/cppgc/garbage_collected.h b/bridge/bindings/qjs/cppgc/garbage_collected.h index e2fd9de77e..c0caed2a6d 100644 --- a/bridge/bindings/qjs/cppgc/garbage_collected.h +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -70,7 +70,8 @@ class MakeGarbageCollectedTrait { template <typename T, typename... Args> T* MakeGarbageCollected(Args&&... args) { - static_assert(std::is_base_of<ScriptWrappable, T>::value, "MakeGarbageCollected T must be Derived from ScriptWrappable."); + static_assert(std::is_base_of<ScriptWrappable, T>::value, + "MakeGarbageCollected T must be Derived from ScriptWrappable."); return MakeLocal<T>(MakeGarbageCollectedTrait<T>::Allocate(std::forward<Args>(args)...)).Get(); } diff --git a/bridge/bindings/qjs/cppgc/local_handle.h b/bridge/bindings/qjs/cppgc/local_handle.h index 681e74f6ee..1f7d85c1ba 100644 --- a/bridge/bindings/qjs/cppgc/local_handle.h +++ b/bridge/bindings/qjs/cppgc/local_handle.h @@ -5,24 +5,25 @@ #ifndef KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ -#include "foundation/macros.h" -#include "foundation/casting.h" -#include <type_traits> #include <quickjs/quickjs.h> +#include <type_traits> +#include "foundation/casting.h" +#include "foundation/macros.h" #include "mutation_scope.h" namespace kraken { -template<typename T> +template <typename T> class LocalTrait; class ScriptWrappable; /** * A stack allocated class which hold object reference temporary. -*/ -template<typename T> + */ +template <typename T> class Local { KRAKEN_STACK_ALLOCATED(); + public: static Local<T> Empty() { return Local<T>(nullptr); } @@ -57,6 +58,6 @@ Local<T> MakeLocal(Args&&... args) { return LocalTrait<T>::Allocate(std::forward<Args>(args)...); } -} +} // namespace kraken #endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index 032167a248..3c799f214d 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -25,9 +25,7 @@ template <typename T, typename = std::is_base_of<ScriptWrappable, T>> class Member { public: Member() = default; - Member(T* ptr) { - SetRaw(ptr); - } + Member(T* ptr) { SetRaw(ptr); } ~Member() { if (raw_ != nullptr) { assert(runtime_ != nullptr); diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index 14310f2759..b314be12dc 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -10,7 +10,7 @@ #include <string> #include "foundation/macros.h" -#define ASSERT_NO_EXCEPTION() ExceptionState().ReturnThis() +#define ASSERT_NO_EXCEPTION() ExceptionState().ReturnThis() namespace kraken { diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index 14c8b4d98b..a2148ec6e7 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -89,7 +89,6 @@ AtomicString CSSStyleDeclaration::removeProperty(const AtomicString& key, Except return InternalRemoveProperty(propertyName); } - void CSSStyleDeclaration::CopyWith(CSSStyleDeclaration* inline_style) { for (auto& attr : inline_style->properties_) { properties_[attr.first] = attr.second; diff --git a/bridge/core/css/legacy/css_style_declaration.h b/bridge/core/css/legacy/css_style_declaration.h index 621cdde50f..1c1b79e2b6 100644 --- a/bridge/core/css/legacy/css_style_declaration.h +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -7,11 +7,11 @@ #define KRAKENBRIDGE_CSS_STYLE_DECLARATION_H #include <unordered_map> +#include "bindings/qjs/atomic_string.h" #include "bindings/qjs/cppgc/member.h" -#include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/exception_state.h" -#include "bindings/qjs/atomic_string.h" #include "bindings/qjs/script_value.h" +#include "bindings/qjs/script_wrappable.h" namespace kraken { @@ -19,6 +19,7 @@ class Element; class CSSStyleDeclaration : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = CSSStyleDeclaration*; static CSSStyleDeclaration* Create(ExecutingContext* context, ExceptionState& exception_state); @@ -42,7 +43,6 @@ class CSSStyleDeclaration : public ScriptWrappable { int32_t owner_element_target_id_; }; - } // namespace kraken #endif // KRAKENBRIDGE_CSS_STYLE_DECLARATION_H diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 0bfd15cb97..a68ec379d5 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -257,7 +257,7 @@ Node* ContainerNode::AppendChild(Node* new_child, ExceptionState& exception_stat return new_child; } -Node * ContainerNode::AppendChild(Node* new_child) { +Node* ContainerNode::AppendChild(Node* new_child) { return AppendChild(new_child, ASSERT_NO_EXCEPTION()); } diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index b4ce511220..98fa41657c 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -54,8 +54,8 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value, std::unique_ptr<NativeString> args_01 = name.ToNativeString(); std::unique_ptr<NativeString> args_02 = value.ToNativeString(); - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::setAttribute, - args_01.release(), args_02.release(), nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::setAttribute, args_01.release(), + args_02.release(), nullptr); } void Element::removeAttribute(const AtomicString& name, ExceptionState& exception_state) { @@ -160,8 +160,7 @@ Element& Element::CloneWithChildren(CloneChildrenFlag flag, Document* document) } Element& Element::CloneWithoutChildren(Document* document) const { - Element& clone = CloneWithoutAttributesAndChildren( - document ? *document : GetDocument()); + Element& clone = CloneWithoutAttributesAndChildren(document ? *document : GetDocument()); assert(IsHTMLElement() == clone.IsHTMLElement()); diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 325865f8c4..8df7698817 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -8,9 +8,9 @@ #include "bindings/qjs/cppgc/garbage_collected.h" #include "container_node.h" +#include "core/css/legacy/css_style_declaration.h" #include "legacy/bounding_client_rect.h" #include "legacy/element_attributes.h" -#include "core/css/legacy/css_style_declaration.h" #include "qjs_scroll_to_options.h" namespace kraken { @@ -82,8 +82,7 @@ class Element : public ContainerNode { bool HasEquivalentAttributes(const Element& other) const; // Step 5 of https://dom.spec.whatwg.org/#concept-node-clone - virtual void CloneNonAttributePropertiesFrom(const Element&, - CloneChildrenFlag) {} + virtual void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) {} void Trace(GCVisitor* visitor) const override; diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 9cf5e7975c..abbd8f3936 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -40,9 +40,7 @@ EventTarget* EventTarget::Create(ExecutingContext* context, ExceptionState& exce } EventTarget::EventTarget(ExecutingContext* context) - : BindingObject(context), - ScriptWrappable(context->ctx()), - event_target_id_(global_event_target_id++) {} + : BindingObject(context), ScriptWrappable(context->ctx()), event_target_id_(global_event_target_id++) {} bool EventTarget::addEventListener(const AtomicString& event_type, const std::shared_ptr<EventListener>& event_listener, diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 401368d3fa..daa74abf69 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -6,13 +6,13 @@ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H -#include "foundation/logging.h" #include "bindings/qjs/cppgc/member.h" #include "bindings/qjs/js_event_listener.h" #include "bindings/qjs/qjs_function.h" #include "bindings/qjs/script_wrappable.h" #include "core/dom/binding_object.h" #include "event_listener_map.h" +#include "foundation/logging.h" #include "foundation/native_string.h" #include "qjs_add_event_listener_options.h" diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index ff9dd88408..7f0e22abf4 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -152,7 +152,7 @@ TEST(Node, replaceBody) { EXPECT_EQ(errorCalled, false); } - TEST(Node, cloneNode) { +TEST(Node, cloneNode) { std::string code = R"( const div = document.createElement('div'); div.style.width = '100px'; @@ -188,7 +188,7 @@ TEST(Node, replaceBody) { EXPECT_EQ(logCalled, true); } - TEST(Node, nestedNode) { +TEST(Node, nestedNode) { std::string code = R"( const div = document.createElement('div'); div.style.width = '100px'; From a67ce99ae84b3f38c0ab2605510a25cd1ac50ecb Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Mon, 2 May 2022 15:02:17 +0800 Subject: [PATCH 114/498] feat: support html parser. --- bridge/CMakeLists.txt | 7 +- bridge/bindings/qjs/binding_initializer.cc | 4 + bridge/bindings/qjs/script_wrappable.h | 3 +- bridge/bindings/qjs/wrapper_type_info.h | 1 + .../core/css/legacy/css_style_declaration.cc | 15 ++ .../core/css/legacy/css_style_declaration.h | 2 + bridge/core/dom/element.cc | 107 ++++---- bridge/core/dom/element.d.ts | 2 +- bridge/core/dom/element.h | 5 +- bridge/core/dom/element_test.cc | 15 +- .../core/dom/legacy/bounding_client_rect.cc | 4 + .../core/dom/legacy/bounding_client_rect.d.ts | 2 + bridge/core/dom/legacy/bounding_client_rect.h | 2 + bridge/core/dom/node.cc | 13 +- bridge/core/dom/node.h | 3 - .../dom/template_content_document_fragment.h | 33 --- bridge/core/html/html_parser.cc | 166 ------------ bridge/core/html/html_parser.h | 29 -- bridge/core/html/html_tag_names.json5 | 2 +- bridge/core/html/html_template_element.cc | 13 + bridge/core/html/html_template_element.d.ts | 7 + bridge/core/html/html_template_element.h | 2 + bridge/core/html/parser/html_parser.cc | 166 ++++++++++++ bridge/core/html/parser/html_parser.h | 34 +++ bridge/core/page.cc | 20 +- bridge/page.cc | 247 ------------------ bridge/test/test.cmake | 1 + 27 files changed, 340 insertions(+), 565 deletions(-) delete mode 100644 bridge/core/dom/template_content_document_fragment.h delete mode 100644 bridge/core/html/html_parser.cc delete mode 100644 bridge/core/html/html_parser.h create mode 100644 bridge/core/html/html_template_element.d.ts create mode 100644 bridge/core/html/parser/html_parser.cc create mode 100644 bridge/core/html/parser/html_parser.h delete mode 100644 bridge/page.cc diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 7ac6786740..0d0b9102c3 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -301,7 +301,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/node.h core/dom/node_traversal.cc core/dom/node_traversal.h - core/dom/template_content_document_fragment.h core/dom/character_data.cc core/dom/character_data.h core/dom/comment.cc @@ -329,6 +328,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/container_node.h core/events/error_event.cc core/events/error_event.h + core/html/parser/html_parser.cc + core/html/parser/html_parser.h core/html/html_collection.cc core/html/html_collection.h core/html/html_element.cc @@ -341,6 +342,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/html/html_body_element.cc core/html/html_html_element.cc core/html/html_html_element.h + core/html/html_template_element.cc + core/html/html_template_element.h # core/html/html_anchor_element.h # core/html/html_anchor_element.cc # core/html/html_template_element.cc @@ -443,6 +446,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_html_body_element.h out/qjs_html_html_element.cc out/qjs_html_html_element.h + out/qjs_html_template_element.cc + out/qjs_html_template_element.h out/html_element_type_helper.h out/qjs_html_unknown_element.cc out/qjs_html_unknown_element.h diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index a6bedd0f40..acee6d9d66 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -21,11 +21,13 @@ #include "qjs_html_head_element.h" #include "qjs_html_html_element.h" #include "qjs_html_unknown_element.h" +#include "qjs_html_template_element.h" #include "qjs_module_manager.h" #include "qjs_node.h" #include "qjs_node_list.h" #include "qjs_text.h" #include "qjs_window.h" +#include "qjs_bounding_client_rect.h" namespace kraken { @@ -50,7 +52,9 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLBodyElement::Install(context); QJSHTMLHtmlElement::Install(context); QJSHTMLUnknownElement::Install(context); + QJSHTMLTemplateElement::Install(context); QJSCSSStyleDeclaration::Install(context); + QJSBoundingClientRect::Install(context); // Legacy bindings, not standard. QJSElementAttributes::Install(context); diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 00c1c38e5b..ab50eecd2e 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -7,6 +7,7 @@ #define KRAKENBRIDGE_SCRIPT_WRAPPABLE_H #include <quickjs/quickjs.h> +#include "foundation/macros.h" #include "bindings/qjs/cppgc/garbage_collected.h" #include "wrapper_type_info.h" @@ -80,7 +81,7 @@ Local<T>::~Local<T>() { if (LIKELY(wrappable->GetExecutingContext()->HasMutationScope())) { wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); } else { - JS_FreeValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); + assert_m(false, "LocalHandle must be used before MemberMutationScope allcated."); } } diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 02c5f075ad..e506dcf54c 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -34,6 +34,7 @@ enum { JS_CLASS_HTML_BODY_ELEMENT, JS_CLASS_HTML_HEAD_ELEMENT, JS_CLASS_HTML_HTML_ELEMENT, + JS_CLASS_HTML_TEMPLATE_ELEMENT, JS_CLASS_HTML_UNKNOWN_ELEMENT, JS_CLASS_CSS_STYLE_DECLARATION, diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index a2148ec6e7..4af2908bd6 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -94,6 +94,21 @@ void CSSStyleDeclaration::CopyWith(CSSStyleDeclaration* inline_style) { properties_[attr.first] = attr.second; } } + +std::string CSSStyleDeclaration::ToString() const { + if (properties_.empty()) + return ""; + + std::string s; + + for (auto& attr : properties_) { + s += attr.first + ": " + attr.second.ToStdString() + ";"; + } + + s += "\""; + return s; +} + AtomicString CSSStyleDeclaration::InternalGetPropertyValue(std::string& name) { name = parseJavaScriptCSSPropertyName(name); diff --git a/bridge/core/css/legacy/css_style_declaration.h b/bridge/core/css/legacy/css_style_declaration.h index 1c1b79e2b6..2c51ded1cf 100644 --- a/bridge/core/css/legacy/css_style_declaration.h +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -35,6 +35,8 @@ class CSSStyleDeclaration : public ScriptWrappable { void CopyWith(CSSStyleDeclaration* attributes); + std::string ToString() const; + private: AtomicString InternalGetPropertyValue(std::string& name); bool InternalSetProperty(std::string& name, const AtomicString& value); diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 98fa41657c..96cb8c1947 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -9,6 +9,8 @@ #include "bindings/qjs/script_promise.h" #include "bindings/qjs/script_promise_resolver.h" #include "core/fileapi/blob.h" +#include "core/dom/document_fragment.h" +#include "core/html/parser/html_parser.h" #include "core/html/html_template_element.h" #include "foundation/native_value_converter.h" @@ -305,64 +307,59 @@ void Element::setScrollLeft(double v, ExceptionState& exception_state) { exception_state); } -std::string Element::outerHTML() const { - // std::string s = "<" + tag_name_.ToStdString(); - // - // // Read attributes - // std::string attributes = 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 += "</" + TagName().ToStdString() + ">"; - - // return s; -} - -void Element::setOuterHTML(const AtomicString& value, ExceptionState& exception_state) {} - -std::string Element::innerHTML() const { +std::string Element::outerHTML() { + std::string s = "<" + tagName().ToStdString(); + + // Read attributes + if (attributes_ != nullptr) { + s += " " + attributes_->ToString(); + } + if (cssom_wrapper_ != nullptr) { + s += " style=\"" + cssom_wrapper_->ToString(); + } + + s += ">"; + + std::string childHTML = innerHTML(); + s += childHTML; + s += "</" + tagName().ToStdString() + ">"; + + return s; +} + +std::string Element::innerHTML() { std::string s; // If Element is TemplateElement, the innerHTML content is the content of documentFragment. - const Node* parent = To<Node>(this); - - // if (auto* template_element = DynamicTo<HTMLTemplateElement>(this)) { - // parent = DynamicTo<Node>(template_element->content()); - // } - - // TODO: add innerHTML support. - // // 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<NodeInstance*>(JS_GetOpaque(c, Node::classId(c))); - // if (node->nodeType == NodeType::ELEMENT_NODE) { - // s += reinterpret_cast<ElementInstance*>(node)->outerHTML(); - // } else if (node->nodeType == NodeType::TEXT_NODE) { - // s += reinterpret_cast<TextNodeInstance*>(node)->toString(); - // } - // - // JS_FreeValue(m_ctx, c); - // } - // return s; -} - -void Element::setInnerHTML(const AtomicString& value, ExceptionState& exception_state) {} + Node* parent = To<Node>(this); + + if (auto* template_element = DynamicTo<HTMLTemplateElement>(this)) { + parent = To<Node>(template_element->content()); + } + + if (parent->firstChild() == nullptr) return s; + + auto* child = parent->firstChild(); + while (child != nullptr) { + if (auto* element = DynamicTo<Element>(child)) { + s += element->outerHTML(); + } else if (auto* text = DynamicTo<Text>(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<HTMLTemplateElement>(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) {} diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index a44ec2baf8..898d455c76 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -12,7 +12,7 @@ interface Element extends Node { readonly clientLeft: number; readonly clientTop: number; readonly clientWidth: number; - outerHTML: string; + readonly outerHTML: string; innerHTML: string; readonly ownerDocument: Document; scrollLeft: number; diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 8df7698817..e060c4b9c0 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -58,9 +58,8 @@ class Element : public ContainerNode { double scrollLeft() const; void setScrollLeft(double v, ExceptionState& exception_state); - std::string outerHTML() const; - void setOuterHTML(const AtomicString& value, ExceptionState& exception_state); - std::string innerHTML() const; + std::string outerHTML(); + std::string innerHTML(); void setInnerHTML(const AtomicString& value, ExceptionState& exception_state); bool HasTagName(const AtomicString&) const; diff --git a/bridge/core/dom/element_test.cc b/bridge/core/dom/element_test.cc index 8e739bc16b..78b23eeb4a 100644 --- a/bridge/core/dom/element_test.cc +++ b/bridge/core/dom/element_test.cc @@ -3,10 +3,10 @@ * Author: Kraken Team. */ -#include "event_target.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" +#include "core/dom/legacy/bounding_client_rect.h" +using namespace kraken; TEST(Element, setAttribute) { bool static errorCalled = false; @@ -63,6 +63,7 @@ TEST(Element, setAttributeWithHTML) { bool static logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; + EXPECT_STREQ(message.c_str(), "100%"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { KRAKEN_LOG(VERBOSE) << errmsg; @@ -72,7 +73,8 @@ TEST(Element, setAttributeWithHTML) { const char* code = "let div = document.createElement('div');" "div.innerHTML = '<img src=\"https://miniapp-nikestore-demo.oss-cn-beijing.aliyuncs.com/white_shoes_v1.png\" " - "style=\"width:100%;height:auto;\">';"; + "style=\"width:100%;height:auto;\">';" + "console.log(div.firstChild.style.width);"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); } @@ -139,8 +141,11 @@ TEST(Element, stringifyBoundingClientRect) { 10.0, 20.0, 30.0, 40.0, 10.0, 20.0, 30.0, 40.0, }; - auto* clientRect = new BoundingClientRect(context, &nativeRect); - context->defineGlobalProperty("boundingClient", clientRect->jsObject); + { + MemberMutationScope scope{context}; + auto* clientRect = BoundingClientRect::Create(context, &nativeRect); + context->DefineGlobalProperty("boundingClient", clientRect->ToQuickJS()); + } const char* code = "console.log(JSON.stringify(boundingClient))"; bridge->evaluateScript(code, strlen(code), "vm://", 0); diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc index 846c307bd8..016bad6cd4 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.cc +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -12,6 +12,10 @@ BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, return MakeGarbageCollected<BoundingClientRect>(context, native_bounding_client_rect); } +BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, ExceptionState& exceptionState) { + return nullptr; +} + BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBoundingClientRect* nativeBoundingClientRect) : ScriptWrappable(context->ctx()), x_(nativeBoundingClientRect->x), diff --git a/bridge/core/dom/legacy/bounding_client_rect.d.ts b/bridge/core/dom/legacy/bounding_client_rect.d.ts index 8262309a13..26fe6df07a 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.d.ts +++ b/bridge/core/dom/legacy/bounding_client_rect.d.ts @@ -7,4 +7,6 @@ interface BoundingClientRect { readonly right: double; readonly bottom: double; readonly left: double; + + new(): void; } diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index ca7925b6f7..c48f653cf7 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -6,6 +6,7 @@ #define KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ #include "bindings/qjs/script_wrappable.h" +#include "bindings/qjs/exception_state.h" namespace kraken { @@ -28,6 +29,7 @@ class BoundingClientRect : public ScriptWrappable { public: BoundingClientRect() = delete; static BoundingClientRect* Create(ExecutingContext* context, NativeBoundingClientRect* native_bounding_client_rect); + static BoundingClientRect* Create(ExecutingContext* context, ExceptionState& exceptionState); explicit BoundingClientRect(ExecutingContext* context, NativeBoundingClientRect* nativeBoundingClientRect); void Trace(GCVisitor* visitor) const override; diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index de8195ae64..417b3943aa 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -11,7 +11,6 @@ #include "empty_node_list.h" #include "node_data.h" #include "node_traversal.h" -#include "template_content_document_fragment.h" #include "text.h" namespace kraken { @@ -314,13 +313,6 @@ void Node::RemovedFrom(ContainerNode& insertion_point) { } } -ContainerNode* Node::ParentOrShadowHostOrTemplateHostNode() const { - auto* this_fragment = DynamicTo<DocumentFragment>(this); - if (this_fragment && this_fragment->IsTemplateContent()) - return static_cast<const TemplateContentDocumentFragment*>(this)->Host(); - return ParentOrShadowHostNode(); -} - ContainerNode* Node::NonShadowBoundaryParentNode() const { return parentNode(); } @@ -363,10 +355,7 @@ bool Node::ContainsIncludingHostElements(const Node& node) const { if (current == this) return true; auto* curr_fragment = DynamicTo<DocumentFragment>(current); - if (curr_fragment && curr_fragment->IsTemplateContent()) - current = static_cast<const TemplateContentDocumentFragment*>(current)->Host(); - else - current = current->ParentOrShadowHostNode(); + current = current->ParentOrShadowHostNode(); } while (current); return false; } diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index efd593bf6b..3a835110fb 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -152,9 +152,6 @@ class Node : public EventTarget { // dispatch synchronous events. virtual void RemovedFrom(ContainerNode& insertion_point); - // Knows about all kinds of hosts. - [[nodiscard]] ContainerNode* ParentOrShadowHostOrTemplateHostNode() const; - // Returns the parent node, but nullptr if the parent node is a ShadowRoot. [[nodiscard]] ContainerNode* NonShadowBoundaryParentNode() const; diff --git a/bridge/core/dom/template_content_document_fragment.h b/bridge/core/dom/template_content_document_fragment.h deleted file mode 100644 index 63566025d9..0000000000 --- a/bridge/core/dom/template_content_document_fragment.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2021-present The Kraken authors. All rights reserved. - */ - -#ifndef KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ -#define KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ - -#include "bindings/qjs/cppgc/gc_visitor.h" -#include "document_fragment.h" -#include "element.h" - -namespace kraken { - -class TemplateContentDocumentFragment final : public DocumentFragment { - public: - TemplateContentDocumentFragment(Document& document, Element* host) - : DocumentFragment(&document, kCreateDocumentFragment), host_(host) {} - - Element* Host() const { return host_.Get(); } - - void Trace(GCVisitor* visitor) const override { - visitor->Trace(host_); - DocumentFragment::Trace(visitor); - } - - private: - bool IsTemplateContent() const override { return true; } - Member<Element> host_; -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_CORE_DOM_TEMPLATE_CONTENT_DOCUMENT_FRAGMENT_H_ diff --git a/bridge/core/html/html_parser.cc b/bridge/core/html/html_parser.cc deleted file mode 100644 index 15016d366f..0000000000 --- a/bridge/core/html/html_parser.cc +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "html_parser.h" -#include "dom/document.h" -#include "dom/text_node.h" -#include "executing_context.h" -#include "foundation/logging.h" - -#include <utility> - -namespace kraken { - -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<ElementInstance*>(JS_GetOpaque(newElementValue, Element::classId())); - root->internalAppendChild(newElementInstance); - parseProperty(newElementInstance, &child->v.element); - - // eval javascript when <script>//code...</script>. - if (child->v.element.children.length > 0) { - if (child->v.element.tag == GUMBO_TAG_SCRIPT) { - const char* code = ((GumboNode*)child->v.element.children.data[0])->v.text.text; - context->evaluateJavaScript(code, strlen(code), "vm://", 0); - } else { - traverseHTML(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<TextNodeInstance*>(JS_GetOpaque(textNodeValue, TextNode::classId())); - root->internalAppendChild(textNodeInstance); - JS_FreeValue(ctx, textNodeValue); - } - } -} - -bool HTMLParser::parseHTML(std::string html, NodeInstance* rootNode, bool isHTMLFragment) { - if (rootNode != nullptr) { - rootNode->internalClearChild(); - - if (!trim(html).empty()) { - GumboOutput* htmlTree = parse(html, isHTMLFragment); - - traverseHTML(rootNode, htmlTree->root); - // Free gumbo parse nodes. - gumbo_destroy_output(&kGumboDefaultOptions, htmlTree); - } - } else { - KRAKEN_LOG(ERROR) << "Root node is null."; - } - - return true; -} - -bool HTMLParser::parseHTML(std::string html, NodeInstance* rootNode) { - return parseHTML(html, rootNode, false); -} - -bool HTMLParser::parseHTML(const char* code, size_t codeLength, NodeInstance* rootNode) { - std::string html = std::string(code, codeLength); - return parseHTML(html, rootNode, false); -} - -bool HTMLParser::parseHTMLFragment(const char* code, size_t codeLength, NodeInstance* rootNode) { - std::string html = std::string(code, codeLength); - return parseHTML(html, rootNode, true); -} - -void HTMLParser::parseProperty(ElementInstance* element, GumboElement* gumboElement) { - ExecutionContext* context = element->context(); - JSContext* ctx = context->ctx(); - - GumboVector* attributes = &gumboElement->attributes; - for (int j = 0; j < attributes->length; ++j) { - auto* attribute = (GumboAttribute*)attributes->data[j] - - std::string strName = attribute->name; - std::string strValue = attribute->value; - - JSValue key = JS_NewString(ctx, strName.c_str()); - JSValue value = JS_NewString(ctx, strValue.c_str()); - - JSValue setAttributeFunc = JS_GetPropertyStr(ctx, element->jsObject, "setAttribute"); - JSValue arguments[] = {key, value}; - - JSValue returnValue = JS_Call(ctx, setAttributeFunc, element->jsObject, 2, arguments); - context->drainPendingPromiseJobs(); - context->handleException(&returnValue); - - JS_FreeValue(ctx, setAttributeFunc); - JS_FreeValue(ctx, key); - JS_FreeValue(ctx, value); - } -} - -} // namespace kraken diff --git a/bridge/core/html/html_parser.h b/bridge/core/html/html_parser.h deleted file mode 100644 index fc0fca0993..0000000000 --- a/bridge/core/html/html_parser.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_HTML_PARSER_H -#define KRAKENBRIDGE_HTML_PARSER_H - -#include "foundation/native_string.h" -#include "third_party/gumbo-parser/src/gumbo.h" - -namespace kraken { - -class HTMLParser { - public: - static bool parseHTML(const char* code, size_t codeLength, NodeInstance* rootNode); - static bool parseHTML(std::string html, NodeInstance* rootNode); - static bool parseHTMLFragment(const char* code, size_t codeLength, NodeInstance* rootNode); - - private: - ExecutionContext* m_context; - static void traverseHTML(NodeInstance* root, GumboNode* node); - static void parseProperty(ElementInstance* element, GumboElement* gumboElement); - - static bool parseHTML(std::string html, NodeInstance* rootNode, bool isHTMLFragment); -}; -} // namespace kraken - -#endif // KRAKENBRIDGE_HTML_PARSER_H diff --git a/bridge/core/html/html_tag_names.json5 b/bridge/core/html/html_tag_names.json5 index b49e934125..bfdf8ff4aa 100644 --- a/bridge/core/html/html_tag_names.json5 +++ b/bridge/core/html/html_tag_names.json5 @@ -38,7 +38,7 @@ // "interfaceName": "HTMLTextareaElement", // "interfaceHeaderDir": "core/html/forms" // }, -// "template", + "template", // { // "name": "img", // "interfaceName": "HTMLImageElement", diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index f73824135f..1c8c2ed36e 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -5,8 +5,21 @@ #include "html_template_element.h" #include "html_names.h" +#include "core/dom/document_fragment.h" namespace kraken { HTMLTemplateElement::HTMLTemplateElement(Document& document) : HTMLElement(html_names::ktemplate, &document) {} + +DocumentFragment* HTMLTemplateElement::content() const { + return ContentInternal(); +} + +DocumentFragment* HTMLTemplateElement::ContentInternal() const { + if (!content_ && GetExecutingContext()) + content_ = DocumentFragment::Create(GetDocument()); + + return content_.Get(); +} + } // namespace kraken diff --git a/bridge/core/html/html_template_element.d.ts b/bridge/core/html/html_template_element.d.ts new file mode 100644 index 0000000000..02c2837ee8 --- /dev/null +++ b/bridge/core/html/html_template_element.d.ts @@ -0,0 +1,7 @@ +import {HTMLElement} from "./html_element"; +import {DocumentFragment} from "../dom/document_fragment"; + +export interface HTMLTemplateElement extends HTMLElement { + readonly content: DocumentFragment; + new(): void; +} diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index 1969d2f784..3673758e61 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -21,6 +21,8 @@ class HTMLTemplateElement : public HTMLElement { DocumentFragment* content() const; private: + DocumentFragment* ContentInternal() const; + mutable Member<DocumentFragment> content_; }; } // namespace kraken diff --git a/bridge/core/html/parser/html_parser.cc b/bridge/core/html/parser/html_parser.cc new file mode 100644 index 0000000000..0fc9039efb --- /dev/null +++ b/bridge/core/html/parser/html_parser.cc @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include <utility> + +#include "core/dom/element.h" +#include "core/dom/document.h" +#include "foundation/logging.h" +#include "html_parser.h" + +namespace kraken { + +inline std::string trim(const std::string& str) { + std::string tmp = str; + tmp.erase(0, tmp.find_first_not_of(' ')); // prefixing spaces + tmp.erase(tmp.find_last_not_of(' ') + 1); // surfixing spaces + return tmp; +} + +// Parse html,isHTMLFragment should be false if need to automatically complete html, head, and body when they are +// missing. +GumboOutput* parse(const std::string& html, bool isHTMLFragment = false) { + // Gumbo-parser parse HTML. + GumboOutput* htmlTree = gumbo_parse_with_options(&kGumboDefaultOptions, html.c_str(), html.length()); + + if (isHTMLFragment) { + // Find body. + const GumboVector* children = &htmlTree->root->v.element.children; + for (int i = 0; i < children->length; ++i) { + auto* child = (GumboNode*)children->data[i]; + if (child->type == GUMBO_NODE_ELEMENT) { + std::string tagName; + if (child->v.element.tag != GUMBO_TAG_UNKNOWN) { + tagName = gumbo_normalized_tagname(child->v.element.tag); + } else { + GumboStringPiece piece = child->v.element.original_tag; + gumbo_tag_from_original_text(&piece); + tagName = std::string(piece.data, piece.length); + } + + if (tagName.compare("body") == 0) { + htmlTree->root = child; + break; + } + } + } + } + + return htmlTree; +} + +void HTMLParser::traverseHTML(Node* root_node, GumboNode* node) { + auto* context = root_node->GetExecutingContext(); + JSContext* ctx = root_node->GetExecutingContext()->ctx(); + + const GumboVector* children = &node->v.element.children; + for (int i = 0; i < children->length; ++i) { + auto* child = (GumboNode*)children->data[i]; + + if (auto* root_container = DynamicTo<ContainerNode>(root_node)) { + if (child->type == GUMBO_NODE_ELEMENT) { + std::string tagName; + if (child->v.element.tag != GUMBO_TAG_UNKNOWN) { + tagName = gumbo_normalized_tagname(child->v.element.tag); + } else { + GumboStringPiece piece = child->v.element.original_tag; + gumbo_tag_from_original_text(&piece); + tagName = std::string(piece.data, piece.length); + } + + auto* element = context->document()->createElement(AtomicString(ctx, tagName), ASSERT_NO_EXCEPTION()); + root_container->AppendChild(element); + parseProperty(element, &child->v.element); + + // eval javascript when <script>//code...</script>. + if (child->v.element.children.length > 0) { + if (child->v.element.tag == GUMBO_TAG_SCRIPT) { + const char* code = ((GumboNode*)child->v.element.children.data[0])->v.text.text; + context->EvaluateJavaScript(code, strlen(code), "vm://", 0); + } else { + traverseHTML(element, child); + } + } + } else if (child->type == GUMBO_NODE_TEXT) { + auto* text = context->document()->createTextNode(AtomicString(ctx, child->v.text.text), ASSERT_NO_EXCEPTION()); + root_container->AppendChild(text); + } + } + } +} + +bool HTMLParser::parseHTML(const std::string& html, Node* root_node, bool isHTMLFragment) { + if (root_node != nullptr) { + if (auto* root_container_node = DynamicTo<ContainerNode>(root_node)) { + root_container_node->RemoveChildren(); + + if (!trim(html).empty()) { + GumboOutput* htmlTree = parse(html, isHTMLFragment); + traverseHTML(root_container_node, htmlTree->root); + // Free gumbo parse nodes. + gumbo_destroy_output(&kGumboDefaultOptions, htmlTree); + } + } + } else { + KRAKEN_LOG(ERROR) << "Root node is null."; + } + + return true; +} + +bool HTMLParser::parseHTML(const std::string& html, Node* root_node) { + return parseHTML(html, root_node, false); +} + +bool HTMLParser::parseHTML(const char* code, size_t codeLength, Node* root_node) { + std::string html = std::string(code, codeLength); + return parseHTML(html, root_node, false); +} + +bool HTMLParser::parseHTMLFragment(const char* code, size_t codeLength, Node* rootNode) { + std::string html = std::string(code, codeLength); + return parseHTML(html, rootNode, true); +} + +void HTMLParser::parseProperty(Element* element, GumboElement* gumboElement) { + auto* context = element->GetExecutingContext(); + JSContext* ctx = context->ctx(); + + GumboVector* attributes = &gumboElement->attributes; + for (int j = 0; j < attributes->length; ++j) { + auto* attribute = (GumboAttribute*)attributes->data[j]; + + if (strcmp(attribute->name, "style") == 0) { + std::vector<std::string> arrStyles; + std::string::size_type prev_pos = 0, pos = 0; + std::string strStyles = attribute->value; + + while ((pos = strStyles.find(';', pos)) != std::string::npos) { + arrStyles.push_back(strStyles.substr(prev_pos, pos - prev_pos)); + prev_pos = ++pos; + } + arrStyles.push_back(strStyles.substr(prev_pos, pos - prev_pos)); + + auto* style = element->style(); + + for (auto& s : arrStyles) { + std::string::size_type position = s.find(':'); + if (position != std::basic_string<char>::npos) { + std::string styleKey = s.substr(0, position); + trim(styleKey); + std::string styleValue = s.substr(position + 1, s.length()); + trim(styleValue); + style->setProperty(AtomicString(ctx, styleKey), AtomicString(ctx, styleValue), ASSERT_NO_EXCEPTION()); + } + } + + } else { + std::string strName = attribute->name; + std::string strValue = attribute->value; + element->setAttribute(AtomicString(ctx, strName), AtomicString(ctx, strValue), ASSERT_NO_EXCEPTION()); + } + } +} + +} // namespace kraken diff --git a/bridge/core/html/parser/html_parser.h b/bridge/core/html/parser/html_parser.h new file mode 100644 index 0000000000..e9a479bc50 --- /dev/null +++ b/bridge/core/html/parser/html_parser.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_HTML_PARSER_H +#define KRAKENBRIDGE_HTML_PARSER_H + +#include <string> +#include <third_party/gumbo-parser/src/gumbo.h> +#include "foundation/native_string.h" + +namespace kraken { + +class Node; +class Element; +class ExecutingContext; + +class HTMLParser { + public: + static bool parseHTML(const char* code, size_t codeLength, Node* rootNode); + static bool parseHTML(const std::string& html, Node* rootNode); + static bool parseHTMLFragment(const char* code, size_t codeLength, Node* rootNode); + + private: + ExecutingContext* context_; + static void traverseHTML(Node* root, GumboNode* node); + static void parseProperty(Element* element, GumboElement* gumboElement); + + static bool parseHTML(const std::string& html, Node* rootNode, bool isHTMLFragment); +}; +} // namespace kraken + +#endif // KRAKENBRIDGE_HTML_PARSER_H diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 9cb60a9959..1fcc44fd40 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -6,8 +6,10 @@ #include <atomic> #include <unordered_map> -#include <core/dart_methods.h> #include "bindings/qjs/binding_initializer.h" +#include "core/dart_methods.h" +#include "core/dom/document.h" +#include "core/html/parser/html_parser.h" #include "foundation/logging.h" #include "page.h" #include "polyfill.h" @@ -32,13 +34,15 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) } bool KrakenPage::parseHTML(const char* code, size_t length) { - // if (!m_context->isValid()) - // return false; - // JSValue bodyValue = JS_GetPropertyStr(m_context->ctx(), m_context->document()->jsObject, "body"); - // auto* body = static_cast<Element*>(JS_GetOpaque(bodyValue, Element::classId)); - // HTMLParser::parseHTML(code, length, body); - // JS_FreeValue(m_context->ctx(), bodyValue); - // return true; + if (!m_context->IsValid()) + return false; + + // Remove all Nodes including body and head. + m_context->document()->documentElement()->RemoveChildren(); + + HTMLParser::parseHTML(code, length, m_context->document()->documentElement()); + + return true; } void KrakenPage::invokeModuleEvent(const NativeString* moduleName, diff --git a/bridge/page.cc b/bridge/page.cc deleted file mode 100644 index 890381c805..0000000000 --- a/bridge/page.cc +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#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/image_element.h" -#include "bindings/qjs/dom/elements/template_element.h" -#include "bindings/qjs/dom/event.h" -#include "bindings/qjs/dom/event_target.h" -#include "bindings/qjs/dom/events/.gen/close_event.h" -#include "bindings/qjs/dom/events/.gen/gesture_event.h" -#include "bindings/qjs/dom/events/.gen/input_event.h" -#include "bindings/qjs/dom/events/.gen/intersection_change.h" -#include "bindings/qjs/dom/events/.gen/media_error_event.h" -#include "bindings/qjs/dom/events/.gen/message_event.h" -#include "bindings/qjs/dom/events/.gen/mouse_event.h" -#include "bindings/qjs/dom/events/.gen/popstate_event.h" -#include "bindings/qjs/dom/events/touch_event.h" -#include "bindings/qjs/dom/style_declaration.h" -#include "bindings/qjs/dom/text_node.h" -#include "bindings/qjs/module_manager.h" - -namespace kraken { - -using namespace binding::qjs; - -std::unordered_map<std::string, NativeByteCode> KrakenPage::pluginByteCode{}; -ConsoleMessageHandler KrakenPage::consoleMessageHandler{nullptr}; - -kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; - -KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId) { -#if ENABLE_PROFILE - auto jsContextStartTime = - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()) - .count(); -#endif - m_context = new ExecutionContext(contextId, handler, this); - -#if ENABLE_PROFILE - auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; - nativePerformance.mark(PERF_JS_CONTEXT_INIT_START, jsContextStartTime); - nativePerformance.mark(PERF_JS_CONTEXT_INIT_END); - nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_START); -#endif - - bindConsole(m_context); - bindTimer(m_context); - bindScreen(m_context); - bindModuleManager(m_context); - bindEventTarget(m_context); - bindBlob(m_context); - bindWindow(m_context); - bindEvent(m_context); - bindCustomEvent(m_context); - bindNode(m_context); - bindDocumentFragment(m_context); - bindTextNode(m_context); - bindCommentNode(m_context); - bindElement(m_context); - bindAnchorElement(m_context); - bindCanvasElement(m_context); - bindImageElement(m_context); - bindInputElement(m_context); - bindObjectElement(m_context); - bindScriptElement(m_context); - bindTemplateElement(m_context); - bindCSSStyleDeclaration(m_context); - bindCloseEvent(m_context); - bindGestureEvent(m_context); - bindInputEvent(m_context); - bindIntersectionChangeEvent(m_context); - bindMediaErrorEvent(m_context); - bindMouseEvent(m_context); - bindMessageEvent(m_context); - bindPopStateEvent(m_context); - bindTouchEvent(m_context); - bindDocument(m_context); - bindPerformance(m_context); - -#if ENABLE_PROFILE - nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); - nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); -#endif - - initKrakenPolyFill(this); - - for (auto& p : pluginByteCode) { - evaluateByteCode(p.second.bytes, p.second.length); - } - -#if ENABLE_PROFILE - nativePerformance.mark(PERF_JS_POLYFILL_INIT_END); -#endif -} - -bool KrakenPage::parseHTML(const char* code, size_t length) { - if (!m_context->isValid()) - return false; - - ElementInstance* documentElement = m_context->document()->getDocumentElement(); - JSContext* ctx = m_context->ctx(); - int32_t len = arrayGetLength(ctx, documentElement->childNodes); - - // Remove all Nodes including body and head. - if (documentElement != nullptr) { - for (int i = len - 1; i >= 0; i--) { - JSValue v = JS_GetPropertyUint32(ctx, documentElement->childNodes, i); - auto* nodeInstance = static_cast<NodeInstance*>(JS_GetOpaque(v, Node::classId(v))); - if (nodeInstance->nodeType == NodeType::ELEMENT_NODE) { - documentElement->internalRemoveChild(nodeInstance); - } - JS_FreeValue(ctx, v); - } - - JS_FreeValue(ctx, documentElement->jsObject); - } - - HTMLParser::parseHTML(code, length, documentElement); - - return true; -} - -void KrakenPage::invokeModuleEvent(NativeString* moduleName, - const char* eventType, - void* rawEvent, - NativeString* extra) { - if (!m_context->isValid()) - return; - - JSValue eventObject = JS_NULL; - if (rawEvent != nullptr) { - std::string type = std::string(eventType); - auto* event = static_cast<RawEvent*>(rawEvent)->bytes; - EventInstance* eventInstance = Event::buildEventInstance(type, m_context, event, false); - eventObject = eventInstance->jsObject; - } - - JSValue moduleNameValue = - JS_NewUnicodeString(m_context->runtime(), m_context->ctx(), moduleName->string, moduleName->length); - JSValue extraObject = JS_NULL; - if (extra != nullptr) { - std::u16string u16Extra = std::u16string(reinterpret_cast<const char16_t*>(extra->string), extra->length); - std::string extraString = toUTF8(u16Extra); - extraObject = JS_ParseJSON(m_context->ctx(), extraString.c_str(), extraString.size(), ""); - } - - { - struct list_head *el, *el1; - list_for_each_safe(el, el1, &m_context->module_job_list) { - auto* module = list_entry(el, ModuleContext, link); - JSValue callback = module->callback; - - JSValue arguments[] = {moduleNameValue, eventObject, extraObject}; - JSValue returnValue = JS_Call(m_context->ctx(), callback, m_context->global(), 3, arguments); - m_context->handleException(&returnValue); - JS_FreeValue(m_context->ctx(), returnValue); - } - } - - JS_FreeValue(m_context->ctx(), moduleNameValue); - - if (rawEvent != nullptr) { - JS_FreeValue(m_context->ctx(), eventObject); - } - if (extra != nullptr) { - JS_FreeValue(m_context->ctx(), extraObject); - } -} - -void KrakenPage::evaluateScript(const NativeString* script, const char* url, int startLine) { - if (!m_context->isValid()) - return; - -#if ENABLE_PROFILE - auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; - nativePerformance.mark(PERF_JS_PARSE_TIME_START); - std::u16string patchedCode = std::u16string(u"performance.mark('js_parse_time_end');") + - std::u16string(reinterpret_cast<const char16_t*>(script->string), script->length); - m_context->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); -#else - m_context->evaluateJavaScript(script->string, script->length, url, startLine); -#endif -} - -void KrakenPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { - if (!m_context->isValid()) - return; - m_context->evaluateJavaScript(script, length, url, startLine); -} - -void KrakenPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { - if (!m_context->isValid()) - return; - m_context->evaluateJavaScript(script, length, url, startLine); -} - -uint8_t* KrakenPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { - if (!m_context->isValid()) - return nullptr; - return m_context->dumpByteCode(script, length, url, byteLength); -} - -void KrakenPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { - if (!m_context->isValid()) - return; - m_context->evaluateByteCode(bytes, byteLength); -} - -KrakenPage::~KrakenPage() { -#if IS_TEST - if (disposeCallback != nullptr) { - disposeCallback(this); - } -#endif - delete m_context; - KrakenPage::pageContextPool[contextId] = nullptr; -} - -void KrakenPage::reportError(const char* errmsg) { - m_handler(m_context->getContextId(), errmsg); -} - -} // namespace kraken diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 15ecd9e872..ef7dcf7e27 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -24,6 +24,7 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./core/dom/events/event_target_test.cc ./core/dom/document_test.cc ./core/dom/node_test.cc + ./core/dom/element_test.cc # ./bindings/qjs/bom/timer_test.cc # ./bindings/qjs/qjs_patch_test.cc # ./bindings/qjs/garbage_collected_test.cc From 36b373ef4001bbef01bc73ad037ddc64f5a3d8f3 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Tue, 3 May 2022 16:33:03 +0800 Subject: [PATCH 115/498] feat: support interator props on scriptwrappable. --- bridge/bindings/qjs/script_wrappable.cc | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 62d71906db..a110fa3a8d 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -92,6 +92,25 @@ static int HandleJSPropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSA return wrapper_type_info->string_property_checker_handler_(ctx, obj, atom); } +static int HandleJSGetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, + uint32_t *plen, + JSValueConst obj) { + // All props and methods are finded in prototype object of scriptwrappable. + JSValue proto = JS_GetPrototype(ctx, obj); + bool result = JS_GetOwnPropertyNames(ctx, ptab, plen, proto, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK); + JS_FreeValue(ctx, proto); + return result; +}; + +static int HandleJSGetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop) { + // Call JSGetOwnPropertyNames will also call HandleJSGetOwnProperty for secondary verify. + JSValue proto = JS_GetPrototype(ctx, obj); + bool result = JS_GetOwnProperty(ctx, desc, proto, prop); + JS_FreeValue(ctx, proto); + return result; +} + void ScriptWrappable::InitializeQuickJSObject() { auto* wrapper_type_info = GetWrapperTypeInfo(); JSRuntime* runtime = runtime_; @@ -127,6 +146,10 @@ void ScriptWrappable::InitializeQuickJSObject() { exotic_methods->has_property = HandleJSPropertyCheckerCallback; } + // Support iterate script wrappable defined properties. + exotic_methods->get_own_property_names = HandleJSGetOwnPropertyNames; + exotic_methods->get_own_property = HandleJSGetOwnProperty; + def.exotic = exotic_methods; def.finalizer = HandleJSObjectFinalized; From aa7f8429ca18667b86345f937e545416e39bf8fd Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Tue, 3 May 2022 08:37:43 +0000 Subject: [PATCH 116/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 4 ++-- bridge/bindings/qjs/script_wrappable.cc | 7 ++----- bridge/bindings/qjs/script_wrappable.h | 2 +- bridge/core/dom/element.cc | 7 ++++--- bridge/core/dom/element_test.cc | 2 +- bridge/core/dom/legacy/bounding_client_rect.h | 2 +- bridge/core/html/html_template_element.cc | 2 +- bridge/core/html/parser/html_parser.cc | 2 +- bridge/core/html/parser/html_parser.h | 2 +- 9 files changed, 14 insertions(+), 16 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index acee6d9d66..6cd626717f 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -6,6 +6,7 @@ #include "binding_initializer.h" #include "core/executing_context.h" +#include "qjs_bounding_client_rect.h" #include "qjs_character_data.h" #include "qjs_comment.h" #include "qjs_console.h" @@ -20,14 +21,13 @@ #include "qjs_html_element.h" #include "qjs_html_head_element.h" #include "qjs_html_html_element.h" -#include "qjs_html_unknown_element.h" #include "qjs_html_template_element.h" +#include "qjs_html_unknown_element.h" #include "qjs_module_manager.h" #include "qjs_node.h" #include "qjs_node_list.h" #include "qjs_text.h" #include "qjs_window.h" -#include "qjs_bounding_client_rect.h" namespace kraken { diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index a110fa3a8d..0a23c669b1 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -92,9 +92,7 @@ static int HandleJSPropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSA return wrapper_type_info->string_property_checker_handler_(ctx, obj, atom); } -static int HandleJSGetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, - uint32_t *plen, - JSValueConst 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); @@ -102,8 +100,7 @@ static int HandleJSGetOwnPropertyNames(JSContext *ctx, JSPropertyEnum **ptab, return result; }; -static int HandleJSGetOwnProperty(JSContext *ctx, JSPropertyDescriptor *desc, - JSValueConst obj, JSAtom prop) { +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); diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index ab50eecd2e..55c0fa2c8e 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -7,8 +7,8 @@ #define KRAKENBRIDGE_SCRIPT_WRAPPABLE_H #include <quickjs/quickjs.h> -#include "foundation/macros.h" #include "bindings/qjs/cppgc/garbage_collected.h" +#include "foundation/macros.h" #include "wrapper_type_info.h" namespace kraken { diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 96cb8c1947..b3e2a15fea 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -8,10 +8,10 @@ #include "bindings/qjs/exception_state.h" #include "bindings/qjs/script_promise.h" #include "bindings/qjs/script_promise_resolver.h" -#include "core/fileapi/blob.h" #include "core/dom/document_fragment.h" -#include "core/html/parser/html_parser.h" +#include "core/fileapi/blob.h" #include "core/html/html_template_element.h" +#include "core/html/parser/html_parser.h" #include "foundation/native_value_converter.h" namespace kraken { @@ -337,7 +337,8 @@ std::string Element::innerHTML() { parent = To<Node>(template_element->content()); } - if (parent->firstChild() == nullptr) return s; + if (parent->firstChild() == nullptr) + return s; auto* child = parent->firstChild(); while (child != nullptr) { diff --git a/bridge/core/dom/element_test.cc b/bridge/core/dom/element_test.cc index 78b23eeb4a..01016e1dfe 100644 --- a/bridge/core/dom/element_test.cc +++ b/bridge/core/dom/element_test.cc @@ -3,9 +3,9 @@ * Author: Kraken Team. */ +#include "core/dom/legacy/bounding_client_rect.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "core/dom/legacy/bounding_client_rect.h" using namespace kraken; TEST(Element, setAttribute) { diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index c48f653cf7..e5ae417dea 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -5,8 +5,8 @@ #ifndef KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ #define KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ -#include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_wrappable.h" namespace kraken { diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index 1c8c2ed36e..20c893e200 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -4,8 +4,8 @@ */ #include "html_template_element.h" -#include "html_names.h" #include "core/dom/document_fragment.h" +#include "html_names.h" namespace kraken { diff --git a/bridge/core/html/parser/html_parser.cc b/bridge/core/html/parser/html_parser.cc index 0fc9039efb..7bde226c92 100644 --- a/bridge/core/html/parser/html_parser.cc +++ b/bridge/core/html/parser/html_parser.cc @@ -4,8 +4,8 @@ #include <utility> -#include "core/dom/element.h" #include "core/dom/document.h" +#include "core/dom/element.h" #include "foundation/logging.h" #include "html_parser.h" diff --git a/bridge/core/html/parser/html_parser.h b/bridge/core/html/parser/html_parser.h index e9a479bc50..629e4c9f63 100644 --- a/bridge/core/html/parser/html_parser.h +++ b/bridge/core/html/parser/html_parser.h @@ -6,8 +6,8 @@ #ifndef KRAKENBRIDGE_HTML_PARSER_H #define KRAKENBRIDGE_HTML_PARSER_H -#include <string> #include <third_party/gumbo-parser/src/gumbo.h> +#include <string> #include "foundation/native_string.h" namespace kraken { From e3217cab49cc44f3d249e495e7db6cad7e3e7eb4 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Fri, 13 May 2022 18:25:27 +0800 Subject: [PATCH 117/498] fix: add dictionary init. --- .../code_generator/src/idl/generateSource.ts | 22 ++++++++++++++++++- .../static/idl_templates/dictionary.cc.tpl | 4 ++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index e148f3e987..29f0b2b744 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -6,6 +6,7 @@ import { FunctionDeclaration, FunctionObject, ParameterMode, + PropsDeclaration, } from "./declaration"; import {addIndent, getClassName} from "./utils"; import {ParameterType} from "./analyzer"; @@ -229,6 +230,24 @@ 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 @@ -384,7 +403,8 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass blob: blob, props: props, object: object, - generateTypeConverter + generateTypeConverter, + generateDictionaryInit }); } case TemplateKind.globalFunction: { diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl index 3d4c3384b6..7efba4a6f1 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl @@ -5,8 +5,8 @@ std::shared_ptr<<%= className %>> <%= className %>::Create(JSContext* ctx, JSVal return std::make_shared<<%= className %>>(ctx, value, exception_state); } -<%= className %>::<%= className %>() {} -<%= className %>::<%= className %>(JSContext* ctx, JSValue value, ExceptionState& exception_state) { +<%= className %>::<%= className %>() <%= generateDictionaryInit(blob, props) %> {} +<%= className %>::<%= className %>(JSContext* ctx, JSValue value, ExceptionState& exception_state): <%= className %>() { FillMembersWithQJSObject(ctx, value, exception_state); } From 87ed6a0ed12ebadb433e8d18b598771b5157de5c Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Fri, 13 May 2022 22:22:47 +0800 Subject: [PATCH 118/498] feat: add kraken test context impl. --- bridge/bindings/qjs/script_value.h | 1 + bridge/core/dom/document_test.cc | 18 +- bridge/core/dom/element_test.cc | 12 +- bridge/core/dom/events/event_target_test.cc | 22 +- bridge/core/dom/node_test.cc | 18 +- bridge/core/executing_context.cc | 2 +- bridge/core/executing_context_test.cc | 28 +- .../core/frame/module_callback_coordinator.cc | 4 + .../core/frame/module_callback_coordinator.h | 2 + bridge/core/frame/module_manager_test.cc | 6 +- bridge/core/page.cc | 104 +++--- bridge/core/page.h | 8 +- bridge/foundation/native_value_converter.cc | 2 +- bridge/foundation/ui_command_buffer.cc | 6 +- bridge/include/kraken_bridge_test.h | 2 +- bridge/kraken_bridge.cc | 8 +- bridge/kraken_bridge_test.cc | 2 +- bridge/test/benchmark/create_element.cc | 4 +- bridge/test/kraken_test_context.cc | 312 +++++++++--------- bridge/test/kraken_test_context.h | 7 +- bridge/test/kraken_test_env.cc | 6 +- bridge/test/run_integration_test.cc | 2 +- 22 files changed, 286 insertions(+), 290 deletions(-) diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index f4e37ab0ed..539632e6e0 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -37,6 +37,7 @@ class ScriptValue final { static ScriptValue Empty(JSContext* ctx); // Wrap an Quickjs JSValue to ScriptValue. explicit ScriptValue(JSContext* ctx, JSValue value) : ctx_(ctx), value_(JS_DupValue(ctx, value)){}; + explicit ScriptValue(JSContext* ctx, const NativeString* string): ctx_(ctx), value_(JS_NewUnicodeString(ctx, string->string(), string->length())) {} explicit ScriptValue(JSContext* ctx) : ctx_(ctx){}; ScriptValue() = default; diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 79771fff05..aa6c66356f 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -19,7 +19,7 @@ TEST(Document, createElement) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "console.log(div);"; @@ -39,7 +39,7 @@ TEST(Document, body) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "console.log(document.body)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -53,7 +53,7 @@ TEST(Document, appendParentWillFail) { 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 = "document.body.appendChild(document.documentElement)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, true); @@ -71,7 +71,7 @@ TEST(Document, createTextNode) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "div.setAttribute('hello', 1234);" @@ -95,7 +95,7 @@ TEST(Document, createComment) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "div.setAttribute('hello', 1234);" @@ -119,7 +119,7 @@ TEST(Document, instanceofNode) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + 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); @@ -137,7 +137,7 @@ TEST(Document, FreedByOutOfScope) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "(() => { let img = document.createElement('div'); })();"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -153,14 +153,14 @@ TEST(Document, createElementShouldWorkWithMultipleContext) { { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); - auto context = bridge->getContext(); + 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->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "(() => { let img = document.createElement('img'); document.body.appendChild(img); })();"; bridge->evaluateScript(code, strlen(code), "vm://", 0); } diff --git a/bridge/core/dom/element_test.cc b/bridge/core/dom/element_test.cc index 01016e1dfe..088820f54f 100644 --- a/bridge/core/dom/element_test.cc +++ b/bridge/core/dom/element_test.cc @@ -19,7 +19,7 @@ TEST(Element, setAttribute) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "div.setAttribute('hello', 1234);" @@ -41,7 +41,7 @@ TEST(Element, getAttribute) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "let string = 'helloworld';" @@ -69,7 +69,7 @@ TEST(Element, setAttributeWithHTML) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "div.innerHTML = '<img src=\"https://miniapp-nikestore-demo.oss-cn-beijing.aliyuncs.com/white_shoes_v1.png\" " @@ -90,7 +90,7 @@ TEST(Element, instanceofNode) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "console.log(div instanceof Node)"; @@ -111,7 +111,7 @@ TEST(Element, instanceofEventTarget) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "console.log(div instanceof EventTarget)"; @@ -135,7 +135,7 @@ TEST(Element, stringifyBoundingClientRect) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); NativeBoundingClientRect nativeRect{ 10.0, 20.0, 30.0, 40.0, 10.0, 20.0, 30.0, 40.0, diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index e62eacab16..75f6e91e60 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -20,7 +20,7 @@ TEST(EventTarget, addEventListener) { KRAKEN_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.dispatchEvent(new Event('click'));"; @@ -39,7 +39,7 @@ TEST(EventTarget, removeEventListener) { KRAKEN_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'));"; @@ -60,7 +60,7 @@ TEST(EventTarget, removeEventListener) { // errorCalled = true; // }); // -// auto context = bridge->getContext(); +// auto context = bridge->GetExecutingContext(); // const char* code = // "let div = document.createElement('div'); div._a = { name: 1}; console.log(div._a); " // "document.body.appendChild(div);"; @@ -79,7 +79,7 @@ TEST(EventTarget, removeEventListener) { // KRAKEN_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; };" @@ -101,7 +101,7 @@ TEST(EventTarget, removeEventListener) { // KRAKEN_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; };" @@ -124,7 +124,7 @@ TEST(EventTarget, removeEventListener) { // KRAKEN_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'));"; @@ -144,7 +144,7 @@ TEST(EventTarget, removeEventListener) { // KRAKEN_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'; @@ -186,7 +186,7 @@ TEST(EventTarget, removeEventListener) { // KRAKEN_LOG(VERBOSE) << errmsg; // errorCalled = true; // }); -// auto context = bridge->getContext(); +// auto context = bridge->GetExecutingContext(); // std::string code = std::string(R"( // class Sample extends EventTarget { // constructor() { @@ -221,7 +221,7 @@ TEST(EventTarget, removeEventListener) { // EXPECT_STREQ(message.c_str(), "1234"); // }; // auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); -// auto context = bridge->getContext(); +// auto context = bridge->GetExecutingContext(); // std::string code = std::string(R"( //{ //// Wrap div in a block scope will be freed by GC @@ -275,12 +275,12 @@ TEST(EventTarget, removeEventListener) { // }; // std::string code = "addEventListener('click', () => {console.log(1)});"; // bridge->evaluateScript(code.c_str(), code.size(), "internal://", 0); -// JS_RunGC(bridge->getContext()->runtime()); +// JS_RunGC(bridge->GetExecutingContext()->runtime()); // // std::string code2 = "addEventListener('appear', () => {console.log(2)});"; // bridge->evaluateScript(code2.c_str(), code2.size(), "internal://", 0); // -// JS_RunGC(bridge->getContext()->runtime()); +// JS_RunGC(bridge->GetExecutingContext()->runtime()); // // std::string code3 = "(function() { var eeee = new Event('appear'); dispatchEvent(eeee); } )();"; // bridge->evaluateScript(code3.c_str(), code3.size(), "internal://", 0); diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 7f0e22abf4..6bef332e6e 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -16,7 +16,7 @@ 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);" @@ -36,7 +36,7 @@ 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(); const char* code = "let div1 = document.createElement('div');" "let div2 = document.createElement('div');" @@ -60,7 +60,7 @@ TEST(Node, textNodeHaveEmptyChildNodes) { 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 text = document.createTextNode('helloworld');" "console.log(text.childNodes);"; @@ -78,7 +78,7 @@ TEST(Node, textContent) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let text1 = document.createTextNode('1234');" "let text2 = document.createTextNode('helloworld');" @@ -100,7 +100,7 @@ TEST(Node, setTextContent) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "div.textContent = '1234';" @@ -119,7 +119,7 @@ TEST(Node, ensureDetached) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "let div = document.createElement('div');" "document.body.appendChild(div);" @@ -143,7 +143,7 @@ TEST(Node, replaceBody) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); // const char* code = "let newbody = document.createElement('body'); document.documentElement.replaceChild(newbody, // document.body)"; const char* code = "document.body = document.createElement('body');"; @@ -181,7 +181,7 @@ TEST(Node, cloneNode) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, false); @@ -230,7 +230,7 @@ TEST(Node, nestedNode) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(errorCalled, false); diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 68e50cb1a0..85424714ab 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -29,7 +29,7 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& auto jsContextStartTime = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()) .count(); - auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; + 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); diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 041f676983..d99eabc5e5 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -13,11 +13,11 @@ using namespace kraken; TEST(Context, isValid) { { auto bridge = TEST_init(); - EXPECT_EQ(bridge->getContext()->IsValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsValid(), true); } { auto bridge = TEST_init(); - EXPECT_EQ(bridge->getContext()->IsValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsValid(), true); } } @@ -237,7 +237,7 @@ generateRejectedPromise(); )"; bridge->evaluateScript(code.c_str(), code.size(), "file://", 0); - TEST_runLoop(bridge->getContext()); + TEST_runLoop(bridge->GetExecutingContext()); EXPECT_EQ(errorHandlerExecuted, false); EXPECT_EQ(logCalled, true); kraken::KrakenPage::consoleMessageHandler = nullptr; @@ -276,7 +276,7 @@ TEST(Context, accessGetUICommandItemsAfterDisposed) { int32_t contextId; { auto bridge = TEST_init(); - contextId = bridge->getContext()->contextId(); + contextId = bridge->GetExecutingContext()->contextId(); } EXPECT_EQ(getUICommandItems(contextId), nullptr); @@ -289,7 +289,7 @@ TEST(Context, disposeContext) { auto bridge = static_cast<kraken::KrakenPage*>(getPage(contextId)); static bool disposed = false; bridge->disposeCallback = [](kraken::KrakenPage* bridge) { disposed = true; }; - disposePage(bridge->getContext()->contextId()); + disposePage(bridge->GetExecutingContext()->contextId()); EXPECT_EQ(disposed, true); } @@ -354,36 +354,36 @@ 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<kraken::NativeString> nativeString = kraken::jsValueToNativeString(bridge->getContext()->ctx(), str); + JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "helloworld"); + std::unique_ptr<kraken::NativeString> nativeString = kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); EXPECT_EQ(nativeString->length(), 10); uint8_t expectedString[10] = {104, 101, 108, 108, 111, 119, 111, 114, 108, 100}; for (int i = 0; i < 10; i++) { EXPECT_EQ(expectedString[i], *(nativeString->string() + i)); } - 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<kraken::NativeString> nativeString = kraken::jsValueToNativeString(bridge->getContext()->ctx(), str); + JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "这是你的优乐美"); + std::unique_ptr<kraken::NativeString> nativeString = kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); std::u16string expectedString = u"这是你的优乐美"; EXPECT_EQ(nativeString->length(), expectedString.size()); for (int i = 0; i < nativeString->length(); i++) { EXPECT_EQ(expectedString[i], *(nativeString->string() + i)); } - 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<kraken::NativeString> nativeString = kraken::jsValueToNativeString(bridge->getContext()->ctx(), str); + JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "……🤪"); + std::unique_ptr<kraken::NativeString> nativeString = kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); std::u16string expectedString = u"……🤪"; EXPECT_EQ(nativeString->length(), expectedString.length()); for (int i = 0; i < nativeString->length(); i++) { EXPECT_EQ(expectedString[i], *(nativeString->string() + i)); } - JS_FreeValue(bridge->getContext()->ctx(), str); + JS_FreeValue(bridge->GetExecutingContext()->ctx(), str); } diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc index de04833a2c..835ae00022 100644 --- a/bridge/core/frame/module_callback_coordinator.cc +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -15,6 +15,10 @@ void ModuleCallbackCoordinator::RemoveModuleCallbacks(std::shared_ptr<ModuleCall listeners_.remove(callback); } +const std::forward_list<std::shared_ptr<ModuleCallback>>* ModuleCallbackCoordinator::listeners() const { + return &listeners_; +} + ModuleCallbackCoordinator::ModuleCallbackCoordinator() {} } // namespace kraken diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_callback_coordinator.h index 9394e16668..f858bd93ee 100644 --- a/bridge/core/frame/module_callback_coordinator.h +++ b/bridge/core/frame/module_callback_coordinator.h @@ -23,6 +23,8 @@ class ModuleCallbackCoordinator final { void AddModuleCallbacks(std::shared_ptr<ModuleCallback>&& callback); void RemoveModuleCallbacks(std::shared_ptr<ModuleCallback> callback); + [[nodiscard]] const std::forward_list<std::shared_ptr<ModuleCallback>>* listeners() const; + private: std::forward_list<std::shared_ptr<ModuleCallback>> listeners_; friend ModuleListener; diff --git a/bridge/core/frame/module_manager_test.cc b/bridge/core/frame/module_manager_test.cc index b8a085ce38..0cf21c9a0f 100644 --- a/bridge/core/frame/module_manager_test.cc +++ b/bridge/core/frame/module_manager_test.cc @@ -13,7 +13,7 @@ TEST(ModuleManager, ShouldReturnCorrectValue) { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); std::string code = std::string(R"( let object = { @@ -42,7 +42,7 @@ TEST(ModuleManager, shouldThrowErrorWhenBadJSON) { }); kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); std::string code = std::string(R"( let object = { @@ -74,7 +74,7 @@ TEST(ModuleManager, invokeModuleError) { "'}"); }; - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); std::string code = std::string(R"( function f() { diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 1fcc44fd40..7cf4629b36 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -22,7 +22,7 @@ kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { - m_context = new ExecutingContext( + context_ = new ExecutingContext( contextId, [](ExecutingContext* context, const char* message) { if (context->dartMethodPtr()->onJsError != nullptr) { @@ -34,13 +34,13 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) } bool KrakenPage::parseHTML(const char* code, size_t length) { - if (!m_context->IsValid()) + if (!context_->IsValid()) return false; // Remove all Nodes including body and head. - m_context->document()->documentElement()->RemoveChildren(); + context_->document()->documentElement()->RemoveChildren(); - HTMLParser::parseHTML(code, length, m_context->document()->documentElement()); + HTMLParser::parseHTML(code, length, context_->document()->documentElement()); return true; } @@ -49,90 +49,78 @@ void KrakenPage::invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* ptr, NativeString* extra) { - // if (!m_context->isValid()) - // return; - // - // JSValue eventObject = JS_NULL; - // if (ptr != nullptr) { - // std::string type = std::string(eventType); - // auto* rawEvent = static_cast<RawEvent*>(ptr)->bytes; - // Event* event = Event::create(m_context->ctx(), reinterpret_cast<NativeEvent*>(rawEvent)); - // eventObject = event->toQuickJS(); - // } - // - // 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); - // } + if (!context_->IsValid()) + return; + + JSContext* ctx = context_->ctx(); + Event* event = nullptr; + if (ptr != nullptr) { + std::string type = std::string(eventType); + auto* rawEvent = static_cast<RawEvent*>(ptr)->bytes; + event = Event::From(context_, reinterpret_cast<NativeEvent*>(rawEvent)); + } + + ScriptValue extraObject = ScriptValue::Empty(ctx); + if (extra != nullptr) { + std::u16string u16Extra = std::u16string(reinterpret_cast<const char16_t*>(extra->string()), extra->length()); + std::string extraString = toUTF8(u16Extra); + extraObject = ScriptValue::CreateJsonObject(ctx, extraString.c_str(), extraString.length()); + } + + auto* listeners = context_->ModuleCallbacks()->listeners(); + for (auto& listener : *listeners) { + ScriptValue arguments[] = {ScriptValue(ctx, moduleName), + event != nullptr ? event->ToValue() : ScriptValue::Empty(ctx), extraObject}; + ScriptValue result = listener->value()->Invoke(ctx, ScriptValue::Empty(ctx), 3, arguments); + if (result.IsException()) { + context_->HandleException(&result); + } + } } void KrakenPage::evaluateScript(const NativeString* script, const char* url, int startLine) { - if (!m_context->IsValid()) + if (!context_->IsValid()) return; #if ENABLE_PROFILE - auto nativePerformance = Performance::instance(m_context)->m_nativePerformance; + 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); - m_context->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); + context_->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); #else - m_context->EvaluateJavaScript(script->string(), script->length(), url, startLine); + context_->EvaluateJavaScript(script->string(), script->length(), url, startLine); #endif } void KrakenPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { - if (!m_context->IsValid()) + if (!context_->IsValid()) return; - m_context->EvaluateJavaScript(script, length, url, startLine); + context_->EvaluateJavaScript(script, length, url, startLine); } void KrakenPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { - if (!m_context->IsValid()) + if (!context_->IsValid()) return; - m_context->EvaluateJavaScript(script, length, url, startLine); + context_->EvaluateJavaScript(script, length, url, startLine); } uint8_t* KrakenPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { - if (!m_context->IsValid()) + if (!context_->IsValid()) return nullptr; - return m_context->DumpByteCode(script, length, url, byteLength); + return context_->DumpByteCode(script, length, url, byteLength); } void KrakenPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { - if (!m_context->IsValid()) + if (!context_->IsValid()) return; - m_context->EvaluateByteCode(bytes, byteLength); + context_->EvaluateByteCode(bytes, byteLength); } void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { size_t i = 0; - auto& dartMethodPointer = m_context->dartMethodPtr(); + auto& dartMethodPointer = context_->dartMethodPtr(); dartMethodPointer->invokeModule = reinterpret_cast<InvokeModule>(methodBytes[i++]); dartMethodPointer->requestBatchUpdate = reinterpret_cast<RequestBatchUpdate>(methodBytes[i++]); @@ -171,12 +159,12 @@ KrakenPage::~KrakenPage() { disposeCallback(this); } #endif - delete m_context; + delete context_; KrakenPage::pageContextPool[contextId] = nullptr; } void KrakenPage::reportError(const char* errmsg) { - m_handler(m_context, errmsg); + handler_(context_, errmsg); } } // namespace kraken diff --git a/bridge/core/page.h b/bridge/core/page.h index db85dc6dc5..28e1aa3f73 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -45,7 +45,7 @@ class KrakenPage final { void registerDartMethods(uint64_t* methodBytes, int32_t length); std::thread::id currentThread() const; - [[nodiscard]] ExecutingContext* getContext() const { return m_context; } + [[nodiscard]] ExecutingContext* GetExecutingContext() const { return context_; } void invokeModuleEvent(const NativeString* moduleName, const char* eventType, void* event, NativeString* extra); void reportError(const char* errmsg); @@ -58,11 +58,11 @@ class KrakenPage final { #endif private: const std::thread::id ownerThreadId; - // FIXME: we must to use raw pointer instead of unique_ptr because we needs to access m_context when dispose page. + // 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* m_context; - JSExceptionHandler m_handler; + ExecutingContext* context_; + JSExceptionHandler handler_; }; } // namespace kraken diff --git a/bridge/foundation/native_value_converter.cc b/bridge/foundation/native_value_converter.cc index 4c52bd2d7f..2825bab773 100644 --- a/bridge/foundation/native_value_converter.cc +++ b/bridge/foundation/native_value_converter.cc @@ -13,7 +13,7 @@ namespace kraken { // int32_t argc, // NativeValue* argv, // NativeValue* returnValue) { -// // auto* context = functionContext->m_context; +// // auto* context = functionContext->context_; // // auto* arguments = new JSValue[argc]; // // for (int i = 0; i < argc; i++) { // // arguments[i] = nativeValueToJSValue(context, argv[i]); diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 2e623ec616..38d412c50e 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -24,7 +24,7 @@ void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr, bo void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND - m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); + context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); #endif update_batched = true; } @@ -36,7 +36,7 @@ void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { void UICommandBuffer::addCommand(int32_t id, UICommand type, NativeString* args_01, void* nativePtr) { if (!update_batched) { #if FLUTTER_BACKEND - m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); + context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); update_batched = true; #endif } @@ -52,7 +52,7 @@ void UICommandBuffer::addCommand(int32_t id, void* nativePtr) { #if FLUTTER_BACKEND if (!update_batched) { - m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); + context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); update_batched = true; } #endif diff --git a/bridge/include/kraken_bridge_test.h b/bridge/include/kraken_bridge_test.h index c524ec92ac..6942dbddba 100644 --- a/bridge/include/kraken_bridge_test.h +++ b/bridge/include/kraken_bridge_test.h @@ -13,7 +13,7 @@ void initTestFramework(int32_t contextId); KRAKEN_EXPORT_C int8_t evaluateTestScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine); -using ExecuteCallback = void* (*)(int32_t contextId, NativeString* status); +using ExecuteCallback = void* (*)(int32_t contextId, void* status); KRAKEN_EXPORT_C void executeTest(int32_t contextId, ExecuteCallback executeCallback); diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 2063d82ad9..bd0df85c64 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -121,7 +121,7 @@ bool checkPage(int32_t contextId, void* context) { if (kraken::KrakenPage::pageContextPool[contextId] == nullptr) return false; auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - return page->getContext() == context; + return page->GetExecutingContext() == context; } void evaluateScripts(int32_t contextId, kraken::NativeString* code, const char* bundleFilename, int startLine) { @@ -209,21 +209,21 @@ void* getUICommandItems(int32_t contextId) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); if (page == nullptr) return nullptr; - return page->getContext()->uiCommandBuffer()->data(); + return page->GetExecutingContext()->uiCommandBuffer()->data(); } int64_t getUICommandItemSize(int32_t contextId) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); if (page == nullptr) return 0; - return page->getContext()->uiCommandBuffer()->size(); + return page->GetExecutingContext()->uiCommandBuffer()->size(); } void clearUICommandItems(int32_t contextId) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); if (page == nullptr) return; - page->getContext()->uiCommandBuffer()->clear(); + page->GetExecutingContext()->uiCommandBuffer()->clear(); } void registerContextDisposedCallbacks(int32_t contextId, Task task, void* data) { diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index a29c261154..82692fe9f5 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -13,7 +13,7 @@ std::unordered_map<int, kraken::KrakenTestContext*> testContextPool = void initTestFramework(int32_t contextId) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - auto testContext = new kraken::KrakenTestContext(page->getContext()); + auto testContext = new kraken::KrakenTestContext(page->GetExecutingContext()); testContextPool[contextId] = testContext; } diff --git a/bridge/test/benchmark/create_element.cc b/bridge/test/benchmark/create_element.cc index 87e87719f7..2c8890bf43 100644 --- a/bridge/test/benchmark/create_element.cc +++ b/bridge/test/benchmark/create_element.cc @@ -10,7 +10,7 @@ auto bridge = TEST_init(); static void CreateRawJavaScriptObjects(benchmark::State& state) { - auto& context = bridge->getContext(); + auto& context = bridge->GetExecutingContext(); std::string code = "var a = {}"; // Perform setup here for (auto _ : state) { @@ -19,7 +19,7 @@ static void CreateRawJavaScriptObjects(benchmark::State& state) { } static void CreateDivElement(benchmark::State& state) { - auto& context = bridge->getContext(); + auto& context = bridge->GetExecutingContext(); std::string code = "var a = document.createElement('div');"; // Perform setup here for (auto _ : state) { diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index 416cb6d063..6d26a018b7 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -4,121 +4,96 @@ */ #include "kraken_test_context.h" +#include "bindings/qjs/member_installer.h" +#include "core/dom/document.h" +#include "core/fileapi/blob.h" +#include "core/html/parser/html_parser.h" +#include "qjs_blob.h" #include "testframework.h" namespace kraken { -KrakenTestContext::KrakenTestContext(ExecutingContext* context) : m_context(context) { - // bridge->owner = this; - // bridge->disposeCallback = [](KrakenPage* bridge) { delete static_cast<KrakenPageTest*>(bridge->owner); }; - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, executeTest, "__kraken_execute_test__", 1); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, matchImageSnapshot, "__kraken_match_image_snapshot__", 3); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, environment, "__kraken_environment__", 0); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulatePointer, "__kraken_simulate_pointer__", 1); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, simulateInputText, "__kraken_simulate_inputtext__", 1); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, triggerGlobalError, "__kraken_trigger_global_error__", 0); - // QJS_GLOBAL_BINDING_FUNCTION(m_page_context, parseHTML, "__kraken_parse_html__", 1); - - // initKrakenTestFramework(bridge); - // init_list_head(&image_link); -} +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."); + } -bool KrakenTestContext::evaluateTestScripts(const uint16_t* code, - size_t codeLength, - const char* sourceURL, - int startLine) { - if (!m_context->IsValid()) - return false; - return m_context->EvaluateJavaScript(code, codeLength, sourceURL, startLine); + if (!JS_IsFunction(ctx, callback)) { + return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); + } + auto bridge = static_cast<KrakenPage*>(context->owner()); + auto bridgeTest = static_cast<KrakenTestContext*>(bridge->owner); + bridgeTest->execute_test_callback_ = QJSFunction::Create(ctx, callback); + return JS_NULL; } -bool KrakenTestContext::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 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)); -static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - // JSValue& callback = argv[0]; - // auto context = static_cast<ExecutionContext*>(JS_GetContextOpaque(ctx)); - // if (!JS_IsObject(callback)) { - // return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); - // } - // - // if (!JS_IsFunction(ctx, callback)) { - // return JS_ThrowTypeError(ctx, "Failed to execute 'executeTest': parameter 1 (callback) is not an function."); - // } - // auto bridge = static_cast<KrakenPage*>(context->getOwner()); - // auto bridgeTest = static_cast<KrakenTestContext*>(bridge->owner); - // bridgeTest->m_executeTestCallback = ScriptValue(ctx, callback); - // return JS_NULL; -} + if (!QJSBlob::HasInstance(context, blobValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + } + auto* blob = toScriptWrappable<Blob>(blobValue); -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<ExecutionContext*>(JS_GetContextOpaque(ctx)); - // - // if (!JS_IsObject(blobValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be - // an Blob object."); - // } - // auto blob = static_cast<kraken::BlobInstance*>(JS_GetOpaque(blobValue, kraken::Blob::kBlobClassID)); - // - // if (blob == nullptr) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be - // an Blob object."); - // } - // - // if (!JS_IsString(screenShotValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be - // an string."); - // } - // - // if (!JS_IsObject(callbackValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is - // not an function."); - // } - // - // if (!JS_IsFunction(ctx, callbackValue)) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is - // not an function."); - // } - // - // if (getDartMethod()->matchImageSnapshot == nullptr) { - // return JS_ThrowTypeError(ctx, "Failed to execute '__kraken_match_image_snapshot__': dart method - // (matchImageSnapshot) is not registered."); - // } - // - // std::unique_ptr<NativeString> screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); - // auto bridge = static_cast<KrakenPageTest*>(static_cast<KrakenPage*>(context->getOwner())->owner); - // auto* callbackContext = new ImageSnapShotContext{JS_DupValue(ctx, callbackValue), context}; - // list_add_tail(&callbackContext->link, &bridge->image_link); - // - // auto fn = [](void* ptr, int32_t contextId, int8_t result, const char* errmsg) { - // auto* callbackContext = static_cast<ImageSnapShotContext*>(ptr); - // JSContext* ctx = callbackContext->context->ctx(); - // - // if (errmsg == nullptr) { - // JSValue arguments[] = {JS_NewBool(ctx, result != 0), JS_NULL}; - // JSValue returnValue = JS_Call(ctx, callbackContext->callback, callbackContext->context->global(), 1, - // arguments); callbackContext->context->handleException(&returnValue); - // } else { - // JSValue errmsgValue = JS_NewString(ctx, errmsg); - // JSValue arguments[] = {JS_NewBool(ctx, false), errmsgValue}; - // JSValue returnValue = JS_Call(ctx, callbackContext->callback, callbackContext->context->global(), 2, - // arguments); callbackContext->context->handleException(&returnValue); JS_FreeValue(ctx, errmsgValue); - // } - // - // callbackContext->context->drainPendingPromiseJobs(); - // JS_FreeValue(callbackContext->context->ctx(), callbackContext->callback); - // list_del(&callbackContext->link); - // }; - // - // getDartMethod()->matchImageSnapshot(callbackContext, context->getContextId(), blob->bytes(), blob->size(), - // screenShotNativeString.get(), fn); + if (blob == nullptr) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 1 (blob) must be an Blob object."); + } + + if (!JS_IsString(screenShotValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 2 (match) must be an string."); + } + + if (!JS_IsObject(callbackValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); + } + + if (!JS_IsFunction(ctx, callbackValue)) { + return JS_ThrowTypeError( + ctx, "Failed to execute '__kraken_match_image_snapshot__': parameter 3 (callback) is not an function."); + } + + if (context->dartMethodPtr()->matchImageSnapshot == nullptr) { + return JS_ThrowTypeError( + ctx, + "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); + } + + std::unique_ptr<NativeString> screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); + auto bridge = static_cast<KrakenTestContext*>(static_cast<KrakenPage*>(context->owner())->owner); + 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); + list_del(&callbackContext->link); + }; + + context->dartMethodPtr()->matchImageSnapshot(callbackContext, context->contextId(), blob->bytes(), blob->size(), + screenShotNativeString.get(), fn); return JS_NULL; } @@ -220,21 +195,15 @@ static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc }; static JSValue parseHTML(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { - // auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(ctx)); - // - // if (argc == 1) { - // JSValue& html = argv[0]; - // - // std::string strHTML = jsValueToStdString(ctx, html); - // - // JSValue bodyValue = JS_GetPropertyStr(context->ctx(), context->document()->jsObject, "body"); - // auto* body = static_cast<ElementInstance*>(JS_GetOpaque(bodyValue, Element::classId())); - // HTMLParser::parseHTML(strHTML, body); - // - // JS_FreeValue(ctx, bodyValue); - // } - // - // return JS_NULL; + auto* context = static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx)); + + if (argc == 1) { + std::string strHTML = AtomicString(ctx, argv[0]).ToStdString(); + auto* body = context->document()->body(); + HTMLParser::parseHTML(strHTML, body); + } + + return JS_NULL; } static JSValue triggerGlobalError(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { @@ -261,46 +230,77 @@ struct ExecuteCallbackContext { }; void KrakenTestContext::invokeExecuteTest(ExecuteCallback executeCallback) { - // if (JS_IsNull(executeTestCallback)) { - // return; - // } - // if (!JS_IsFunction(m_page_context->ctx(), executeTestCallback)) { - // return; - // } - // - // auto done = [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* func_data) - // -> JSValue { - // JSValue& statusValue = argv[0]; - // JSValue proxyObject = func_data[0]; - // auto* callbackContext = static_cast<ExecuteCallbackContext*>(JS_GetOpaque(proxyObject, 1)); - // - // if (!JS_IsString(statusValue)) { - // return JS_ThrowTypeError(ctx, "failed to execute 'done': parameter 1 (status) is not a string"); - // } - // - // std::unique_ptr<NativeString> status = kraken::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; + 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"); + } + + std::unique_ptr<NativeString> status = kraken::jsValueToNativeString(ctx, statusValue); + callbackContext->executeCallback(callbackContext->context->contextId(), status.get()); + return JS_NULL; + }; + auto* callbackContext = new ExecuteCallbackContext(context_, executeCallback); + execute_test_proxy_object_ = JS_NewObject(context_->ctx()); + JS_SetOpaque(execute_test_proxy_object_, callbackContext); + JSValue callbackData[]{execute_test_proxy_object_}; + JSValue callback = JS_NewCFunctionData(context_->ctx(), done, 0, 0, 1, callbackData); + + ScriptValue arguments[] = {ScriptValue(context_->ctx(), callback)}; + ScriptValue result = + execute_test_callback_->Invoke(context_->ctx(), ScriptValue::Empty(context_->ctx()), 1, arguments); + context_->HandleException(&result); + context_->DrainPendingPromiseJobs(); + JS_FreeValue(context_->ctx(), callback); + execute_test_callback_ = nullptr; +} + +KrakenTestContext::KrakenTestContext(ExecutingContext* context) : context_(context), page_(static_cast<KrakenPage*>(context->owner())) { + page_->owner = this; + page_->disposeCallback = [](KrakenPage* bridge) { delete static_cast<KrakenTestContext*>(bridge->owner); }; + + std::initializer_list<MemberInstaller::FunctionConfig> functionConfig{ + {"__kraken_execute_test__", executeTest, 1}, + {"__kraken_match_image_snapshot__", matchImageSnapshot, 3}, + {"__kraken_environment__", environment, 0}, + {"__kraken_simulate_pointer__", simulatePointer, 1}, + {"__kraken_simulate_inputtext__", simulateInputText, 1}, + {"__kraken_trigger_global_error__", triggerGlobalError, 0}, + {"__kraken_parse_html__", parseHTML, 1}, + }; + + MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); + initKrakenTestFramework(context); +} + +bool KrakenTestContext::evaluateTestScripts(const uint16_t* code, + size_t codeLength, + const char* sourceURL, + int startLine) { + if (!context_->IsValid()) + return false; + return context_->EvaluateJavaScript(code, codeLength, sourceURL, startLine); +} + +bool KrakenTestContext::parseTestHTML(const uint16_t* code, size_t codeLength) { + if (!context_->IsValid()) + return false; + std::string utf8Code = toUTF8(std::u16string(reinterpret_cast<const char16_t*>(code), codeLength)); + return page_->parseHTML(utf8Code.c_str(), utf8Code.length()); } void KrakenTestContext::registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length) { size_t i = 0; - auto& dartMethodPtr = m_context->dartMethodPtr(); + auto& dartMethodPtr = context_->dartMethodPtr(); dartMethodPtr->onJsError = reinterpret_cast<OnJSError>(methodBytes[i++]); dartMethodPtr->matchImageSnapshot = reinterpret_cast<MatchImageSnapshot>(methodBytes[i++]); diff --git a/bridge/test/kraken_test_context.h b/bridge/test/kraken_test_context.h index 74a6517351..ba3512a037 100644 --- a/bridge/test/kraken_test_context.h +++ b/bridge/test/kraken_test_context.h @@ -30,12 +30,13 @@ class KrakenTestContext final { void invokeExecuteTest(ExecuteCallback executeCallback); void registerTestEnvDartMethods(uint64_t* methodBytes, int32_t length); - // ScriptValue m_executeTestCallback{m_context->ctx()}; - // ScriptValue m_executeTestProxyObject{m_context->ctx()}; + 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* m_context{nullptr}; + ExecutingContext* context_{nullptr}; + KrakenPage* page_; }; } // namespace kraken diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 4b84aa67b6..f609b4c67a 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -141,14 +141,14 @@ uint32_t TEST_requestAnimationFrame(kraken::FrameCallback* frameCallback, int32_ void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - auto* context = page->getContext(); + auto* context = page->GetExecutingContext(); JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(ScriptState::runtime())); ts->os_frameCallbacks.erase(id); } void TEST_clearTimeout(int32_t contextId, int32_t timerId) { auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - auto* context = page->getContext(); + auto* context = page->GetExecutingContext(); JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(ScriptState::runtime())); ts->os_timers.erase(timerId); } @@ -199,7 +199,7 @@ std::unique_ptr<kraken::KrakenPage> TEST_init(OnJSError onJsError) { }); initTestFramework(contextId); auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - auto* context = page->getContext(); + auto* context = page->GetExecutingContext(); JSThreadState* th = new JSThreadState(); JS_SetRuntimeOpaque(ScriptState::runtime(), th); diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index da748017a6..a29cd19779 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -32,7 +32,7 @@ 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); From 88eb36f19b6a731ffffdbbe5359084762c731875 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sat, 14 May 2022 12:18:22 +0800 Subject: [PATCH 119/498] fix: fix compile --- bridge/foundation/ui_command_buffer.cc | 4 ++-- bridge/foundation/ui_command_buffer.h | 2 +- bridge/include/kraken_bridge.h | 2 -- bridge/kraken_bridge.cc | 6 ------ 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 38d412c50e..651d3387d3 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -9,11 +9,11 @@ namespace kraken { -UICommandBuffer::UICommandBuffer(ExecutingContext* context) : m_context(context) {} +UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) {} void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr, bool batchedUpdate) { if (batchedUpdate) { - m_context->dartMethodPtr()->requestBatchUpdate(m_context->contextId()); + context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); update_batched = true; } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 30ec5139f1..c99a7da186 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -70,7 +70,7 @@ class UICommandBuffer { void clear(); private: - ExecutingContext* m_context{nullptr}; + ExecutingContext* context_{nullptr}; std::atomic<bool> update_batched{false}; std::vector<UICommandItem> queue; }; diff --git a/bridge/include/kraken_bridge.h b/bridge/include/kraken_bridge.h index 8461032186..6377f7a64e 100644 --- a/bridge/include/kraken_bridge.h +++ b/bridge/include/kraken_bridge.h @@ -57,8 +57,6 @@ void invokeModuleEvent(int32_t contextId, KRAKEN_EXPORT_C void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length); KRAKEN_EXPORT_C -NativeScreen* createScreen(double width, double height); -KRAKEN_EXPORT_C KrakenInfo* getKrakenInfo(); KRAKEN_EXPORT_C void dispatchUITask(int32_t contextId, void* context, void* callback); diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index bd0df85c64..df39b1247b 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -167,12 +167,6 @@ void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t lengt context->registerDartMethods(methodBytes, length); } -NativeScreen* createScreen(double width, double height) { - // screen.width = width; - // screen.height = height; - // return &screen; -} - static KrakenInfo* krakenInfo{nullptr}; KrakenInfo* getKrakenInfo() { From 25a91c1abac3fcac72b11bded095fb88ef6bab00 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sat, 14 May 2022 12:33:30 +0800 Subject: [PATCH 120/498] fix: fix compile --- bridge/core/frame/console.cc | 4 ++-- bridge/core/page.cc | 3 +-- bridge/foundation/logging.cc | 6 +++--- bridge/foundation/logging.h | 4 +++- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 8ac18f27d3..79e94b2e86 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -17,7 +17,7 @@ void Console::__kraken_print__(ExecutingContext* context, std::stringstream stream; std::string buffer = log.ToStdString(); stream << buffer; - printLog(context->contextId(), stream, level != built_in_string::kempty_string ? level.ToStdString() : "info", + printLog(context, stream, level != built_in_string::kempty_string ? level.ToStdString() : "info", nullptr); } @@ -25,7 +25,7 @@ void Console::__kraken_print__(ExecutingContext* context, const AtomicString& lo std::stringstream stream; std::string buffer = log.ToStdString(); stream << buffer; - printLog(context->contextId(), stream, "info", nullptr); + printLog(context, stream, "info", nullptr); } } // namespace kraken diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 7cf4629b36..92f72af3ab 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -131,8 +131,6 @@ void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { dartMethodPointer->requestAnimationFrame = reinterpret_cast<RequestAnimationFrame>(methodBytes[i++]); dartMethodPointer->cancelAnimationFrame = reinterpret_cast<CancelAnimationFrame>(methodBytes[i++]); dartMethodPointer->getScreen = reinterpret_cast<GetScreen>(methodBytes[i++]); - dartMethodPointer->devicePixelRatio = reinterpret_cast<DevicePixelRatio>(methodBytes[i++]); - dartMethodPointer->platformBrightness = reinterpret_cast<PlatformBrightness>(methodBytes[i++]); dartMethodPointer->toBlob = reinterpret_cast<ToBlob>(methodBytes[i++]); dartMethodPointer->flushUICommand = reinterpret_cast<FlushUICommand>(methodBytes[i++]); dartMethodPointer->initWindow = reinterpret_cast<InitWindow>(methodBytes[i++]); @@ -145,6 +143,7 @@ void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { #endif dartMethodPointer->onJsError = reinterpret_cast<OnJSError>(methodBytes[i++]); + dartMethodPointer->onJsLog = reinterpret_cast<OnJSLog>(methodBytes[i++]); assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); } diff --git a/bridge/foundation/logging.cc b/bridge/foundation/logging.cc index 03de4bbad7..888465edb8 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -94,7 +94,7 @@ void pipeMessageToInspector(JSGlobalContextRef ctx, const std::string message, c }; #endif -void printLog(int32_t contextId, std::stringstream& stream, std::string level, void* ctx) { +void printLog(ExecutingContext* context, std::stringstream& stream, std::string level, void* ctx) { MessageLevel _log_level = MessageLevel::Info; switch (level[0]) { case 'l': @@ -125,8 +125,8 @@ void printLog(int32_t contextId, std::stringstream& stream, std::string level, v kraken::KrakenPage::consoleMessageHandler(ctx, stream.str(), static_cast<int>(_log_level)); } - if (kraken::getDartMethod()->onJsLog != nullptr) { - kraken::getDartMethod()->onJsLog(contextId, static_cast<int>(_log_level), stream.str().c_str()); + if (context->dartMethodPtr()->onJsLog != nullptr) { + context->dartMethodPtr()->onJsLog(context->contextId(), static_cast<int>(_log_level), stream.str().c_str()); } } diff --git a/bridge/foundation/logging.h b/bridge/foundation/logging.h index efe937e694..6c4516c66a 100644 --- a/bridge/foundation/logging.h +++ b/bridge/foundation/logging.h @@ -22,6 +22,8 @@ namespace kraken { +class ExecutingContext; + typedef int LogSeverity; // Default log levels. Negative values can be used for verbose log levels. @@ -60,7 +62,7 @@ class LogMessage { const int line_; }; -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); } // namespace kraken From 8279634c71545749b5dc022e9ad650abe55c728f Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sat, 14 May 2022 12:39:34 +0800 Subject: [PATCH 121/498] chore: clean up. --- bridge/bindings/qjs/dom/all_collection.cc | 0 .../html/html_text_area_element.d.ts} | 13 ++++++------- 2 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 bridge/bindings/qjs/dom/all_collection.cc rename bridge/{bindings/qjs/dom/elements/textarea_element.d.ts => core/html/html_text_area_element.d.ts} (67%) diff --git a/bridge/bindings/qjs/dom/all_collection.cc b/bridge/bindings/qjs/dom/all_collection.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/elements/textarea_element.d.ts b/bridge/core/html/html_text_area_element.d.ts similarity index 67% rename from bridge/bindings/qjs/dom/elements/textarea_element.d.ts rename to bridge/core/html/html_text_area_element.d.ts index a318a5df47..05798d6a38 100644 --- a/bridge/bindings/qjs/dom/elements/textarea_element.d.ts +++ b/bridge/core/html/html_text_area_element.d.ts @@ -1,18 +1,17 @@ -interface HostObject {} -interface Element {} +import {Element} from "../dom/element"; // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element -interface TextareaElement extends Element { +interface HTMLTextAreaElement extends Element { defaultValue: string; value: string; - cols: long; - rows: long; + cols: double; + rows: double; wrap: string; autofocus: boolean; autocomplete: string; disabled: boolean; - minLength: long; - maxLength: long; + minLength: double; + maxLength: double; name: string; placeholder: string; readonly: boolean; From 79a90db3556dec238abe0df9e3f8e02cd2bf7672 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Mon, 16 May 2022 11:45:47 +0800 Subject: [PATCH 122/498] feat: use generator for window_or_worker_global_scope --- bridge/CMakeLists.txt | 6 +- bridge/bindings/qjs/binding_initializer.cc | 3 +- bridge/bindings/qjs/qjs_window.cc | 139 ----- bridge/bindings/qjs/qjs_window.h | 22 - bridge/bindings/qjs/script_value.h | 3 +- bridge/bindings/qjs/script_wrappable.cc | 3 +- bridge/bindings/qjs/script_wrappable.h | 5 +- .../core/css/legacy/css_style_declaration.cc | 8 +- bridge/core/dart_methods.h | 6 +- bridge/core/dom/binding_object.h | 2 + bridge/core/dom/comment.cc | 4 +- bridge/core/dom/document.cc | 4 + bridge/core/dom/element.cc | 7 +- bridge/core/dom/events/event_target.cc | 4 + bridge/core/dom/events/event_target.h | 6 +- bridge/core/dom/legacy/element_attributes.cc | 2 +- bridge/core/dom/text.h | 6 +- bridge/core/executing_context_test.cc | 9 +- bridge/core/frame/console.cc | 3 +- bridge/core/frame/window.cc | 523 ++++++++---------- bridge/core/frame/window.d.ts | 5 + bridge/core/frame/window.h | 69 +-- .../frame/window_or_worker_global_scope.cc | 30 +- .../frame/window_or_worker_global_scope.d.ts | 10 + .../frame/window_or_worker_global_scope.h | 8 +- bridge/foundation/ui_command_buffer.cc | 36 +- bridge/foundation/ui_command_buffer.h | 28 +- bridge/test/benchmark/create_element.cc | 2 +- bridge/test/kraken_test_context.cc | 3 +- bridge/test/kraken_test_env.cc | 5 +- kraken/lib/src/bridge/binding.dart | 4 +- kraken/lib/src/bridge/bridge.dart | 6 +- kraken/lib/src/bridge/from_native.dart | 8 +- kraken/lib/src/devtools/server.dart | 2 +- kraken/lib/src/devtools/service.dart | 4 +- 35 files changed, 393 insertions(+), 592 deletions(-) delete mode 100644 bridge/bindings/qjs/qjs_window.cc delete mode 100644 bridge/bindings/qjs/qjs_window.h create mode 100644 bridge/core/frame/window.d.ts create mode 100644 bridge/core/frame/window_or_worker_global_scope.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 0d0b9102c3..455607d7bd 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -241,8 +241,6 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") bindings/qjs/rejected_promises.cc bindings/qjs/pending_promises.cc bindings/qjs/pending_promises.h - bindings/qjs/qjs_window.cc - bindings/qjs/qjs_window.h # Core sources core/executing_context.cc @@ -278,6 +276,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/frame/module_callback.cc core/frame/module_callback.h core/frame/module_callback_coordinator.cc + core/frame/window.h + core/frame/window.cc core/frame/module_callback_coordinator.h core/css/legacy/css_style_declaration.cc core/css/legacy/css_style_declaration.h @@ -388,6 +388,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_console.h out/qjs_module_manager.cc out/qjs_module_manager.h + out/qjs_window_or_worker_global_scope.cc + out/qjs_window_or_worker_global_scope.h out/qjs_blob.cc out/qjs_blob.h out/qjs_event.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 6cd626717f..163b46b1b9 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -28,13 +28,14 @@ #include "qjs_node_list.h" #include "qjs_text.h" #include "qjs_window.h" +#include "qjs_window_or_worker_global_scope.h" namespace kraken { void InstallBindings(ExecutingContext* context) { // Must follow the inheritance order when install. // Exp: Node extends EventTarget, EventTarget must be install first. - QJSWindow::installGlobalFunctions(context); + QJSWindowOrWorkerGlobalScope::Install(context); QJSModuleManager::Install(context); QJSConsole::Install(context); QJSEventTarget::Install(context); diff --git a/bridge/bindings/qjs/qjs_window.cc b/bridge/bindings/qjs/qjs_window.cc deleted file mode 100644 index ef4be8e605..0000000000 --- a/bridge/bindings/qjs/qjs_window.cc +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "qjs_window.h" -#include <quickjs/quickjs.h> -#include "core/executing_context.h" -#include "core/frame/window_or_worker_global_scope.h" -#include "exception_state.h" -#include "member_installer.h" -#include "qjs_function.h" - -namespace kraken { - -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<ExecutingContext*>(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."); - } - - auto handler = QJSFunction::Create(ctx, callbackValue); - ExceptionState exceptionState; - - int32_t timerId = WindowOrWorkerGlobalScope::setTimeout(context, handler, timeout, &exceptionState); - - if (exceptionState.HasException()) { - return exceptionState.ToQuickJS(); - } - - // `-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<ExecutingContext*>(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."); - } - - auto handler = QJSFunction::Create(ctx, callbackValue); - ExceptionState exception; - int32_t timerId = WindowOrWorkerGlobalScope::setInterval(context, handler, timeout, &exception); - - if (exception.HasException()) { - return exception.ToQuickJS(); - } - - 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<ExecutingContext*>(JS_GetContextOpaque(ctx)); - - JSValue timeIdValue = argv[0]; - if (!JS_IsNumber(timeIdValue)) { - return JS_NULL; - } - - int32_t id; - JS_ToInt32(ctx, &id, timeIdValue); - - ExceptionState exception; - WindowOrWorkerGlobalScope::clearTimeout(context, id, &exception); - - if (exception.HasException()) { - return exception.ToQuickJS(); - } - - return JS_NULL; -} - -void QJSWindow::installGlobalFunctions(ExecutingContext* context) { - std::initializer_list<MemberInstaller::FunctionConfig> functionConfig{ - {"setTimeout", setTimeout, 2}, - {"setInterval", setInterval, 2}, - {"clearTimeout", clearTimeout, 0}, - }; - - MemberInstaller::InstallFunctions(context, context->Global(), functionConfig); -} - -} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_window.h b/bridge/bindings/qjs/qjs_window.h deleted file mode 100644 index 6a9d52e2e2..0000000000 --- a/bridge/bindings/qjs/qjs_window.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_QJS_WINDOW_H -#define KRAKENBRIDGE_QJS_WINDOW_H - -#include <quickjs/quickjs.h> - -namespace kraken { - -class ExecutingContext; - -class QJSWindow final { - public: - static void installGlobalFunctions(ExecutingContext* ctx); -}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_QJS_WINDOW_H diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 539632e6e0..15680b6e84 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -37,7 +37,8 @@ class ScriptValue final { static ScriptValue Empty(JSContext* ctx); // Wrap an Quickjs JSValue to ScriptValue. explicit ScriptValue(JSContext* ctx, JSValue value) : ctx_(ctx), value_(JS_DupValue(ctx, value)){}; - explicit ScriptValue(JSContext* ctx, const NativeString* string): ctx_(ctx), value_(JS_NewUnicodeString(ctx, string->string(), string->length())) {} + explicit ScriptValue(JSContext* ctx, const NativeString* string) + : ctx_(ctx), value_(JS_NewUnicodeString(ctx, string->string(), string->length())) {} explicit ScriptValue(JSContext* ctx) : ctx_(ctx){}; ScriptValue() = default; diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 0a23c669b1..a03b8526a2 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -9,7 +9,8 @@ namespace kraken { -ScriptWrappable::ScriptWrappable(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)) {} +ScriptWrappable::ScriptWrappable(JSContext* ctx) + : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), context_(ExecutingContext::From(ctx)) {} JSValue ScriptWrappable::ToQuickJS() { return JS_DupValue(ctx_, jsObject_); diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 55c0fa2c8e..a1db89bb36 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -51,9 +51,7 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { JSValue ToQuickJSUnsafe() const; ScriptValue ToValue(); - FORCE_INLINE ExecutingContext* GetExecutingContext() const { - return static_cast<ExecutingContext*>(JS_GetContextOpaque(ctx_)); - }; + FORCE_INLINE ExecutingContext* GetExecutingContext() const { return context_; }; FORCE_INLINE JSContext* ctx() const { return ctx_; } FORCE_INLINE JSRuntime* runtime() const { return runtime_; } @@ -62,6 +60,7 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { private: JSValue jsObject_{JS_NULL}; JSContext* ctx_{nullptr}; + ExecutingContext* context_{nullptr}; JSRuntime* runtime_{nullptr}; friend class GCVisitor; }; diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index 4af2908bd6..ade1d1622e 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -126,8 +126,8 @@ bool CSSStyleDeclaration::InternalSetProperty(std::string& name, const AtomicStr std::unique_ptr<NativeString> args_01 = stringToNativeString(name); std::unique_ptr<NativeString> args_02 = value.ToNativeString(); - GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::setStyle, args_01.release(), - args_02.release(), nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::kSetStyle, + args_01.release(), args_02.release(), nullptr); return true; } @@ -144,8 +144,8 @@ AtomicString CSSStyleDeclaration::InternalRemoveProperty(std::string& name) { std::unique_ptr<NativeString> args_01 = stringToNativeString(name); std::unique_ptr<NativeString> args_02 = jsValueToNativeString(ctx(), JS_NULL); - GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::setStyle, args_01.release(), - args_02.release(), nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::kSetStyle, + args_01.release(), args_02.release(), nullptr); return return_value; } diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index 33228a0b55..444fec4ccf 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -35,7 +35,11 @@ typedef int32_t (*RequestAnimationFrame)(void* callbackContext, int32_t contextI 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)(); diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h index 9f8b1a90ba..f519fec5f7 100644 --- a/bridge/core/dom/binding_object.h +++ b/bridge/core/dom/binding_object.h @@ -59,6 +59,8 @@ class BindingObject { NativeValue GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const; NativeValue SetBindingProperty(const AtomicString& prop, NativeValue value, ExceptionState& exception_state) const; + const NativeBindingObject* bindingObject() const { return &binding_object_; } + private: ExecutingContext* context_{nullptr}; NativeBindingObject binding_object_{this}; diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc index 4884475957..b042f4ecd0 100644 --- a/bridge/core/dom/comment.cc +++ b/bridge/core/dom/comment.cc @@ -19,7 +19,9 @@ Comment* Comment::Create(Document& document) { } Comment::Comment(TreeScope& tree_scope, ConstructionType type) - : CharacterData(tree_scope, built_in_string::kempty_string, 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; diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 89c4278aa8..358cb54cf8 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -24,6 +24,10 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ Document::Document(ExecutingContext* context) : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) { document_element_ = MakeGarbageCollected<HTMLHtmlElement>(*this); + +#if FLUTTER_BACKEND + GetExecutingContext()->dartMethodPtr()->initDocument(context->contextId(), (void*)bindingObject()); +#endif } Element* Document::createElement(const AtomicString& name, ExceptionState& exception_state) { diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index b3e2a15fea..68c70aae7f 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -17,7 +17,10 @@ namespace kraken { Element::Element(const AtomicString& tag_name, Document* document, Node::ConstructionType construction_type) - : ContainerNode(document, construction_type), tag_name_(tag_name) {} + : ContainerNode(document, construction_type), tag_name_(tag_name) { + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateElement, + tag_name.ToNativeString().release(), (void*)bindingObject()); +} ElementAttributes& Element::EnsureElementAttributes() { if (attributes_ == nullptr) { @@ -56,7 +59,7 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value, std::unique_ptr<NativeString> args_01 = name.ToNativeString(); std::unique_ptr<NativeString> args_02 = value.ToNativeString(); - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::setAttribute, args_01.release(), + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kSetAttribute, args_01.release(), args_02.release(), nullptr); } diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index abbd8f3936..feb6ae329d 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -39,6 +39,10 @@ EventTarget* EventTarget::Create(ExecutingContext* context, ExceptionState& exce return MakeGarbageCollected<EventTargetWithInlineData>(context); } +EventTarget::~EventTarget() { +// GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, nullptr); +} + EventTarget::EventTarget(ExecutingContext* context) : BindingObject(context), ScriptWrappable(context->ctx()), event_target_id_(global_event_target_id++) {} diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index daa74abf69..58cc9059f8 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -87,7 +87,7 @@ class EventTarget : public ScriptWrappable, public BindingObject { static EventTarget* Create(ExecutingContext* context, ExceptionState& exception_state); EventTarget() = delete; - ~EventTarget() = default; + ~EventTarget(); explicit EventTarget(ExecutingContext* context); bool addEventListener(const AtomicString& event_type, @@ -258,9 +258,9 @@ class EventTargetWithInlineData : public EventTarget { // // 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 javascript code set a property on EventTarget instance, EventTarget::kSetAttribute callback will be called // when -// // property are not defined by Object.defineProperty or setAttribute. +// // property are not defined by Object.defineProperty or kSetAttribute. // // We store there values in here. // EventTargetProperties m_properties{m_ctx}; // diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 084512c8ca..6d622fcce1 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -37,7 +37,7 @@ bool ElementAttributes::setAttribute(const AtomicString& name, if (numberIndex) { exception_state.ThrowException( ctx(), ErrorType::TypeError, - "Failed to execute 'setAttribute' on 'Element': '" + name.ToStdString() + "' is not a valid attribute name."); + "Failed to execute 'kSetAttribute' on 'Element': '" + name.ToStdString() + "' is not a valid attribute name."); return false; } diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h index a1863d6cc5..e9398cd7b5 100644 --- a/bridge/core/dom/text.h +++ b/bridge/core/dom/text.h @@ -19,8 +19,10 @@ class Text : public CharacterData { 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) {} + Text(TreeScope& tree_scope, const AtomicString& data, ConstructionType type) : CharacterData(tree_scope, data, type) { + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateTextNode, + data.ToNativeString().release(), (void*)bindingObject()); + } NodeType nodeType() const override; diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index ce40c8ae82..d21f44a5cc 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -354,7 +354,8 @@ TEST(Context, evaluateByteCode) { TEST(jsValueToNativeString, utf8String) { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "helloworld"); - std::unique_ptr<kraken::NativeString> nativeString = kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); + std::unique_ptr<kraken::NativeString> nativeString = + kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); EXPECT_EQ(nativeString->length(), 10); uint8_t expectedString[10] = {104, 101, 108, 108, 111, 119, 111, 114, 108, 100}; for (int i = 0; i < 10; i++) { @@ -366,7 +367,8 @@ TEST(jsValueToNativeString, utf8String) { TEST(jsValueToNativeString, unicodeChinese) { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "这是你的优乐美"); - std::unique_ptr<kraken::NativeString> nativeString = kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); + std::unique_ptr<kraken::NativeString> nativeString = + kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); std::u16string expectedString = u"这是你的优乐美"; EXPECT_EQ(nativeString->length(), expectedString.size()); for (int i = 0; i < nativeString->length(); i++) { @@ -378,7 +380,8 @@ TEST(jsValueToNativeString, unicodeChinese) { TEST(jsValueToNativeString, emoji) { auto bridge = TEST_init([](int32_t contextId, const char* errmsg) {}); JSValue str = JS_NewString(bridge->GetExecutingContext()->ctx(), "……🤪"); - std::unique_ptr<kraken::NativeString> nativeString = kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); + std::unique_ptr<kraken::NativeString> nativeString = + kraken::jsValueToNativeString(bridge->GetExecutingContext()->ctx(), str); std::u16string expectedString = u"……🤪"; EXPECT_EQ(nativeString->length(), expectedString.length()); for (int i = 0; i < nativeString->length(); i++) { diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 79e94b2e86..9b9039b4e1 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -17,8 +17,7 @@ void Console::__kraken_print__(ExecutingContext* context, std::stringstream stream; std::string buffer = log.ToStdString(); stream << buffer; - printLog(context, stream, level != built_in_string::kempty_string ? level.ToStdString() : "info", - nullptr); + printLog(context, stream, level != built_in_string::kempty_string ? level.ToStdString() : "info", nullptr); } void Console::__kraken_print__(ExecutingContext* context, const AtomicString& log, ExceptionState& exception_state) { diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index 68aec41b5d..c3cb381284 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -4,291 +4,248 @@ #include "window.h" #include "bindings/qjs/cppgc/garbage_collected.h" -#include "bindings/qjs/dom/document.h" -#include "bindings/qjs/dom/events/.gen/message_event.h" -#include "bindings/qjs/qjs_engine_patch.h" -#include "dart_methods.h" namespace kraken { -void bindWindow(std::unique_ptr<ExecutionContext>& context) { - auto* contextData = context->contextData(); - JSValue classObject = contextData->constructorForType(&windowTypeInfo); - JSValue prototypeObject = contextData->prototypeForType(&windowTypeInfo); - - // Install methods. - INSTALL_FUNCTION(Window, prototypeObject, open, 1); - installFunctionProperty(context.get(), prototypeObject, "scroll", Window::m_scrollTo_, 2); - INSTALL_FUNCTION(Window, prototypeObject, scrollTo, 2); - INSTALL_FUNCTION(Window, prototypeObject, scrollBy, 2); - INSTALL_FUNCTION(Window, prototypeObject, postMessage, 3); - INSTALL_FUNCTION(Window, prototypeObject, requestAnimationFrame, 1); - INSTALL_FUNCTION(Window, prototypeObject, cancelAnimationFrame, 1); - - // Install Getter and Setter properties. - INSTALL_READONLY_PROPERTY(Window, prototypeObject, devicePixelRatio); - INSTALL_READONLY_PROPERTY(Window, prototypeObject, colorScheme); - INSTALL_READONLY_PROPERTY(Window, prototypeObject, __location__); - INSTALL_READONLY_PROPERTY(Window, prototypeObject, location); - INSTALL_READONLY_PROPERTY(Window, prototypeObject, window); - INSTALL_READONLY_PROPERTY(Window, prototypeObject, parent); - INSTALL_READONLY_PROPERTY(Window, prototypeObject, scrollX); - INSTALL_READONLY_PROPERTY(Window, prototypeObject, scrollY); - INSTALL_READONLY_PROPERTY(Window, prototypeObject, self); - - INSTALL_PROPERTY(Window, prototypeObject, onerror); - - // Set globalThis and Window's prototype to EventTarget's prototype to support EventTarget methods in global. - JS_SetPrototype(context->ctx(), context->global(), prototypeObject); - context->defineGlobalProperty("Window", classObject); - - // Hide window instance to global object, to get access to window when get property on globalObject. - auto* window = makeGarbageCollected<Window>()->initialize<Window>(context->ctx(), &Window::classId); - JS_SetOpaque(context->global(), window); - context->defineGlobalProperty("__window__", window->toQuickJS()); -} - -JSValue ensureWindowIsGlobal(EventTargetInstance* target) { - if (target == target->context()->window()) { - return target->context()->global(); - } - return target->jsObject; -} - -JSValue Window::open(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto window = static_cast<WindowInstance*>(JS_GetOpaque(this_val, Window::classId())); - NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0])}; - return window->invokeBindingMethod("open", 1, arguments); -} - -IMPL_FUNCTION(Window, scrollTo)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -#if FLUTTER_BACKEND - getDartMethod()->flushUICommand(); - auto window = static_cast<WindowInstance*>(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<WindowInstance*>(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); -} - -IMPL_FUNCTION(Window, postMessage)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - JSValue messageValue = argv[0]; - JSValue globalObjectValue = JS_GetGlobalObject(ctx); - auto* window = static_cast<Window*>(JS_GetOpaque(globalObjectValue, JSValueGetClassId(this_val))); - - 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<MessageEventInstance*>(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; -} - -IMPL_FUNCTION(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<ExecutionContext*>(JS_GetContextOpaque(ctx)); - auto window = static_cast<Window*>(JS_GetOpaque(context->global(), JSValueGetClassId(this_val))); - - JSValue callbackValue = argv[0]; - - if (!JS_IsObject(callbackValue)) { - return JS_ThrowTypeError(ctx, - "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); - } - - if (!JS_IsFunction(ctx, callbackValue)) { - return JS_ThrowTypeError(ctx, - "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); - } - - // Flutter backend implements check -#if FLUTTER_BACKEND - if (getDartMethod()->flushUICommand == nullptr) { - return JS_ThrowTypeError( - ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); - } - // Flush all pending ui messages. - getDartMethod()->flushUICommand(); - - if (getDartMethod()->requestAnimationFrame == nullptr) { - return JS_ThrowTypeError( - ctx, "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) is not registered."); - } -#endif - - auto* frameCallback = makeGarbageCollected<FrameCallback>(JS_DupValue(ctx, callbackValue)) - ->initialize<FrameCallback>(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); -} - -IMPL_FUNCTION(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<ExecutionContext*>(JS_GetContextOpaque(ctx)); - auto window = static_cast<Window*>(JS_GetOpaque(context->global(), JSValueGetClassId(this_val))); - - 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; -} - -Window* Window::create(JSContext* ctx) { - auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(ctx)); - JSValue prototype = context->contextData()->prototypeForType(&windowTypeInfo); - - auto* window = makeGarbageCollected<Window>()->initialize<Window>(ctx, &classId, nullptr); - - // Let window inherit Window prototype methods. - JS_SetPrototype(ctx, window->toQuickJS(), prototype); - - return window; -} - -Document* Window::document() { - return context()->document(); -} - -Window::Window() { - if (getDartMethod()->initWindow != nullptr) { - getDartMethod()->initWindow(context()->getContextId(), nativeEventTarget); - } - - m_location = makeGarbageCollected<Location>()->initialize<Location>(m_ctx, &Location::classId); -} - -void Window::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - EventTarget::trace(rt, val, mark_func); - JS_MarkValue(rt, onerror, mark_func); -} - -IMPL_PROPERTY_GETTER(Window, devicePixelRatio)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast<WindowInstance*>(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<WindowInstance*>(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<WindowInstance*>(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<WindowInstance*>(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<Window*>(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - if (window == nullptr) - return JS_UNDEFINED; - return JS_DupValue(ctx, window->m_location->toQuickJS()); -} - -IMPL_PROPERTY_GETTER(Window, location)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* window = static_cast<Window*>(JS_GetOpaque(this_val, 1)); - return JS_GetPropertyStr(ctx, window->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<WindowInstance*>(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<WindowInstance*>(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<Window*>(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<Window*>(JS_GetOpaque(this_val, 1)); - JSValue eventString = JS_NewString(ctx, "onerror"); - JSString* p = JS_VALUE_GET_STRING(eventString); - JSValue onerrorHandler = argv[0]; - window->setAttributesEventHandler(p, onerrorHandler); - - if (!JS_IsNull(window->onerror)) { - JS_FreeValue(ctx, window->onerror); - } - - window->onerror = JS_DupValue(ctx, onerrorHandler); - JS_FreeValue(ctx, eventString); - return JS_NULL; -} - -IMPL_PROPERTY_GETTER(Window, self)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - return JS_GetGlobalObject(ctx); -} +Window::Window(ExecutingContext* context) : EventTargetWithInlineData(context) {} + +// +// JSValue Window::open(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto window = static_cast<WindowInstance*>(JS_GetOpaque(this_val, Window::classId())); +// NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0])}; +// return window->invokeBindingMethod("open", 1, arguments); +//} +// +// IMPL_FUNCTION(Window, scrollTo)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +//#if FLUTTER_BACKEND +// getDartMethod()->flushUICommand(); +// auto window = static_cast<WindowInstance*>(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<WindowInstance*>(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); +//} +// +// IMPL_FUNCTION(Window, postMessage)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// JSValue messageValue = argv[0]; +// JSValue globalObjectValue = JS_GetGlobalObject(ctx); +// auto* window = static_cast<Window*>(JS_GetOpaque(globalObjectValue, JSValueGetClassId(this_val))); +// +// 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<MessageEventInstance*>(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; +//} +// +// IMPL_FUNCTION(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<ExecutionContext*>(JS_GetContextOpaque(ctx)); +// auto window = static_cast<Window*>(JS_GetOpaque(context->global(), JSValueGetClassId(this_val))); +// +// JSValue callbackValue = argv[0]; +// +// if (!JS_IsObject(callbackValue)) { +// return JS_ThrowTypeError(ctx, +// "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); +// } +// +// if (!JS_IsFunction(ctx, callbackValue)) { +// return JS_ThrowTypeError(ctx, +// "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); +// } +// +// // Flutter backend implements check +//#if FLUTTER_BACKEND +// if (getDartMethod()->flushUICommand == nullptr) { +// return JS_ThrowTypeError( +// ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); +// } +// // Flush all pending ui messages. +// getDartMethod()->flushUICommand(); +// +// if (getDartMethod()->requestAnimationFrame == nullptr) { +// return JS_ThrowTypeError( +// ctx, "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) is not registered."); +// } +//#endif +// +// auto* frameCallback = makeGarbageCollected<FrameCallback>(JS_DupValue(ctx, callbackValue)) +// ->initialize<FrameCallback>(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); +//} +// +// IMPL_FUNCTION(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<ExecutionContext*>(JS_GetContextOpaque(ctx)); +// auto window = static_cast<Window*>(JS_GetOpaque(context->global(), JSValueGetClassId(this_val))); +// +// 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; +//} +// +// Window* Window::create(JSContext* ctx) { +// auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(ctx)); +// JSValue prototype = context->contextData()->prototypeForType(&windowTypeInfo); +// +// auto* window = makeGarbageCollected<Window>()->initialize<Window>(ctx, &classId, nullptr); +// +// // Let window inherit Window prototype methods. +// JS_SetPrototype(ctx, window->toQuickJS(), prototype); +// +// return window; +//} +// +// Document* Window::document() { +// return context()->document(); +//} +// +// Window::Window() { +// if (getDartMethod()->initWindow != nullptr) { +// getDartMethod()->initWindow(context()->getContextId(), nativeEventTarget); +// } +// +// m_location = makeGarbageCollected<Location>()->initialize<Location>(m_ctx, &Location::classId); +//} +// +// void Window::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { +// EventTarget::trace(rt, val, mark_func); +// JS_MarkValue(rt, onerror, mark_func); +//} +// +// IMPL_PROPERTY_GETTER(Window, devicePixelRatio)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* window = static_cast<WindowInstance*>(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<WindowInstance*>(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<WindowInstance*>(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<WindowInstance*>(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<Window*>(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); +// if (window == nullptr) +// return JS_UNDEFINED; +// return JS_DupValue(ctx, window->m_location->toQuickJS()); +//} +// +// IMPL_PROPERTY_GETTER(Window, location)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* window = static_cast<Window*>(JS_GetOpaque(this_val, 1)); +// return JS_GetPropertyStr(ctx, window->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<WindowInstance*>(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<WindowInstance*>(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<Window*>(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<Window*>(JS_GetOpaque(this_val, 1)); +// JSValue eventString = JS_NewString(ctx, "onerror"); +// JSString* p = JS_VALUE_GET_STRING(eventString); +// JSValue onerrorHandler = argv[0]; +// window->setAttributesEventHandler(p, onerrorHandler); +// +// if (!JS_IsNull(window->onerror)) { +// JS_FreeValue(ctx, window->onerror); +// } +// +// window->onerror = JS_DupValue(ctx, onerrorHandler); +// JS_FreeValue(ctx, eventString); +// return JS_NULL; +//} +// +// IMPL_PROPERTY_GETTER(Window, self)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// return JS_GetGlobalObject(ctx); +//} } // namespace kraken diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts new file mode 100644 index 0000000000..8f43687f0e --- /dev/null +++ b/bridge/core/frame/window.d.ts @@ -0,0 +1,5 @@ +import {EventTarget} from "../dom/events/event_target"; + +interface Window extends EventTarget { + open(url?: string): Window | null; +} diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index 4fa17f1755..a972195984 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -6,60 +6,35 @@ #ifndef KRAKENBRIDGE_WINDOW_H #define KRAKENBRIDGE_WINDOW_H -#include "bindings/qjs/bom/location.h" -#include "bindings/qjs/dom/event_target.h" -#include "bindings/qjs/executing_context.h" #include "bindings/qjs/wrapper_type_info.h" +#include "core/dom/events/event_target.h" namespace kraken { -void bindWindow(ExecutionContext* context); - -class Window : public EventTarget { +class Window : public EventTargetWithInlineData { public: - Window(); - - static JSClassID classId; - static Window* create(JSContext* ctx); - - DEFINE_FUNCTION(open); - DEFINE_FUNCTION(scrollTo); - DEFINE_FUNCTION(scrollBy); - DEFINE_FUNCTION(postMessage); - DEFINE_FUNCTION(requestAnimationFrame); - DEFINE_FUNCTION(cancelAnimationFrame); - - 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(self); - - DEFINE_PROTOTYPE_PROPERTY(onerror); - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - - private: - Document* document(); - - Location* m_location{nullptr}; - JSValue onerror{JS_NULL}; - friend ExecutionContext; + Window() = delete; + Window(ExecutingContext* context); + + // DEFINE_FUNCTION(open); + // DEFINE_FUNCTION(scrollTo); + // DEFINE_FUNCTION(scrollBy); + // DEFINE_FUNCTION(postMessage); + // DEFINE_FUNCTION(requestAnimationFrame); + // DEFINE_FUNCTION(cancelAnimationFrame); + // + // 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(self); + // DEFINE_PROTOTYPE_PROPERTY(onerror); }; -auto windowCreator = - [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) - -> JSValue { - auto* window = Window::create(ctx); - return window->toQuickJS(); -}; - -const WrapperTypeInfo windowTypeInfo = {"Window", &eventTargetTypeInfo, windowCreator}; - } // namespace kraken #endif // KRAKENBRIDGE_WINDOW_H diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 4176467b7e..4b461cab55 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -49,14 +49,20 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e 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) { + 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."); + exception.ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'setTimeout': dart method (setTimeout) is not registered."); return -1; } #endif @@ -74,13 +80,19 @@ int WindowOrWorkerGlobalScope::setTimeout(ExecutingContext* context, 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) { + ExceptionState& exception) { if (context->dartMethodPtr()->setInterval == nullptr) { - exception->ThrowException(context->ctx(), ErrorType::InternalError, - "Failed to execute 'setInterval': dart method (setInterval) is not registered."); + exception.ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'setInterval': dart method (setInterval) is not registered."); return -1; } @@ -97,10 +109,10 @@ int WindowOrWorkerGlobalScope::setInterval(ExecutingContext* context, return timerId; } -void WindowOrWorkerGlobalScope::clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState* exception) { +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."); + exception.ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'clearTimeout': dart method (clearTimeout) is not registered."); return; } diff --git a/bridge/core/frame/window_or_worker_global_scope.d.ts b/bridge/core/frame/window_or_worker_global_scope.d.ts new file mode 100644 index 0000000000..83c71d3f35 --- /dev/null +++ b/bridge/core/frame/window_or_worker_global_scope.d.ts @@ -0,0 +1,10 @@ +// @ts-ignore +declare const setTimeout: (callback: Function, timeout?: double) => int64; + +// @ts-ignore +declare const setInterval: (callback: Function, timeout?: double) => int64; + +// @ts-ignore +declare const clearTimeout: (handle: double) => void; + + diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h index 7f5be4a213..657f061755 100644 --- a/bridge/core/frame/window_or_worker_global_scope.h +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -17,12 +17,14 @@ class WindowOrWorkerGlobalScope { static int setTimeout(ExecutingContext* context, std::shared_ptr<QJSFunction> handler, int32_t timeout, - ExceptionState* exception); + 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 void clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState* exception); + ExceptionState& exception); + static int setInterval(ExecutingContext* context, std::shared_ptr<QJSFunction> handler, ExceptionState& exception); + static void clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState& exception); }; } // namespace kraken diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index cd3c1492b9..659186fd06 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -8,38 +8,17 @@ namespace kraken { -UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) {} - -void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr, bool batchedUpdate) { - if (batchedUpdate) { - context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); - update_batched = true; - } - - UICommandItem item{id, static_cast<int32_t>(type), nativePtr}; - queue.emplace_back(item); +UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) { + queue.reserve(0); } void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { - if (!update_batched) { -#if FLUTTER_BACKEND - context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); -#endif - update_batched = true; - } - UICommandItem item{id, static_cast<int32_t>(type), nativePtr}; queue.emplace_back(item); } void UICommandBuffer::addCommand(int32_t id, UICommand type, NativeString* args_01, void* nativePtr) { - if (!update_batched) { -#if FLUTTER_BACKEND - context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); - update_batched = true; -#endif - } - + assert(args_01 != nullptr); UICommandItem item{id, static_cast<int32_t>(type), args_01, nativePtr}; queue.emplace_back(item); } @@ -49,12 +28,8 @@ void UICommandBuffer::addCommand(int32_t id, NativeString* args_01, NativeString* args_02, void* nativePtr) { -#if FLUTTER_BACKEND - if (!update_batched) { - context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); - update_batched = true; - } -#endif + assert(args_01 != nullptr); + assert(args_02 != nullptr); UICommandItem item{id, static_cast<int32_t>(type), args_01, args_02, nativePtr}; queue.emplace_back(item); } @@ -73,7 +48,6 @@ void UICommandBuffer::clear() { delete[] reinterpret_cast<const uint16_t*>(command.string_02); } queue.clear(); - update_batched = false; } } // namespace kraken diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 49a653ebb4..ac468210f5 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -15,19 +15,19 @@ namespace kraken { class ExecutingContext; enum class UICommand { - createElement, - createTextNode, - createComment, - disposeEventTarget, - addEvent, - removeNode, - insertAdjacentNode, - setStyle, - setAttribute, - removeAttribute, - cloneNode, - removeEvent, - createDocumentFragment, + kCreateElement, + kCreateTextNode, + kCreateComment, + kDisposeEventTarget, + kAddEvent, + kRemoveNode, + kInsertAdjacentNode, + kSetStyle, + kSetAttribute, + kRemoveAttribute, + kCloneNode, + kRemoveEvent, + kCreateDocumentFragment, }; struct UICommandItem { @@ -60,7 +60,6 @@ class UICommandBuffer { public: UICommandBuffer() = delete; explicit UICommandBuffer(ExecutingContext* context); - void addCommand(int32_t id, UICommand type, void* nativePtr, bool batchedUpdate); void addCommand(int32_t id, UICommand type, void* nativePtr); void addCommand(int32_t id, UICommand type, NativeString* args_01, NativeString* args_02, void* nativePtr); void addCommand(int32_t id, UICommand type, NativeString* args_01, void* nativePtr); @@ -70,7 +69,6 @@ class UICommandBuffer { private: ExecutingContext* context_{nullptr}; - std::atomic<bool> update_batched{false}; std::vector<UICommandItem> queue; }; diff --git a/bridge/test/benchmark/create_element.cc b/bridge/test/benchmark/create_element.cc index 169a2adbe8..20806e6f89 100644 --- a/bridge/test/benchmark/create_element.cc +++ b/bridge/test/benchmark/create_element.cc @@ -19,7 +19,7 @@ static void CreateRawJavaScriptObjects(benchmark::State& state) { static void CreateDivElement(benchmark::State& state) { auto& context = bridge->GetExecutingContext(); - std::string code = "var a = document.createElement('div');"; + std::string code = "var a = document.kCreateElement('div');"; // Perform setup here for (auto _ : state) { context->EvaluateJavaScript(code.c_str(), code.size(), "internal://", 0); diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index 6d26a018b7..092038ad08 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -263,7 +263,8 @@ void KrakenTestContext::invokeExecuteTest(ExecuteCallback executeCallback) { execute_test_callback_ = nullptr; } -KrakenTestContext::KrakenTestContext(ExecutingContext* context) : context_(context), page_(static_cast<KrakenPage*>(context->owner())) { +KrakenTestContext::KrakenTestContext(ExecutingContext* context) + : context_(context), page_(static_cast<KrakenPage*>(context->owner())) { page_->owner = this; page_->disposeCallback = [](KrakenPage* bridge) { delete static_cast<KrakenTestContext*>(bridge->owner); }; diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 25af1301fb..2f69af13ff 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -198,14 +198,15 @@ std::unique_ptr<kraken::KrakenPage> TEST_init(OnJSError onJsError) { initJSPagePool(1024 * 1024); inited = true; }); + + TEST_mockDartMethods(contextId, onJsError); + initTestFramework(contextId); auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); auto* context = page->GetExecutingContext(); JSThreadState* th = new JSThreadState(); JS_SetRuntimeOpaque(ScriptState::runtime(), th); - TEST_mockDartMethods(contextId, onJsError); - return std::unique_ptr<kraken::KrakenPage>(page); } diff --git a/kraken/lib/src/bridge/binding.dart b/kraken/lib/src/bridge/binding.dart index f454a4a2f8..7f3e87dc2f 100644 --- a/kraken/lib/src/bridge/binding.dart +++ b/kraken/lib/src/bridge/binding.dart @@ -166,7 +166,7 @@ abstract class BindingBridge { Map<String, List<EventHandler>> eventHandlers = target.getEventHandlers(); List<EventHandler>? handlers = eventHandlers[type]; if (handlers != null) { - return !handlers.contains(_dispatchBindingEvent); + return !handlers.contains(_dispatchEventToNative); } return true; } @@ -175,7 +175,7 @@ abstract class BindingBridge { Map<String, List<EventHandler>> eventHandlers = target.getEventHandlers(); List<EventHandler>? handlers = eventHandlers[type]; if (handlers != null) { - return handlers.contains(_dispatchBindingEvent); + return handlers.contains(_dispatchEventToNative); } return false; } diff --git a/kraken/lib/src/bridge/bridge.dart b/kraken/lib/src/bridge/bridge.dart index 47d93d66ea..7143324aa0 100644 --- a/kraken/lib/src/bridge/bridge.dart +++ b/kraken/lib/src/bridge/bridge.dart @@ -24,9 +24,6 @@ int initBridge() { PerformanceTiming.instance().mark(PERF_BRIDGE_REGISTER_DART_METHOD_START); } - // Register methods first to share ptrs for bridge polyfill. - registerDartMethodsToCpp(); - // Setup binding bridge. BindingBridge.setup(); @@ -59,5 +56,8 @@ int initBridge() { } } + // Register methods first to share ptrs for bridge polyfill. + registerDartMethodsToCpp(contextId); + return contextId; } diff --git a/kraken/lib/src/bridge/from_native.dart b/kraken/lib/src/bridge/from_native.dart index c742ebebbb..450971c839 100644 --- a/kraken/lib/src/bridge/from_native.dart +++ b/kraken/lib/src/bridge/from_native.dart @@ -406,17 +406,17 @@ final List<int> _dartNativeMethods = [ _nativeOnJsLog.address, ]; -typedef NativeRegisterDartMethods = Void Function(Pointer<Uint64> methodBytes, Int32 length); -typedef DartRegisterDartMethods = void Function(Pointer<Uint64> methodBytes, int length); +typedef NativeRegisterDartMethods = Void Function(Int32 contextId, Pointer<Uint64> methodBytes, Int32 length); +typedef DartRegisterDartMethods = void Function(int contextId, Pointer<Uint64> methodBytes, int length); final DartRegisterDartMethods _registerDartMethods = KrakenDynamicLibrary .ref .lookup<NativeFunction<NativeRegisterDartMethods>>('registerDartMethods') .asFunction(); -void registerDartMethodsToCpp() { +void registerDartMethodsToCpp(int contextId) { Pointer<Uint64> bytes = malloc.allocate<Uint64>(sizeOf<Uint64>() * _dartNativeMethods.length); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - _registerDartMethods(bytes, _dartNativeMethods.length); + _registerDartMethods(contextId, bytes, _dartNativeMethods.length); } diff --git a/kraken/lib/src/devtools/server.dart b/kraken/lib/src/devtools/server.dart index e43d13f581..9459697e92 100644 --- a/kraken/lib/src/devtools/server.dart +++ b/kraken/lib/src/devtools/server.dart @@ -96,7 +96,7 @@ void initInspectorServerNativeBinding(int contextId) { Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - _registerInspectorServerDartMethods(bytes, _dartNativeMethods.length); + _registerInspectorServerDartMethods(contextId, bytes, _dartNativeMethods.length); } void serverIsolateEntryPoint(SendPort isolateToMainStream) { diff --git a/kraken/lib/src/devtools/service.dart b/kraken/lib/src/devtools/service.dart index ea6255a114..ee206dbfc3 100644 --- a/kraken/lib/src/devtools/service.dart +++ b/kraken/lib/src/devtools/service.dart @@ -115,12 +115,12 @@ class ChromeDevToolsService extends DevToolsService { // @TODO: Implement and remove. // ignore: unused_element - static bool _registerUIDartMethodsToCpp() { + static bool _registerUIDartMethodsToCpp(int contextId) { final DartRegisterDartMethods _registerDartMethods = KrakenDynamicLibrary.ref.lookup<NativeFunction<NativeRegisterDartMethods>>('registerUIDartMethods').asFunction(); Pointer<Uint64> bytes = malloc.allocate<Uint64>(_dartNativeMethods.length * sizeOf<Uint64>()); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - _registerDartMethods(bytes, _dartNativeMethods.length); + _registerDartMethods(contextId, bytes, _dartNativeMethods.length); return true; } } From c6f29e4c0ecc96b211014f1aaa7d4fe3119cb176 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Mon, 16 May 2022 11:59:28 +0800 Subject: [PATCH 123/498] test: add timer test. --- bridge/core/frame/{timer_test.cc => dom_timer_test.cc} | 9 ++++----- bridge/test/kraken_test_env.cc | 7 ++++--- bridge/test/test.cmake | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) rename bridge/core/frame/{timer_test.cc => dom_timer_test.cc} (91%) diff --git a/bridge/core/frame/timer_test.cc b/bridge/core/frame/dom_timer_test.cc similarity index 91% rename from bridge/core/frame/timer_test.cc rename to bridge/core/frame/dom_timer_test.cc index 8616c6af99..5149c2cd9a 100644 --- a/bridge/core/frame/timer_test.cc +++ b/bridge/core/frame/dom_timer_test.cc @@ -4,7 +4,8 @@ #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" + +using namespace kraken; TEST(Timer, setTimeout) { auto bridge = TEST_init(); @@ -35,8 +36,7 @@ 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) { @@ -61,6 +61,5 @@ clearTimeout(timer); )"; bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(bridge->getContext()); - disposePage(0); + TEST_runLoop(bridge->GetExecutingContext()); } diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 2f69af13ff..69e6c04584 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -52,8 +52,8 @@ 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) { @@ -243,8 +243,9 @@ static bool jsPool(kraken::ExecutingContext* 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; diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index ef7dcf7e27..ff15b25881 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -25,6 +25,7 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./core/dom/document_test.cc ./core/dom/node_test.cc ./core/dom/element_test.cc + ./core/frame/dom_timer_test.cc # ./bindings/qjs/bom/timer_test.cc # ./bindings/qjs/qjs_patch_test.cc # ./bindings/qjs/garbage_collected_test.cc From 461650a279fe4dba620cfaee615be1ede0abf540 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Mon, 16 May 2022 20:13:13 +0800 Subject: [PATCH 124/498] feat: add message event and part of window impl. --- bridge/CMakeLists.txt | 8 + bridge/bindings/qjs/binding_initializer.cc | 6 + bridge/bindings/qjs/converter_impl.h | 11 ++ bridge/bindings/qjs/script_wrappable.cc | 2 +- bridge/bindings/qjs/script_wrappable.h | 2 +- bridge/bindings/qjs/wrapper_type_info.h | 2 + bridge/core/dom/binding_call_methods.json5 | 3 +- bridge/core/dom/element.cc | 17 ++- bridge/core/events/error_event.d.ts | 1 + bridge/core/events/input_event.d.ts | 2 + bridge/core/events/message_event.cc | 49 ++++++ bridge/core/events/message_event.d.ts | 10 ++ bridge/core/events/message_event.h | 39 +++++ bridge/core/events/message_event_init.d.ts | 11 ++ bridge/core/executing_context.cc | 19 ++- bridge/core/executing_context.h | 3 + bridge/core/frame/window.cc | 143 +++++++++++------- bridge/core/frame/window.d.ts | 12 ++ bridge/core/frame/window.h | 21 +++ bridge/core/frame/window_test.cc | 135 ++++++++++------- bridge/core/page.cc | 1 + .../code_generator/src/idl/analyzer.ts | 6 +- .../code_generator/src/idl/generateHeader.ts | 2 +- .../code_generator/src/idl/generateSource.ts | 45 ++++-- .../static/idl_templates/base.cc.tpl | 2 + .../static/idl_templates/dictionary.cc.tpl | 4 +- .../static/idl_templates/interface.cc.tpl | 26 +++- bridge/test/test.cmake | 1 + 28 files changed, 431 insertions(+), 152 deletions(-) create mode 100644 bridge/core/events/message_event.cc create mode 100644 bridge/core/events/message_event.d.ts create mode 100644 bridge/core/events/message_event.h create mode 100644 bridge/core/events/message_event_init.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 455607d7bd..3984db5273 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -328,6 +328,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/container_node.h core/events/error_event.cc core/events/error_event.h + core/events/message_event.h + core/events/message_event.cc core/html/parser/html_parser.cc core/html/parser/html_parser.h core/html/html_collection.cc @@ -390,6 +392,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_module_manager.h out/qjs_window_or_worker_global_scope.cc out/qjs_window_or_worker_global_scope.h + out/qjs_window.cc + out/qjs_window.h out/qjs_blob.cc out/qjs_blob.h out/qjs_event.cc @@ -400,6 +404,10 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_event_listener_options.h out/qjs_error_event.h out/qjs_error_event.cc + out/qjs_message_event.cc + out/qjs_message_event.h + out/qjs_message_event_init.h + out/qjs_message_event_init.cc out/qjs_error_event_init.h out/qjs_error_event_init.cc out/qjs_event_init.h diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 163b46b1b9..77b5998ebd 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -15,6 +15,8 @@ #include "qjs_element.h" #include "qjs_element_attributes.h" #include "qjs_event.h" +#include "qjs_message_event.h" +#include "qjs_error_event.h" #include "qjs_event_target.h" #include "qjs_html_body_element.h" #include "qjs_html_div_element.h" @@ -39,7 +41,11 @@ void InstallBindings(ExecutingContext* context) { QJSModuleManager::Install(context); QJSConsole::Install(context); QJSEventTarget::Install(context); + QJSWindow::Install(context); + context->InstallGlobal(); QJSEvent::Install(context); + QJSErrorEvent::Install(context); + QJSMessageEvent::Install(context); QJSNode::Install(context); QJSNodeList::Install(context); QJSDocument::Install(context); diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 3ad673e078..272dd1b762 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -9,6 +9,7 @@ #include <type_traits> #include "atomic_string.h" #include "converter.h" +#include "core/frame/window.h" #include "core/dom/document.h" #include "core/dom/events/event.h" #include "core/dom/events/event_target.h" @@ -411,6 +412,16 @@ struct Converter<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T 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<Window> : public ConverterBase<Window> { + static Window* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + return toScriptWrappable<Window>(value); + } + static JSValue ToValue(JSContext* ctx, Window* window) { return JS_DupValue(ctx, window->GetExecutingContext()->Global()); } + static JSValue ToValue(JSContext* ctx, const Window* window) { return JS_DupValue(ctx, window->GetExecutingContext()->Global()); } }; }; // namespace kraken diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index a03b8526a2..b151b745f6 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -12,7 +12,7 @@ namespace kraken { ScriptWrappable::ScriptWrappable(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), context_(ExecutingContext::From(ctx)) {} -JSValue ScriptWrappable::ToQuickJS() { +JSValue ScriptWrappable::ToQuickJS() const { return JS_DupValue(ctx_, jsObject_); } diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index a1db89bb36..8592f464b4 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -47,7 +47,7 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { void Trace(GCVisitor* visitor) const override{}; - JSValue ToQuickJS(); + JSValue ToQuickJS() const; JSValue ToQuickJSUnsafe() const; ScriptValue ToValue(); diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index e506dcf54c..ca35bd99f6 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -18,7 +18,9 @@ enum { JS_CLASS_BLOB, JS_CLASS_EVENT, JS_CLASS_ERROR_EVENT, + JS_CLASS_MESSAGE_EVENT, JS_CLASS_EVENT_TARGET, + JS_CLASS_WINDOW, JS_CLASS_NODE, JS_CLASS_ELEMENT, JS_CLASS_DOCUMENT, diff --git a/bridge/core/dom/binding_call_methods.json5 b/bridge/core/dom/binding_call_methods.json5 index 043eb7c2de..ef8da76cd5 100644 --- a/bridge/core/dom/binding_call_methods.json5 +++ b/bridge/core/dom/binding_call_methods.json5 @@ -19,6 +19,7 @@ "scrollTop", "getBoundingClientRect", ["getPropertyMagic", "%g"], - ["setPropertyMagic", "%s"] + ["setPropertyMagic", "%s"], + "open" ] } diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 68c70aae7f..20447b88b9 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -93,10 +93,13 @@ void Element::scroll(double x, double y, ExceptionState& exception_state) { InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); } -// TODO: add this support. void Element::scroll(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state) { - exception_state.ThrowException(ctx(), ErrorType::InternalError, - "scroll API which accept scrollToOptions not supported."); + GetExecutingContext()->dartMethodPtr()->flushUICommand(); + const NativeValue args[] = { + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->left()), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->top()), + }; + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); } void Element::scrollBy(ExceptionState& exception_state) { @@ -113,8 +116,12 @@ void Element::scrollBy(double x, double y, ExceptionState& exception_state) { } void Element::scrollBy(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state) { - exception_state.ThrowException(ctx(), ErrorType::InternalError, - "scrollBy API which accept scrollToOptions not supported."); + GetExecutingContext()->dartMethodPtr()->flushUICommand(); + const NativeValue args[] = { + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->left()), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->top()), + }; + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); } void Element::scrollTo(ExceptionState& exception_state) { diff --git a/bridge/core/events/error_event.d.ts b/bridge/core/events/error_event.d.ts index c94949c2dc..f04dc51934 100644 --- a/bridge/core/events/error_event.d.ts +++ b/bridge/core/events/error_event.d.ts @@ -1,4 +1,5 @@ import {ErrorEventInit} from "./error_event_init"; +import {Event} from "../dom/events/event"; interface ErrorEvent extends Event { readonly message: string; diff --git a/bridge/core/events/input_event.d.ts b/bridge/core/events/input_event.d.ts index 304ab79609..481679126b 100644 --- a/bridge/core/events/input_event.d.ts +++ b/bridge/core/events/input_event.d.ts @@ -1,3 +1,5 @@ +import {Event} from "../dom/events/event"; + interface InputEvent extends Event { readonly inputType: string; readonly data: string; diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc new file mode 100644 index 0000000000..1df8e1857d --- /dev/null +++ b/bridge/core/events/message_event.cc @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#include "message_event.h" + +namespace kraken { + +MessageEvent* MessageEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { + return MakeGarbageCollected<MessageEvent>(context, type); +} + +MessageEvent* MessageEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MessageEventInit>& init, + ExceptionState& exception_state) { + return MakeGarbageCollected<MessageEvent>(context, type, init); +} + +MessageEvent::MessageEvent(ExecutingContext* context, const AtomicString& type) : Event(context) {} + +MessageEvent::MessageEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MessageEventInit>& init) + : Event(context), + data_(init->data()), + origin_(init->origin()), + lastEventId_(init->lastEventId()), + source_(init->source()) {} + +ScriptValue MessageEvent::data() const { + return data_; +} + +AtomicString MessageEvent::origin() const { + return origin_; +} + +AtomicString MessageEvent::lastEventId() const { + return lastEventId_; +} + +AtomicString MessageEvent::source() const { + return source_; +} + +} // namespace kraken diff --git a/bridge/core/events/message_event.d.ts b/bridge/core/events/message_event.d.ts new file mode 100644 index 0000000000..9d9a161dbd --- /dev/null +++ b/bridge/core/events/message_event.d.ts @@ -0,0 +1,10 @@ +import {Event} from "../dom/events/event"; +import {MessageEventInit} from "./message_event_init"; + +export interface MessageEvent extends Event { + new(type: string, init?: MessageEventInit): MessageEvent; + readonly data: any; + readonly origin: string; + readonly lastEventId: string; + readonly source: string; +} diff --git a/bridge/core/events/message_event.h b/bridge/core/events/message_event.h new file mode 100644 index 0000000000..7e8f5f0040 --- /dev/null +++ b/bridge/core/events/message_event.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021-present The Kraken authors. All rights reserved. + */ + +#ifndef KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ +#define KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ + +#include "core/dom/events/event.h" +#include "qjs_message_event_init.h" + +namespace kraken { + +class MessageEvent : public Event { + DEFINE_WRAPPERTYPEINFO(); + + public: + using ImplType = MessageEvent*; + + static MessageEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); + static MessageEvent* Create(ExecutingContext* context, const AtomicString& type, const std::shared_ptr<MessageEventInit> &init, ExceptionState& exception_state); + + explicit MessageEvent(ExecutingContext* context, const AtomicString& type); + explicit MessageEvent(ExecutingContext* context, const AtomicString& type, const std::shared_ptr<MessageEventInit> &init); + + ScriptValue data() const; + AtomicString origin() const; + AtomicString lastEventId() const; + AtomicString source() const; + + private: + ScriptValue data_; + AtomicString origin_; + AtomicString lastEventId_; + AtomicString source_; +}; + +} + +#endif // KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ diff --git a/bridge/core/events/message_event_init.d.ts b/bridge/core/events/message_event_init.d.ts new file mode 100644 index 0000000000..480e3b6075 --- /dev/null +++ b/bridge/core/events/message_event_init.d.ts @@ -0,0 +1,11 @@ +import { EventInit } from "../dom/events/event_init"; + +// @ts-ignore +@Dictionary() +export interface MessageEventInit extends EventInit { + data: any; + origin: string; + lastEventId: string; + source: string; + // TODO: add ports property. +} diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 85424714ab..c014ef84c9 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -8,6 +8,7 @@ #include "core/dom/document.h" #include "core/html/html_html_element.h" #include "polyfill.h" +#include "qjs_window.h" #include "foundation/logging.h" @@ -44,16 +45,7 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& JSContext* ctx = script_state_.ctx(); global_object_ = JS_GetGlobalObject(script_state_.ctx()); - JSValue windowGetter = JS_NewCFunction( - ctx, - [](JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) -> JSValue { - return JS_GetGlobalObject(ctx); - }, - "get", 0); - JSAtom windowKey = JS_NewAtom(ctx, "window"); - JS_DefinePropertyGetSet(ctx, global_object_, windowKey, windowGetter, JS_UNDEFINED, - JS_PROP_HAS_GET | JS_PROP_ENUMERABLE); - JS_FreeAtom(ctx, windowKey); + JS_SetContextOpaque(ctx, this); JS_SetHostPromiseRejectionTracker(script_state_.runtime(), promiseRejectTracker, nullptr); @@ -384,6 +376,13 @@ void ExecutingContext::InstallDocument() { DefineGlobalProperty("document", document_->ToQuickJS()); } +void ExecutingContext::InstallGlobal() { + MemberMutationScope mutation_scope{this}; + auto* window = MakeGarbageCollected<Window>(this); + JS_SetPrototype(ctx(), Global(), window->ToQuickJSUnsafe()); + JS_SetOpaque(Global(), window); +} + // An lock free context validator. bool isContextValid(int32_t contextId) { if (contextId > running_context_list) diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 9c21bd2a1c..67ded141b6 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -73,6 +73,9 @@ class ExecutingContext { ExecutionContextData* contextData(); uint8_t* DumpByteCode(const char* code, uint32_t codeLength, const char* sourceURL, size_t* bytecodeLength); + // Make global object inherit from WindowProperties. + void InstallGlobal(); + // Gets the DOMTimerCoordinator which maintains the "active timer // list" of tasks created by setTimeout and setInterval. The // DOMTimerCoordinator is owned by the ExecutionContext and should diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index c3cb381284..40bd4cb444 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -3,69 +3,98 @@ */ #include "window.h" +#include "binding_call_methods.h" #include "bindings/qjs/cppgc/garbage_collected.h" +#include "core/events/message_event.h" +#include "event_type_names.h" +#include "foundation/native_value_converter.h" namespace kraken { Window::Window(ExecutingContext* context) : EventTargetWithInlineData(context) {} -// -// JSValue Window::open(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// auto window = static_cast<WindowInstance*>(JS_GetOpaque(this_val, Window::classId())); -// NativeValue arguments[] = {jsValueToNativeValue(ctx, argv[0])}; -// return window->invokeBindingMethod("open", 1, arguments); -//} -// -// IMPL_FUNCTION(Window, scrollTo)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -//#if FLUTTER_BACKEND -// getDartMethod()->flushUICommand(); -// auto window = static_cast<WindowInstance*>(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<WindowInstance*>(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); -//} -// -// IMPL_FUNCTION(Window, postMessage)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// JSValue messageValue = argv[0]; -// JSValue globalObjectValue = JS_GetGlobalObject(ctx); -// auto* window = static_cast<Window*>(JS_GetOpaque(globalObjectValue, JSValueGetClassId(this_val))); -// -// 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<MessageEventInstance*>(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; -//} -// +Window* Window::open(ExceptionState& exception_state) { + return this; +} + +Window* Window::open(const AtomicString& url, ExceptionState& exception_state) { + const NativeValue args[] = { + NativeValueConverter<NativeTypeString>::ToNativeValue(url.ToNativeString().release()), + }; + InvokeBindingMethod(binding_call_methods::kopen, 1, args, exception_state); +} + +void Window::scroll(ExceptionState& exception_state) { + return scroll(0, 0, exception_state); +} + +void Window::scroll(double x, double y, ExceptionState& exception_state) { + const NativeValue args[] = { + NativeValueConverter<NativeTypeDouble>::ToNativeValue(x), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(y), + }; + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); +} + +void Window::scroll(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state) { + const NativeValue args[] = { + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->left()), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->top()), + }; + InvokeBindingMethod(binding_call_methods::kscroll, 2, args, exception_state); +} + +void Window::scrollBy(ExceptionState& exception_state) { + return scrollBy(0, 0, exception_state); +} + +void Window::scrollBy(double x, double y, ExceptionState& exception_state) { + const NativeValue args[] = { + NativeValueConverter<NativeTypeDouble>::ToNativeValue(x), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(y), + }; + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); +} + +void Window::scrollBy(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state) { + const NativeValue args[] = { + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->left()), + NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->top()), + }; + InvokeBindingMethod(binding_call_methods::kscrollBy, 2, args, exception_state); +} + +void Window::scrollTo(ExceptionState& exception_state) { + return scroll(exception_state); +} + +void Window::scrollTo(double x, double y, ExceptionState& exception_state) { + return scroll(x, y, exception_state); +} + +void Window::scrollTo(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state) { + return scroll(options, exception_state); +} + +void Window::postMessage(const ScriptValue& message, ExceptionState& exception_state) { + auto event_init = MessageEventInit::Create(); + event_init->setData(message); + auto* message_event = + MessageEvent::Create(GetExecutingContext(), event_type_names::kmessage, event_init, exception_state); + dispatchEvent(message_event, exception_state); +} + +void Window::postMessage(const ScriptValue& message, + const AtomicString& target_origin, + ExceptionState& exception_state) { + auto event_init = MessageEventInit::Create(); + event_init->setData(message); + event_init->setOrigin(target_origin); + auto* message_event = + MessageEvent::Create(GetExecutingContext(), event_type_names::kmessage, event_init, exception_state); + dispatchEvent(message_event, exception_state); +} + // IMPL_FUNCTION(Window, requestAnimationFrame)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // if (argc <= 0) { // return JS_ThrowTypeError(ctx, diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts index 8f43687f0e..6414a5cba6 100644 --- a/bridge/core/frame/window.d.ts +++ b/bridge/core/frame/window.d.ts @@ -1,5 +1,17 @@ import {EventTarget} from "../dom/events/event_target"; +import {ScrollOptions} from "../dom/scroll_options"; +import {ScrollToOptions} from "../dom/scroll_to_options"; interface Window extends EventTarget { open(url?: string): Window | null; + scrollTo(options?: ScrollToOptions): void; + scrollTo(x: number, y: number): void; + scrollBy(options?: ScrollToOptions): void; + scrollBy(x: number, y: number): void; + + postMessage(message: any, targetOrigin: string): void; + postMessage(message: any): void; + + readonly window: Window; + new(): void; } diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index a972195984..f4fc1f6f14 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -8,14 +8,35 @@ #include "bindings/qjs/wrapper_type_info.h" #include "core/dom/events/event_target.h" +#include "bindings/qjs/atomic_string.h" +#include "qjs_scroll_to_options.h" namespace kraken { class Window : public EventTargetWithInlineData { + DEFINE_WRAPPERTYPEINFO(); public: Window() = delete; Window(ExecutingContext* context); + Window* open(ExceptionState& exception_state); + Window* open(const AtomicString& url, ExceptionState& exception_state); + + const Window* window() 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); + // DEFINE_FUNCTION(open); // DEFINE_FUNCTION(scrollTo); // DEFINE_FUNCTION(scrollBy); diff --git a/bridge/core/frame/window_test.cc b/bridge/core/frame/window_test.cc index 0b3b87ffbc..a67fb47131 100644 --- a/bridge/core/frame/window_test.cc +++ b/bridge/core/frame/window_test.cc @@ -5,7 +5,27 @@ #include "window.h" #include "gtest/gtest.h" #include "kraken_test_env.h" -#include "page.h" + +using namespace kraken; + +TEST(Window, windowIsGlobalThis) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "true"); + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = "console.log(window === globalThis)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} TEST(Window, instanceofEventTarget) { bool static errorCalled = false; @@ -18,7 +38,7 @@ TEST(Window, instanceofEventTarget) { KRAKEN_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->getContext(); + auto context = bridge->GetExecutingContext(); const char* code = "console.log(window instanceof EventTarget)"; bridge->evaluateScript(code, strlen(code), "vm://", 0); @@ -28,6 +48,7 @@ TEST(Window, instanceofEventTarget) { TEST(Window, requestAnimationFrame) { auto bridge = TEST_init(); + bool static logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "456"); @@ -40,60 +61,62 @@ requestAnimationFrame(() => { )"; bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(bridge->getContext()); -} + TEST_runLoop(bridge->GetExecutingContext()); -TEST(Window, cancelAnimationFrame) { - auto bridge = TEST_init(); - - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { abort(); }; - - std::string code = R"( -let id = requestAnimationFrame(() => { - console.log('456'); -}); -cancelAnimationFrame(id); -)"; - - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - TEST_runLoop(bridge->getContext()); -} - -TEST(Window, postMessage) { - { - auto bridge = TEST_init(); - static bool logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "{\"data\":1234} "); - }; - - std::string code = std::string(R"( -window.onmessage = (message) => { - console.log(JSON.stringify(message.data), message.origin); -}; -window.postMessage({ - data: 1234 -}, '*'); -)"); - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - EXPECT_EQ(logCalled, true); - } - // Use block scope to release previous page, and allocate new page. - { TEST_init(); } -} - -TEST(Window, location) { - auto bridge = TEST_init(); - static bool logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - logCalled = true; - EXPECT_STREQ(message.c_str(), "true true"); - }; - - std::string code = std::string(R"( - console.log(window.location !== undefined, window.location === document.location); - )"); - bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); EXPECT_EQ(logCalled, true); } +// +//TEST(Window, cancelAnimationFrame) { +// auto bridge = TEST_init(); +// +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { abort(); }; +// +// std::string code = R"( +//let id = requestAnimationFrame(() => { +// console.log('456'); +//}); +//cancelAnimationFrame(id); +//)"; +// +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// TEST_runLoop(bridge->getContext()); +//} +// +//TEST(Window, postMessage) { +// { +// auto bridge = TEST_init(); +// static bool logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "{\"data\":1234} "); +// }; +// +// std::string code = std::string(R"( +//window.onmessage = (message) => { +// console.log(JSON.stringify(message.data), message.origin); +//}; +//window.postMessage({ +// data: 1234 +//}, '*'); +//)"); +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// EXPECT_EQ(logCalled, true); +// } +// // Use block scope to release previous page, and allocate new page. +// { TEST_init(); } +//} +// +//TEST(Window, location) { +// auto bridge = TEST_init(); +// static bool logCalled = false; +// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// logCalled = true; +// EXPECT_STREQ(message.c_str(), "true true"); +// }; +// +// std::string code = std::string(R"( +// console.log(window.location !== undefined, window.location === document.location); +// )"); +// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); +// EXPECT_EQ(logCalled, true); +//} diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 92f72af3ab..6c6ba965bd 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -9,6 +9,7 @@ #include "bindings/qjs/binding_initializer.h" #include "core/dart_methods.h" #include "core/dom/document.h" +#include "core/frame/window.h" #include "core/html/parser/html_parser.h" #include "foundation/logging.h" #include "page.h" diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 4a60ea37e1..e7023e525b 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -91,7 +91,7 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete if (mode) mode.dartImpl = true; let argument = typeReference.typeArguments![0]; // @ts-ignore - return argument.typeName.text; + return getParameterBaseType(argument); } return identifier; @@ -161,7 +161,9 @@ function walkProgram(statement: ts.Statement) { let propKind = m.type; if (propKind) { - prop.type = getParameterType(propKind); + let mode = new ParameterMode(); + prop.type = getParameterType(propKind, mode); + prop.typeMode = mode; if (prop.type[0] === FunctionArgumentType.function) { let f = (m.type as ts.FunctionTypeNode); let functionProps = prop as FunctionDeclaration; diff --git a/bridge/scripts/code_generator/src/idl/generateHeader.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts index e8d257dfbe..b120cd42b7 100644 --- a/bridge/scripts/code_generator/src/idl/generateHeader.ts +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -4,7 +4,7 @@ import {IDLBlob} from "./IDLBlob"; import {getClassName} from "./utils"; import fs from 'fs'; import path from 'path'; -import {generateTypeConverter, generateTypeValue} from "./generateSource"; +import {generateIDLTypeConverter, generateTypeValue} from "./generateSource"; import {GenerateOptions} from "./generator"; export enum TemplateKind { diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 29f0b2b744..6a17031f4c 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -68,12 +68,12 @@ export function generateTypeValue(type: ParameterType[]): string { return ''; } -export function generateTypeConverter(type: ParameterType[]): string { +export function generateIDLTypeConverter(type: ParameterType[]): string { let haveNull = type.some(t => t === FunctionArgumentType.null); let returnValue = ''; if (type[0] === FunctionArgumentType.array) { - returnValue = `IDLSequence<${generateTypeConverter(type.slice(1))}>`; + returnValue = `IDLSequence<${generateIDLTypeConverter(type.slice(1))}>`; } else if (typeof type[0] === 'string') { returnValue = type[0]; } else { @@ -113,8 +113,32 @@ export function generateTypeConverter(type: ParameterType[]): string { return returnValue; } +function generateNativeValueTypeConverter(type: ParameterType[]): string { + let returnValue = ''; + + switch (type[0]) { + case FunctionArgumentType.int32: + returnValue = `NativeTypeInt64`; + break; + case FunctionArgumentType.int64: + returnValue = 'NativeTypeInt64'; + break; + case FunctionArgumentType.double: + returnValue = `NativeTypeDouble`; + break; + case FunctionArgumentType.boolean: + returnValue = `NativeTypeBool`; + break; + case FunctionArgumentType.dom_string: + returnValue = `NativeTypeString`; + break; + } + + return returnValue; +} + function generateRequiredInitBody(argument: FunctionArguments, argsIndex: number) { - let type = generateTypeConverter(argument.type); + let type = generateIDLTypeConverter(argument.type); let hasArgumentCheck = type.indexOf('Element') >= 0 || type.indexOf('Node') >= 0 || type === 'EventTarget'; @@ -150,7 +174,7 @@ ${returnValueAssignment} self->${generateCallMethodName(declare.name)}(${[...pre } - return `auto&& args_${argument.name} = Converter<IDLOptional<${generateTypeConverter(argument.type)}>>::FromValue(ctx, argv[${argsIndex}], 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(); } @@ -232,9 +256,9 @@ 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]) { + switch (prop.type[0]) { case FunctionArgumentType.boolean: { - return `${prop.name}_(false)`; + return `${prop.name}_(false)`; } } return '' @@ -264,7 +288,7 @@ function generateReturnValueInit(blob: IDLBlob, type: ParameterType[], options: return `${type[0]}* return_value = nullptr;`; } } - return `Converter<${generateTypeConverter(type)}>::ImplType return_value;`; + return `Converter<${generateIDLTypeConverter(type)}>::ImplType return_value;`; } function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], mode?: ParameterMode, options: GenFunctionBodyOptions = { @@ -286,7 +310,7 @@ function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], mode?: } } - return `Converter<${generateTypeConverter(type)}>::ToValue(ctx, std::move(return_value))`; + return `Converter<${generateIDLTypeConverter(type)}>::ToValue(ctx, std::move(return_value))`; } type GenFunctionBodyOptions = { isConstructor?: boolean, isInstanceMethod?: boolean }; @@ -393,7 +417,8 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass generateOverLoadSwitchBody, overloadMethods, filtedMethods, - generateTypeConverter + generateIDLTypeConverter, + generateNativeValueTypeConverter }); } case TemplateKind.Dictionary: { @@ -403,7 +428,7 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass blob: blob, props: props, object: object, - generateTypeConverter, + generateIDLTypeConverter, generateDictionaryInit }); } diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl index 1b43016714..608dfed39a 100644 --- a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl @@ -3,6 +3,8 @@ */ #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" diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl index 7efba4a6f1..72968a5df2 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl @@ -20,7 +20,7 @@ bool <%= className %>::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dict } <% _.forEach(props, function(prop, index) { %> - JS_SetPropertyStr(ctx, qjs_dictionary, "<%= prop.name %>_", Converter<<%= generateTypeConverter(prop.type) %>>::ToValue(ctx, <%= prop.name %>_)); + JS_SetPropertyStr(ctx, qjs_dictionary, "<%= prop.name %>_", Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, <%= prop.name %>_)); <% }); %> return true; @@ -38,7 +38,7 @@ void <%= className %>::FillMembersWithQJSObject(JSContext* ctx, JSValue value, E <% _.forEach(props, function(prop, index) { %> { JSValue v = JS_GetPropertyStr(ctx, value, "<%= prop.name %>"); - <%= prop.name %>_ = Converter<<%= generateTypeConverter(prop.type) %>>::FromValue(ctx, v, exception_state); + <%= prop.name %>_ = Converter<<%= generateIDLTypeConverter(prop.type) %>>::FromValue(ctx, v, exception_state); JS_FreeValue(ctx, v); } diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index aca284f4bc..9c3d5d1cf4 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -18,7 +18,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob return exception_state.ToQuickJS(); } - return Converter<<%= generateTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); + return Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); }; <% } else { %> JSValue QJS<%= className %>::StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key) { @@ -29,7 +29,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } - return Converter<<%= generateTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); + return Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); }; <% } %> <% if (!object.indexedProp.readonly) { %> @@ -38,7 +38,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; MemberMutationScope scope{ExecutingContext::From(ctx)}; - auto&& v = Converter<<%= generateTypeConverter(object.indexedProp.type) %>>::FromValue(ctx, value, exception_state); + auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::FromValue(ctx, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; } @@ -53,7 +53,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; MemberMutationScope scope{ExecutingContext::From(ctx)}; - auto&& v = Converter<<%= generateTypeConverter(object.indexedProp.type) %>>::FromValue(ctx, value, exception_state); + auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::FromValue(ctx, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; } @@ -93,19 +93,33 @@ static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); assert(<%= blob.filename %> != nullptr); MemberMutationScope scope{ExecutingContext::From(ctx)}; - return Converter<<%= generateTypeConverter(prop.type) %>>::ToValue(ctx, <%= blob.filename %>-><%= prop.name %>()); + + <% if (prop.typeMode && prop.typeMode.dartImpl) { %> + ExceptionState exception_state; + typename NativeTypeDouble::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(<%= blob.filename %>->GetBindingProperty(binding_call_methods::k<%= prop.name %>, exception_state)); + if (UNLIKELY(exception_state.HasException())) { + return exception_state.ToQuickJS(); + } + return Converter<IDLDouble>::ToValue(ctx, v); + <% } else { %> + return Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, <%= blob.filename %>-><%= prop.name %>()); + <% } %> } <% if (!prop.readonly) { %> static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); ExceptionState exception_state; - auto&& v = Converter<<%= generateTypeConverter(prop.type) %>>::FromValue(ctx, argv[0], exception_state); + auto&& v = Converter<<%= generateIDLTypeConverter(prop.type) %>>::FromValue(ctx, argv[0], exception_state); if (exception_state.HasException()) { return exception_state.ToQuickJS(); } MemberMutationScope scope{ExecutingContext::From(ctx)}; + <% if (prop.typeMode && prop.typeMode.dartImpl) { %> + <%= blob.filename %>->SetBindingProperty(binding_call_methods::k<%= prop.name %>, NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::ToNativeValue(v),exception_state); + <% } else {%> <%= blob.filename %>->set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(v, exception_state); + <% } %> if (exception_state.HasException()) { return exception_state.ToQuickJS(); } diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index ff15b25881..7099e3548d 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -26,6 +26,7 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./core/dom/node_test.cc ./core/dom/element_test.cc ./core/frame/dom_timer_test.cc + ./core/frame/window_test.cc # ./bindings/qjs/bom/timer_test.cc # ./bindings/qjs/qjs_patch_test.cc # ./bindings/qjs/garbage_collected_test.cc From 192e13fa0841c1595bf094b629d8ce3aada53d8d Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Mon, 16 May 2022 12:14:00 +0000 Subject: [PATCH 125/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 4 ++-- bridge/bindings/qjs/converter_impl.h | 12 ++++++++---- bridge/core/dom/comment.cc | 3 ++- bridge/core/dom/events/event_target.cc | 2 +- bridge/core/events/message_event.h | 11 ++++++++--- bridge/core/frame/window.cc | 2 +- bridge/core/frame/window.h | 3 ++- bridge/core/frame/window_test.cc | 14 +++++++------- 8 files changed, 31 insertions(+), 20 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 77b5998ebd..e2e8095cad 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -14,9 +14,8 @@ #include "qjs_document.h" #include "qjs_element.h" #include "qjs_element_attributes.h" -#include "qjs_event.h" -#include "qjs_message_event.h" #include "qjs_error_event.h" +#include "qjs_event.h" #include "qjs_event_target.h" #include "qjs_html_body_element.h" #include "qjs_html_div_element.h" @@ -25,6 +24,7 @@ #include "qjs_html_html_element.h" #include "qjs_html_template_element.h" #include "qjs_html_unknown_element.h" +#include "qjs_message_event.h" #include "qjs_module_manager.h" #include "qjs_node.h" #include "qjs_node_list.h" diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 272dd1b762..664c784e0c 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -9,13 +9,13 @@ #include <type_traits> #include "atomic_string.h" #include "converter.h" -#include "core/frame/window.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" @@ -415,13 +415,17 @@ struct Converter<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T static JSValue ToValue(JSContext* ctx, const T* value) { return value->ToQuickJS(); } }; -template<> +template <> struct Converter<Window> : public ConverterBase<Window> { static Window* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { return toScriptWrappable<Window>(value); } - static JSValue ToValue(JSContext* ctx, Window* window) { return JS_DupValue(ctx, window->GetExecutingContext()->Global()); } - static JSValue ToValue(JSContext* ctx, const Window* window) { return JS_DupValue(ctx, window->GetExecutingContext()->Global()); } + static JSValue ToValue(JSContext* ctx, Window* window) { + return JS_DupValue(ctx, window->GetExecutingContext()->Global()); + } + static JSValue ToValue(JSContext* ctx, const Window* window) { + return JS_DupValue(ctx, window->GetExecutingContext()->Global()); + } }; }; // namespace kraken diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc index b042f4ecd0..db84b3f304 100644 --- a/bridge/core/dom/comment.cc +++ b/bridge/core/dom/comment.cc @@ -20,7 +20,8 @@ Comment* Comment::Create(Document& document) { Comment::Comment(TreeScope& tree_scope, ConstructionType type) : CharacterData(tree_scope, built_in_string::kempty_string, type) { - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateComment,(void*)bindingObject()); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateComment, + (void*)bindingObject()); } Node::NodeType Comment::nodeType() const { diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index feb6ae329d..e363ef4b59 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -40,7 +40,7 @@ EventTarget* EventTarget::Create(ExecutingContext* context, ExceptionState& exce } EventTarget::~EventTarget() { -// GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, nullptr); + // GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, nullptr); } EventTarget::EventTarget(ExecutingContext* context) diff --git a/bridge/core/events/message_event.h b/bridge/core/events/message_event.h index 7e8f5f0040..c6f17aa893 100644 --- a/bridge/core/events/message_event.h +++ b/bridge/core/events/message_event.h @@ -17,10 +17,15 @@ class MessageEvent : public Event { 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); + static MessageEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MessageEventInit>& init, + ExceptionState& exception_state); explicit MessageEvent(ExecutingContext* context, const AtomicString& type); - explicit MessageEvent(ExecutingContext* context, const AtomicString& type, const std::shared_ptr<MessageEventInit> &init); + explicit MessageEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr<MessageEventInit>& init); ScriptValue data() const; AtomicString origin() const; @@ -34,6 +39,6 @@ class MessageEvent : public Event { AtomicString source_; }; -} +} // namespace kraken #endif // KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index 40bd4cb444..b38edcc77e 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -19,7 +19,7 @@ Window* Window::open(ExceptionState& exception_state) { Window* Window::open(const AtomicString& url, ExceptionState& exception_state) { const NativeValue args[] = { - NativeValueConverter<NativeTypeString>::ToNativeValue(url.ToNativeString().release()), + NativeValueConverter<NativeTypeString>::ToNativeValue(url.ToNativeString().release()), }; InvokeBindingMethod(binding_call_methods::kopen, 1, args, exception_state); } diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index f4fc1f6f14..61223fb437 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -6,15 +6,16 @@ #ifndef KRAKENBRIDGE_WINDOW_H #define KRAKENBRIDGE_WINDOW_H +#include "bindings/qjs/atomic_string.h" #include "bindings/qjs/wrapper_type_info.h" #include "core/dom/events/event_target.h" -#include "bindings/qjs/atomic_string.h" #include "qjs_scroll_to_options.h" namespace kraken { class Window : public EventTargetWithInlineData { DEFINE_WRAPPERTYPEINFO(); + public: Window() = delete; Window(ExecutingContext* context); diff --git a/bridge/core/frame/window_test.cc b/bridge/core/frame/window_test.cc index a67fb47131..92075a0f64 100644 --- a/bridge/core/frame/window_test.cc +++ b/bridge/core/frame/window_test.cc @@ -66,23 +66,23 @@ requestAnimationFrame(() => { EXPECT_EQ(logCalled, true); } // -//TEST(Window, cancelAnimationFrame) { +// TEST(Window, cancelAnimationFrame) { // auto bridge = TEST_init(); // // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { abort(); }; // // std::string code = R"( -//let id = requestAnimationFrame(() => { +// let id = requestAnimationFrame(() => { // console.log('456'); //}); -//cancelAnimationFrame(id); +// cancelAnimationFrame(id); //)"; // // bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); // TEST_runLoop(bridge->getContext()); //} // -//TEST(Window, postMessage) { +// TEST(Window, postMessage) { // { // auto bridge = TEST_init(); // static bool logCalled = false; @@ -92,10 +92,10 @@ requestAnimationFrame(() => { // }; // // std::string code = std::string(R"( -//window.onmessage = (message) => { +// window.onmessage = (message) => { // console.log(JSON.stringify(message.data), message.origin); //}; -//window.postMessage({ +// window.postMessage({ // data: 1234 //}, '*'); //)"); @@ -106,7 +106,7 @@ requestAnimationFrame(() => { // { TEST_init(); } //} // -//TEST(Window, location) { +// TEST(Window, location) { // auto bridge = TEST_init(); // static bool logCalled = false; // kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { From a19218e41ea1c55dd0ccafe73259ca5f050f8bbf Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Tue, 17 May 2022 10:29:45 +0800 Subject: [PATCH 126/498] tmp: add window requestanimationframe --- bridge/CMakeLists.txt | 2 ++ bridge/bindings/qjs/script_value.h | 1 + bridge/core/dom/document.cc | 4 +++ bridge/core/dom/document.h | 4 +++ .../dom/frame_request_callback_collection.cc | 34 +++++++------------ .../dom/frame_request_callback_collection.h | 9 +++-- .../core/dom/scripted_animation_controller.cc | 30 +++++++--------- .../core/dom/scripted_animation_controller.h | 17 +++------- bridge/core/frame/window.cc | 19 +++++++++++ bridge/core/frame/window.d.ts | 2 ++ bridge/core/frame/window.h | 4 ++- 11 files changed, 69 insertions(+), 57 deletions(-) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 3984db5273..f0debafbf3 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -314,6 +314,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/dom/element_traversal.h core/dom/document.cc core/dom/document.h + core/dom/scripted_animation_controller.cc + core/dom/scripted_animation_controller.h core/dom/node_data.cc core/dom/node_data.h core/dom/document_fragment.h diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 15680b6e84..1832f02ae5 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -39,6 +39,7 @@ class ScriptValue final { explicit ScriptValue(JSContext* ctx, JSValue value) : ctx_(ctx), value_(JS_DupValue(ctx, value)){}; explicit ScriptValue(JSContext* ctx, const NativeString* string) : ctx_(ctx), value_(JS_NewUnicodeString(ctx, string->string(), string->length())) {} + explicit ScriptValue(JSContext* ctx, double v): ctx_(ctx), value_(JS_NewFloat64(ctx, v)) {} explicit ScriptValue(JSContext* ctx) : ctx_(ctx){}; ScriptValue() = default; diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 358cb54cf8..60c1e78cd5 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -188,6 +188,10 @@ HTMLHeadElement* Document::head() const { return Traversal<HTMLHeadElement>::FirstChild(*de); } +uint32_t Document::RequestAnimationFrame(const std::shared_ptr<FrameCallback>& callback) { + return script_animation_controller_.RegisterFrameCallback(callback); +} + void Document::Trace(GCVisitor* visitor) const { visitor->Trace(document_element_); ContainerNode::Trace(visitor); diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 5f87295d4b..894cf9bc01 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -12,6 +12,7 @@ #include "core/dom/document_fragment.h" #include "core/dom/text.h" #include "html_element_type_helper.h" +#include "scripted_animation_controller.h" #include "tree_scope.h" namespace kraken { @@ -66,11 +67,14 @@ class Document : public ContainerNode, public TreeScope { } int NodeCount() const { return node_count_; } + uint32_t RequestAnimationFrame(const std::shared_ptr<FrameCallback>& callback); + void Trace(GCVisitor* visitor) const override; private: int node_count_; Member<Element> document_element_; + ScriptAnimationController script_animation_controller_; }; } // namespace kraken diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc index 97fcfcd270..05154988b9 100644 --- a/bridge/core/dom/frame_request_callback_collection.cc +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -8,36 +8,28 @@ namespace kraken { -FrameCallback::FrameCallback(ExecutingContext* context, JSValue callback) : context_(context), callback_(callback) {} +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, const std::shared_ptr<QJSFunction>& callback) + : context_(context), callback_(callback) {} void FrameCallback::Fire(double highResTimeStamp) { - if (!JS_IsFunction(context_->ctx(), callback_)) + if (callback_ == nullptr) return; - /* 'callback' might be destroyed when calling itself (if it frees the - handler), so must take extra care */ - JS_DupValue(context_->ctx(), callback_); + JSContext* ctx = context_->ctx(); - JSValue arguments[] = {JS_NewFloat64(context_->ctx(), highResTimeStamp)}; + ScriptValue arguments[] = {ScriptValue(ctx, highResTimeStamp)}; - JSValue returnValue = JS_Call(context_->ctx(), callback_, JS_UNDEFINED, 1, arguments); + ScriptValue return_value = callback_->Invoke(ctx, ScriptValue::Empty(ctx), 1, arguments); context_->DrainPendingPromiseJobs(); - JS_FreeValue(context_->ctx(), callback_); - - if (JS_IsException(returnValue)) { - context_->HandleException(&returnValue); + if (return_value.IsException()) { + context_->HandleException(&return_value); } - - JS_FreeValue(context_->ctx(), returnValue); -} - -void FrameCallback::Trace(GCVisitor* visitor) const { - visitor->Trace(callback_); -} - -void FrameCallback::Dispose() const { - JS_FreeValueRT(JS_GetRuntime(context_->ctx()), callback_); } void FrameRequestCallbackCollection::RegisterFrameCallback(uint32_t callbackId, FrameCallback* frameCallback) { diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index 203ed232f9..5b4b96ecc3 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -14,17 +14,16 @@ namespace kraken { // invoked when a script-based animation needs to be resampled. class FrameCallback { public: - FrameCallback(ExecutingContext* context, JSValue callback); + std::shared_ptr<FrameCallback> Create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback); + + FrameCallback(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback); void Fire(double highResTimeStamp); ExecutingContext* context() { return context_; }; - void Trace(GCVisitor* visitor) const; - void Dispose() const; - private: - JSValue callback_{JS_NULL}; + std::shared_ptr<QJSFunction> callback_; int32_t callbackId_{-1}; ExecutingContext* context_{nullptr}; }; diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index 12560ee5b8..aaa8be7037 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -3,7 +3,6 @@ */ #include "scripted_animation_controller.h" -#include "dart_methods.h" #include "frame_request_callback_collection.h" #if UNIT_TEST @@ -12,21 +11,14 @@ namespace kraken { -JSClassID ScriptAnimationController::classId{0}; - -void ScriptAnimationController::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - auto* controller = static_cast<ScriptAnimationController*>(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); -} +//void ScriptAnimationController::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { +// auto* controller = static_cast<ScriptAnimationController*>(JS_GetOpaque(val, ScriptAnimationController::classId)); +// controller->frame_request_callback_collection_.trace(rt, JS_UNDEFINED, mark_func); +//} static void handleRAFTransientCallback(void* ptr, int32_t contextId, double highResTimeStamp, const char* errmsg) { auto* frameCallback = static_cast<FrameCallback*>(ptr); - auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(frameCallback->ctx())); + auto* context = static_cast<ExecutingContext*>(JS_GetContextOpaque(frameCallback->ctx())); if (!context->isValid()) return; @@ -43,23 +35,25 @@ static void handleRAFTransientCallback(void* ptr, int32_t contextId, double high context->drainPendingPromiseJobs(); } -uint32_t ScriptAnimationController::registerFrameCallback(FrameCallback* frameCallback) { - auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(m_ctx)); +uint32_t ScriptAnimationController::RegisterFrameCallback(const std::shared_ptr<FrameCallback>& callback) { +// auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(m_ctx)); uint32_t requestId = getDartMethod()->requestAnimationFrame(frameCallback, context->getContextId(), handleRAFTransientCallback); // Register frame callback to collection. - m_frameRequestCallbackCollection.registerFrameCallback(requestId, frameCallback); + frame_request_callback_collection_.registerFrameCallback(requestId, frameCallback); return requestId; } -void ScriptAnimationController::cancelFrameCallback(uint32_t callbackId) { +void ScriptAnimationController::CancelFrameCallback(uint32_t callbackId) { auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(m_ctx)); getDartMethod()->cancelAnimationFrame(context->getContextId(), callbackId); - m_frameRequestCallbackCollection.cancelFrameCallback(callbackId); + frame_request_callback_collection_.cancelFrameCallback(callbackId); } +void ScriptAnimationController::Trace(GCVisitor* visitor) const {} + } // namespace kraken diff --git a/bridge/core/dom/scripted_animation_controller.h b/bridge/core/dom/scripted_animation_controller.h index aa8ec93a30..5a944a7120 100644 --- a/bridge/core/dom/scripted_animation_controller.h +++ b/bridge/core/dom/scripted_animation_controller.h @@ -10,23 +10,16 @@ namespace kraken { -class ScriptAnimationController : public GarbageCollected<ScriptAnimationController> { +class ScriptAnimationController { public: - static JSClassID classId; - - ScriptAnimationController* initialize(JSContext* ctx, JSClassID* classId) override; + ScriptAnimationController() = delete; // 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; + uint32_t RegisterFrameCallback(const std::shared_ptr<FrameCallback>& callback); + void CancelFrameCallback(uint32_t callbackId); private: - FrameRequestCallbackCollection m_frameRequestCallbackCollection; + FrameRequestCallbackCollection frame_request_callback_collection_; }; } // namespace kraken diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index b38edcc77e..4a7db534d2 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -95,6 +95,25 @@ void Window::postMessage(const ScriptValue& message, dispatchEvent(message_event, exception_state); } +double Window::requestAnimationFrame(const std::shared_ptr<QJSFunction>& callback, ExceptionState& exceptionState) { + GetExecutingContext()->dartMethodPtr()->flushUICommand(); + + int32_t request_id = GetExecutingContext()->document(); + +// auto* frameCallback = makeGarbageCollected<FrameCallback>(JS_DupValue(ctx, callbackValue)) +// ->initialize<FrameCallback>(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); +} + // IMPL_FUNCTION(Window, requestAnimationFrame)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // if (argc <= 0) { // return JS_ThrowTypeError(ctx, diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts index 6414a5cba6..71fa0fffd6 100644 --- a/bridge/core/frame/window.d.ts +++ b/bridge/core/frame/window.d.ts @@ -12,6 +12,8 @@ interface Window extends EventTarget { postMessage(message: any, targetOrigin: string): void; postMessage(message: any): void; + requestAnimationFrame(callback: Function): double; + readonly window: Window; new(): void; } diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index 61223fb437..a7b84f7ed2 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -23,7 +23,7 @@ class Window : public EventTargetWithInlineData { Window* open(ExceptionState& exception_state); Window* open(const AtomicString& url, ExceptionState& exception_state); - const Window* window() const { return this; } + [[nodiscard]] const Window* window() const { return this; } void scroll(ExceptionState& exception_state); void scroll(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state); @@ -38,6 +38,8 @@ class Window : public EventTargetWithInlineData { 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); + // DEFINE_FUNCTION(open); // DEFINE_FUNCTION(scrollTo); // DEFINE_FUNCTION(scrollBy); From 5994369d4fe76ad9bda0c181fae949f2a62d2d93 Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Tue, 17 May 2022 02:30:30 +0000 Subject: [PATCH 127/498] Committing clang-format changes --- bridge/bindings/qjs/script_value.h | 2 +- .../core/dom/scripted_animation_controller.cc | 4 ++-- .../core/dom/scripted_animation_controller.h | 2 +- bridge/core/frame/window.cc | 18 +++++++++--------- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 1832f02ae5..710d0a994f 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -39,7 +39,7 @@ class ScriptValue final { explicit ScriptValue(JSContext* ctx, JSValue value) : ctx_(ctx), value_(JS_DupValue(ctx, value)){}; explicit ScriptValue(JSContext* ctx, const NativeString* string) : ctx_(ctx), value_(JS_NewUnicodeString(ctx, string->string(), string->length())) {} - explicit ScriptValue(JSContext* ctx, double v): ctx_(ctx), value_(JS_NewFloat64(ctx, v)) {} + explicit ScriptValue(JSContext* ctx, double v) : ctx_(ctx), value_(JS_NewFloat64(ctx, v)) {} explicit ScriptValue(JSContext* ctx) : ctx_(ctx){}; ScriptValue() = default; diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index aaa8be7037..7ef479047c 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -11,7 +11,7 @@ namespace kraken { -//void ScriptAnimationController::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { +// void ScriptAnimationController::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { // auto* controller = static_cast<ScriptAnimationController*>(JS_GetOpaque(val, ScriptAnimationController::classId)); // controller->frame_request_callback_collection_.trace(rt, JS_UNDEFINED, mark_func); //} @@ -36,7 +36,7 @@ static void handleRAFTransientCallback(void* ptr, int32_t contextId, double high } uint32_t ScriptAnimationController::RegisterFrameCallback(const std::shared_ptr<FrameCallback>& callback) { -// auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(m_ctx)); + // auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(m_ctx)); uint32_t requestId = getDartMethod()->requestAnimationFrame(frameCallback, context->getContextId(), handleRAFTransientCallback); diff --git a/bridge/core/dom/scripted_animation_controller.h b/bridge/core/dom/scripted_animation_controller.h index 5a944a7120..f0866c01e2 100644 --- a/bridge/core/dom/scripted_animation_controller.h +++ b/bridge/core/dom/scripted_animation_controller.h @@ -10,7 +10,7 @@ namespace kraken { -class ScriptAnimationController { +class ScriptAnimationController { public: ScriptAnimationController() = delete; diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index 4a7db534d2..8a33d87ace 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -100,18 +100,18 @@ double Window::requestAnimationFrame(const std::shared_ptr<QJSFunction>& callbac int32_t request_id = GetExecutingContext()->document(); -// auto* frameCallback = makeGarbageCollected<FrameCallback>(JS_DupValue(ctx, callbackValue)) -// ->initialize<FrameCallback>(ctx, &FrameCallback::classId); + // auto* frameCallback = makeGarbageCollected<FrameCallback>(JS_DupValue(ctx, callbackValue)) + // ->initialize<FrameCallback>(ctx, &FrameCallback::classId); -// int32_t requestId = window->document()->requestAnimationFrame(frameCallback); + // 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); + // if (requestId == -1) { + // return JS_ThrowTypeError(ctx, + // "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) + // executed " "with unexpected error."); + // } + // return JS_NewUint32(ctx, requestId); } // IMPL_FUNCTION(Window, requestAnimationFrame)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { From 4721a27f3279dd0186e569e43ba1f07b31f1ec81 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Tue, 17 May 2022 15:39:16 +0800 Subject: [PATCH 128/498] feat: add window requestAnimiationFrame impl. --- bridge/bindings/qjs/atomic_string.h | 2 + bridge/bindings/qjs/converter_impl.h | 2 +- bridge/core/dom/document.cc | 9 ++- bridge/core/dom/document.h | 3 +- .../dom/frame_request_callback_collection.cc | 35 ++++---- .../dom/frame_request_callback_collection.h | 17 ++-- .../core/dom/scripted_animation_controller.cc | 55 ++++++------- .../core/dom/scripted_animation_controller.h | 8 +- bridge/core/events/message_event.cc | 4 +- bridge/core/frame/window.cc | 37 +++++---- bridge/core/frame/window.d.ts | 1 + bridge/core/frame/window.h | 1 + bridge/core/frame/window_test.cc | 81 ++++++++++--------- .../code_generator/src/idl/generateSource.ts | 2 +- 14 files changed, 136 insertions(+), 121 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index ed7ed1b13b..1b323602e5 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -41,6 +41,8 @@ class AtomicString { // 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_); }; diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 664c784e0c..2e5acc60bb 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -144,7 +144,7 @@ struct Converter<IDLAny> : public ConverterBase<IDLAny> { return ScriptValue(ctx, value); } - static JSValue ToValue(JSContext* ctx, ScriptValue value) { return value.QJSValue(); } + static JSValue ToValue(JSContext* ctx, ScriptValue value) { return JS_DupValue(ctx, value.QJSValue()); } }; template <> diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 60c1e78cd5..446f18eb5d 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -188,12 +188,17 @@ HTMLHeadElement* Document::head() const { return Traversal<HTMLHeadElement>::FirstChild(*de); } -uint32_t Document::RequestAnimationFrame(const std::shared_ptr<FrameCallback>& callback) { - return script_animation_controller_.RegisterFrameCallback(callback); +uint32_t Document::RequestAnimationFrame(const std::shared_ptr<FrameCallback>& callback, ExceptionState& exception_state) { + return script_animation_controller_.RegisterFrameCallback(callback, exception_state); +} + +void Document::CancelAnimationFrame(uint32_t request_id, ExceptionState& exception_state) { + script_animation_controller_.CancelFrameCallback(GetExecutingContext(), request_id, exception_state); } void Document::Trace(GCVisitor* visitor) const { visitor->Trace(document_element_); + script_animation_controller_.Trace(visitor); ContainerNode::Trace(visitor); } diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 894cf9bc01..1b81616401 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -67,7 +67,8 @@ class Document : public ContainerNode, public TreeScope { } int NodeCount() const { return node_count_; } - uint32_t RequestAnimationFrame(const std::shared_ptr<FrameCallback>& callback); + uint32_t RequestAnimationFrame(const std::shared_ptr<FrameCallback>& callback, ExceptionState& exception_state); + void CancelAnimationFrame(uint32_t request_id, ExceptionState& exception_state); void Trace(GCVisitor* visitor) const override; diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc index 05154988b9..d73046c91b 100644 --- a/bridge/core/dom/frame_request_callback_collection.cc +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -4,6 +4,8 @@ */ #include "frame_request_callback_collection.h" + +#include <utility> #include "bindings/qjs/cppgc/gc_visitor.h" namespace kraken { @@ -13,8 +15,8 @@ std::shared_ptr<FrameCallback> FrameCallback::Create(ExecutingContext* context, return std::make_shared<FrameCallback>(context, callback); } -FrameCallback::FrameCallback(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback) - : context_(context), callback_(callback) {} +FrameCallback::FrameCallback(ExecutingContext* context, std::shared_ptr<QJSFunction> callback) + : context_(context), callback_(std::move(callback)) {} void FrameCallback::Fire(double highResTimeStamp) { if (callback_ == nullptr) @@ -32,33 +34,24 @@ void FrameCallback::Fire(double highResTimeStamp) { } } -void FrameRequestCallbackCollection::RegisterFrameCallback(uint32_t callbackId, FrameCallback* frameCallback) { - frameCallbacks_[callbackId] = frameCallback; +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; - FrameCallback* callback = frameCallbacks_[callbackId]; - - // Push this timer to abandoned list to mark this timer is deprecated. - abandonedCallbacks_.emplace_back(callback); - frameCallbacks_.erase(callbackId); } -void FrameRequestCallbackCollection::Trace(GCVisitor* visitor) { - for (auto& callback : frameCallbacks_) { - callback.second->Trace(visitor); - } - - // Recycle all abandoned callbacks. - if (!abandonedCallbacks_.empty()) { - for (auto& callback : abandonedCallbacks_) { - callback->Trace(visitor); - } - // All abandoned timers should be freed at the sweep stage. - abandonedCallbacks_.clear(); +void FrameRequestCallbackCollection::Trace(GCVisitor* visitor) const { + for (auto& entry : frameCallbacks_) { + entry.second->Trace(visitor); } } diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index 5b4b96ecc3..c06b9c5b85 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -14,29 +14,30 @@ namespace kraken { // invoked when a script-based animation needs to be resampled. class FrameCallback { public: - std::shared_ptr<FrameCallback> Create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback); + static std::shared_ptr<FrameCallback> Create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback); - FrameCallback(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_; - int32_t callbackId_{-1}; ExecutingContext* context_{nullptr}; }; class FrameRequestCallbackCollection final { public: - void Trace(GCVisitor* visitor); - void RegisterFrameCallback(uint32_t callbackId, FrameCallback* frameCallback); - void CancelFrameCallback(uint32_t callbackId); + 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, FrameCallback*> frameCallbacks_; - std::vector<FrameCallback*> abandonedCallbacks_; + std::unordered_map<uint32_t, std::shared_ptr<FrameCallback>> frameCallbacks_; }; } // namespace kraken diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index 7ef479047c..c02f15434b 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -5,55 +5,56 @@ #include "scripted_animation_controller.h" #include "frame_request_callback_collection.h" -#if UNIT_TEST -#include "kraken_test_env.h" -#endif - namespace kraken { -// void ScriptAnimationController::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { -// auto* controller = static_cast<ScriptAnimationController*>(JS_GetOpaque(val, ScriptAnimationController::classId)); -// controller->frame_request_callback_collection_.trace(rt, JS_UNDEFINED, mark_func); -//} - static void handleRAFTransientCallback(void* ptr, int32_t contextId, double highResTimeStamp, const char* errmsg) { - auto* frameCallback = static_cast<FrameCallback*>(ptr); - auto* context = static_cast<ExecutingContext*>(JS_GetContextOpaque(frameCallback->ctx())); + auto* frame_callback = static_cast<FrameCallback*>(ptr); + auto* context = frame_callback->context(); - if (!context->isValid()) + if (!context->IsValid()) return; if (errmsg != nullptr) { - JSValue exception = JS_ThrowTypeError(frameCallback->ctx(), "%s", errmsg); - context->handleException(&exception); + JSValue exception = JS_ThrowTypeError(frame_callback->context()->ctx(), "%s", errmsg); + context->HandleException(&exception); return; } // Trigger callbacks. - frameCallback->fire(highResTimeStamp); - - context->drainPendingPromiseJobs(); + frame_callback->Fire(highResTimeStamp); } -uint32_t ScriptAnimationController::RegisterFrameCallback(const std::shared_ptr<FrameCallback>& callback) { - // auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(m_ctx)); +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 = - getDartMethod()->requestAnimationFrame(frameCallback, context->getContextId(), handleRAFTransientCallback); + uint32_t requestId = context->dartMethodPtr()->requestAnimationFrame(frame_callback.get(), context->contextId(), + handleRAFTransientCallback); // Register frame callback to collection. - frame_request_callback_collection_.registerFrameCallback(requestId, frameCallback); + frame_request_callback_collection_.RegisterFrameCallback(requestId, frame_callback); return requestId; } -void ScriptAnimationController::CancelFrameCallback(uint32_t callbackId) { - auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(m_ctx)); - getDartMethod()->cancelAnimationFrame(context->getContextId(), callbackId); +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; + } - frame_request_callback_collection_.cancelFrameCallback(callbackId); + context->dartMethodPtr()->cancelAnimationFrame(context->contextId(), callbackId); + frame_request_callback_collection_.CancelFrameCallback(callbackId); } -void ScriptAnimationController::Trace(GCVisitor* visitor) const {} +void ScriptAnimationController::Trace(GCVisitor* visitor) const { + frame_request_callback_collection_.Trace(visitor); +} } // namespace kraken diff --git a/bridge/core/dom/scripted_animation_controller.h b/bridge/core/dom/scripted_animation_controller.h index f0866c01e2..75832c1767 100644 --- a/bridge/core/dom/scripted_animation_controller.h +++ b/bridge/core/dom/scripted_animation_controller.h @@ -12,11 +12,11 @@ namespace kraken { class ScriptAnimationController { public: - ScriptAnimationController() = delete; - // Animation frame callbacks are used for requestAnimationFrame(). - uint32_t RegisterFrameCallback(const std::shared_ptr<FrameCallback>& callback); - void CancelFrameCallback(uint32_t callbackId); + 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_; diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index 1df8e1857d..6f2323706a 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -19,12 +19,12 @@ MessageEvent* MessageEvent::Create(ExecutingContext* context, return MakeGarbageCollected<MessageEvent>(context, type, init); } -MessageEvent::MessageEvent(ExecutingContext* context, const AtomicString& type) : Event(context) {} +MessageEvent::MessageEvent(ExecutingContext* context, const AtomicString& type) : Event(context, type) {} MessageEvent::MessageEvent(ExecutingContext* context, const AtomicString& type, const std::shared_ptr<MessageEventInit>& init) - : Event(context), + : Event(context, type), data_(init->data()), origin_(init->origin()), lastEventId_(init->lastEventId()), diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index 8a33d87ace..60287cb0cd 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -5,6 +5,7 @@ #include "window.h" #include "binding_call_methods.h" #include "bindings/qjs/cppgc/garbage_collected.h" +#include "core/dom/document.h" #include "core/events/message_event.h" #include "event_type_names.h" #include "foundation/native_value_converter.h" @@ -96,22 +97,30 @@ void Window::postMessage(const ScriptValue& message, } double Window::requestAnimationFrame(const std::shared_ptr<QJSFunction>& callback, ExceptionState& exceptionState) { - GetExecutingContext()->dartMethodPtr()->flushUICommand(); - - int32_t request_id = GetExecutingContext()->document(); - - // auto* frameCallback = makeGarbageCollected<FrameCallback>(JS_DupValue(ctx, callbackValue)) - // ->initialize<FrameCallback>(ctx, &FrameCallback::classId); - - // int32_t requestId = window->document()->requestAnimationFrame(frameCallback); + if (GetExecutingContext()->dartMethodPtr()->flushUICommand == nullptr) { + exceptionState.ThrowException( + ctx(), ErrorType::InternalError, + "Failed to execute 'flushUICommand': dart method (flushUICommand) executed " + "with unexpected error."); + return 0; + } + GetExecutingContext()->dartMethodPtr()->flushUICommand(); + auto frame_callback = FrameCallback::Create(GetExecutingContext(), callback); + uint32_t request_id = GetExecutingContext()->document()->RequestAnimationFrame(frame_callback, exceptionState); // `-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); + 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); } // IMPL_FUNCTION(Window, requestAnimationFrame)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts index 71fa0fffd6..51ee2379ec 100644 --- a/bridge/core/frame/window.d.ts +++ b/bridge/core/frame/window.d.ts @@ -13,6 +13,7 @@ interface Window extends EventTarget { postMessage(message: any): void; requestAnimationFrame(callback: Function): double; + cancelAnimationFrame(request_id: double): void; readonly window: Window; new(): void; diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index a7b84f7ed2..9476b2c690 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -39,6 +39,7 @@ class Window : public EventTargetWithInlineData { 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); // DEFINE_FUNCTION(open); // DEFINE_FUNCTION(scrollTo); diff --git a/bridge/core/frame/window_test.cc b/bridge/core/frame/window_test.cc index 92075a0f64..932e5de52e 100644 --- a/bridge/core/frame/window_test.cc +++ b/bridge/core/frame/window_test.cc @@ -52,6 +52,7 @@ TEST(Window, requestAnimationFrame) { kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "456"); + logCalled = true; }; std::string code = R"( @@ -65,46 +66,46 @@ requestAnimationFrame(() => { EXPECT_EQ(logCalled, true); } -// -// TEST(Window, cancelAnimationFrame) { -// auto bridge = TEST_init(); -// -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { abort(); }; -// -// std::string code = R"( -// let id = requestAnimationFrame(() => { -// console.log('456'); -//}); -// cancelAnimationFrame(id); -//)"; -// -// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); -// TEST_runLoop(bridge->getContext()); -//} -// -// TEST(Window, postMessage) { -// { -// auto bridge = TEST_init(); -// static bool logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "{\"data\":1234} "); -// }; -// -// std::string code = std::string(R"( -// window.onmessage = (message) => { -// console.log(JSON.stringify(message.data), message.origin); -//}; -// window.postMessage({ -// data: 1234 -//}, '*'); -//)"); -// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); -// EXPECT_EQ(logCalled, true); -// } -// // Use block scope to release previous page, and allocate new page. -// { TEST_init(); } -//} + + TEST(Window, cancelAnimationFrame) { + auto bridge = TEST_init(); + + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { abort(); }; + + std::string code = R"( + let id = requestAnimationFrame(() => { + console.log('456'); +}); + cancelAnimationFrame(id); +)"; + + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + TEST_runLoop(bridge->GetExecutingContext()); +} + + TEST(Window, postMessage) { + { + auto bridge = TEST_init(); + static bool logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "{\"data\":1234} *"); + }; + + std::string code = std::string(R"( + window.addEventListener('message', (message) => { + console.log(JSON.stringify(message.data), message.origin); +}); + window.postMessage({ + data: 1234 +}, '*'); +)"); + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + EXPECT_EQ(logCalled, true); + } + // Use block scope to release previous page, and allocate new page. + { TEST_init(); } +} // // TEST(Window, location) { // auto bridge = TEST_init(); diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 6a17031f4c..22edc9e2b2 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -219,7 +219,7 @@ function generateFunctionCallBody(blob: IDLBlob, declaration: FunctionDeclaratio returnValueAssignment = 'return_value ='; } if (options.isInstanceMethod) { - call = `auto* self = toScriptWrappable<${getClassName(blob)}>(this_val); + 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(',')});`; From 46dccea10dc05535153c1481f9c754cfd87a38db Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Tue, 17 May 2022 17:03:39 +0800 Subject: [PATCH 129/498] feat: add window location. --- bridge/CMakeLists.txt | 4 + bridge/bindings/qjs/binding_initializer.cc | 2 + bridge/core/dom/binding_call_methods.json5 | 6 +- bridge/core/dom/document.cc | 8 + bridge/core/dom/document.d.ts | 2 + bridge/core/dom/document.h | 2 + bridge/core/frame/legacy/location.cc | 21 ++ bridge/core/frame/legacy/location.d.ts | 1 + bridge/core/frame/legacy/location.h | 21 ++ bridge/core/frame/location.cc | 45 ----- bridge/core/frame/location.h | 45 ----- bridge/core/frame/window.cc | 182 ------------------ bridge/core/frame/window.d.ts | 10 + bridge/core/frame/window.h | 20 +- bridge/core/frame/window_test.cc | 30 +-- bridge/polyfill/src/bridge.ts | 3 + bridge/polyfill/src/index.ts | 4 +- bridge/polyfill/src/location.ts | 6 +- .../static/idl_templates/interface.cc.tpl | 4 +- 19 files changed, 102 insertions(+), 314 deletions(-) create mode 100644 bridge/core/frame/legacy/location.cc create mode 100644 bridge/core/frame/legacy/location.d.ts create mode 100644 bridge/core/frame/legacy/location.h delete mode 100644 bridge/core/frame/location.cc delete mode 100644 bridge/core/frame/location.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index f0debafbf3..c653f632b1 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -278,6 +278,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/frame/module_callback_coordinator.cc core/frame/window.h core/frame/window.cc + core/frame/legacy/location.cc + core/frame/legacy/location.h core/frame/module_callback_coordinator.h core/css/legacy/css_style_declaration.cc core/css/legacy/css_style_declaration.h @@ -396,6 +398,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_window_or_worker_global_scope.h out/qjs_window.cc out/qjs_window.h + out/qjs_location.cc + out/qjs_location.h out/qjs_blob.cc out/qjs_blob.h out/qjs_event.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index e2e8095cad..7c4eb430df 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -31,6 +31,7 @@ #include "qjs_text.h" #include "qjs_window.h" #include "qjs_window_or_worker_global_scope.h" +#include "qjs_location.h" namespace kraken { @@ -38,6 +39,7 @@ 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); diff --git a/bridge/core/dom/binding_call_methods.json5 b/bridge/core/dom/binding_call_methods.json5 index ef8da76cd5..992cd7c4ed 100644 --- a/bridge/core/dom/binding_call_methods.json5 +++ b/bridge/core/dom/binding_call_methods.json5 @@ -20,6 +20,10 @@ "getBoundingClientRect", ["getPropertyMagic", "%g"], ["setPropertyMagic", "%s"], - "open" + "open", + "devicePixelRatio", + "colorScheme", + "scrollX", + "scrollY" ] } diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 446f18eb5d..1fb057da7b 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -138,6 +138,14 @@ void Document::InitDocumentElement() { AppendChild(document_element_, exception_state); } +// Legacy impl: Get the JS polyfill impl from global object. +ScriptValue Document::location() const { + JSValue location = JS_GetPropertyStr(ctx(), GetExecutingContext()->Global(), "location"); + ScriptValue result = ScriptValue(ctx(), location); + JS_FreeValue(ctx(), location); + return result; +} + HTMLBodyElement* Document::body() const { if (!IsA<HTMLHtmlElement>(documentElement())) return nullptr; diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts index a93c8748c9..975d358613 100644 --- a/bridge/core/dom/document.d.ts +++ b/bridge/core/dom/document.d.ts @@ -11,6 +11,8 @@ interface Document extends Node { body: HTMLBodyElement | null; readonly head: HTMLHeadElement | null; readonly documentElement: HTMLHtmlElement; + // Legacy impl: get the polyfill implements from global object. + readonly location: any; createElement(tagName: string): Element; createTextNode(value: string): Text; diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 1b81616401..38d9b92637 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -60,6 +60,8 @@ class Document : public ContainerNode, public TreeScope { [[nodiscard]] HTMLHeadElement* head() const; void setHead(HTMLHeadElement* head, ExceptionState& exception_state); + ScriptValue location() const; + void IncrementNodeCount() { node_count_++; } void DecrementNodeCount() { assert(node_count_ > 0); diff --git a/bridge/core/frame/legacy/location.cc b/bridge/core/frame/legacy/location.cc new file mode 100644 index 0000000000..419aa215ac --- /dev/null +++ b/bridge/core/frame/legacy/location.cc @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "location.h" +#include "core/executing_context.h" + +namespace kraken { + +void Location::__kraken_location_reload__(ExecutingContext* context, ExceptionState& exception_state) { + if (context->dartMethodPtr()->reloadApp == nullptr) { + exception_state.ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute 'reload': dart method (reloadApp) is not registered."); + return; + } + + context->dartMethodPtr()->flushUICommand(); + context->dartMethodPtr()->reloadApp(context->contextId()); +} + +} // namespace kraken diff --git a/bridge/core/frame/legacy/location.d.ts b/bridge/core/frame/legacy/location.d.ts new file mode 100644 index 0000000000..8567de2aea --- /dev/null +++ b/bridge/core/frame/legacy/location.d.ts @@ -0,0 +1 @@ +declare const __kraken_location_reload__: () => void; diff --git a/bridge/core/frame/legacy/location.h b/bridge/core/frame/legacy/location.h new file mode 100644 index 0000000000..7d8dee3f7c --- /dev/null +++ b/bridge/core/frame/legacy/location.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#ifndef KRAKENBRIDGE_LOCATION_H +#define KRAKENBRIDGE_LOCATION_H + +#include "bindings/qjs/script_wrappable.h" +#include "bindings/qjs/exception_state.h" + +namespace kraken { + +class Location { + public: + static void __kraken_location_reload__(ExecutingContext* context, ExceptionState& exception_state); +}; + +} // namespace kraken + +#endif // KRAKENBRIDGE_LOCATION_H diff --git a/bridge/core/frame/location.cc b/bridge/core/frame/location.cc deleted file mode 100644 index b43cee23a8..0000000000 --- a/bridge/core/frame/location.cc +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "location.h" -#include <utility> -#include "dart_methods.h" - -namespace kraken { - -void bindLocation(std::unique_ptr<ExecutionContext>& context) { - auto* contextData = context->contextData(); - JSValue classObject = contextData->constructorForType(&locationTypeInfo); - JSValue prototypeObject = contextData->prototypeForType(&locationTypeInfo); - - // Install methods - INSTALL_FUNCTION(Location, prototypeObject, reload, 0); - - context->defineGlobalProperty("Location", classObject); -} - -JSClassID Location::classId{0}; - -Location* Location::create(JSContext* ctx) { - return makeGarbageCollected<Location>()->initialize<Location>(ctx, &classId); -} - -IMPL_FUNCTION(Location, reload)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* location = static_cast<Location*>(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->context()->getContextId()); - - return JS_NULL; -} - -void Location::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const {} - -void Location::dispose() const {} - -} // namespace kraken diff --git a/bridge/core/frame/location.h b/bridge/core/frame/location.h deleted file mode 100644 index a02e84307b..0000000000 --- a/bridge/core/frame/location.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#ifndef KRAKENBRIDGE_LOCATION_H -#define KRAKENBRIDGE_LOCATION_H - -#include "bindings/qjs/cppgc/garbage_collected.h" -#include "bindings/qjs/executing_context.h" -#include "bindings/qjs/wrapper_type_info.h" - -namespace kraken { - -void bindLocation(std::unique_ptr<ExecutionContext>& context); - -class Location : public GarbageCollected<Location> { - public: - static JSClassID classId; - static Location* create(JSContext* ctx); - - DEFINE_FUNCTION(reload); - - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - void dispose() const override; -}; - -auto locationCreator = - [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) - -> JSValue { - auto* type = static_cast<const WrapperTypeInfo*>(JS_GetOpaque(func_obj, JSValueGetClassId(func_obj))); - auto* location = Location::create(ctx); - auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(ctx)); - JSValue prototype = context->contextData()->prototypeForType(type); - - // Let eventTarget instance inherit EventTarget prototype methods. - JS_SetPrototype(ctx, location->toQuickJS(), prototype); - return location->toQuickJS(); -}; - -const WrapperTypeInfo locationTypeInfo = {"Location", nullptr, locationCreator}; - -} // namespace kraken - -#endif // KRAKENBRIDGE_LOCATION_H diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index 60287cb0cd..c45f99452c 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -123,186 +123,4 @@ void Window::cancelAnimationFrame(double request_id, ExceptionState& exception_s GetExecutingContext()->document()->CancelAnimationFrame(static_cast<uint32_t>(request_id), exception_state); } -// IMPL_FUNCTION(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<ExecutionContext*>(JS_GetContextOpaque(ctx)); -// auto window = static_cast<Window*>(JS_GetOpaque(context->global(), JSValueGetClassId(this_val))); -// -// JSValue callbackValue = argv[0]; -// -// if (!JS_IsObject(callbackValue)) { -// return JS_ThrowTypeError(ctx, -// "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); -// } -// -// if (!JS_IsFunction(ctx, callbackValue)) { -// return JS_ThrowTypeError(ctx, -// "Failed to execute 'requestAnimationFrame': parameter 1 (callback) must be a function."); -// } -// -// // Flutter backend implements check -//#if FLUTTER_BACKEND -// if (getDartMethod()->flushUICommand == nullptr) { -// return JS_ThrowTypeError( -// ctx, "Failed to execute '__kraken_flush_ui_command__': dart method (flushUICommand) is not registered."); -// } -// // Flush all pending ui messages. -// getDartMethod()->flushUICommand(); -// -// if (getDartMethod()->requestAnimationFrame == nullptr) { -// return JS_ThrowTypeError( -// ctx, "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) is not registered."); -// } -//#endif -// -// auto* frameCallback = makeGarbageCollected<FrameCallback>(JS_DupValue(ctx, callbackValue)) -// ->initialize<FrameCallback>(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); -//} -// -// IMPL_FUNCTION(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<ExecutionContext*>(JS_GetContextOpaque(ctx)); -// auto window = static_cast<Window*>(JS_GetOpaque(context->global(), JSValueGetClassId(this_val))); -// -// 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; -//} -// -// Window* Window::create(JSContext* ctx) { -// auto* context = static_cast<ExecutionContext*>(JS_GetContextOpaque(ctx)); -// JSValue prototype = context->contextData()->prototypeForType(&windowTypeInfo); -// -// auto* window = makeGarbageCollected<Window>()->initialize<Window>(ctx, &classId, nullptr); -// -// // Let window inherit Window prototype methods. -// JS_SetPrototype(ctx, window->toQuickJS(), prototype); -// -// return window; -//} -// -// Document* Window::document() { -// return context()->document(); -//} -// -// Window::Window() { -// if (getDartMethod()->initWindow != nullptr) { -// getDartMethod()->initWindow(context()->getContextId(), nativeEventTarget); -// } -// -// m_location = makeGarbageCollected<Location>()->initialize<Location>(m_ctx, &Location::classId); -//} -// -// void Window::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { -// EventTarget::trace(rt, val, mark_func); -// JS_MarkValue(rt, onerror, mark_func); -//} -// -// IMPL_PROPERTY_GETTER(Window, devicePixelRatio)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// auto* window = static_cast<WindowInstance*>(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<WindowInstance*>(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<WindowInstance*>(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<WindowInstance*>(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<Window*>(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); -// if (window == nullptr) -// return JS_UNDEFINED; -// return JS_DupValue(ctx, window->m_location->toQuickJS()); -//} -// -// IMPL_PROPERTY_GETTER(Window, location)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// auto* window = static_cast<Window*>(JS_GetOpaque(this_val, 1)); -// return JS_GetPropertyStr(ctx, window->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<WindowInstance*>(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<WindowInstance*>(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<Window*>(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<Window*>(JS_GetOpaque(this_val, 1)); -// JSValue eventString = JS_NewString(ctx, "onerror"); -// JSString* p = JS_VALUE_GET_STRING(eventString); -// JSValue onerrorHandler = argv[0]; -// window->setAttributesEventHandler(p, onerrorHandler); -// -// if (!JS_IsNull(window->onerror)) { -// JS_FreeValue(ctx, window->onerror); -// } -// -// window->onerror = JS_DupValue(ctx, onerrorHandler); -// JS_FreeValue(ctx, eventString); -// return JS_NULL; -//} -// -// IMPL_PROPERTY_GETTER(Window, self)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// return JS_GetGlobalObject(ctx); -//} - } // namespace kraken diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts index 51ee2379ec..0512b96223 100644 --- a/bridge/core/frame/window.d.ts +++ b/bridge/core/frame/window.d.ts @@ -16,5 +16,15 @@ interface Window extends EventTarget { cancelAnimationFrame(request_id: double): void; readonly window: Window; + readonly parent: Window; + readonly self: Window; + + 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 index 9476b2c690..f927415928 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -24,6 +24,8 @@ class Window : public EventTargetWithInlineData { Window* open(const AtomicString& url, ExceptionState& exception_state); [[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); @@ -40,24 +42,6 @@ class Window : public EventTargetWithInlineData { double requestAnimationFrame(const std::shared_ptr<QJSFunction>& callback, ExceptionState& exceptionState); void cancelAnimationFrame(double request_id, ExceptionState& exception_state); - - // DEFINE_FUNCTION(open); - // DEFINE_FUNCTION(scrollTo); - // DEFINE_FUNCTION(scrollBy); - // DEFINE_FUNCTION(postMessage); - // DEFINE_FUNCTION(requestAnimationFrame); - // DEFINE_FUNCTION(cancelAnimationFrame); - // - // 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(self); - // DEFINE_PROTOTYPE_PROPERTY(onerror); }; } // namespace kraken diff --git a/bridge/core/frame/window_test.cc b/bridge/core/frame/window_test.cc index 932e5de52e..78c98d4562 100644 --- a/bridge/core/frame/window_test.cc +++ b/bridge/core/frame/window_test.cc @@ -106,18 +106,18 @@ requestAnimationFrame(() => { // Use block scope to release previous page, and allocate new page. { TEST_init(); } } -// -// TEST(Window, location) { -// auto bridge = TEST_init(); -// static bool logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "true true"); -// }; -// -// std::string code = std::string(R"( -// console.log(window.location !== undefined, window.location === document.location); -// )"); -// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); -// EXPECT_EQ(logCalled, true); -//} + + TEST(Window, location) { + auto bridge = TEST_init(); + static bool logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + EXPECT_STREQ(message.c_str(), "true true true"); + }; + + std::string code = std::string(R"( + console.log(window.location !== undefined, window.location === location, window.location === document.location); + )"); + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + EXPECT_EQ(logCalled, true); +} diff --git a/bridge/polyfill/src/bridge.ts b/bridge/polyfill/src/bridge.ts index d9079f0743..7d946d7928 100644 --- a/bridge/polyfill/src/bridge.ts +++ b/bridge/polyfill/src/bridge.ts @@ -22,5 +22,8 @@ export const krakenInvokeModule = __kraken_invoke_module__; declare const __kraken_add_module_listener__: (fn: (moduleName: string, event: Event, extra: string) => void) => void; export const addKrakenModuleListener = __kraken_add_module_listener__; +declare const __kraken_location_reload__: () => void; +export const krakenLocationReload = __kraken_location_reload__; + declare const __kraken_print__: (log: string, level?: string) => void; export const krakenPrint = __kraken_print__; diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index 536f259a97..7050fb5692 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -3,7 +3,7 @@ import './dom'; import { console } from './console'; // import { fetch, Request, Response, Headers } from './fetch'; // import { matchMedia } from './match-media'; -// import { location } from './location'; +import { location } from './location'; // import { history } from './history'; // import { navigator } from './navigator'; // import { XMLHttpRequest } from './xhr'; @@ -21,7 +21,7 @@ defineGlobalProperty('console', console); // defineGlobalProperty('Headers', Headers); // defineGlobalProperty('fetch', fetch); // defineGlobalProperty('matchMedia', matchMedia); -// defineGlobalProperty('location', location); +defineGlobalProperty('location', location); // defineGlobalProperty('history', history); // defineGlobalProperty('navigator', navigator); // defineGlobalProperty('XMLHttpRequest', XMLHttpRequest); diff --git a/bridge/polyfill/src/location.ts b/bridge/polyfill/src/location.ts index 65319e9627..ff7b8b856a 100644 --- a/bridge/polyfill/src/location.ts +++ b/bridge/polyfill/src/location.ts @@ -1,15 +1,13 @@ import { URL } from './url'; +import { krakenLocationReload } from './bridge'; import { kraken } from './kraken'; -// @ts-ignore -const krakenLocation = window.__location__; // Lazy parse url. let _url: URL; export function getUrl() : URL { return _url ? _url : (_url = new URL(location.href)); } -const bindReload = krakenLocation.reload.bind(krakenLocation); export const location = { get href() { return kraken.invokeModule('Location', 'getHref'); @@ -48,7 +46,7 @@ export const location = { }; }, get reload() { - return bindReload; + return krakenLocationReload.bind(this); }, get replace() { return (replaceURL: string) => { diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index 9c3d5d1cf4..6d7e412f6c 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -96,11 +96,11 @@ static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst <% if (prop.typeMode && prop.typeMode.dartImpl) { %> ExceptionState exception_state; - typename NativeTypeDouble::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(<%= blob.filename %>->GetBindingProperty(binding_call_methods::k<%= prop.name %>, exception_state)); + typename <%= generateNativeValueTypeConverter(prop.type) %>::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(<%= blob.filename %>->GetBindingProperty(binding_call_methods::k<%= prop.name %>, exception_state)); if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } - return Converter<IDLDouble>::ToValue(ctx, v); + return Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, v); <% } else { %> return Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, <%= blob.filename %>-><%= prop.name %>()); <% } %> From c0bca99922a43012e94c708ec133e7a9eab456e0 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Tue, 17 May 2022 20:52:11 +0800 Subject: [PATCH 130/498] feat: optimize ui command feat: remove init window and init document dart methods. --- bridge/CMakeLists.txt | 2 +- bridge/bindings/qjs/binding_initializer.cc | 1 - .../core/css/legacy/css_style_declaration.cc | 4 +- bridge/core/dart_methods.h | 4 -- bridge/core/dom/binding_call_methods.json5 | 4 +- bridge/core/dom/document.cc | 5 +- bridge/core/dom/element.cc | 6 +- bridge/core/dom/text.h | 2 +- bridge/core/executing_context.cc | 3 + bridge/core/frame/window.cc | 5 +- bridge/core/page.cc | 2 - bridge/foundation/ui_command_buffer.cc | 11 ++-- bridge/foundation/ui_command_buffer.h | 12 ++-- bridge/include/kraken_bridge.h | 2 +- bridge/kraken_bridge.cc | 10 +-- bridge/test/kraken_test_env.cc | 6 -- kraken/example/lib/main.dart | 2 +- kraken/lib/src/bridge/bridge.dart | 1 - kraken/lib/src/bridge/from_native.dart | 20 ------ kraken/lib/src/bridge/to_native.dart | 21 +++---- kraken/lib/src/launcher/controller.dart | 62 ++++++++++--------- scripts/build_darwin_dylib.js | 2 +- scripts/tasks.js | 14 ++--- 23 files changed, 87 insertions(+), 114 deletions(-) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index c653f632b1..8ec5334106 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.10.0) -set(CMAKE_OSX_DEPLOYMENT_TARGET 10.11) project(KrakenBridge) +set(CMAKE_OSX_DEPLOYMENT_TARGET 10.11) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 7c4eb430df..113bd47917 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -44,7 +44,6 @@ void InstallBindings(ExecutingContext* context) { QJSConsole::Install(context); QJSEventTarget::Install(context); QJSWindow::Install(context); - context->InstallGlobal(); QJSEvent::Install(context); QJSErrorEvent::Install(context); QJSMessageEvent::Install(context); diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index ade1d1622e..d4e3d27b07 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -127,7 +127,7 @@ bool CSSStyleDeclaration::InternalSetProperty(std::string& name, const AtomicStr std::unique_ptr<NativeString> args_01 = stringToNativeString(name); std::unique_ptr<NativeString> args_02 = value.ToNativeString(); GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::kSetStyle, - args_01.release(), args_02.release(), nullptr); + std::move(args_01), std::move(args_02), nullptr); return true; } @@ -145,7 +145,7 @@ AtomicString CSSStyleDeclaration::InternalRemoveProperty(std::string& name) { std::unique_ptr<NativeString> args_01 = stringToNativeString(name); std::unique_ptr<NativeString> args_02 = jsValueToNativeString(ctx(), JS_NULL); GetExecutingContext()->uiCommandBuffer()->addCommand(owner_element_target_id_, UICommand::kSetStyle, - args_01.release(), args_02.release(), nullptr); + std::move(args_01), std::move(args_02), nullptr); return return_value; } diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index 444fec4ccf..eefba783e3 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -43,8 +43,6 @@ typedef void (*ToBlob)(void* callbackContext, 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); using MatchImageSnapshotCallback = void (*)(void* callbackContext, int32_t contextId, int8_t, const char* errmsg); using MatchImageSnapshot = void (*)(void* callbackContext, @@ -94,8 +92,6 @@ struct DartMethodPointer { #if ENABLE_PROFILE GetPerformanceEntries getPerformanceEntries{nullptr}; #endif - InitWindow initWindow{nullptr}; - InitDocument initDocument{nullptr}; }; } // namespace kraken diff --git a/bridge/core/dom/binding_call_methods.json5 b/bridge/core/dom/binding_call_methods.json5 index 992cd7c4ed..c1ee8efa6e 100644 --- a/bridge/core/dom/binding_call_methods.json5 +++ b/bridge/core/dom/binding_call_methods.json5 @@ -24,6 +24,8 @@ "devicePixelRatio", "colorScheme", "scrollX", - "scrollY" + "scrollY", + "innerWidth", + "innerHeight", ] } diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 1fb057da7b..394c6413d4 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -23,11 +23,8 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ Document::Document(ExecutingContext* context) : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) { + GetExecutingContext()->uiCommandBuffer()->addCommand(context->contextId(), UICommand::kCreateDocument, (void*)bindingObject()); document_element_ = MakeGarbageCollected<HTMLHtmlElement>(*this); - -#if FLUTTER_BACKEND - GetExecutingContext()->dartMethodPtr()->initDocument(context->contextId(), (void*)bindingObject()); -#endif } Element* Document::createElement(const AtomicString& name, ExceptionState& exception_state) { diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 20447b88b9..d4bcd01e8e 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -19,7 +19,7 @@ namespace kraken { Element::Element(const AtomicString& tag_name, Document* document, Node::ConstructionType construction_type) : ContainerNode(document, construction_type), tag_name_(tag_name) { GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateElement, - tag_name.ToNativeString().release(), (void*)bindingObject()); + std::move(tag_name.ToNativeString()), (void*)bindingObject()); } ElementAttributes& Element::EnsureElementAttributes() { @@ -59,8 +59,8 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value, std::unique_ptr<NativeString> args_01 = name.ToNativeString(); std::unique_ptr<NativeString> args_02 = value.ToNativeString(); - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kSetAttribute, args_01.release(), - args_02.release(), nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kSetAttribute, std::move(args_01), + std::move(args_02), nullptr); } void Element::removeAttribute(const AtomicString& name, ExceptionState& exception_state) { diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h index e9398cd7b5..3fd3a4dca1 100644 --- a/bridge/core/dom/text.h +++ b/bridge/core/dom/text.h @@ -21,7 +21,7 @@ class Text : public CharacterData { Text(TreeScope& tree_scope, const AtomicString& data, ConstructionType type) : CharacterData(tree_scope, data, type) { GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateTextNode, - data.ToNativeString().release(), (void*)bindingObject()); + std::move(data.ToNativeString()), (void*)bindingObject()); } NodeType nodeType() const override; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index c014ef84c9..f1b9f97ee5 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -55,6 +55,9 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& // Install document. InstallDocument(); + // Binding global object and window. + InstallGlobal(); + #if ENABLE_PROFILE nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index c45f99452c..6405184622 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -12,7 +12,10 @@ namespace kraken { -Window::Window(ExecutingContext* context) : EventTargetWithInlineData(context) {} +Window::Window(ExecutingContext* context) : EventTargetWithInlineData(context) { + KRAKEN_LOG(VERBOSE) << "Add Create Window Command"; + context->uiCommandBuffer()->addCommand(context->contextId(), UICommand::kCreateWindow, (void*)bindingObject()); +} Window* Window::open(ExceptionState& exception_state) { return this; diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 6c6ba965bd..fc999b2297 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -134,8 +134,6 @@ void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { dartMethodPointer->getScreen = reinterpret_cast<GetScreen>(methodBytes[i++]); dartMethodPointer->toBlob = reinterpret_cast<ToBlob>(methodBytes[i++]); dartMethodPointer->flushUICommand = reinterpret_cast<FlushUICommand>(methodBytes[i++]); - dartMethodPointer->initWindow = reinterpret_cast<InitWindow>(methodBytes[i++]); - dartMethodPointer->initDocument = reinterpret_cast<InitDocument>(methodBytes[i++]); #if ENABLE_PROFILE methodPointer->getPerformanceEntries = reinterpret_cast<GetPerformanceEntries>(methodBytes[i++]); diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 659186fd06..bb02a261e7 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -5,6 +5,7 @@ #include "ui_command_buffer.h" #include "core/dart_methods.h" #include "core/executing_context.h" +#include "foundation/logging.h" namespace kraken { @@ -17,20 +18,20 @@ void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { queue.emplace_back(item); } -void UICommandBuffer::addCommand(int32_t id, UICommand type, NativeString* args_01, void* nativePtr) { +void UICommandBuffer::addCommand(int32_t id, UICommand type, std::unique_ptr<NativeString>&& args_01, void* nativePtr) { assert(args_01 != nullptr); - UICommandItem item{id, static_cast<int32_t>(type), args_01, nativePtr}; + UICommandItem item{id, static_cast<int32_t>(type), args_01.release(), nativePtr}; queue.emplace_back(item); } void UICommandBuffer::addCommand(int32_t id, UICommand type, - NativeString* args_01, - NativeString* args_02, + 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, args_02, nativePtr}; + UICommandItem item{id, static_cast<int32_t>(type), args_01.release(), args_02.release(), nativePtr}; queue.emplace_back(item); } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index ac468210f5..fb57b5b65e 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -18,6 +18,8 @@ enum class UICommand { kCreateElement, kCreateTextNode, kCreateComment, + kCreateDocument, + kCreateWindow, kDisposeEventTarget, kAddEvent, kRemoveNode, @@ -33,15 +35,15 @@ enum class UICommand { struct UICommandItem { UICommandItem(int32_t id, int32_t type, NativeString* args_01, NativeString* args_02, void* nativePtr) : type(type), - string_01(reinterpret_cast<int64_t>(new NativeString(args_01))), + string_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_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_01(reinterpret_cast<int64_t>((new NativeString(args_01))->string())), args_01_length(args_01->length()), id(id), nativePtr(reinterpret_cast<int64_t>(nativePtr)){}; @@ -61,8 +63,8 @@ class UICommandBuffer { UICommandBuffer() = delete; explicit UICommandBuffer(ExecutingContext* context); void addCommand(int32_t id, UICommand type, void* nativePtr); - void addCommand(int32_t id, UICommand type, NativeString* args_01, NativeString* args_02, void* nativePtr); - void addCommand(int32_t id, UICommand type, NativeString* args_01, void* nativePtr); + void addCommand(int32_t id, UICommand type, std::unique_ptr<NativeString>&& args_01, std::unique_ptr<NativeString>&& args_02, void* nativePtr); + void addCommand(int32_t id, UICommand type, std::unique_ptr<NativeString>&& args_01, void* nativePtr); UICommandItem* data(); int64_t size(); void clear(); diff --git a/bridge/include/kraken_bridge.h b/bridge/include/kraken_bridge.h index 99a41bf9c9..28fc928636 100644 --- a/bridge/include/kraken_bridge.h +++ b/bridge/include/kraken_bridge.h @@ -40,7 +40,7 @@ void* getPage(int32_t contextId); bool checkPage(int32_t contextId); bool checkPage(int32_t contextId, void* context); KRAKEN_EXPORT_C -void evaluateScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine); +void evaluateScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int32_t startLine); KRAKEN_EXPORT_C void evaluateQuickjsByteCode(int32_t contextId, uint8_t* bytes, int32_t byteLen); KRAKEN_EXPORT_C diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 233302a5b5..9ddae2b439 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -123,10 +123,10 @@ bool checkPage(int32_t contextId, void* context) { return page->GetExecutingContext() == context; } -void evaluateScripts(int32_t contextId, kraken::NativeString* code, const char* bundleFilename, int startLine) { +void evaluateScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine) { assert(checkPage(contextId) && "evaluateScripts: contextId is not valid"); auto context = static_cast<kraken::KrakenPage*>(getPage(contextId)); - context->evaluateScript(code, bundleFilename, startLine); + context->evaluateScript(reinterpret_cast<kraken::NativeString*>(code), bundleFilename, startLine); } void evaluateQuickjsByteCode(int32_t contextId, uint8_t* bytes, int32_t byteLen) { @@ -151,13 +151,13 @@ void reloadJsContext(int32_t contextId) { } void invokeModuleEvent(int32_t contextId, - kraken::NativeString* moduleName, + NativeString* moduleName, const char* eventType, void* event, - kraken::NativeString* extra) { + NativeString* extra) { assert(checkPage(contextId) && "invokeEventListener: contextId is not valid"); auto context = static_cast<kraken::KrakenPage*>(getPage(contextId)); - context->invokeModuleEvent(moduleName, eventType, event, extra); + context->invokeModuleEvent(reinterpret_cast<kraken::NativeString*>(moduleName), eventType, event, reinterpret_cast<kraken::NativeString*>(extra)); } void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length) { diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 69e6c04584..a8ef28606e 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -172,10 +172,6 @@ void TEST_toBlob(void* callbackContext, void TEST_flushUICommand() {} -void TEST_initWindow(int32_t contextId, void* nativePtr) {} - -void TEST_initDocument(int32_t contextId, void* nativePtr) {} - void TEST_onJsLog(int32_t contextId, int32_t level, const char*) {} #if ENABLE_PROFILE @@ -288,8 +284,6 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { 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 diff --git a/kraken/example/lib/main.dart b/kraken/example/lib/main.dart index 1e9a1ca5b1..6ff3592cca 100644 --- a/kraken/example/lib/main.dart +++ b/kraken/example/lib/main.dart @@ -94,7 +94,7 @@ class _MyHomePageState extends State<MyBrowser> { devToolsService: ChromeDevToolsService(), viewportWidth: viewportSize.width - queryData.padding.horizontal, viewportHeight: viewportSize.height - appBar.preferredSize.height - queryData.padding.vertical, - bundle: KrakenBundle.fromUrl('assets:assets/bundle.html'), + bundle: KrakenBundle.fromUrl('assets:assets/bundle.js'), ), )); } diff --git a/kraken/lib/src/bridge/bridge.dart b/kraken/lib/src/bridge/bridge.dart index 7143324aa0..eda8bfefac 100644 --- a/kraken/lib/src/bridge/bridge.dart +++ b/kraken/lib/src/bridge/bridge.dart @@ -40,7 +40,6 @@ int initBridge() { // Port flutter's frame callback into bridge. SchedulerBinding.instance!.addPersistentFrameCallback((_) { flushUICommand(); - flushUICommandCallback(); }); }); } diff --git a/kraken/lib/src/bridge/from_native.dart b/kraken/lib/src/bridge/from_native.dart index 450971c839..afee58ce8a 100644 --- a/kraken/lib/src/bridge/from_native.dart +++ b/kraken/lib/src/bridge/from_native.dart @@ -328,24 +328,6 @@ void _flushUICommand() { final Pointer<NativeFunction<NativeFlushUICommand>> _nativeFlushUICommand = Pointer.fromFunction(_flushUICommand); -typedef NativeInitWindow = Void Function(Int32 contextId, Pointer<NativeBindingObject> nativePtr); -typedef DartInitWindow = void Function(int contextId, Pointer<NativeBindingObject> nativePtr); - -void _initWindow(int contextId, Pointer<NativeBindingObject> nativePtr) { - KrakenViewController.windowNativePtrMap[contextId] = nativePtr; -} - -final Pointer<NativeFunction<NativeInitWindow>> _nativeInitWindow = Pointer.fromFunction(_initWindow); - -typedef NativeInitDocument = Void Function(Int32 contextId, Pointer<NativeBindingObject> nativePtr); -typedef DartInitDocument = void Function(int contextId, Pointer<NativeBindingObject> nativePtr); - -void _initDocument(int contextId, Pointer<NativeBindingObject> nativePtr) { - KrakenViewController.documentNativePtrMap[contextId] = nativePtr; -} - -final Pointer<NativeFunction<NativeInitDocument>> _nativeInitDocument = Pointer.fromFunction(_initDocument); - typedef NativePerformanceGetEntries = Pointer<NativePerformanceEntryList> Function(Int32 contextId); typedef DartPerformanceGetEntries = Pointer<NativePerformanceEntryList> Function(int contextId); @@ -399,8 +381,6 @@ final List<int> _dartNativeMethods = [ _nativeGetScreen.address, _nativeToBlob.address, _nativeFlushUICommand.address, - _nativeInitWindow.address, - _nativeInitDocument.address, _nativeGetEntries.address, _nativeOnJsError.address, _nativeOnJsLog.address, diff --git a/kraken/lib/src/bridge/to_native.dart b/kraken/lib/src/bridge/to_native.dart index 0e53f8869c..b2c19f83a7 100644 --- a/kraken/lib/src/bridge/to_native.dart +++ b/kraken/lib/src/bridge/to_native.dart @@ -276,19 +276,6 @@ Future<void> reloadJSContext(int contextId) async { return completer.future; } -typedef NativeFlushUICommandCallback = Void Function(); -typedef DartFlushUICommandCallback = void Function(); - -final DartFlushUICommandCallback _flushUICommandCallback = KrakenDynamicLibrary - .ref - .lookup<NativeFunction<NativeFlushUICommandCallback>>( - 'flushUICommandCallback') - .asFunction(); - -void flushUICommandCallback() { - _flushUICommandCallback(); -} - typedef NativeDispatchUITask = Void Function( Int32 contextId, Pointer<Void> context, Pointer<Void> callback); typedef DartDispatchUITask = void Function( @@ -307,6 +294,8 @@ enum UICommandType { createElement, createTextNode, createComment, + createDocument, + createWindow, disposeEventTarget, addEvent, removeNode, @@ -504,6 +493,12 @@ void flushUICommand() { controller.view.createElement( id, nativePtr.cast<NativeBindingObject>(), command.args[0]); break; + case UICommandType.createDocument: + controller.view.initDocument(nativePtr.cast<NativeBindingObject>()); + break; + case UICommandType.createWindow: + controller.view.initWindow(nativePtr.cast<NativeBindingObject>()); + break; case UICommandType.createTextNode: controller.view.createTextNode( id, nativePtr.cast<NativeBindingObject>(), command.args[0]); diff --git a/kraken/lib/src/launcher/controller.dart b/kraken/lib/src/launcher/controller.dart index b52c170a6a..d8f67d0b98 100644 --- a/kraken/lib/src/launcher/controller.dart +++ b/kraken/lib/src/launcher/controller.dart @@ -66,8 +66,6 @@ abstract class DevToolsService { // An kraken View Controller designed for multiple kraken view control. class KrakenViewController implements WidgetsBindingObserver, ElementsBindingObserver { - static Map<int, Pointer<NativeBindingObject>> documentNativePtrMap = {}; - static Map<int, Pointer<NativeBindingObject>> windowNativePtrMap = {}; KrakenController rootController; @@ -149,8 +147,32 @@ class KrakenViewController defineBuiltInElements(); + + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_ELEMENT_MANAGER_INIT_END); + } + } + + // Index value which identify javascript runtime context. + late int _contextId; + int get contextId => _contextId; + + // Enable print debug message when rendering. + bool enableDebug; + + // Kraken have already disposed. + bool _disposed = false; + + bool get disposed => _disposed; + + late RenderViewportBox viewport; + late Document document; + late Window window; + + void initDocument(Pointer<NativeBindingObject> pointer) { + print('init document'); document = Document( - BindingContext(_contextId, documentNativePtrMap[_contextId]!), + BindingContext(_contextId, pointer), viewport: viewport, controller: rootController, gestureListener: gestureListener, @@ -158,12 +180,6 @@ class KrakenViewController ); _setEventTarget(DOCUMENT_ID, document); - window = Window( - BindingContext(_contextId, windowNativePtrMap[_contextId]!), - document); - _registerPlatformBrightnessChange(); - _setEventTarget(WINDOW_ID, window); - // Listeners need to be registered to window in order to dispatch events on demand. if (gestureListener != null) { GestureListener listener = gestureListener!; @@ -183,6 +199,14 @@ class KrakenViewController document.addEventListener(EVENT_DRAG, (Event event) => listener.onDrag!(event as GestureEvent)); } } + } + + void initWindow(Pointer<NativeBindingObject> pointer) { + window = Window( + BindingContext(_contextId, pointer), + document); + _registerPlatformBrightnessChange(); + _setEventTarget(WINDOW_ID, window); // Blur input element when new input focused. window.addEventListener(EVENT_CLICK, (event) { @@ -194,28 +218,8 @@ class KrakenViewController (event.target as Element).focus(); } }); - - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_ELEMENT_MANAGER_INIT_END); - } } - // Index value which identify javascript runtime context. - late int _contextId; - int get contextId => _contextId; - - // Enable print debug message when rendering. - bool enableDebug; - - // Kraken have already disposed. - bool _disposed = false; - - bool get disposed => _disposed; - - late RenderViewportBox viewport; - late Document document; - late Window window; - void evaluateJavaScripts(String code) { assert(!_disposed, 'Kraken have already disposed'); evaluateScripts(_contextId, code); diff --git a/scripts/build_darwin_dylib.js b/scripts/build_darwin_dylib.js index 293be43ca6..f6ac607ceb 100644 --- a/scripts/build_darwin_dylib.js +++ b/scripts/build_darwin_dylib.js @@ -8,7 +8,7 @@ require('./tasks'); // Run tasks series( - 'macos-dylib-clean', + // 'macos-dylib-clean', 'compile-polyfill', 'build-darwin-kraken-lib', )((err) => { diff --git a/scripts/tasks.js b/scripts/tasks.js index 982afb8f54..f649d51da2 100644 --- a/scripts/tasks.js +++ b/scripts/tasks.js @@ -211,9 +211,9 @@ task('build-darwin-kraken-lib', done => { if (targetJSEngine === 'quickjs') { krakenTargets.push('kraken_unit_test'); } - if (buildMode === 'Debug') { - krakenTargets.push('kraken_test'); - } + // if (buildMode === 'Debug') { + // krakenTargets.push('kraken_test'); + // } execSync(`cmake --build ${paths.bridge}/cmake-build-macos-x86_64 --target ${krakenTargets.join(' ')} -- -j 6`, { stdio: 'inherit' @@ -809,7 +809,7 @@ task('run-benchmark', async (done) => { const err = new Error('The IP address was not found.'); done(err); } - + let androidDevices = getDevicesInfo(); let performanceInfos = execSync( @@ -829,7 +829,7 @@ task('run-benchmark', async (done) => { let performanceDatas = JSON.parse(match[0]); // Remove the top and the bottom five from the final numbers to eliminate fluctuations, and calculate the average. performanceDatas = performanceDatas.sort().slice(5, performanceDatas.length - 5); - + // Save performance list to file and upload to OSS. const listFile = path.join(__dirname, `${viewType}-load-time-list.js`); fs.writeFileSync(listFile, `performanceCallback('${viewType}LoadtimeList', [${performanceDatas.toString()}]);`); @@ -849,8 +849,8 @@ task('run-benchmark', async (done) => { } } } - + execSync('adb uninstall com.example.performance_tests'); - + done(); }); From 6297c4401916d4c92fb09f831ffc52a0f6e89e95 Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Tue, 17 May 2022 12:53:06 +0000 Subject: [PATCH 131/498] Committing clang-format changes --- bridge/bindings/qjs/atomic_string.h | 4 +++- bridge/bindings/qjs/binding_initializer.cc | 2 +- bridge/core/dom/document.cc | 6 ++++-- bridge/core/dom/scripted_animation_controller.cc | 11 ++++++++--- bridge/core/frame/legacy/location.cc | 3 ++- bridge/core/frame/legacy/location.h | 4 ++-- bridge/core/frame/window.cc | 7 +++---- bridge/core/frame/window_test.cc | 6 +++--- bridge/foundation/ui_command_buffer.h | 6 +++++- bridge/kraken_bridge.cc | 3 ++- 10 files changed, 33 insertions(+), 19 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 1b323602e5..4e213b292e 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -41,7 +41,9 @@ class AtomicString { // Return the undefined string value from atom key. JSValue ToQuickJS(JSContext* ctx) const { - if (ctx_ == nullptr) { return JS_NULL; } + if (ctx_ == nullptr) { + return JS_NULL; + } assert(ctx_ != nullptr); return JS_AtomToValue(ctx, atom_); diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 113bd47917..3b37921704 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -24,6 +24,7 @@ #include "qjs_html_html_element.h" #include "qjs_html_template_element.h" #include "qjs_html_unknown_element.h" +#include "qjs_location.h" #include "qjs_message_event.h" #include "qjs_module_manager.h" #include "qjs_node.h" @@ -31,7 +32,6 @@ #include "qjs_text.h" #include "qjs_window.h" #include "qjs_window_or_worker_global_scope.h" -#include "qjs_location.h" namespace kraken { diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 394c6413d4..70a075080f 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -23,7 +23,8 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ Document::Document(ExecutingContext* context) : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) { - GetExecutingContext()->uiCommandBuffer()->addCommand(context->contextId(), UICommand::kCreateDocument, (void*)bindingObject()); + GetExecutingContext()->uiCommandBuffer()->addCommand(context->contextId(), UICommand::kCreateDocument, + (void*)bindingObject()); document_element_ = MakeGarbageCollected<HTMLHtmlElement>(*this); } @@ -193,7 +194,8 @@ HTMLHeadElement* Document::head() const { return Traversal<HTMLHeadElement>::FirstChild(*de); } -uint32_t Document::RequestAnimationFrame(const std::shared_ptr<FrameCallback>& callback, ExceptionState& exception_state) { +uint32_t Document::RequestAnimationFrame(const std::shared_ptr<FrameCallback>& callback, + ExceptionState& exception_state) { return script_animation_controller_.RegisterFrameCallback(callback, exception_state); } diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index c02f15434b..16dcbafcca 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -24,11 +24,14 @@ static void handleRAFTransientCallback(void* ptr, int32_t contextId, double high frame_callback->Fire(highResTimeStamp); } -uint32_t ScriptAnimationController::RegisterFrameCallback(const std::shared_ptr<FrameCallback>& frame_callback, ExceptionState& exception_state) { +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."); + exception_state.ThrowException( + context->ctx(), ErrorType::InternalError, + "Failed to execute 'requestAnimationFrame': dart method (requestAnimationFrame) is not registered."); return -1; } @@ -45,7 +48,9 @@ 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."); + exception_state.ThrowException( + context->ctx(), ErrorType::InternalError, + "Failed to execute 'cancelAnimationFrame': dart method (cancelAnimationFrame) is not registered."); return; } diff --git a/bridge/core/frame/legacy/location.cc b/bridge/core/frame/legacy/location.cc index 419aa215ac..7f5aa9175f 100644 --- a/bridge/core/frame/legacy/location.cc +++ b/bridge/core/frame/legacy/location.cc @@ -10,7 +10,8 @@ namespace kraken { void Location::__kraken_location_reload__(ExecutingContext* context, ExceptionState& exception_state) { if (context->dartMethodPtr()->reloadApp == nullptr) { - exception_state.ThrowException(context->ctx(), ErrorType::InternalError, "Failed to execute 'reload': dart method (reloadApp) is not registered."); + exception_state.ThrowException(context->ctx(), ErrorType::InternalError, + "Failed to execute 'reload': dart method (reloadApp) is not registered."); return; } diff --git a/bridge/core/frame/legacy/location.h b/bridge/core/frame/legacy/location.h index 7d8dee3f7c..86c2be89d5 100644 --- a/bridge/core/frame/legacy/location.h +++ b/bridge/core/frame/legacy/location.h @@ -6,12 +6,12 @@ #ifndef KRAKENBRIDGE_LOCATION_H #define KRAKENBRIDGE_LOCATION_H -#include "bindings/qjs/script_wrappable.h" #include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_wrappable.h" namespace kraken { -class Location { +class Location { public: static void __kraken_location_reload__(ExecutingContext* context, ExceptionState& exception_state); }; diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index 6405184622..e971dc9973 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -101,10 +101,9 @@ void Window::postMessage(const ScriptValue& message, 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."); + exceptionState.ThrowException(ctx(), ErrorType::InternalError, + "Failed to execute 'flushUICommand': dart method (flushUICommand) executed " + "with unexpected error."); return 0; } diff --git a/bridge/core/frame/window_test.cc b/bridge/core/frame/window_test.cc index 78c98d4562..cc3a4c85bc 100644 --- a/bridge/core/frame/window_test.cc +++ b/bridge/core/frame/window_test.cc @@ -67,7 +67,7 @@ requestAnimationFrame(() => { EXPECT_EQ(logCalled, true); } - TEST(Window, cancelAnimationFrame) { +TEST(Window, cancelAnimationFrame) { auto bridge = TEST_init(); kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { abort(); }; @@ -83,7 +83,7 @@ requestAnimationFrame(() => { TEST_runLoop(bridge->GetExecutingContext()); } - TEST(Window, postMessage) { +TEST(Window, postMessage) { { auto bridge = TEST_init(); static bool logCalled = false; @@ -107,7 +107,7 @@ requestAnimationFrame(() => { { TEST_init(); } } - TEST(Window, location) { +TEST(Window, location) { auto bridge = TEST_init(); static bool logCalled = false; kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index fb57b5b65e..28386daf82 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -63,7 +63,11 @@ class UICommandBuffer { UICommandBuffer() = delete; explicit UICommandBuffer(ExecutingContext* context); void addCommand(int32_t id, UICommand type, void* nativePtr); - void addCommand(int32_t id, UICommand type, std::unique_ptr<NativeString>&& args_01, std::unique_ptr<NativeString>&& args_02, void* nativePtr); + void addCommand(int32_t id, + UICommand type, + std::unique_ptr<NativeString>&& args_01, + 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(); diff --git a/bridge/kraken_bridge.cc b/bridge/kraken_bridge.cc index 9ddae2b439..0de3d93808 100644 --- a/bridge/kraken_bridge.cc +++ b/bridge/kraken_bridge.cc @@ -157,7 +157,8 @@ void invokeModuleEvent(int32_t contextId, NativeString* extra) { assert(checkPage(contextId) && "invokeEventListener: contextId is not valid"); auto context = static_cast<kraken::KrakenPage*>(getPage(contextId)); - context->invokeModuleEvent(reinterpret_cast<kraken::NativeString*>(moduleName), eventType, event, reinterpret_cast<kraken::NativeString*>(extra)); + context->invokeModuleEvent(reinterpret_cast<kraken::NativeString*>(moduleName), eventType, event, + reinterpret_cast<kraken::NativeString*>(extra)); } void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length) { From 8679a0e3a34eb41b011a0030433677fb36f17978 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Wed, 18 May 2022 01:13:08 +0800 Subject: [PATCH 132/498] fix: fix document and window id and fix ui command. --- bridge/core/dom/container_node.cc | 11 +++++++++++ bridge/core/dom/document.cc | 3 +-- bridge/core/frame/window.cc | 3 +-- kraken/lib/src/bridge/to_native.dart | 4 ++-- kraken/lib/src/launcher/controller.dart | 12 ++++-------- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index a68ec379d5..1f27894d05 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -353,6 +353,8 @@ void ContainerNode::RemoveBetween(Node* previous_child, Node* next_child, Node& old_child.SetPreviousSibling(nullptr); old_child.SetNextSibling(nullptr); old_child.SetParentOrShadowHostNode(nullptr); + + GetExecutingContext()->uiCommandBuffer()->addCommand(old_child.eventTargetId(), UICommand::kRemoveNode, nullptr); } template <typename Functor> @@ -403,6 +405,15 @@ void ContainerNode::AppendChildCommon(Node& child) { SetFirstChild(&child); } SetLastChild(&child); + + std::string target_id = std::to_string(child.eventTargetId()); + std::string position = "beforeend"; + + std::unique_ptr<NativeString> args_01 = stringToNativeString(target_id); + std::unique_ptr<NativeString> args_02 = stringToNativeString(position); + + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kInsertAdjacentNode, + std::move(args_01), std::move(args_02), nullptr); } void ContainerNode::NotifyNodeInserted(Node& root) { diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 70a075080f..c449774815 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -23,8 +23,7 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ Document::Document(ExecutingContext* context) : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) { - GetExecutingContext()->uiCommandBuffer()->addCommand(context->contextId(), UICommand::kCreateDocument, - (void*)bindingObject()); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateDocument, (void*)bindingObject()); document_element_ = MakeGarbageCollected<HTMLHtmlElement>(*this); } diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index e971dc9973..a431622546 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -13,8 +13,7 @@ namespace kraken { Window::Window(ExecutingContext* context) : EventTargetWithInlineData(context) { - KRAKEN_LOG(VERBOSE) << "Add Create Window Command"; - context->uiCommandBuffer()->addCommand(context->contextId(), UICommand::kCreateWindow, (void*)bindingObject()); + context->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateWindow, (void*)bindingObject()); } Window* Window::open(ExceptionState& exception_state) { diff --git a/kraken/lib/src/bridge/to_native.dart b/kraken/lib/src/bridge/to_native.dart index b2c19f83a7..7273157060 100644 --- a/kraken/lib/src/bridge/to_native.dart +++ b/kraken/lib/src/bridge/to_native.dart @@ -494,10 +494,10 @@ void flushUICommand() { id, nativePtr.cast<NativeBindingObject>(), command.args[0]); break; case UICommandType.createDocument: - controller.view.initDocument(nativePtr.cast<NativeBindingObject>()); + controller.view.initDocument(id, nativePtr.cast<NativeBindingObject>()); break; case UICommandType.createWindow: - controller.view.initWindow(nativePtr.cast<NativeBindingObject>()); + controller.view.initWindow(id, nativePtr.cast<NativeBindingObject>()); break; case UICommandType.createTextNode: controller.view.createTextNode( diff --git a/kraken/lib/src/launcher/controller.dart b/kraken/lib/src/launcher/controller.dart index d8f67d0b98..814b24cbf9 100644 --- a/kraken/lib/src/launcher/controller.dart +++ b/kraken/lib/src/launcher/controller.dart @@ -25,9 +25,6 @@ import 'package:kraken/module.dart'; import 'package:kraken/rendering.dart'; import 'package:kraken/widget.dart'; -const int WINDOW_ID = -1; -const int DOCUMENT_ID = -2; - // Error handler when load bundle failed. typedef LoadHandler = void Function(KrakenController controller); typedef LoadErrorHandler = void Function(FlutterError error, StackTrace stack); @@ -169,8 +166,7 @@ class KrakenViewController late Document document; late Window window; - void initDocument(Pointer<NativeBindingObject> pointer) { - print('init document'); + void initDocument(int targetId, Pointer<NativeBindingObject> pointer) { document = Document( BindingContext(_contextId, pointer), viewport: viewport, @@ -178,7 +174,7 @@ class KrakenViewController gestureListener: gestureListener, widgetDelegate: widgetDelegate, ); - _setEventTarget(DOCUMENT_ID, document); + _setEventTarget(targetId, document); // Listeners need to be registered to window in order to dispatch events on demand. if (gestureListener != null) { @@ -201,12 +197,12 @@ class KrakenViewController } } - void initWindow(Pointer<NativeBindingObject> pointer) { + void initWindow(int targetId, Pointer<NativeBindingObject> pointer) { window = Window( BindingContext(_contextId, pointer), document); _registerPlatformBrightnessChange(); - _setEventTarget(WINDOW_ID, window); + _setEventTarget(targetId, window); // Blur input element when new input focused. window.addEventListener(EVENT_CLICK, (event) { From 5ff9df685edcfadd431b95fa9d74177b3836f561 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Wed, 18 May 2022 01:46:36 +0800 Subject: [PATCH 133/498] fix: fix insertBefore. --- bridge/core/dom/container_node.cc | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index 1f27894d05..b6b161ca97 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -142,7 +142,7 @@ Node* ContainerNode::InsertBefore(Node* new_child, Node* ref_child, ExceptionSta // 5. Insert node into parent before reference child. NodeVector post_insertion_notification_targets; - post_insertion_notification_targets.reserve(kInitialNodeVectorSize); + { InsertNodeVector(targets, ref_child, AdoptAndInsertBefore(), &post_insertion_notification_targets); } return new_child; } @@ -394,6 +394,11 @@ void ContainerNode::InsertBeforeCommon(Node& next_child, Node& new_child) { new_child.SetParentOrShadowHostNode(this); new_child.SetPreviousSibling(prev); new_child.SetNextSibling(&next_child); + + std::unique_ptr<NativeString> args_01 = stringToNativeString(std::to_string(new_child.eventTargetId())); + std::unique_ptr<NativeString> args_02 = stringToNativeString("beforebegin"); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kInsertAdjacentNode, + std::move(args_01), std::move(args_02), nullptr); } void ContainerNode::AppendChildCommon(Node& child) { @@ -406,11 +411,8 @@ void ContainerNode::AppendChildCommon(Node& child) { } SetLastChild(&child); - std::string target_id = std::to_string(child.eventTargetId()); - std::string position = "beforeend"; - - std::unique_ptr<NativeString> args_01 = stringToNativeString(target_id); - std::unique_ptr<NativeString> args_02 = stringToNativeString(position); + std::unique_ptr<NativeString> args_01 = stringToNativeString(std::to_string(child.eventTargetId())); + std::unique_ptr<NativeString> args_02 = stringToNativeString("beforeend"); GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kInsertAdjacentNode, std::move(args_01), std::move(args_02), nullptr); From 65d6f89836d1d7b48d3870669de78e6c01195b9e Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Wed, 18 May 2022 02:03:32 +0800 Subject: [PATCH 134/498] fix: fix parse HTML. --- bridge/bindings/qjs/cppgc/member.h | 8 +++----- bridge/bindings/qjs/script_wrappable.h | 2 +- bridge/core/dom/node_test.cc | 1 + bridge/core/page.cc | 2 ++ kraken/example/lib/main.dart | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index 3c799f214d..20e21c8878 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -46,11 +46,7 @@ class Member { return; auto* wrappable = To<ScriptWrappable>(raw_); // Record the free operation to avoid JSObject had been freed immediately. - if (LIKELY(wrappable->GetExecutingContext()->HasMutationScope())) { - wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); - } else { - JS_FreeValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); - } + wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); raw_ = nullptr; } @@ -86,6 +82,8 @@ class Member { void SetRaw(T* p) { if (p != nullptr) { auto* wrappable = To<ScriptWrappable>(p); + assert_m(wrappable->GetExecutingContext()->HasMutationScope(), + "Member must be used after MemberMutationScope allcated."); runtime_ = wrappable->runtime(); JS_DupValue(wrappable->ctx(), wrappable->ToQuickJSUnsafe()); } diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 8592f464b4..bed127e5f6 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -80,7 +80,7 @@ Local<T>::~Local<T>() { if (LIKELY(wrappable->GetExecutingContext()->HasMutationScope())) { wrappable->GetExecutingContext()->mutationScope()->RecordFree(wrappable); } else { - assert_m(false, "LocalHandle must be used before MemberMutationScope allcated."); + assert_m(false, "LocalHandle must be used after MemberMutationScope allcated."); } } diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 2bfda47b8c..cf4ce4c718 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -36,6 +36,7 @@ TEST(Node, childNodes) { }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); auto context = bridge->GetExecutingContext(); + MemberMutationScope scope{context}; const char* code = "let div1 = document.createElement('div');" "let div2 = document.createElement('div');" diff --git a/bridge/core/page.cc b/bridge/core/page.cc index fc999b2297..63e6742de1 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -38,6 +38,8 @@ bool KrakenPage::parseHTML(const char* code, size_t length) { if (!context_->IsValid()) return false; + MemberMutationScope scope{context_}; + // Remove all Nodes including body and head. context_->document()->documentElement()->RemoveChildren(); diff --git a/kraken/example/lib/main.dart b/kraken/example/lib/main.dart index 6ff3592cca..1e9a1ca5b1 100644 --- a/kraken/example/lib/main.dart +++ b/kraken/example/lib/main.dart @@ -94,7 +94,7 @@ class _MyHomePageState extends State<MyBrowser> { devToolsService: ChromeDevToolsService(), viewportWidth: viewportSize.width - queryData.padding.horizontal, viewportHeight: viewportSize.height - appBar.preferredSize.height - queryData.padding.vertical, - bundle: KrakenBundle.fromUrl('assets:assets/bundle.js'), + bundle: KrakenBundle.fromUrl('assets:assets/bundle.html'), ), )); } From fc917a37fe87d1f18d552c7a62ec1abadc0f70f3 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Wed, 18 May 2022 11:45:23 +0800 Subject: [PATCH 135/498] chore: roll back task scripts. --- scripts/build_darwin_dylib.js | 2 +- scripts/tasks.js | 36 +++-------------------------------- 2 files changed, 4 insertions(+), 34 deletions(-) diff --git a/scripts/build_darwin_dylib.js b/scripts/build_darwin_dylib.js index f6ac607ceb..293be43ca6 100644 --- a/scripts/build_darwin_dylib.js +++ b/scripts/build_darwin_dylib.js @@ -8,7 +8,7 @@ require('./tasks'); // Run tasks series( - // 'macos-dylib-clean', + 'macos-dylib-clean', 'compile-polyfill', 'build-darwin-kraken-lib', )((err) => { diff --git a/scripts/tasks.js b/scripts/tasks.js index f649d51da2..519b2ddf5a 100644 --- a/scripts/tasks.js +++ b/scripts/tasks.js @@ -149,33 +149,6 @@ task('clean', () => { } }); -const libOutputPath = join(TARGET_PATH, platform, 'lib'); - -function findDebugJSEngine(platform) { - if (platform == 'macos' || platform == 'ios') { - let packageConfigFilePath = path.join(paths.kraken, '.dart_tool/package_config.json'); - - if (!fs.existsSync(packageConfigFilePath)) { - execSync('flutter pub get', { - cwd: paths.kraken, - stdio: 'inherit' - }); - } - - let packageConfig = require(packageConfigFilePath); - let packages = packageConfig.packages; - - let jscPackageInfo = packages.find((i) => i.name === 'jsc'); - if (!jscPackageInfo) { - throw new Error('Can not locate `jsc` dart package, please add jsc deps before build kraken libs.'); - } - - let rootUri = jscPackageInfo.rootUri; - let jscPackageLocation = path.join(paths.kraken, '.dart_tool', rootUri); - return path.join(jscPackageLocation, platform, 'JavaScriptCore.framework'); - } -} - task('build-darwin-kraken-lib', done => { let externCmakeArgs = []; let buildType = 'Debug'; @@ -211,9 +184,9 @@ task('build-darwin-kraken-lib', done => { if (targetJSEngine === 'quickjs') { krakenTargets.push('kraken_unit_test'); } - // if (buildMode === 'Debug') { - // krakenTargets.push('kraken_test'); - // } + if (buildMode === 'Debug') { + krakenTargets.push('kraken_test'); + } execSync(`cmake --build ${paths.bridge}/cmake-build-macos-x86_64 --target ${krakenTargets.join(' ')} -- -j 6`, { stdio: 'inherit' @@ -221,9 +194,6 @@ task('build-darwin-kraken-lib', done => { const binaryPath = path.join(paths.bridge, `build/macos/lib/x86_64/libkraken.dylib`); - if (targetJSEngine === 'jsc') { - execSync(`install_name_tool -change /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/JavaScriptCore @rpath/JavaScriptCore.framework/Versions/A/JavaScriptCore ${binaryPath}`); - } if (buildMode == 'Release' || buildMode == 'RelWithDebInfo') { execSync(`dsymutil ${binaryPath}`, { stdio: 'inherit' }); execSync(`strip -S -X -x ${binaryPath}`, { stdio: 'inherit' }); From 0652de3e655b0252ae1ef766e9661d977f6b0354 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Thu, 19 May 2022 16:32:12 +0800 Subject: [PATCH 136/498] feat: add screen impl. --- bridge/CMakeLists.txt | 4 ++ bridge/bindings/qjs/binding_initializer.cc | 2 + bridge/bindings/qjs/wrapper_type_info.h | 1 + bridge/core/dart_methods.h | 3 -- bridge/core/dom/binding_call_methods.json5 | 5 +++ bridge/core/dom/binding_object.cc | 15 ++++++- bridge/core/dom/binding_object.h | 11 +++-- bridge/core/frame/screen.cc | 49 ++-------------------- bridge/core/frame/screen.d.ts | 10 +++++ bridge/core/frame/screen.h | 27 ++++++------ bridge/core/frame/window.cc | 13 ++++++ bridge/core/frame/window.d.ts | 2 + bridge/core/frame/window.h | 8 ++++ bridge/core/page.cc | 1 - bridge/foundation/native_value.cc | 14 +++---- bridge/foundation/native_value.h | 3 +- bridge/foundation/native_value_converter.h | 17 +++++--- kraken/lib/src/bridge/from_native.dart | 17 +++----- kraken/lib/src/bridge/native_value.dart | 32 ++++---------- kraken/lib/src/dom/window.dart | 4 +- 20 files changed, 118 insertions(+), 120 deletions(-) create mode 100644 bridge/core/frame/screen.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 8ec5334106..a9f5e734b8 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -278,6 +278,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") core/frame/module_callback_coordinator.cc core/frame/window.h core/frame/window.cc + core/frame/screen.h + core/frame/screen.cc core/frame/legacy/location.cc core/frame/legacy/location.h core/frame/module_callback_coordinator.h @@ -440,6 +442,8 @@ if ($ENV{KRAKEN_JS_ENGINE} MATCHES "quickjs") out/qjs_css_style_declaration.h out/qjs_text.cc out/qjs_text.h + out/qjs_screen.cc + out/qjs_screen.h out/qjs_node_list.cc out/qjs_node_list.h out/event_type_names.h diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 3b37921704..9747f8ca6f 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -30,6 +30,7 @@ #include "qjs_node.h" #include "qjs_node_list.h" #include "qjs_text.h" +#include "qjs_screen.h" #include "qjs_window.h" #include "qjs_window_or_worker_global_scope.h" @@ -63,6 +64,7 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLTemplateElement::Install(context); QJSCSSStyleDeclaration::Install(context); QJSBoundingClientRect::Install(context); + QJSScreen::Install(context); // Legacy bindings, not standard. QJSElementAttributes::Install(context); diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index ca35bd99f6..2113a06c44 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -23,6 +23,7 @@ enum { JS_CLASS_WINDOW, JS_CLASS_NODE, JS_CLASS_ELEMENT, + JS_CLASS_SCREEN, JS_CLASS_DOCUMENT, JS_CLASS_CHARACTER_DATA, JS_CLASS_TEXT, diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index eefba783e3..c76db7e00c 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -11,7 +11,6 @@ #include <memory> #include <thread> -#include "core/frame/screen.h" #include "foundation/native_string.h" namespace kraken { @@ -34,7 +33,6 @@ 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, @@ -80,7 +78,6 @@ struct DartMethodPointer { ClearTimeout clearTimeout{nullptr}; RequestAnimationFrame requestAnimationFrame{nullptr}; CancelAnimationFrame cancelAnimationFrame{nullptr}; - GetScreen getScreen{nullptr}; ToBlob toBlob{nullptr}; OnJSError onJsError{nullptr}; OnJSLog onJsLog{nullptr}; diff --git a/bridge/core/dom/binding_call_methods.json5 b/bridge/core/dom/binding_call_methods.json5 index c1ee8efa6e..3065d28e7b 100644 --- a/bridge/core/dom/binding_call_methods.json5 +++ b/bridge/core/dom/binding_call_methods.json5 @@ -27,5 +27,10 @@ "scrollY", "innerWidth", "innerHeight", + "availWidth", + "availHeight", + "width", + "height", + "screen" ] } diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc index 31fbd91300..05f87a1c41 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/dom/binding_object.cc @@ -6,6 +6,7 @@ #include "binding_call_methods.h" #include "bindings/qjs/exception_state.h" #include "core/executing_context.h" +#include "foundation/logging.h" namespace kraken { @@ -20,19 +21,29 @@ void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_ob } BindingObject::BindingObject(ExecutingContext* context) : context_(context) {} +BindingObject::~BindingObject() { + delete binding_object_; +} + +void BindingObject::BindDartObject(NativeBindingObject* native_binding_object) { + native_binding_object->binding_target_ = this; + native_binding_object->invoke_binding_methods_from_dart = NativeBindingObject::HandleCallFromDartSide; + binding_object_ = native_binding_object; +} NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, int32_t argc, const NativeValue* argv, ExceptionState& exception_state) const { - if (binding_object_.invoke_bindings_methods_from_native == nullptr) { + if (binding_object_->invoke_bindings_methods_from_native == nullptr) { exception_state.ThrowException(context_->ctx(), ErrorType::InternalError, "Failed to call dart method: invokeBindingMethod not initialized."); return Native_NewNull(); } + KRAKEN_LOG(VERBOSE) << " binding object_ " << &binding_object_; NativeValue return_value = Native_NewNull(); - binding_object_.invoke_bindings_methods_from_native(&binding_object_, &return_value, + binding_object_->invoke_bindings_methods_from_native(binding_object_, &return_value, method.ToNativeString().release(), argc, argv); return return_value; } diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h index f519fec5f7..682582396b 100644 --- a/bridge/core/dom/binding_object.h +++ b/bridge/core/dom/binding_object.h @@ -45,8 +45,9 @@ struct NativeBindingObject { class BindingObject { public: + using ImplType = BindingObject*; BindingObject() = delete; - ~BindingObject() = default; + ~BindingObject(); explicit BindingObject(ExecutingContext* context); // Handle call from dart side. @@ -59,11 +60,15 @@ class BindingObject { NativeValue GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const; NativeValue SetBindingProperty(const AtomicString& prop, NativeValue value, ExceptionState& exception_state) const; - const NativeBindingObject* bindingObject() const { return &binding_object_; } + const NativeBindingObject* bindingObject() const { return binding_object_; } + + protected: + // NativeBindingObject may allocated at Dart side. Binding this with Dart allocated NativeBindingObject. + void BindDartObject(NativeBindingObject* native_binding_object); private: ExecutingContext* context_{nullptr}; - NativeBindingObject binding_object_{this}; + NativeBindingObject* binding_object_{new NativeBindingObject(this)}; }; } // namespace kraken diff --git a/bridge/core/frame/screen.cc b/bridge/core/frame/screen.cc index c630861c96..6d9d12a7fb 100644 --- a/bridge/core/frame/screen.cc +++ b/bridge/core/frame/screen.cc @@ -3,54 +3,13 @@ */ #include "screen.h" +#include "core/frame/window.h" +#include "foundation/native_value_converter.h" namespace kraken { -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<ExecutionContext*>(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<ExecutionContext*>(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<ExecutionContext*>(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<ExecutionContext*>(JS_GetContextOpaque(ctx)); - NativeScreen* screen = getDartMethod()->getScreen(context->getContextId()); - return JS_NewFloat64(ctx, screen->height); +Screen::Screen(Window* window, NativeBindingObject* native_binding_object) : EventTargetWithInlineData(window->GetExecutingContext()) { + BindDartObject(native_binding_object); } } // namespace kraken diff --git a/bridge/core/frame/screen.d.ts b/bridge/core/frame/screen.d.ts new file mode 100644 index 0000000000..0f2478771b --- /dev/null +++ b/bridge/core/frame/screen.d.ts @@ -0,0 +1,10 @@ +import {EventTarget} from "../dom/events/event_target"; + +export interface Screen extends EventTarget { + readonly availWidth: DartImpl<double>; + readonly availHeight: DartImpl<int64>; + readonly width: DartImpl<int64>; + readonly height: DartImpl<int64>; + + new(): void; +} diff --git a/bridge/core/frame/screen.h b/bridge/core/frame/screen.h index 98be8f7a82..de476f90bf 100644 --- a/bridge/core/frame/screen.h +++ b/bridge/core/frame/screen.h @@ -6,26 +6,23 @@ #ifndef KRAKENBRIDGE_SCREEN_H #define KRAKENBRIDGE_SCREEN_H +#include "core/dom/events/event_target.h" + namespace kraken { -struct NativeScreen { - double width; - double height; -}; +class Window; -// class Screen : public HostObject { -// public: -// explicit Screen(ExecutionContext* context) : HostObject(context, "Screen"){}; -// -// private: -// DEFINE_READONLY_PROPERTY(width); -// DEFINE_READONLY_PROPERTY(height); -//}; +struct NativeScreen {}; -// void bindScreen(ExecutionContext* context); +class Screen : public EventTargetWithInlineData { + DEFINE_WRAPPERTYPEINFO(); + public: + using ImplType = Screen*; + explicit Screen(Window* window, NativeBindingObject* binding_object); -} // namespace kraken + private: +}; -class screen {}; +} #endif // KRAKENBRIDGE_SCREEN_H diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index a431622546..ef9517582d 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -27,6 +27,14 @@ Window* Window::open(const AtomicString& url, ExceptionState& exception_state) { InvokeBindingMethod(binding_call_methods::kopen, 1, args, exception_state); } +Screen* Window::screen() { + if (screen_ == nullptr) { + NativeValue value = GetBindingProperty(binding_call_methods::kscreen, ASSERT_NO_EXCEPTION()); + screen_ = MakeGarbageCollected<Screen>(this, NativeValueConverter<NativeTypePointer<NativeBindingObject>>::FromNativeValue(value)); + } + return screen_; +} + void Window::scroll(ExceptionState& exception_state) { return scroll(0, 0, exception_state); } @@ -124,4 +132,9 @@ void Window::cancelAnimationFrame(double request_id, ExceptionState& exception_s GetExecutingContext()->document()->CancelAnimationFrame(static_cast<uint32_t>(request_id), exception_state); } +void Window::Trace(GCVisitor* visitor) const { + visitor->Trace(screen_); + EventTargetWithInlineData::Trace(visitor); +} + } // namespace kraken diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts index 0512b96223..a6a0294c7f 100644 --- a/bridge/core/frame/window.d.ts +++ b/bridge/core/frame/window.d.ts @@ -1,6 +1,7 @@ import {EventTarget} from "../dom/events/event_target"; import {ScrollOptions} from "../dom/scroll_options"; import {ScrollToOptions} from "../dom/scroll_to_options"; +import {Screen} from "./screen"; interface Window extends EventTarget { open(url?: string): Window | null; @@ -18,6 +19,7 @@ interface Window extends EventTarget { readonly window: Window; readonly parent: Window; readonly self: Window; + readonly screen: Screen; readonly scrollX: DartImpl<double>; readonly scrollY: DartImpl<double>; diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index f927415928..8e932cf4f5 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -9,6 +9,7 @@ #include "bindings/qjs/atomic_string.h" #include "bindings/qjs/wrapper_type_info.h" #include "core/dom/events/event_target.h" +#include "screen.h" #include "qjs_scroll_to_options.h" namespace kraken { @@ -23,6 +24,8 @@ class Window : public EventTargetWithInlineData { 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; } @@ -42,6 +45,11 @@ class Window : public EventTargetWithInlineData { double requestAnimationFrame(const std::shared_ptr<QJSFunction>& callback, ExceptionState& exceptionState); void cancelAnimationFrame(double request_id, ExceptionState& exception_state); + + void Trace(GCVisitor *visitor) const override; + + private: + Member<Screen> screen_; }; } // namespace kraken diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 63e6742de1..366b955122 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -133,7 +133,6 @@ void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { dartMethodPointer->clearTimeout = reinterpret_cast<ClearTimeout>(methodBytes[i++]); dartMethodPointer->requestAnimationFrame = reinterpret_cast<RequestAnimationFrame>(methodBytes[i++]); dartMethodPointer->cancelAnimationFrame = reinterpret_cast<CancelAnimationFrame>(methodBytes[i++]); - dartMethodPointer->getScreen = reinterpret_cast<GetScreen>(methodBytes[i++]); dartMethodPointer->toBlob = reinterpret_cast<ToBlob>(methodBytes[i++]); dartMethodPointer->flushUICommand = reinterpret_cast<FlushUICommand>(methodBytes[i++]); diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 7eb3566e4b..60ff30ff70 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -11,12 +11,11 @@ namespace kraken { NativeValue Native_NewNull() { - return (NativeValue){0, .u = {.int64 = 0}, NativeTag::TAG_NULL}; + return (NativeValue){.u = {.int64 = 0}, NativeTag::TAG_NULL}; } NativeValue Native_NewString(NativeString* string) { return (NativeValue){ - 0, .u = {.ptr = static_cast<void*>(string)}, NativeTag::TAG_STRING, }; @@ -29,20 +28,21 @@ NativeValue Native_NewCString(std::string string) { } NativeValue Native_NewFloat64(double value) { + int64_t result; + memcpy(&result, reinterpret_cast<void*>(&value), sizeof(double)); + return (NativeValue){ - value, - .u = {.ptr = nullptr}, + .u = {.int64 = result}, NativeTag::TAG_FLOAT64, }; } NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr) { - return (NativeValue){static_cast<double>(pointerType), .u = {.ptr = ptr}, NativeTag::TAG_POINTER}; + return (NativeValue){.u = {.ptr = ptr}, NativeTag::TAG_POINTER}; } NativeValue Native_NewBool(bool value) { return (NativeValue){ - 0, .u = {.int64 = value ? 1 : 0}, NativeTag::TAG_BOOL, }; @@ -50,7 +50,6 @@ NativeValue Native_NewBool(bool value) { NativeValue Native_NewInt64(int64_t value) { return (NativeValue){ - 0, .u = {.int64 = value}, NativeTag::TAG_INT, }; @@ -66,7 +65,6 @@ NativeValue Native_NewJSON(const ScriptValue& value) { AtomicString str = json.ToString(); auto native_string = str.ToNativeString(); NativeValue result = (NativeValue){ - 0, .u = {.ptr = static_cast<void*>(native_string.release())}, NativeTag::TAG_JSON, }; diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 03dc02e203..8a1b6b1726 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -30,7 +30,7 @@ enum class JSPointerType { NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, - NativeEventTarget = 4 + BindingObject = 4 }; class ExecutingContext; @@ -38,7 +38,6 @@ class ScriptValue; // Exchange data struct between dart and C++ struct NativeValue { - double float64; union { int64_t int64; void* ptr; diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index f0c3dff4bd..3ff901967c 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -8,6 +8,7 @@ #include "native_type.h" #include "native_value.h" +#include "core/dom/binding_object.h" namespace kraken { @@ -55,7 +56,11 @@ template <> struct NativeValueConverter<NativeTypeDouble> : public NativeValueConverterBase<NativeTypeDouble> { static NativeValue ToNativeValue(ImplType value) { return Native_NewFloat64(value); } - static ImplType FromNativeValue(NativeValue value) { return value.float64; } + static ImplType FromNativeValue(NativeValue value) { + double result; + memcpy(&result, reinterpret_cast<void*>(&value.u.int64), sizeof(double)); + return result; + } }; template <> @@ -68,7 +73,9 @@ struct NativeValueConverter<NativeTypeJSON> : public NativeValueConverterBase<Na }; class NativeBoundingClientRect; -class NativeEventTarget; +class BindingObject; +struct NativeBindingObject; +class NativeScreen; class NativeCanvasRenderingContext2D; template <> @@ -81,9 +88,9 @@ struct NativeValueConverter<NativeTypePointer<NativeBoundingClientRect>> }; template <> -struct NativeValueConverter<NativeTypePointer<NativeEventTarget>> - : public NativeValueConverterBase<NativeTypePointer<NativeEventTarget>> { - static NativeValue ToNativeValue(ImplType value) { return Native_NewPtr(JSPointerType::NativeEventTarget, value); } +struct NativeValueConverter<NativeTypePointer<NativeBindingObject>> + : public NativeValueConverterBase<NativeTypePointer<NativeBindingObject>> { + static NativeValue ToNativeValue(ImplType value) { return Native_NewPtr(JSPointerType::BindingObject, value); } static ImplType FromNativeValue(NativeValue value) { return static_cast<ImplType>(value.u.ptr); } }; diff --git a/kraken/lib/src/bridge/from_native.dart b/kraken/lib/src/bridge/from_native.dart index afee58ce8a..3c48ac67b5 100644 --- a/kraken/lib/src/bridge/from_native.dart +++ b/kraken/lib/src/bridge/from_native.dart @@ -5,7 +5,6 @@ import 'dart:convert'; import 'dart:ffi'; import 'dart:typed_data'; -import 'dart:ui'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; @@ -47,6 +46,12 @@ int doubleToUint64(double value) { return byteData.getUint64(0); } +int doubleToInt64(double value) { + var byteData = ByteData(8); + byteData.setFloat64(0, value); + return byteData.getInt64(0); +} + double uInt64ToDouble(int value) { var byteData = ByteData(8); byteData.setInt64(0, value); @@ -279,15 +284,6 @@ void _cancelAnimationFrame(int contextId, int timerId) { final Pointer<NativeFunction<NativeCancelAnimationFrame>> _nativeCancelAnimationFrame = Pointer.fromFunction(_cancelAnimationFrame); -typedef NativeGetScreen = Pointer<Void> Function(); - -Pointer<Void> _getScreen() { - Size size = window.physicalSize; - return createScreen(size.width / window.devicePixelRatio, size.height / window.devicePixelRatio); -} - -final Pointer<NativeFunction<NativeGetScreen>> _nativeGetScreen = Pointer.fromFunction(_getScreen); - typedef NativeAsyncBlobCallback = Void Function( Pointer<Void> callbackContext, Int32 contextId, Pointer<Utf8>, Pointer<Uint8>, Int32); typedef DartAsyncBlobCallback = void Function( @@ -378,7 +374,6 @@ final List<int> _dartNativeMethods = [ _nativeClearTimeout.address, _nativeRequestAnimationFrame.address, _nativeCancelAnimationFrame.address, - _nativeGetScreen.address, _nativeToBlob.address, _nativeFlushUICommand.address, _nativeGetEntries.address, diff --git a/kraken/lib/src/bridge/native_value.dart b/kraken/lib/src/bridge/native_value.dart index a8881006e9..f55445542c 100644 --- a/kraken/lib/src/bridge/native_value.dart +++ b/kraken/lib/src/bridge/native_value.dart @@ -7,11 +7,9 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:kraken/bridge.dart'; +import 'package:kraken/foundation.dart'; class NativeValue extends Struct { - @Double() - external double float64; - @Int64() external int u; @@ -36,7 +34,7 @@ enum JSPointerType { NativeFunctionContext, NativeBoundingClientRect, NativeCanvasRenderingContext2D, - NativeBindingObject + BindingObject } typedef AnonymousNativeFunction = dynamic Function(List<dynamic> args); @@ -77,19 +75,9 @@ dynamic fromNativeValue(Pointer<NativeValue> nativeValue) { case JSValueType.TAG_NULL: return null; case JSValueType.TAG_FLOAT64: - return nativeValue.ref.float64; + return uInt64ToDouble(nativeValue.ref.u); case JSValueType.TAG_POINTER: - JSPointerType pointerType = JSPointerType.values[nativeValue.ref.float64.toInt()]; - switch (pointerType) { - case JSPointerType.NativeBoundingClientRect: - return Pointer.fromAddress(nativeValue.ref.u).cast<NativeBoundingClientRect>(); - case JSPointerType.NativeCanvasRenderingContext2D: - return Pointer.fromAddress(nativeValue.ref.u).cast<NativeCanvasRenderingContext2D>(); - case JSPointerType.NativeBindingObject: - return Pointer.fromAddress(nativeValue.ref.u).cast<NativeBindingObject>(); - default: - return Pointer.fromAddress(nativeValue.ref.u); - } + return Pointer.fromAddress(nativeValue.ref.u); case JSValueType.TAG_FUNCTION: case JSValueType.TAG_ASYNC_FUNCTION: break; @@ -112,20 +100,16 @@ void toNativeValue(Pointer<NativeValue> target, value) { target.ref.u = value ? 1 : 0; } else if (value is double) { target.ref.tag = JSValueType.TAG_FLOAT64.index; - target.ref.float64 = value; + target.ref.u = doubleToInt64(value); } else if (value is String) { target.ref.tag = JSValueType.TAG_STRING.index; target.ref.u = stringToNativeString(value).address; } else if (value is Pointer) { target.ref.tag = JSValueType.TAG_POINTER.index; target.ref.u = value.address; - if (value is Pointer<NativeBoundingClientRect>) { - target.ref.float64 = JSPointerType.NativeBoundingClientRect.index.toDouble(); - } else if (value is Pointer<NativeCanvasRenderingContext2D>) { - target.ref.float64 = JSPointerType.NativeCanvasRenderingContext2D.index.toDouble(); - } else if (value is Pointer<NativeBindingObject>) { - target.ref.float64 = JSPointerType.NativeBindingObject.index.toDouble(); - } + } else if (value is BindingObject) { + target.ref.tag = JSValueType.TAG_POINTER.index; + target.ref.u = (value.pointer as Pointer?)!.address; } else if (value is AsyncAnonymousNativeFunction) { int id = _functionId++; _asyncFunctionMap[id] = value; diff --git a/kraken/lib/src/dom/window.dart b/kraken/lib/src/dom/window.dart index a89deae991..f5406b2c09 100644 --- a/kraken/lib/src/dom/window.dart +++ b/kraken/lib/src/dom/window.dart @@ -2,6 +2,8 @@ * Copyright (C) 2019-present The Kraken authors. All rights reserved. */ import 'dart:ui'; +import 'dart:ffi'; +import 'package:ffi/ffi.dart'; import 'package:kraken/bridge.dart'; import 'package:kraken/dom.dart'; @@ -16,7 +18,7 @@ class Window extends EventTarget { final Screen screen; Window(BindingContext? context, this.document) - : screen = Screen(context), super(context); + : screen = Screen(BindingContext(context!.contextId, malloc.allocate(sizeOf<NativeBindingObject>()))), super(context); @override EventTarget? get parentEventTarget => null; From f5499b94dea28b729fe89937c5b1e7b32b94191c Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Mon, 23 May 2022 16:36:08 +0800 Subject: [PATCH 137/498] refactor: refactor flushUICommand. --- bridge/core/dart_methods.h | 2 +- bridge/core/dom/binding_object.cc | 4 +- bridge/core/dom/element.cc | 14 +- bridge/core/executing_context.cc | 4 + bridge/core/executing_context.h | 9 +- bridge/core/frame/legacy/location.cc | 2 +- bridge/core/frame/window.cc | 2 +- kraken/lib/src/bridge/bridge.dart | 5 +- kraken/lib/src/bridge/from_native.dart | 8 +- kraken/lib/src/bridge/to_native.dart | 237 ++++++++++++------------ kraken/lib/src/dom/window.dart | 4 +- kraken/lib/src/launcher/controller.dart | 4 +- 12 files changed, 152 insertions(+), 143 deletions(-) diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index c76db7e00c..93a21a65a3 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -40,7 +40,7 @@ typedef void (*ToBlob)(void* callbackContext, 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 (*FlushUICommand)(int32_t contextId); using MatchImageSnapshotCallback = void (*)(void* callbackContext, int32_t contextId, int8_t, const char* errmsg); using MatchImageSnapshot = void (*)(void* callbackContext, diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc index 05f87a1c41..d8c2fd7fe7 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/dom/binding_object.cc @@ -49,7 +49,7 @@ NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, } NativeValue BindingObject::GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const { - context_->dartMethodPtr()->flushUICommand(); + context_->FlushUICommand(); const NativeValue argv[] = {Native_NewString(prop.ToNativeString().release())}; return InvokeBindingMethod(binding_call_methods::kgetPropertyMagic, 1, argv, exception_state); } @@ -57,7 +57,7 @@ NativeValue BindingObject::GetBindingProperty(const AtomicString& prop, Exceptio NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, NativeValue value, ExceptionState& exception_state) const { - context_->dartMethodPtr()->flushUICommand(); + context_->FlushUICommand(); const NativeValue argv[] = {Native_NewString(prop.ToNativeString().release()), value}; return InvokeBindingMethod(binding_call_methods::ksetPropertyMagic, 2, argv, exception_state); } diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index d4bcd01e8e..3b5cadee44 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -68,7 +68,7 @@ void Element::removeAttribute(const AtomicString& name, ExceptionState& exceptio } BoundingClientRect* Element::getBoundingClientRect(ExceptionState& exception_state) { - GetExecutingContext()->dartMethodPtr()->flushUICommand(); + GetExecutingContext()->FlushUICommand(); NativeValue result = InvokeBindingMethod(binding_call_methods::kgetBoundingClientRect, 0, nullptr, exception_state); return BoundingClientRect::Create( GetExecutingContext(), @@ -76,7 +76,7 @@ BoundingClientRect* Element::getBoundingClientRect(ExceptionState& exception_sta } void Element::click(ExceptionState& exception_state) { - GetExecutingContext()->dartMethodPtr()->flushUICommand(); + GetExecutingContext()->FlushUICommand(); InvokeBindingMethod(binding_call_methods::kclick, 0, nullptr, exception_state); } @@ -85,7 +85,7 @@ void Element::scroll(ExceptionState& exception_state) { } void Element::scroll(double x, double y, ExceptionState& exception_state) { - GetExecutingContext()->dartMethodPtr()->flushUICommand(); + GetExecutingContext()->FlushUICommand(); const NativeValue args[] = { NativeValueConverter<NativeTypeDouble>::ToNativeValue(x), NativeValueConverter<NativeTypeDouble>::ToNativeValue(y), @@ -94,7 +94,7 @@ void Element::scroll(double x, double y, ExceptionState& exception_state) { } void Element::scroll(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state) { - GetExecutingContext()->dartMethodPtr()->flushUICommand(); + GetExecutingContext()->FlushUICommand(); const NativeValue args[] = { NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->left()), NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->top()), @@ -107,7 +107,7 @@ void Element::scrollBy(ExceptionState& exception_state) { } void Element::scrollBy(double x, double y, ExceptionState& exception_state) { - GetExecutingContext()->dartMethodPtr()->flushUICommand(); + GetExecutingContext()->FlushUICommand(); const NativeValue args[] = { NativeValueConverter<NativeTypeDouble>::ToNativeValue(x), NativeValueConverter<NativeTypeDouble>::ToNativeValue(y), @@ -116,7 +116,7 @@ void Element::scrollBy(double x, double y, ExceptionState& exception_state) { } void Element::scrollBy(const std::shared_ptr<ScrollToOptions>& options, ExceptionState& exception_state) { - GetExecutingContext()->dartMethodPtr()->flushUICommand(); + GetExecutingContext()->FlushUICommand(); const NativeValue args[] = { NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->left()), NativeValueConverter<NativeTypeDouble>::ToNativeValue(options->top()), @@ -233,7 +233,7 @@ class ElementSnapshotReader { }; void ElementSnapshotReader::Start() { - context_->dartMethodPtr()->flushUICommand(); + context_->FlushUICommand(); auto callback = [](void* ptr, int32_t contextId, const char* error, uint8_t* bytes, int32_t length) -> void { auto* reader = static_cast<ElementSnapshotReader*>(ptr); diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index f1b9f97ee5..85b2b0faff 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -309,6 +309,10 @@ static void dispatchPromiseRejectionEvent(const char* eventType, // } } +void ExecutingContext::FlushUICommand() { + dartMethodPtr()->flushUICommand(context_id_); +} + void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error) { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 67ded141b6..93d11c8086 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -103,9 +103,8 @@ class ExecutingContext { FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; FORCE_INLINE std::unique_ptr<DartMethodPointer>& dartMethodPtr() { return dart_method_ptr_; } - std::chrono::time_point<std::chrono::system_clock> time_origin_; - - int32_t unique_id_; + // Force dart side to execute the pending ui commands. + void FlushUICommand(); static void DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, @@ -117,6 +116,10 @@ class ExecutingContext { static std::unordered_map<std::string, NativeByteCode> pluginByteCode; private: + + std::chrono::time_point<std::chrono::system_clock> time_origin_; + int32_t unique_id_; + void InstallDocument(); static void promiseRejectTracker(JSContext* ctx, diff --git a/bridge/core/frame/legacy/location.cc b/bridge/core/frame/legacy/location.cc index 7f5aa9175f..2767efeecf 100644 --- a/bridge/core/frame/legacy/location.cc +++ b/bridge/core/frame/legacy/location.cc @@ -15,7 +15,7 @@ void Location::__kraken_location_reload__(ExecutingContext* context, ExceptionSt return; } - context->dartMethodPtr()->flushUICommand(); + context->FlushUICommand(); context->dartMethodPtr()->reloadApp(context->contextId()); } diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index ef9517582d..c4246501e6 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -114,7 +114,7 @@ double Window::requestAnimationFrame(const std::shared_ptr<QJSFunction>& callbac return 0; } - GetExecutingContext()->dartMethodPtr()->flushUICommand(); + GetExecutingContext()->FlushUICommand(); auto frame_callback = FrameCallback::Create(GetExecutingContext(), callback); uint32_t request_id = GetExecutingContext()->document()->RequestAnimationFrame(frame_callback, exceptionState); // `-1` represents some error occurred. diff --git a/kraken/lib/src/bridge/bridge.dart b/kraken/lib/src/bridge/bridge.dart index eda8bfefac..0b08bb81f9 100644 --- a/kraken/lib/src/bridge/bridge.dart +++ b/kraken/lib/src/bridge/bridge.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/scheduler.dart'; import 'package:kraken/module.dart'; +import 'package:kraken/launcher.dart'; import 'binding.dart'; import 'from_native.dart'; @@ -19,7 +20,7 @@ int kKrakenJSPagePoolSize = 1024; bool _firstView = true; /// Init bridge -int initBridge() { +int initBridge(KrakenViewController view) { if (kProfileMode) { PerformanceTiming.instance().mark(PERF_BRIDGE_REGISTER_DART_METHOD_START); } @@ -39,7 +40,7 @@ int initBridge() { Future.microtask(() { // Port flutter's frame callback into bridge. SchedulerBinding.instance!.addPersistentFrameCallback((_) { - flushUICommand(); + flushUICommand(view); }); }); } diff --git a/kraken/lib/src/bridge/from_native.dart b/kraken/lib/src/bridge/from_native.dart index 3c48ac67b5..c0cd1e9c6b 100644 --- a/kraken/lib/src/bridge/from_native.dart +++ b/kraken/lib/src/bridge/from_native.dart @@ -309,14 +309,14 @@ void _toBlob(Pointer<Void> callbackContext, int contextId, final Pointer<NativeFunction<NativeToBlob>> _nativeToBlob = Pointer.fromFunction(_toBlob); -typedef NativeFlushUICommand = Void Function(); -typedef DartFlushUICommand = void Function(); +typedef NativeFlushUICommand = Void Function(Int32 contextId); +typedef DartFlushUICommand = void Function(int contextId); -void _flushUICommand() { +void _flushUICommand(int contextId) { if (kProfileMode) { PerformanceTiming.instance().mark(PERF_DOM_FLUSH_UI_COMMAND_START); } - flushUICommand(); + flushUICommandWithContextId(contextId); if (kProfileMode) { PerformanceTiming.instance().mark(PERF_DOM_FLUSH_UI_COMMAND_END); } diff --git a/kraken/lib/src/bridge/to_native.dart b/kraken/lib/src/bridge/to_native.dart index 7273157060..60e68d0375 100644 --- a/kraken/lib/src/bridge/to_native.dart +++ b/kraken/lib/src/bridge/to_native.dart @@ -79,8 +79,7 @@ final DartInvokeEventListener _invokeModuleEvent = KrakenDynamicLibrary .lookup<NativeFunction<NativeInvokeEventListener>>('invokeModuleEvent') .asFunction(); -void invokeModuleEvent( - int contextId, String moduleName, Event? event, String extra) { +void invokeModuleEvent(int contextId, String moduleName, Event? event, String extra) { if (KrakenController.getControllerOfJSContextId(contextId) == null) { return; } @@ -96,15 +95,14 @@ void invokeModuleEvent( } typedef DartDispatchEvent = int Function( - int contextId, - Pointer<NativeBindingObject> nativeBindingObject, - Pointer<NativeString> eventType, - Pointer<Void> nativeEvent, - int isCustomEvent -); - -void emitModuleEvent( - int contextId, String moduleName, Event? event, String extra) { + int contextId, + Pointer<NativeBindingObject> nativeBindingObject, + Pointer<NativeString> eventType, + Pointer<Void> nativeEvent, + int isCustomEvent + ); + +void emitModuleEvent(int contextId, String moduleName, Event? event, String extra) { invokeModuleEvent(contextId, moduleName, event, extra); } @@ -141,6 +139,7 @@ final DartParseHTML _parseHTML = KrakenDynamicLibrary.ref .asFunction(); int _anonymousScriptEvaluationId = 0; + void evaluateScripts(int contextId, String code, {String? url, int line = 0}) { if (KrakenController.getControllerOfJSContextId(contextId) == null) { return; @@ -236,7 +235,7 @@ typedef DartRegisterPluginByteCode = void Function( final DartRegisterPluginByteCode _registerPluginByteCode = KrakenDynamicLibrary .ref .lookup<NativeFunction<NativeRegisterPluginByteCode>>( - 'registerPluginByteCode') + 'registerPluginByteCode') .asFunction(); void registerPluginByteCode(Uint8List bytecode, String name) { @@ -285,8 +284,7 @@ final DartDispatchUITask _dispatchUITask = KrakenDynamicLibrary.ref .lookup<NativeFunction<NativeDispatchUITask>>('dispatchUITask') .asFunction(); -void dispatchUITask( - int contextId, Pointer<Void> context, Pointer<Void> callback) { +void dispatchUITask(int contextId, Pointer<Void> context, Pointer<Void> callback) { _dispatchUITask(contextId, context, callback); } @@ -381,8 +379,7 @@ final bool isEnabledLog = // We found there are performance bottleneck of reading native memory with Dart FFI API. // So we align all UI instructions to a whole block of memory, and then convert them into a dart array at one time, // To ensure the fastest subsequent random access. -List<UICommand> readNativeUICommandToDart( - Pointer<Uint64> nativeCommandItems, int commandLength, int contextId) { +List<UICommand> readNativeUICommandToDart(Pointer<Uint64> nativeCommandItems, int commandLength, int contextId) { List<int> rawMemory = nativeCommandItems .cast<Int64>() .asTypedList(commandLength * nativeCommandSize) @@ -452,117 +449,119 @@ void clearUICommand(int contextId) { _clearUICommandItems(contextId); } -void flushUICommand() { - Map<int, KrakenController?> controllerMap = - KrakenController.getControllerMap(); - for (KrakenController? controller in controllerMap.values) { - if (controller == null) continue; - Pointer<Uint64> nativeCommandItems = - _getUICommandItems(controller.view.contextId); - int commandLength = _getUICommandItemSize(controller.view.contextId); - - if (commandLength == 0 || nativeCommandItems == nullptr) { - continue; - } +void flushUICommandWithContextId(int contextId) { + KrakenController? controller = KrakenController.getControllerOfJSContextId(contextId); + if (controller != null) { + flushUICommand(controller.view); + } +} - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_START); - } +void flushUICommand(KrakenViewController view) { + Pointer<Uint64> nativeCommandItems = + _getUICommandItems(view.contextId); + int commandLength = _getUICommandItemSize(view.contextId); - List<UICommand> commands = readNativeUICommandToDart( - nativeCommandItems, commandLength, controller.view.contextId); + if (commandLength == 0 || nativeCommandItems == nullptr) { + return; + } + + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_START); + } - SchedulerBinding.instance!.scheduleFrame(); + List<UICommand> commands = readNativeUICommandToDart( + nativeCommandItems, commandLength, view.contextId); - if (kProfileMode) { - PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_END); - } + SchedulerBinding.instance!.scheduleFrame(); - Map<int, bool> pendingStylePropertiesTargets = {}; - - // For new ui commands, we needs to tell engine to update frames. - for (int i = 0; i < commandLength; i++) { - UICommand command = commands[i]; - UICommandType commandType = command.type; - int id = command.id; - Pointer nativePtr = command.nativePtr; - - try { - switch (commandType) { - case UICommandType.createElement: - controller.view.createElement( - id, nativePtr.cast<NativeBindingObject>(), command.args[0]); - break; - case UICommandType.createDocument: - controller.view.initDocument(id, nativePtr.cast<NativeBindingObject>()); - break; - case UICommandType.createWindow: - controller.view.initWindow(id, nativePtr.cast<NativeBindingObject>()); - break; - case UICommandType.createTextNode: - controller.view.createTextNode( - id, nativePtr.cast<NativeBindingObject>(), command.args[0]); - break; - case UICommandType.createComment: - controller.view - .createComment(id, nativePtr.cast<NativeBindingObject>()); - break; - case UICommandType.disposeEventTarget: - controller.view.disposeEventTarget(id); - break; - case UICommandType.addEvent: - controller.view.addEvent(id, command.args[0]); - break; - case UICommandType.removeEvent: - controller.view.removeEvent(id, command.args[0]); - break; - case UICommandType.insertAdjacentNode: - int childId = int.parse(command.args[0]); - String position = command.args[1]; - controller.view.insertAdjacentNode(id, position, childId); - break; - case UICommandType.removeNode: - controller.view.removeNode(id); - break; - case UICommandType.cloneNode: - int newId = int.parse(command.args[0]); - controller.view.cloneNode(id, newId); - break; - case UICommandType.setStyle: - String key = command.args[0]; - String value = command.args[1]; - controller.view.setInlineStyle(id, key, value); - pendingStylePropertiesTargets[id] = true; - break; - case UICommandType.setAttribute: - String key = command.args[0]; - String value = command.args[1]; - controller.view.setAttribute(id, key, value); - break; - case UICommandType.removeAttribute: - String key = command.args[0]; - controller.view.removeAttribute(id, key); - break; - case UICommandType.createDocumentFragment: - controller.view.createDocumentFragment( - id, nativePtr.cast<NativeBindingObject>()); - break; - default: - break; - } - } catch (e, stack) { - print('$e\n$stack'); + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_END); + } + + Map<int, bool> pendingStylePropertiesTargets = {}; + + // For new ui commands, we needs to tell engine to update frames. + for (int i = 0; i < commandLength; i++) { + UICommand command = commands[i]; + UICommandType commandType = command.type; + int id = command.id; + Pointer nativePtr = command.nativePtr; + + try { + switch (commandType) { + case UICommandType.createElement: + view.createElement( + id, nativePtr.cast<NativeBindingObject>(), command.args[0]); + break; + case UICommandType.createDocument: + view.initDocument(id, nativePtr.cast<NativeBindingObject>()); + break; + case UICommandType.createWindow: + view.initWindow(id, nativePtr.cast<NativeBindingObject>()); + break; + case UICommandType.createTextNode: + view.createTextNode( + id, nativePtr.cast<NativeBindingObject>(), command.args[0]); + break; + case UICommandType.createComment: + view + .createComment(id, nativePtr.cast<NativeBindingObject>()); + break; + case UICommandType.disposeEventTarget: + view.disposeEventTarget(id); + break; + case UICommandType.addEvent: + view.addEvent(id, command.args[0]); + break; + case UICommandType.removeEvent: + view.removeEvent(id, command.args[0]); + break; + case UICommandType.insertAdjacentNode: + int childId = int.parse(command.args[0]); + String position = command.args[1]; + view.insertAdjacentNode(id, position, childId); + break; + case UICommandType.removeNode: + view.removeNode(id); + break; + case UICommandType.cloneNode: + int newId = int.parse(command.args[0]); + view.cloneNode(id, newId); + break; + case UICommandType.setStyle: + String key = command.args[0]; + String value = command.args[1]; + view.setInlineStyle(id, key, value); + pendingStylePropertiesTargets[id] = true; + break; + case UICommandType.setAttribute: + String key = command.args[0]; + String value = command.args[1]; + view.setAttribute(id, key, value); + break; + case UICommandType.removeAttribute: + String key = command.args[0]; + view.removeAttribute(id, key); + break; + case UICommandType.createDocumentFragment: + view.createDocumentFragment( + id, nativePtr.cast<NativeBindingObject>()); + break; + default: + break; } + } catch (e, stack) { + print('$e\n$stack'); } + } - // For pending style properties, we needs to flush to render style. - for (int id in pendingStylePropertiesTargets.keys) { - try { - controller.view.flushPendingStyleProperties(id); - } catch (e, stack) { - print('$e\n$stack'); - } + // For pending style properties, we needs to flush to render style. + for (int id in pendingStylePropertiesTargets.keys) { + try { + view.flushPendingStyleProperties(id); + } catch (e, stack) { + print('$e\n$stack'); } - pendingStylePropertiesTargets.clear(); } + pendingStylePropertiesTargets.clear(); } diff --git a/kraken/lib/src/dom/window.dart b/kraken/lib/src/dom/window.dart index f5406b2c09..11d79a38a8 100644 --- a/kraken/lib/src/dom/window.dart +++ b/kraken/lib/src/dom/window.dart @@ -103,8 +103,8 @@ class Window extends EventTarget { @override void dispatchEvent(Event event) { // Events such as EVENT_DOM_CONTENT_LOADED need to ensure that listeners are flushed and registered. - if (event.type == EVENT_DOM_CONTENT_LOADED || event.type == EVENT_LOAD || event.type == EVENT_ERROR) { - flushUICommand(); + if (contextId != null && event.type == EVENT_DOM_CONTENT_LOADED || event.type == EVENT_LOAD || event.type == EVENT_ERROR) { + flushUICommandWithContextId(contextId!); } super.dispatchEvent(event); } diff --git a/kraken/lib/src/launcher/controller.dart b/kraken/lib/src/launcher/controller.dart index 814b24cbf9..a65f033c86 100644 --- a/kraken/lib/src/launcher/controller.dart +++ b/kraken/lib/src/launcher/controller.dart @@ -116,7 +116,7 @@ class KrakenViewController PerformanceTiming.instance().mark(PERF_BRIDGE_INIT_START); } BindingBridge.setup(); - _contextId = contextId ?? initBridge(); + _contextId = contextId ?? initBridge(this); if (kProfileMode) { PerformanceTiming.instance().mark(PERF_BRIDGE_INIT_END); @@ -144,6 +144,8 @@ class KrakenViewController defineBuiltInElements(); + // Execute UICommand.createDocument and UICommand.createWindow to initialize window and document. + flushUICommand(this); if (kProfileMode) { PerformanceTiming.instance().mark(PERF_ELEMENT_MANAGER_INIT_END); From cb6f1c02a1cc165dbcfe91e57c476ef5d0d9078a Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Mon, 23 May 2022 17:21:00 +0800 Subject: [PATCH 138/498] fix: fix integration test. --- bridge/include/kraken_bridge_test.h | 2 +- bridge/kraken_bridge_test.cc | 4 +-- bridge/polyfill/src/test/index.js | 25 +------------------ bridge/test/kraken_test_context.cc | 3 +++ integration_tests/lib/bridge/from_native.dart | 8 +++--- integration_tests/lib/main.dart | 3 +-- 6 files changed, 12 insertions(+), 33 deletions(-) diff --git a/bridge/include/kraken_bridge_test.h b/bridge/include/kraken_bridge_test.h index efab68a480..68ea49be02 100644 --- a/bridge/include/kraken_bridge_test.h +++ b/bridge/include/kraken_bridge_test.h @@ -10,7 +10,7 @@ KRAKEN_EXPORT_C void initTestFramework(int32_t contextId); KRAKEN_EXPORT_C -int8_t evaluateTestScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine); +int8_t evaluateTestScripts(int32_t contextId, void* code, const char* bundleFilename, int startLine); using ExecuteCallback = void* (*)(int32_t contextId, void* status); diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index b2eb1648ba..71dfab29df 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -16,9 +16,9 @@ void initTestFramework(int32_t contextId) { testContextPool[contextId] = testContext; } -int8_t evaluateTestScripts(int32_t contextId, kraken::NativeString* code, const char* bundleFilename, int startLine) { +int8_t evaluateTestScripts(int32_t contextId, void* code, const char* bundleFilename, int startLine) { auto testContext = testContextPool[contextId]; - return testContext->evaluateTestScripts(code->string(), code->length(), bundleFilename, startLine); + return testContext->evaluateTestScripts(static_cast<kraken::NativeString*>(code)->string(), static_cast<kraken::NativeString*>(code)->length(), bundleFilename, startLine); } void executeTest(int32_t contextId, ExecuteCallback executeCallback) { diff --git a/bridge/polyfill/src/test/index.js b/bridge/polyfill/src/test/index.js index 967b90ab86..71eb5db12e 100644 --- a/bridge/polyfill/src/test/index.js +++ b/bridge/polyfill/src/test/index.js @@ -3,7 +3,6 @@ const ConsoleReporter = require('./console-reporter'); const jasmine = jasmineCore.core(jasmineCore); const env = jasmine.getEnv({ suppressLoadErrors: true }); const jasmineInterface = jasmineCore.interface(jasmine, env); -const environment = __kraken_environment__(); const global = globalThis; let timers = []; @@ -83,31 +82,9 @@ function createPrinter(logger) { } } -function HtmlSpecFilter(options) { - var filterString = - options && - options.filterString() && - options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); - var filterPattern = new RegExp(filterString); - - this.matches = function (specName) { - return filterPattern.test(specName); - }; -} - -var specFilter = new HtmlSpecFilter({ - filterString() { - return environment.KRAKEN_TEST_FILTER; - } -}); - let config = { oneFailurePerSpec: true, - failFast: environment.KRAKEN_STOP_ON_FAIL === 'true', - random: false, - specFilter: function (spec) { - return specFilter.matches(spec.getFullName()); - } + random: false }; env.configure(config); diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index 092038ad08..a9c07fd82e 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -244,8 +244,11 @@ void KrakenTestContext::invokeExecuteTest(ExecuteCallback executeCallback) { return JS_ThrowTypeError(ctx, "failed to execute 'done': parameter 1 (status) is not a string"); } + KRAKEN_LOG(VERBOSE) << "Done.."; + std::unique_ptr<NativeString> status = kraken::jsValueToNativeString(ctx, statusValue); callbackContext->executeCallback(callbackContext->context->contextId(), status.get()); + JS_FreeValue(ctx, proxyObject); return JS_NULL; }; auto* callbackContext = new ExecuteCallbackContext(context_, executeCallback); diff --git a/integration_tests/lib/bridge/from_native.dart b/integration_tests/lib/bridge/from_native.dart index 3a3eb69327..cfcdd9794d 100644 --- a/integration_tests/lib/bridge/from_native.dart +++ b/integration_tests/lib/bridge/from_native.dart @@ -140,15 +140,15 @@ final List<int> _dartNativeMethods = [ _nativeSimulateInputText.address ]; -typedef Native_RegisterTestEnvDartMethods = Void Function(Pointer<Uint64> methodBytes, Int32 length); -typedef Dart_RegisterTestEnvDartMethods = void Function(Pointer<Uint64> methodBytes, int length); +typedef Native_RegisterTestEnvDartMethods = Void Function(Int32 contextId, Pointer<Uint64> methodBytes, Int32 length); +typedef Dart_RegisterTestEnvDartMethods = void Function(int contextId, Pointer<Uint64> methodBytes, int length); final Dart_RegisterTestEnvDartMethods _registerTestEnvDartMethods = KrakenDynamicLibrary.ref.lookup<NativeFunction<Native_RegisterTestEnvDartMethods>>('registerTestEnvDartMethods').asFunction(); -void registerDartTestMethodsToCpp() { +void registerDartTestMethodsToCpp(int contextId) { Pointer<Uint64> bytes = malloc.allocate<Uint64>(sizeOf<Uint64>() * _dartNativeMethods.length); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - _registerTestEnvDartMethods(bytes, _dartNativeMethods.length); + _registerTestEnvDartMethods(contextId, bytes, _dartNativeMethods.length); } diff --git a/integration_tests/lib/main.dart b/integration_tests/lib/main.dart index b4da5d49d6..66a01c49fb 100644 --- a/integration_tests/lib/main.dart +++ b/integration_tests/lib/main.dart @@ -96,10 +96,9 @@ void main() async { testTextInput = TestTextInput(); WidgetsBinding.instance!.addPostFrameCallback((_) async { - registerDartTestMethodsToCpp(); int contextId = kraken.controller!.view.contextId; - initTestFramework(contextId); + registerDartTestMethodsToCpp(contextId); addJSErrorListener(contextId, print); // Preload load test cases String code = spec.readAsStringSync(); From 970156080467014f1282255f5bd4af90cbf4e483 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Wed, 25 May 2022 19:08:20 +0800 Subject: [PATCH 139/498] fix: fix integration test and timer. --- bridge/bindings/qjs/binding_initializer.cc | 2 ++ bridge/bindings/qjs/script_promise.cc | 2 +- .../bindings/qjs/script_promise_resolver.cc | 10 +++++-- bridge/bindings/qjs/script_promise_resolver.h | 7 +++-- .../core/css/legacy/css_style_declaration.cc | 4 +++ .../css/legacy/css_style_declaration_test.cc | 27 +++++++++++++++++++ bridge/core/dom/element.cc | 12 ++++++--- bridge/core/dom/node.d.ts | 1 + bridge/core/fileapi/blob.cc | 10 +++---- bridge/core/frame/dom_timer.cc | 6 +++-- bridge/core/frame/dom_timer.h | 16 +++++++++-- bridge/core/frame/dom_timer_coordinator.cc | 5 +++- bridge/core/frame/dom_timer_test.cc | 22 ++++++++++++++- .../frame/window_or_worker_global_scope.cc | 2 ++ bridge/polyfill/src/test/index.js | 7 ++--- bridge/test/kraken_test_context.cc | 2 -- bridge/test/kraken_test_env.cc | 15 ++++++++--- bridge/test/run_integration_test.cc | 6 +++-- bridge/test/test.cmake | 1 + integration_tests/lib/plugin.dart | 3 +-- kraken/lib/src/module/timer.dart | 3 +++ 21 files changed, 131 insertions(+), 32 deletions(-) create mode 100644 bridge/core/css/legacy/css_style_declaration_test.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 9747f8ca6f..b212bfe386 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -32,6 +32,7 @@ #include "qjs_text.h" #include "qjs_screen.h" #include "qjs_window.h" +#include "qjs_blob.h" #include "qjs_window_or_worker_global_scope.h" namespace kraken { @@ -65,6 +66,7 @@ void InstallBindings(ExecutingContext* context) { QJSCSSStyleDeclaration::Install(context); QJSBoundingClientRect::Install(context); QJSScreen::Install(context); + QJSBlob::Install(context); // Legacy bindings, not standard. QJSElementAttributes::Install(context); diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index 376d8fa9e4..c23eee774a 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -20,7 +20,7 @@ ScriptPromise::ScriptPromise(JSContext* ctx, JSValue promise) : ctx_(ctx) { } JSValue ScriptPromise::ToQuickJS() { - return JS_NULL; + return JS_DupValue(ctx_, promise_.QJSValue()); } void ScriptPromise::Trace(GCVisitor* visitor) {} diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index 9345c542d5..5a08ced0eb 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -9,8 +9,8 @@ namespace kraken { -ScriptPromiseResolver* ScriptPromiseResolver::Create(ExecutingContext* context) { - return new ScriptPromiseResolver(context); +std::shared_ptr<ScriptPromiseResolver> ScriptPromiseResolver::Create(ExecutingContext* context) { + return std::make_shared<ScriptPromiseResolver>(context); } ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context) : context_(context) { @@ -22,6 +22,12 @@ ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context) : contex context->GetPendingPromises()->TrackPendingPromises(ScriptPromise(context_->ctx(), promise_)); } +ScriptPromiseResolver::~ScriptPromiseResolver() { + JS_FreeValue(context_->ctx(), promise_); + JS_FreeValue(context_->ctx(), resolve_func_); + JS_FreeValue(context_->ctx(), reject_func_); +} + ScriptPromise ScriptPromiseResolver::Promise() { return ScriptPromise(context_->ctx(), promise_); } diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 16e0612231..72bee032d8 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -14,9 +14,10 @@ namespace kraken { class ScriptPromiseResolver { public: - static ScriptPromiseResolver* Create(ExecutingContext* context); + static std::shared_ptr<ScriptPromiseResolver> 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 @@ -51,7 +52,9 @@ class ScriptPromiseResolver { return; assert(new_state == kResolving || new_state == kRejecting); state_ = new_state; - ResolveOrRejectImmediately(toQuickJS(context_->ctx(), value)); + JSValue qjs_value = toQuickJS(context_->ctx(), value); + ResolveOrRejectImmediately(qjs_value); + JS_FreeValue(context_->ctx(), qjs_value); } void ResolveOrRejectImmediately(JSValue value); diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index d4e3d27b07..3163f091ec 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -122,6 +122,10 @@ AtomicString CSSStyleDeclaration::InternalGetPropertyValue(std::string& name) { bool CSSStyleDeclaration::InternalSetProperty(std::string& name, const AtomicString& value) { name = parseJavaScriptCSSPropertyName(name); + if (properties_[name] == value) { + return true; + } + properties_[name] = value; std::unique_ptr<NativeString> args_01 = stringToNativeString(name); 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..02f2539949 --- /dev/null +++ b/bridge/core/css/legacy/css_style_declaration_test.cc @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 Alibaba Inc. All rights reserved. + * Author: Kraken Team. + */ + +#include "gtest/gtest.h" +#include "kraken_test_env.h" + +using namespace kraken; + +TEST(CSSStyleDeclaration, setStyleData) { + bool static errorCalled = false; + bool static logCalled = false; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + logCalled = true; + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + KRAKEN_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); +} diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 3b5cadee44..14a668fe85 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -4,6 +4,8 @@ */ #include "element.h" + +#include <utility> #include "binding_call_methods.h" #include "bindings/qjs/exception_state.h" #include "bindings/qjs/script_promise.h" @@ -215,9 +217,9 @@ class ElementSnapshotReader { public: ElementSnapshotReader(ExecutingContext* context, Element* element, - ScriptPromiseResolver* resolver, + std::shared_ptr<ScriptPromiseResolver> resolver, double device_pixel_ratio) - : context_(context), element_(element), resolver_(resolver), device_pixel_ratio_(device_pixel_ratio) { + : context_(context), element_(element), resolver_(std::move(resolver)), device_pixel_ratio_(device_pixel_ratio) { Start(); }; @@ -228,7 +230,7 @@ class ElementSnapshotReader { private: ExecutingContext* context_; Element* element_; - ScriptPromiseResolver* resolver_; + std::shared_ptr<ScriptPromiseResolver> resolver_; double device_pixel_ratio_; }; @@ -250,12 +252,14 @@ void ElementSnapshotReader::Start() { } 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*>(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); @@ -266,7 +270,7 @@ ScriptPromise Element::toBlob(ExceptionState& exception_state) { } ScriptPromise Element::toBlob(double device_pixel_ratio, ExceptionState& exception_state) { - auto* resolver = ScriptPromiseResolver::Create(GetExecutingContext()); + auto resolver = ScriptPromiseResolver::Create(GetExecutingContext()); new ElementSnapshotReader(GetExecutingContext(), this, resolver, device_pixel_ratio); return resolver->Promise(); } diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts index 9a4d804d5b..aff9dfd9fe 100644 --- a/bridge/core/dom/node.d.ts +++ b/bridge/core/dom/node.d.ts @@ -68,6 +68,7 @@ interface Node extends EventTarget { 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/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 048ed75e64..9243c917ee 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -15,8 +15,8 @@ class BlobReaderClient { public: enum ReadType { kReadAsText, kReadAsArrayBuffer }; - BlobReaderClient(ExecutingContext* context, Blob* blob, ScriptPromiseResolver* resolver, ReadType read_type) - : context_(context), blob_(blob), resolver_(resolver), read_type_(read_type) { + 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(); }; @@ -26,7 +26,7 @@ class BlobReaderClient { private: ExecutingContext* context_; Blob* blob_; - ScriptPromiseResolver* resolver_; + std::shared_ptr<ScriptPromiseResolver> resolver_; ReadType read_type_; }; @@ -113,13 +113,13 @@ std::string Blob::type() { } ScriptPromise Blob::arrayBuffer(ExceptionState& exception_state) { - auto* resolver = ScriptPromiseResolver::Create(GetExecutingContext()); + 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()); + auto resolver = ScriptPromiseResolver::Create(GetExecutingContext()); new BlobReaderClient(GetExecutingContext(), this, resolver, BlobReaderClient::ReadType::kReadAsText); return resolver->Promise(); } diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index f8541ba86f..8d3f92bd8c 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -4,6 +4,8 @@ */ #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" @@ -14,12 +16,12 @@ namespace kraken { -std::shared_ptr<DOMTimer> DOMTimer::create(ExecutingContext* context, std::shared_ptr<QJSFunction> callback) { +std::shared_ptr<DOMTimer> DOMTimer::create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback) { return std::make_shared<DOMTimer>(context, callback); } DOMTimer::DOMTimer(ExecutingContext* context, std::shared_ptr<QJSFunction> callback) - : context_(context), callback_(callback) {} + : context_(context), callback_(std::move(callback)), status_(TimerStatus::kPending) {} void DOMTimer::Fire() { if (!callback_->IsFunction(context_->ctx())) diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index 9f64f3f098..94cf082133 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -14,21 +14,33 @@ namespace kraken { class DOMTimer { public: - static std::shared_ptr<DOMTimer> create(ExecutingContext* context, std::shared_ptr<QJSFunction> callback); + enum TimerStatus { + kPending, + kExecuting, + kFinished + }; + + static std::shared_ptr<DOMTimer> create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback); DOMTimer(ExecutingContext* context, std::shared_ptr<QJSFunction> callback); // Trigger timer callback. void Fire(); - int32_t timerId() const { return timerId_; }; + [[nodiscard]] int32_t timerId() const { return timerId_; }; void setTimerId(int32_t timerId); + void SetStatus(TimerStatus status) { + status_ = status; + } + [[nodiscard]] TimerStatus status() const { return status_; } + ExecutingContext* context() { return context_; } private: ExecutingContext* context_{nullptr}; int32_t timerId_{-1}; int32_t isInterval_{false}; + TimerStatus status_; std::shared_ptr<QJSFunction> callback_; }; diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 18c402c7fa..c043b25c95 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -51,7 +51,10 @@ void* DOMTimerCoordinator::removeTimeoutById(int32_t timerId) { return nullptr; auto timer = m_activeTimers[timerId]; - m_activeTimers.erase(timerId); + if (timer->status() == DOMTimer::kPending) { + m_activeTimers.erase(timerId); + } + return nullptr; } diff --git a/bridge/core/frame/dom_timer_test.cc b/bridge/core/frame/dom_timer_test.cc index 5149c2cd9a..ad3534ba5a 100644 --- a/bridge/core/frame/dom_timer_test.cc +++ b/bridge/core/frame/dom_timer_test.cc @@ -41,8 +41,11 @@ console.log('1234'); TEST(Timer, clearTimeout) { auto bridge = TEST_init(); + static bool log_called = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + log_called = true; + }; std::string code = R"( function getCachedData() { @@ -58,6 +61,23 @@ let timer = setTimeout(async () => { console.log(data); }, 10); clearTimeout(timer); +)"; + + bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); + TEST_runLoop(bridge->GetExecutingContext()); + + EXPECT_EQ(log_called, false); +} + +TEST(Timer, clearTimeoutWhenSetTimeout) { + auto bridge = TEST_init(); + + kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + + std::string code = R"( +let timer = setTimeout(() => { + clearTimeout(timer); +}, 10); )"; bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 4b461cab55..35ce9fdb53 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -34,7 +34,9 @@ static void handleTransientCallback(void* ptr, int32_t contextId, const char* er if (!context->IsValid()) return; + timer->SetStatus(DOMTimer::TimerStatus::kExecuting); handleTimerCallback(timer, errmsg); + timer->SetStatus(DOMTimer::TimerStatus::kFinished); context->Timers()->removeTimeoutById(timer->timerId()); } diff --git a/bridge/polyfill/src/test/index.js b/bridge/polyfill/src/test/index.js index 71eb5db12e..1aaf3cd6d2 100644 --- a/bridge/polyfill/src/test/index.js +++ b/bridge/polyfill/src/test/index.js @@ -135,9 +135,10 @@ global.simulateInputText = __kraken_simulate_inputtext__; function resetDocumentElement() { window.scrollTo(0, 0); - document.removeChild(document.documentElement); - let html = document.createElement('html'); - document.appendChild(html); + + while(document.documentElement.firstChild) { + document.documentElement.firstChild.remove(); + } let head = document.createElement('head'); document.documentElement.appendChild(head); diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index a9c07fd82e..65b78390c3 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -68,7 +68,6 @@ static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int arg } std::unique_ptr<NativeString> screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); - auto bridge = static_cast<KrakenTestContext*>(static_cast<KrakenPage*>(context->owner())->owner); auto* callbackContext = new ImageSnapShotContext{JS_DupValue(ctx, callbackValue), context}; auto fn = [](void* ptr, int32_t contextId, int8_t result, const char* errmsg) { @@ -89,7 +88,6 @@ static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int arg callbackContext->context->DrainPendingPromiseJobs(); JS_FreeValue(callbackContext->context->ctx(), callbackContext->callback); - list_del(&callbackContext->link); }; context->dartMethodPtr()->matchImageSnapshot(callbackContext, context->contextId(), blob->bytes(), blob->size(), diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index a8ef28606e..5a06d9fa29 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -164,11 +164,21 @@ NativeString* TEST_platformBrightness(int32_t contextId) { return nullptr; } -void TEST_toBlob(void* callbackContext, + +void TEST_toBlob(void* ptr, int32_t contextId, AsyncBlobCallback blobCallback, int32_t elementId, - double devicePixelRatio) {} + double devicePixelRatio) { + uint8_t bytes[5] = { + 0x01, + 0x02, + 0x03, + 0x04, + 0x05 + }; + blobCallback(ptr, contextId, nullptr, bytes, 5); +} void TEST_flushUICommand() {} @@ -281,7 +291,6 @@ void TEST_mockDartMethods(int32_t contextId, 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), }; diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index 3238ff3cde..3ba76ec151 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -7,7 +7,9 @@ #include "gtest/gtest.h" #include "kraken_bridge_test.h" #include "kraken_test_env.h" -#include "page.h" +#include "kraken_bridge_test.h" + +using namespace kraken; std::string readTestSpec() { std::string filepath = std::string(SPEC_FILE_PATH) + "/../integration_tests/.specs/core.build.js"; @@ -36,7 +38,7 @@ TEST(IntegrationTest, runSpecs) { std::string code = readTestSpec(); bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); - executeTest(context->contextId(), [](int32_t contextId, NativeString* status) -> void* { + executeTest(context->contextId(), [](int32_t contextId, void* status) -> void* { KRAKEN_LOG(VERBOSE) << "done"; return nullptr; }); diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 7099e3548d..401171b24d 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -27,6 +27,7 @@ list(APPEND KRAKEN_UNIT_TEST_SOURCE ./core/dom/element_test.cc ./core/frame/dom_timer_test.cc ./core/frame/window_test.cc + ./core/css/legacy/css_style_declaration_test.cc # ./bindings/qjs/bom/timer_test.cc # ./bindings/qjs/qjs_patch_test.cc # ./bindings/qjs/garbage_collected_test.cc diff --git a/integration_tests/lib/plugin.dart b/integration_tests/lib/plugin.dart index 4e9b031acf..39d68d8048 100644 --- a/integration_tests/lib/plugin.dart +++ b/integration_tests/lib/plugin.dart @@ -95,8 +95,6 @@ void main() async { )); WidgetsBinding.instance!.addPostFrameCallback((_) async { - registerDartTestMethodsToCpp(); - List<Future<String>> testResults = []; for (int i = 0; i < widgets.length; i++) { @@ -105,6 +103,7 @@ void main() async { addJSErrorListener(contextId, (String err) { print(err); }); + registerDartTestMethodsToCpp(contextId); Map<String, String> payload = allSpecsPayload[i]; diff --git a/kraken/lib/src/module/timer.dart b/kraken/lib/src/module/timer.dart index 3e9a5d598e..e33693684b 100644 --- a/kraken/lib/src/module/timer.dart +++ b/kraken/lib/src/module/timer.dart @@ -62,7 +62,9 @@ mixin TimerMixin { int setTimeout(int timeout, void Function() callback) { Duration timeoutDurationMS = Duration(milliseconds: timeout); int id = _timerId++; + print('set timer: $id'); _timerMap[id] = Timer(timeoutDurationMS, () { + print('trigger timer: $id'); callback(); _timerMap.remove(id); }); @@ -72,6 +74,7 @@ mixin TimerMixin { void clearTimeout(int timerId) { // If timer already executed, which will be removed. if (_timerMap[timerId] != null) { + print('clear timer $timerId'); _timerMap[timerId]!.cancel(); _timerMap.remove(timerId); } From 05e59b9a15c99ec57bdc3199001f933e37c2c3d9 Mon Sep 17 00:00:00 2001 From: "chenghuai.dtc" <chenghuai.dtc@alibaba-inc.com> Date: Mon, 30 May 2022 14:55:24 +0800 Subject: [PATCH 140/498] fix: fix promise state. --- bridge/bindings/qjs/script_promise_resolver.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index 5a08ced0eb..0deba31467 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -13,7 +13,7 @@ std::shared_ptr<ScriptPromiseResolver> ScriptPromiseResolver::Create(ExecutingCo return std::make_shared<ScriptPromiseResolver>(context); } -ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context) : context_(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]; From 3cdd44a4297d7c26ab804743bb1ba3278d3c4295 Mon Sep 17 00:00:00 2001 From: openkraken-bot <openkraken@list.alibaba-inc.com> Date: Mon, 30 May 2022 06:56:19 +0000 Subject: [PATCH 141/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 4 ++-- bridge/bindings/qjs/script_promise_resolver.cc | 3 ++- bridge/core/dom/binding_object.cc | 2 +- bridge/core/dom/document.cc | 3 ++- bridge/core/executing_context.h | 1 - bridge/core/fileapi/blob.cc | 5 ++++- bridge/core/frame/dom_timer.h | 10 ++-------- bridge/core/frame/screen.cc | 3 ++- bridge/core/frame/screen.h | 3 ++- bridge/core/frame/window.cc | 3 ++- bridge/core/frame/window.h | 4 ++-- bridge/foundation/native_value_converter.h | 2 +- bridge/kraken_bridge_test.cc | 4 +++- bridge/test/kraken_test_env.cc | 9 +-------- bridge/test/run_integration_test.cc | 1 - 15 files changed, 26 insertions(+), 31 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index b212bfe386..9e68e3a854 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -6,6 +6,7 @@ #include "binding_initializer.h" #include "core/executing_context.h" +#include "qjs_blob.h" #include "qjs_bounding_client_rect.h" #include "qjs_character_data.h" #include "qjs_comment.h" @@ -29,10 +30,9 @@ #include "qjs_module_manager.h" #include "qjs_node.h" #include "qjs_node_list.h" -#include "qjs_text.h" #include "qjs_screen.h" +#include "qjs_text.h" #include "qjs_window.h" -#include "qjs_blob.h" #include "qjs_window_or_worker_global_scope.h" namespace kraken { diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index 0deba31467..a7b2e615aa 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -13,7 +13,8 @@ std::shared_ptr<ScriptPromiseResolver> ScriptPromiseResolver::Create(ExecutingCo return std::make_shared<ScriptPromiseResolver>(context); } -ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context) : context_(context), state_(ResolutionState::kPending) { +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]; diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc index d8c2fd7fe7..6d39a9249c 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/dom/binding_object.cc @@ -44,7 +44,7 @@ NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, KRAKEN_LOG(VERBOSE) << " binding object_ " << &binding_object_; NativeValue return_value = Native_NewNull(); binding_object_->invoke_bindings_methods_from_native(binding_object_, &return_value, - method.ToNativeString().release(), argc, argv); + method.ToNativeString().release(), argc, argv); return return_value; } diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index c449774815..7697721dad 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -23,7 +23,8 @@ Document* Document::Create(ExecutingContext* context, ExceptionState& exception_ Document::Document(ExecutingContext* context) : ContainerNode(context, this, ConstructionType::kCreateDocument), TreeScope(*this) { - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateDocument, (void*)bindingObject()); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateDocument, + (void*)bindingObject()); document_element_ = MakeGarbageCollected<HTMLHtmlElement>(*this); } diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 93d11c8086..8295b3ac27 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -116,7 +116,6 @@ class ExecutingContext { static std::unordered_map<std::string, NativeByteCode> pluginByteCode; private: - std::chrono::time_point<std::chrono::system_clock> time_origin_; int32_t unique_id_; diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 9243c917ee..cb2d91b6cf 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -15,7 +15,10 @@ class BlobReaderClient { public: enum ReadType { kReadAsText, kReadAsArrayBuffer }; - BlobReaderClient(ExecutingContext* context, Blob* blob, std::shared_ptr<ScriptPromiseResolver> resolver, ReadType read_type) + 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(); }; diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index 94cf082133..225dd75fbb 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -14,11 +14,7 @@ namespace kraken { class DOMTimer { public: - enum TimerStatus { - kPending, - kExecuting, - kFinished - }; + enum TimerStatus { kPending, kExecuting, kFinished }; static std::shared_ptr<DOMTimer> create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback); DOMTimer(ExecutingContext* context, std::shared_ptr<QJSFunction> callback); @@ -29,9 +25,7 @@ class DOMTimer { [[nodiscard]] int32_t timerId() const { return timerId_; }; void setTimerId(int32_t timerId); - void SetStatus(TimerStatus status) { - status_ = status; - } + void SetStatus(TimerStatus status) { status_ = status; } [[nodiscard]] TimerStatus status() const { return status_; } ExecutingContext* context() { return context_; } diff --git a/bridge/core/frame/screen.cc b/bridge/core/frame/screen.cc index 6d9d12a7fb..278fb039a7 100644 --- a/bridge/core/frame/screen.cc +++ b/bridge/core/frame/screen.cc @@ -8,7 +8,8 @@ namespace kraken { -Screen::Screen(Window* window, NativeBindingObject* native_binding_object) : EventTargetWithInlineData(window->GetExecutingContext()) { +Screen::Screen(Window* window, NativeBindingObject* native_binding_object) + : EventTargetWithInlineData(window->GetExecutingContext()) { BindDartObject(native_binding_object); } diff --git a/bridge/core/frame/screen.h b/bridge/core/frame/screen.h index de476f90bf..aee755fe78 100644 --- a/bridge/core/frame/screen.h +++ b/bridge/core/frame/screen.h @@ -16,6 +16,7 @@ struct NativeScreen {}; class Screen : public EventTargetWithInlineData { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = Screen*; explicit Screen(Window* window, NativeBindingObject* binding_object); @@ -23,6 +24,6 @@ class Screen : public EventTargetWithInlineData { private: }; -} +} // namespace kraken #endif // KRAKENBRIDGE_SCREEN_H diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index c4246501e6..5854703375 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -30,7 +30,8 @@ Window* Window::open(const AtomicString& url, ExceptionState& exception_state) { Screen* Window::screen() { if (screen_ == nullptr) { NativeValue value = GetBindingProperty(binding_call_methods::kscreen, ASSERT_NO_EXCEPTION()); - screen_ = MakeGarbageCollected<Screen>(this, NativeValueConverter<NativeTypePointer<NativeBindingObject>>::FromNativeValue(value)); + screen_ = MakeGarbageCollected<Screen>( + this, NativeValueConverter<NativeTypePointer<NativeBindingObject>>::FromNativeValue(value)); } return screen_; } diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index 8e932cf4f5..437632adca 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -9,8 +9,8 @@ #include "bindings/qjs/atomic_string.h" #include "bindings/qjs/wrapper_type_info.h" #include "core/dom/events/event_target.h" -#include "screen.h" #include "qjs_scroll_to_options.h" +#include "screen.h" namespace kraken { @@ -46,7 +46,7 @@ class Window : public EventTargetWithInlineData { double requestAnimationFrame(const std::shared_ptr<QJSFunction>& callback, ExceptionState& exceptionState); void cancelAnimationFrame(double request_id, ExceptionState& exception_state); - void Trace(GCVisitor *visitor) const override; + void Trace(GCVisitor* visitor) const override; private: Member<Screen> screen_; diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 3ff901967c..77691dd6c2 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -6,9 +6,9 @@ #ifndef KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ #define KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ +#include "core/dom/binding_object.h" #include "native_type.h" #include "native_value.h" -#include "core/dom/binding_object.h" namespace kraken { diff --git a/bridge/kraken_bridge_test.cc b/bridge/kraken_bridge_test.cc index 71dfab29df..2e9f0e61f0 100644 --- a/bridge/kraken_bridge_test.cc +++ b/bridge/kraken_bridge_test.cc @@ -18,7 +18,9 @@ void initTestFramework(int32_t contextId) { int8_t evaluateTestScripts(int32_t contextId, void* code, const char* bundleFilename, int startLine) { auto testContext = testContextPool[contextId]; - return testContext->evaluateTestScripts(static_cast<kraken::NativeString*>(code)->string(), static_cast<kraken::NativeString*>(code)->length(), bundleFilename, startLine); + return testContext->evaluateTestScripts(static_cast<kraken::NativeString*>(code)->string(), + static_cast<kraken::NativeString*>(code)->length(), bundleFilename, + startLine); } void executeTest(int32_t contextId, ExecuteCallback executeCallback) { diff --git a/bridge/test/kraken_test_env.cc b/bridge/test/kraken_test_env.cc index 5a06d9fa29..dd3200e937 100644 --- a/bridge/test/kraken_test_env.cc +++ b/bridge/test/kraken_test_env.cc @@ -164,19 +164,12 @@ NativeString* TEST_platformBrightness(int32_t contextId) { return nullptr; } - void TEST_toBlob(void* ptr, int32_t contextId, AsyncBlobCallback blobCallback, int32_t elementId, double devicePixelRatio) { - uint8_t bytes[5] = { - 0x01, - 0x02, - 0x03, - 0x04, - 0x05 - }; + uint8_t bytes[5] = {0x01, 0x02, 0x03, 0x04, 0x05}; blobCallback(ptr, contextId, nullptr, bytes, 5); } diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index 3ba76ec151..bbd4c7672a 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -7,7 +7,6 @@ #include "gtest/gtest.h" #include "kraken_bridge_test.h" #include "kraken_test_env.h" -#include "kraken_bridge_test.h" using namespace kraken; From 2507197efe1a69c0ff023fb5d4dfbd0af0a4ae95 Mon Sep 17 00:00:00 2001 From: openwebf-bot <openwebf@openwebf.com> Date: Mon, 8 Aug 2022 15:00:13 +0000 Subject: [PATCH 142/498] Committing clang-format changes --- bridge/core/css/legacy/css_style_declaration_test.cc | 4 +--- bridge/core/dart_methods.h | 4 ++-- bridge/core/dom/node_test.cc | 4 +--- bridge/core/page.h | 3 ++- bridge/core/timing/performance.cc | 2 +- bridge/webf_bridge_test.cc | 2 +- 6 files changed, 8 insertions(+), 11 deletions(-) diff --git a/bridge/core/css/legacy/css_style_declaration_test.cc b/bridge/core/css/legacy/css_style_declaration_test.cc index f98ecc7bc1..e3bc4fe637 100644 --- a/bridge/core/css/legacy/css_style_declaration_test.cc +++ b/bridge/core/css/legacy/css_style_declaration_test.cc @@ -12,9 +12,7 @@ using namespace kraken; TEST(CSSStyleDeclaration, setStyleData) { 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; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index 705a83667f..8bff8c2f71 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -8,9 +8,9 @@ /// Functions implements at dart side, including timer, Rendering and module API. /// Communicate via Dart FFI. -#include "webf_bridge.h" #include <memory> #include <thread> +#include "webf_bridge.h" #include "foundation/native_string.h" #define WEBF_EXPORT __attribute__((__visibility__("default"))) @@ -93,6 +93,6 @@ struct DartMethodPointer { #endif }; -} // namespace webf +} // namespace kraken #endif diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 07f20d456d..30f52d81f0 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -137,9 +137,7 @@ TEST(Node, ensureDetached) { TEST(Node, replaceBody) { 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; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; diff --git a/bridge/core/page.h b/bridge/core/page.h index 009ac0a9f2..383206a612 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -23,7 +23,8 @@ using ConsoleMessageHandler = std::function<void(void* ctx, const std::string& m /// 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; diff --git a/bridge/core/timing/performance.cc b/bridge/core/timing/performance.cc index af1070125c..a77cb43039 100644 --- a/bridge/core/timing/performance.cc +++ b/bridge/core/timing/performance.cc @@ -634,4 +634,4 @@ Rendering: %.*fms #endif -} // namespace webf +} // namespace kraken diff --git a/bridge/webf_bridge_test.cc b/bridge/webf_bridge_test.cc index 2e9f0e61f0..a7a6709b34 100644 --- a/bridge/webf_bridge_test.cc +++ b/bridge/webf_bridge_test.cc @@ -2,9 +2,9 @@ * Copyright (C) 2020-present The Kraken authors. All rights reserved. */ -#include "kraken_bridge_test.h" #include <atomic> #include "bindings/qjs/native_string_utils.h" +#include "kraken_bridge_test.h" #include "kraken_test_context.h" std::unordered_map<int, kraken::KrakenTestContext*> testContextPool = From b41289f4cddf080600903d67b86349ffc4a29ea7 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Tue, 9 Aug 2022 10:57:47 +0800 Subject: [PATCH 143/498] fix: clean up merge remains. --- bridge/CMakeLists.txt | 2 - bridge/bindings/qjs/atomic_string.cc | 3 +- bridge/bindings/qjs/atomic_string.h | 5 +- bridge/bindings/qjs/atomic_string_test.cc | 5 +- bridge/bindings/qjs/binding_initializer.cc | 6 +- bridge/bindings/qjs/binding_initializer.h | 6 +- bridge/bindings/qjs/bom/blob.cc | 0 bridge/bindings/qjs/bom/blob.h | 0 bridge/bindings/qjs/bom/console.cc | 0 bridge/bindings/qjs/bom/console.h | 0 .../bindings/qjs/bom/dom_timer_coordinator.cc | 0 bridge/bindings/qjs/bom/location.cc | 0 bridge/bindings/qjs/bom/location.h | 0 bridge/bindings/qjs/converter.h | 6 +- bridge/bindings/qjs/converter_impl.h | 6 +- bridge/bindings/qjs/cppgc/garbage_collected.h | 7 +- bridge/bindings/qjs/cppgc/gc_visitor.cc | 7 +- bridge/bindings/qjs/cppgc/gc_visitor.h | 7 +- bridge/bindings/qjs/cppgc/local_handle.h | 3 +- bridge/bindings/qjs/cppgc/member.h | 3 +- bridge/bindings/qjs/cppgc/mutation_scope.cc | 3 +- bridge/bindings/qjs/cppgc/mutation_scope.h | 3 +- bridge/bindings/qjs/dictionary_base.cc | 6 +- bridge/bindings/qjs/dictionary_base.h | 6 +- bridge/bindings/qjs/dom/all_collection.cc | 0 bridge/bindings/qjs/dom/all_collection.h | 0 bridge/bindings/qjs/dom/comment_node.cc | 0 bridge/bindings/qjs/dom/comment_node.h | 0 bridge/bindings/qjs/dom/css_property_list.h | 436 ------------- bridge/bindings/qjs/dom/custom_event.cc | 0 bridge/bindings/qjs/dom/custom_event.h | 0 bridge/bindings/qjs/dom/document.cc | 0 bridge/bindings/qjs/dom/document.h | 0 bridge/bindings/qjs/dom/document_fragment.cc | 0 bridge/bindings/qjs/dom/document_fragment.h | 0 bridge/bindings/qjs/dom/document_test.cc | 0 bridge/bindings/qjs/dom/element.cc | 0 bridge/bindings/qjs/dom/element.h | 0 .../qjs/dom/elements/image_element.cc | 0 .../bindings/qjs/dom/elements/image_element.h | 0 bridge/bindings/qjs/dom/event.cc | 0 bridge/bindings/qjs/dom/event.h | 0 bridge/bindings/qjs/dom/event_listener_map.cc | 0 bridge/bindings/qjs/dom/event_listener_map.h | 0 bridge/bindings/qjs/dom/event_target.cc | 0 bridge/bindings/qjs/dom/event_target.h | 0 bridge/bindings/qjs/dom/event_target_test.cc | 0 bridge/bindings/qjs/dom/event_type_names.cc | 0 bridge/bindings/qjs/dom/event_type_names.h | 0 .../dom/frame_request_callback_collection.cc | 0 .../dom/frame_request_callback_collection.h | 0 bridge/bindings/qjs/dom/node.cc | 0 bridge/bindings/qjs/dom/node.h | 0 bridge/bindings/qjs/dom/style_declaration.cc | 0 bridge/bindings/qjs/exception_message.cc | 6 +- bridge/bindings/qjs/exception_message.h | 6 +- bridge/bindings/qjs/exception_state.cc | 7 +- bridge/bindings/qjs/exception_state.h | 7 +- bridge/bindings/qjs/executing_context.cc | 0 bridge/bindings/qjs/executing_context.h | 0 bridge/bindings/qjs/garbage_collected.h | 0 bridge/bindings/qjs/garbage_collected_test.cc | 587 ------------------ bridge/bindings/qjs/generated_code_helper.h | 5 +- bridge/bindings/qjs/host_class.h | 0 bridge/bindings/qjs/host_class_test.cc | 0 bridge/bindings/qjs/host_object.cc | 0 bridge/bindings/qjs/host_object.h | 0 bridge/bindings/qjs/host_object_test.cc | 0 bridge/bindings/qjs/html_parser.cc | 0 bridge/bindings/qjs/html_parser.h | 0 bridge/bindings/qjs/idl_type.h | 6 +- .../bindings/qjs/js_based_event_listener.cc | 6 +- bridge/bindings/qjs/js_based_event_listener.h | 6 +- bridge/bindings/qjs/js_event_handler.cc | 6 +- bridge/bindings/qjs/js_event_handler.h | 6 +- bridge/bindings/qjs/js_event_listener.cc | 6 +- bridge/bindings/qjs/js_event_listener.h | 6 +- bridge/bindings/qjs/member_installer.cc | 6 +- bridge/bindings/qjs/member_installer.h | 6 +- bridge/bindings/qjs/module_manager.cc | 0 bridge/bindings/qjs/module_manager.h | 0 bridge/bindings/qjs/native_string_utils.cc | 6 +- bridge/bindings/qjs/native_string_utils.h | 6 +- bridge/bindings/qjs/native_value.cc | 0 bridge/bindings/qjs/pending_promises.cc | 7 +- bridge/bindings/qjs/pending_promises.h | 7 +- ...patch_test.cc => qjs_engine_patch_test.cc} | 0 bridge/bindings/qjs/qjs_function.cc | 7 +- bridge/bindings/qjs/qjs_function.h | 7 +- bridge/bindings/qjs/qjs_interface_bridge.cc | 9 - bridge/bindings/qjs/qjs_interface_bridge.h | 6 +- bridge/bindings/qjs/script_promise.cc | 7 +- bridge/bindings/qjs/script_promise.h | 7 +- .../bindings/qjs/script_promise_resolver.cc | 7 +- bridge/bindings/qjs/script_promise_resolver.h | 7 +- bridge/bindings/qjs/script_value.cc | 7 +- bridge/bindings/qjs/script_value.h | 7 +- bridge/bindings/qjs/script_value_test.cc | 5 +- bridge/bindings/qjs/script_wrappable.cc | 6 +- bridge/bindings/qjs/script_wrappable.h | 6 +- bridge/bindings/qjs/source_location.cc | 7 +- bridge/bindings/qjs/source_location.h | 7 +- bridge/bindings/qjs/to_quickjs.h | 7 +- bridge/bindings/qjs/wrapper_type_info.h | 7 +- .../core/css/legacy/css_style_declaration.cc | 7 +- .../core/css/legacy/css_style_declaration.h | 7 +- bridge/core/dart_methods.h | 5 +- bridge/core/dom/binding_object.cc | 5 +- bridge/core/dom/binding_object.h | 5 +- bridge/core/dom/character_data.cc | 5 +- bridge/core/dom/character_data.h | 5 +- bridge/core/dom/child_node_list.cc | 5 +- bridge/core/dom/child_node_list.h | 5 +- bridge/core/dom/collection_index_cache.h | 5 +- bridge/core/dom/comment.cc | 7 +- bridge/core/dom/comment.h | 7 +- bridge/core/dom/container_node.cc | 5 +- bridge/core/dom/container_node.h | 5 +- bridge/core/dom/document.cc | 7 +- bridge/core/dom/document.h | 7 +- bridge/core/dom/document_fragment.cc | 7 +- bridge/core/dom/document_fragment.h | 7 +- bridge/core/dom/document_test.cc | 7 +- bridge/core/dom/element.cc | 7 +- bridge/core/dom/element.h | 7 +- bridge/core/dom/element_traversal.h | 7 +- bridge/core/dom/empty_node_list.cc | 5 +- bridge/core/dom/empty_node_list.h | 5 +- bridge/core/dom/events/custom_event.cc | 7 +- bridge/core/dom/events/custom_event.h | 7 +- bridge/core/dom/events/event.cc | 7 +- bridge/core/dom/events/event.h | 7 +- bridge/core/dom/events/event_listener.h | 7 +- bridge/core/dom/events/event_listener_map.cc | 7 +- bridge/core/dom/events/event_listener_map.h | 7 +- bridge/core/dom/events/event_target.cc | 7 +- bridge/core/dom/events/event_target.h | 7 +- bridge/core/dom/events/event_target_impl.cc | 7 +- bridge/core/dom/events/event_target_impl.h | 7 +- bridge/core/dom/events/event_target_test.cc | 7 +- .../dom/events/registered_eventListener.cc | 7 +- .../dom/events/registered_eventListener.h | 7 +- .../dom/frame_request_callback_collection.cc | 7 +- .../dom/frame_request_callback_collection.h | 7 +- .../core/dom/legacy/bounding_client_rect.cc | 5 +- bridge/core/dom/legacy/bounding_client_rect.h | 5 +- bridge/core/dom/legacy/element_attributes.cc | 5 +- bridge/core/dom/legacy/element_attributes.h | 5 +- bridge/core/dom/legacy/space_split_string.cc | 5 +- bridge/core/dom/legacy/space_split_string.h | 5 +- bridge/core/dom/node.cc | 5 +- bridge/core/dom/node.h | 5 +- bridge/core/dom/node_data.cc | 5 +- bridge/core/dom/node_data.h | 5 +- bridge/core/dom/node_list.h | 5 +- bridge/core/dom/node_traversal.cc | 5 +- bridge/core/dom/node_traversal.h | 5 +- .../core/dom/scripted_animation_controller.cc | 5 +- .../core/dom/scripted_animation_controller.h | 5 +- bridge/core/dom/text.cc | 5 +- bridge/core/dom/text.h | 5 +- bridge/core/dom/tree_scope.cc | 5 +- bridge/core/dom/tree_scope.h | 5 +- bridge/core/events/error_event.cc | 7 +- bridge/core/events/error_event.h | 7 +- bridge/core/events/message_event.cc | 5 +- bridge/core/events/message_event.h | 5 +- bridge/core/executing_context.cc | 7 +- bridge/core/executing_context.h | 7 +- bridge/core/executing_context_data.cc | 7 +- bridge/core/executing_context_data.h | 7 +- bridge/core/fileapi/array_buffer_data.h | 7 +- bridge/core/fileapi/blob.cc | 7 +- bridge/core/fileapi/blob.h | 7 +- bridge/core/fileapi/blob_part.cc | 7 +- bridge/core/fileapi/blob_part.h | 7 +- bridge/core/fileapi/blob_property_bag.cc | 7 +- bridge/core/fileapi/blob_property_bag.h | 7 +- bridge/core/frame/console.cc | 7 +- bridge/core/frame/console.h | 7 +- bridge/core/frame/dom_timer.cc | 7 +- bridge/core/frame/dom_timer.h | 7 +- bridge/core/frame/dom_timer_coordinator.cc | 7 +- bridge/core/frame/legacy/location.cc | 7 +- bridge/core/frame/legacy/location.h | 7 +- bridge/core/frame/module_callback.cc | 7 +- bridge/core/frame/module_callback.h | 7 +- .../core/frame/module_callback_coordinator.cc | 7 +- .../core/frame/module_callback_coordinator.h | 7 +- bridge/core/frame/module_listener.cc | 7 +- bridge/core/frame/module_listener.h | 7 +- .../core/frame/module_listener_container.cc | 7 +- bridge/core/frame/module_listener_container.h | 7 +- bridge/core/frame/module_manager.cc | 7 +- bridge/core/frame/module_manager.h | 7 +- bridge/core/frame/screen.cc | 5 +- bridge/core/frame/screen.h | 7 +- bridge/core/frame/window.cc | 5 +- bridge/core/frame/window.h | 7 +- .../frame/window_or_worker_global_scope.cc | 7 +- .../frame/window_or_worker_global_scope.h | 7 +- .../core/html/canvas/html_canvas_element.cc | 7 +- bridge/core/html/canvas/html_canvas_element.h | 7 +- bridge/core/html/forms/html_input_element.cc | 7 +- bridge/core/html/forms/html_input_element.h | 7 +- .../core/html/forms/html_textarea_element.cc | 7 +- .../core/html/forms/html_textarea_element.h | 7 +- bridge/core/html/html_anchor_element.cc | 7 +- bridge/core/html/html_anchor_element.h | 7 +- bridge/core/html/html_body_element.cc | 7 +- bridge/core/html/html_body_element.h | 7 +- bridge/core/html/html_collection.cc | 5 +- bridge/core/html/html_collection.h | 5 +- bridge/core/html/html_div_element.cc | 5 +- bridge/core/html/html_div_element.h | 5 +- bridge/core/html/html_element.cc | 5 +- bridge/core/html/html_element.h | 5 +- bridge/core/html/html_head_element.cc | 7 +- bridge/core/html/html_head_element.h | 7 +- bridge/core/html/html_html_element.cc | 7 +- bridge/core/html/html_html_element.h | 7 +- bridge/core/html/html_image_element.cc | 7 +- bridge/core/html/html_image_element.h | 7 +- bridge/core/html/html_script_element.cc | 7 +- bridge/core/html/html_script_element.h | 7 +- bridge/core/html/html_template_element.cc | 7 +- bridge/core/html/html_template_element.h | 7 +- bridge/core/html/html_unknown_element.cc | 7 +- bridge/core/html/html_unknown_element.h | 7 +- bridge/core/html/parser/html_parser.cc | 5 +- bridge/core/html/parser/html_parser.h | 7 +- bridge/core/page.cc | 7 +- bridge/core/script_state.cc | 7 +- bridge/core/script_state.h | 7 +- bridge/dart_methods.cc | 0 bridge/foundation/ascii_types.h | 7 +- bridge/foundation/casting.h | 6 +- bridge/foundation/closure.h | 0 bridge/foundation/colors.h | 5 +- bridge/foundation/logging.cc | 5 +- bridge/foundation/logging.h | 5 +- bridge/foundation/macros.h | 6 +- bridge/foundation/native_string.cc | 7 +- bridge/foundation/native_string.h | 7 +- bridge/foundation/native_type.h | 7 +- bridge/foundation/native_value.cc | 7 +- bridge/foundation/native_value_converter.cc | 5 +- bridge/foundation/native_value_converter.h | 7 +- bridge/foundation/ref_counted_internal.h | 5 +- bridge/foundation/ref_counter.h | 5 +- bridge/foundation/ref_ptr.h | 5 +- bridge/foundation/ref_ptr_internal.h | 5 +- bridge/foundation/string_view.cc | 7 +- bridge/foundation/string_view.h | 7 +- bridge/foundation/ui_command_buffer.cc | 5 +- bridge/foundation/ui_command_buffer.h | 5 +- bridge/foundation/ui_task_queue.h | 5 +- bridge/include/webf_bridge.h | 9 +- bridge/include/webf_bridge_test.h | 11 +- bridge/page.cc | 0 bridge/page_test.cc | 0 bridge/page_test.h | 0 bridge/polyfill/src/bridge.ts | 13 + .../static/idl_templates/base.cc.tpl | 5 +- .../static/idl_templates/base.h.tpl | 5 +- .../json_templates/element_factory.cc.tpl | 5 +- .../json_templates/element_factory.h.tpl | 5 +- bridge/test/kraken_test_context.cc | 6 +- bridge/test/kraken_test_context.h | 6 +- bridge/webf_bridge.cc | 5 +- bridge/webf_bridge_test.cc | 5 +- 271 files changed, 646 insertions(+), 1712 deletions(-) delete mode 100644 bridge/bindings/qjs/bom/blob.cc delete mode 100644 bridge/bindings/qjs/bom/blob.h delete mode 100644 bridge/bindings/qjs/bom/console.cc delete mode 100644 bridge/bindings/qjs/bom/console.h delete mode 100644 bridge/bindings/qjs/bom/dom_timer_coordinator.cc delete mode 100644 bridge/bindings/qjs/bom/location.cc delete mode 100644 bridge/bindings/qjs/bom/location.h delete mode 100644 bridge/bindings/qjs/dom/all_collection.cc delete mode 100644 bridge/bindings/qjs/dom/all_collection.h delete mode 100644 bridge/bindings/qjs/dom/comment_node.cc delete mode 100644 bridge/bindings/qjs/dom/comment_node.h delete mode 100644 bridge/bindings/qjs/dom/css_property_list.h delete mode 100644 bridge/bindings/qjs/dom/custom_event.cc delete mode 100644 bridge/bindings/qjs/dom/custom_event.h delete mode 100644 bridge/bindings/qjs/dom/document.cc delete mode 100644 bridge/bindings/qjs/dom/document.h delete mode 100644 bridge/bindings/qjs/dom/document_fragment.cc delete mode 100644 bridge/bindings/qjs/dom/document_fragment.h delete mode 100644 bridge/bindings/qjs/dom/document_test.cc delete mode 100644 bridge/bindings/qjs/dom/element.cc delete mode 100644 bridge/bindings/qjs/dom/element.h delete mode 100644 bridge/bindings/qjs/dom/elements/image_element.cc delete mode 100644 bridge/bindings/qjs/dom/elements/image_element.h delete mode 100644 bridge/bindings/qjs/dom/event.cc delete mode 100644 bridge/bindings/qjs/dom/event.h delete mode 100644 bridge/bindings/qjs/dom/event_listener_map.cc delete mode 100644 bridge/bindings/qjs/dom/event_listener_map.h delete mode 100644 bridge/bindings/qjs/dom/event_target.cc delete mode 100644 bridge/bindings/qjs/dom/event_target.h delete mode 100644 bridge/bindings/qjs/dom/event_target_test.cc delete mode 100644 bridge/bindings/qjs/dom/event_type_names.cc delete mode 100644 bridge/bindings/qjs/dom/event_type_names.h delete mode 100644 bridge/bindings/qjs/dom/frame_request_callback_collection.cc delete mode 100644 bridge/bindings/qjs/dom/frame_request_callback_collection.h delete mode 100644 bridge/bindings/qjs/dom/node.cc delete mode 100644 bridge/bindings/qjs/dom/node.h delete mode 100644 bridge/bindings/qjs/dom/style_declaration.cc delete mode 100644 bridge/bindings/qjs/executing_context.cc delete mode 100644 bridge/bindings/qjs/executing_context.h delete mode 100644 bridge/bindings/qjs/garbage_collected.h delete mode 100644 bridge/bindings/qjs/garbage_collected_test.cc delete mode 100644 bridge/bindings/qjs/host_class.h delete mode 100644 bridge/bindings/qjs/host_class_test.cc delete mode 100644 bridge/bindings/qjs/host_object.cc delete mode 100644 bridge/bindings/qjs/host_object.h delete mode 100644 bridge/bindings/qjs/host_object_test.cc delete mode 100644 bridge/bindings/qjs/html_parser.cc delete mode 100644 bridge/bindings/qjs/html_parser.h delete mode 100644 bridge/bindings/qjs/module_manager.cc delete mode 100644 bridge/bindings/qjs/module_manager.h delete mode 100644 bridge/bindings/qjs/native_value.cc rename bridge/bindings/qjs/{qjs_patch_test.cc => qjs_engine_patch_test.cc} (100%) delete mode 100644 bridge/bindings/qjs/qjs_interface_bridge.cc delete mode 100644 bridge/dart_methods.cc delete mode 100644 bridge/foundation/closure.h delete mode 100644 bridge/page.cc delete mode 100644 bridge/page_test.cc delete mode 100644 bridge/page_test.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 400aaecdd9..6ed1e16f1d 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -72,7 +72,6 @@ endif() list(APPEND BRIDGE_SOURCE webf_bridge.cc ${CMAKE_CURRENT_SOURCE_DIR}/include/webf_bridge.h - ${CMAKE_CURRENT_SOURCE_DIR}/include/webf_foundation.h foundation/logging.cc foundation/logging.h foundation/colors.h @@ -222,7 +221,6 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") bindings/qjs/script_promise.cc bindings/qjs/script_promise.h bindings/qjs/to_quickjs.h - bindings/qjs/qjs_interface_bridge.cc bindings/qjs/qjs_interface_bridge.h bindings/qjs/script_promise_resolver.cc bindings/qjs/script_promise_resolver.h diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index dc24878101..d7a9019d36 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -1,5 +1,6 @@ /* - * Copyright (C) 2021-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 "atomic_string.h" diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 4e213b292e..3d2b1aff9e 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ #define KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ diff --git a/bridge/bindings/qjs/atomic_string_test.cc b/bridge/bindings/qjs/atomic_string_test.cc index e9ba3e389a..8c1c9db9ac 100644 --- a/bridge/bindings/qjs/atomic_string_test.cc +++ b/bridge/bindings/qjs/atomic_string_test.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "atomic_string.h" #include <quickjs/quickjs.h> diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 9e68e3a854..40820da636 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* 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" diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h index fca2265bf1..05a957773c 100644 --- a/bridge/bindings/qjs/binding_initializer.h +++ b/bridge/bindings/qjs/binding_initializer.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDING_INITIALIZER_H #define KRAKENBRIDGE_BINDING_INITIALIZER_H diff --git a/bridge/bindings/qjs/bom/blob.cc b/bridge/bindings/qjs/bom/blob.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/bom/blob.h b/bridge/bindings/qjs/bom/blob.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/bom/console.cc b/bridge/bindings/qjs/bom/console.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/bom/console.h b/bridge/bindings/qjs/bom/console.h deleted file mode 100644 index e69de29bb2..0000000000 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 e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/bom/location.cc b/bridge/bindings/qjs/bom/location.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/bom/location.h b/bridge/bindings/qjs/bom/location.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/converter.h b/bridge/bindings/qjs/converter.h index 1d76de01c6..5592aead3a 100644 --- a/bridge/bindings/qjs/converter.h +++ b/bridge/bindings/qjs/converter.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CONVERTER_H #define KRAKENBRIDGE_CONVERTER_H diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 2e5acc60bb..17139dea79 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ #define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/cppgc/garbage_collected.h b/bridge/bindings/qjs/cppgc/garbage_collected.h index c0caed2a6d..0bb2cc4658 100644 --- a/bridge/bindings/qjs/cppgc/garbage_collected.h +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_GARBAGE_COLLECTED_H #define KRAKENBRIDGE_GARBAGE_COLLECTED_H diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.cc b/bridge/bindings/qjs/cppgc/gc_visitor.cc index 5826531338..5d7595bee2 100644 --- a/bridge/bindings/qjs/cppgc/gc_visitor.cc +++ b/bridge/bindings/qjs/cppgc/gc_visitor.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.h b/bridge/bindings/qjs/cppgc/gc_visitor.h index 230e6a736e..ce1a3562c3 100644 --- a/bridge/bindings/qjs/cppgc/gc_visitor.h +++ b/bridge/bindings/qjs/cppgc/gc_visitor.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_GC_VISITOR_H #define KRAKENBRIDGE_GC_VISITOR_H diff --git a/bridge/bindings/qjs/cppgc/local_handle.h b/bridge/bindings/qjs/cppgc/local_handle.h index 1f7d85c1ba..b93f6020e7 100644 --- a/bridge/bindings/qjs/cppgc/local_handle.h +++ b/bridge/bindings/qjs/cppgc/local_handle.h @@ -1,5 +1,6 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index 20e21c8878..10c3926791 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -1,5 +1,6 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.cc b/bridge/bindings/qjs/cppgc/mutation_scope.cc index 1679eff203..e947e02c09 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.cc +++ b/bridge/bindings/qjs/cppgc/mutation_scope.cc @@ -1,5 +1,6 @@ /* - * Copyright (C) 2021-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 "mutation_scope.h" diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.h b/bridge/bindings/qjs/cppgc/mutation_scope.h index 14fefbc593..a68e11f98e 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.h +++ b/bridge/bindings/qjs/cppgc/mutation_scope.h @@ -1,5 +1,6 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ diff --git a/bridge/bindings/qjs/dictionary_base.cc b/bridge/bindings/qjs/dictionary_base.cc index bc2ff2c4fe..9dd0898d0c 100644 --- a/bridge/bindings/qjs/dictionary_base.cc +++ b/bridge/bindings/qjs/dictionary_base.cc @@ -1,7 +1,7 @@ /* - * Copyright (C) 2022 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #include "dictionary_base.h" diff --git a/bridge/bindings/qjs/dictionary_base.h b/bridge/bindings/qjs/dictionary_base.h index e1e75631b9..3473d4033b 100644 --- a/bridge/bindings/qjs/dictionary_base.h +++ b/bridge/bindings/qjs/dictionary_base.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2022 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ diff --git a/bridge/bindings/qjs/dom/all_collection.cc b/bridge/bindings/qjs/dom/all_collection.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/all_collection.h b/bridge/bindings/qjs/dom/all_collection.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/comment_node.cc b/bridge/bindings/qjs/dom/comment_node.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/comment_node.h b/bridge/bindings/qjs/dom/comment_node.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/css_property_list.h b/bridge/bindings/qjs/dom/css_property_list.h deleted file mode 100644 index 646ebb3135..0000000000 --- a/bridge/bindings/qjs/dom/css_property_list.h +++ /dev/null @@ -1,436 +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_CSS_PROPERTY_LIST_H_ -#define BRIDGE_BINDINGS_QJS_DOM_CSS_PROPERTY_LIST_H_ - -#include <string> -#include <unordered_map> - -namespace webf { - -std::unordered_map<std::string, bool> cssPropertyList{{"accentColor", true}, - {"additiveSymbols", true}, - {"alignContent", true}, - {"alignItems", true}, - {"alignSelf", true}, - {"alignmentBaseline", true}, - {"all", true}, - {"animation", true}, - {"animationDelay", true}, - {"animationDirection", true}, - {"animationDuration", true}, - {"animationFillMode", true}, - {"animationIterationCount", true}, - {"animationName", true}, - {"animationPlayState", true}, - {"animationTimingFunction", true}, - {"appRegion", true}, - {"appearance", true}, - {"ascentOverride", true}, - {"aspectRatio", true}, - {"backdropFilter", true}, - {"backfaceVisibility", true}, - {"background", true}, - {"backgroundAttachment", true}, - {"backgroundBlendMode", true}, - {"backgroundClip", true}, - {"backgroundColor", true}, - {"backgroundImage", true}, - {"backgroundOrigin", true}, - {"backgroundPosition", true}, - {"backgroundPositionX", true}, - {"backgroundPositionY", true}, - {"backgroundRepeat", true}, - {"backgroundRepeatX", true}, - {"backgroundRepeatY", true}, - {"backgroundSize", true}, - {"baselineShift", true}, - {"blockSize", true}, - {"border", true}, - {"borderBlock", true}, - {"borderBlockColor", true}, - {"borderBlockEnd", true}, - {"borderBlockEndColor", true}, - {"borderBlockEndStyle", true}, - {"borderBlockEndWidth", true}, - {"borderBlockStart", true}, - {"borderBlockStartColor", true}, - {"borderBlockStartStyle", true}, - {"borderBlockStartWidth", true}, - {"borderBlockStyle", true}, - {"borderBlockWidth", true}, - {"borderBottom", true}, - {"borderBottomColor", true}, - {"borderBottomLeftRadius", true}, - {"borderBottomRightRadius", true}, - {"borderBottomStyle", true}, - {"borderBottomWidth", true}, - {"borderCollapse", true}, - {"borderColor", true}, - {"borderEndEndRadius", true}, - {"borderEndStartRadius", true}, - {"borderImage", true}, - {"borderImageOutset", true}, - {"borderImageRepeat", true}, - {"borderImageSlice", true}, - {"borderImageSource", true}, - {"borderImageWidth", true}, - {"borderInline", true}, - {"borderInlineColor", true}, - {"borderInlineEnd", true}, - {"borderInlineEndColor", true}, - {"borderInlineEndStyle", true}, - {"borderInlineEndWidth", true}, - {"borderInlineStart", true}, - {"borderInlineStartColor", true}, - {"borderInlineStartStyle", true}, - {"borderInlineStartWidth", true}, - {"borderInlineStyle", true}, - {"borderInlineWidth", true}, - {"borderLeft", true}, - {"borderLeftColor", true}, - {"borderLeftStyle", true}, - {"borderLeftWidth", true}, - {"borderRadius", true}, - {"borderRight", true}, - {"borderRightColor", true}, - {"borderRightStyle", true}, - {"borderRightWidth", true}, - {"borderSpacing", true}, - {"borderStartEndRadius", true}, - {"borderStartStartRadius", true}, - {"borderStyle", true}, - {"borderTop", true}, - {"borderTopColor", true}, - {"borderTopLeftRadius", true}, - {"borderTopRightRadius", true}, - {"borderTopStyle", true}, - {"borderTopWidth", true}, - {"borderWidth", true}, - {"bottom", true}, - {"boxShadow", true}, - {"boxSizing", true}, - {"breakAfter", true}, - {"breakBefore", true}, - {"breakInside", true}, - {"bufferedRendering", true}, - {"captionSide", true}, - {"caretColor", true}, - {"clear", true}, - {"clip", true}, - {"clipPath", true}, - {"clipRule", true}, - {"color", true}, - {"colorInterpolation", true}, - {"colorInterpolationFilters", true}, - {"colorRendering", true}, - {"colorScheme", true}, - {"columnCount", true}, - {"columnFill", true}, - {"columnGap", true}, - {"columnRule", true}, - {"columnRuleColor", true}, - {"columnRuleStyle", true}, - {"columnRuleWidth", true}, - {"columnSpan", true}, - {"columnWidth", true}, - {"columns", true}, - {"content", true}, - {"contentVisibility", true}, - {"counterIncrement", true}, - {"counterReset", true}, - {"counterSet", true}, - {"cursor", true}, - {"cx", true}, - {"cy", true}, - {"d", true}, - {"descentOverride", true}, - {"direction", true}, - {"display", true}, - {"dominantBaseline", true}, - {"emptyCells", true}, - {"fallback", true}, - {"fill", true}, - {"fillOpacity", true}, - {"fillRule", true}, - {"filter", true}, - {"flex", true}, - {"flexBasis", true}, - {"flexDirection", true}, - {"flexFlow", true}, - {"flexGrow", true}, - {"flexShrink", true}, - {"flexWrap", true}, - {"float", true}, - {"floodColor", true}, - {"floodOpacity", true}, - {"font", true}, - {"fontDisplay", true}, - {"fontFamily", true}, - {"fontFeatureSettings", true}, - {"fontKerning", true}, - {"fontOpticalSizing", true}, - {"fontSize", true}, - {"fontStretch", true}, - {"fontStyle", true}, - {"fontSynthesis", true}, - {"fontSynthesisSmallCaps", true}, - {"fontSynthesisStyle", true}, - {"fontSynthesisWeight", true}, - {"fontVariant", true}, - {"fontVariantCaps", true}, - {"fontVariantEastAsian", true}, - {"fontVariantLigatures", true}, - {"fontVariantNumeric", true}, - {"fontVariationSettings", true}, - {"fontWeight", true}, - {"forcedColorAdjust", true}, - {"gap", true}, - {"grid", true}, - {"gridArea", true}, - {"gridAutoColumns", true}, - {"gridAutoFlow", true}, - {"gridAutoRows", true}, - {"gridColumn", true}, - {"gridColumnEnd", true}, - {"gridColumnGap", true}, - {"gridColumnStart", true}, - {"gridGap", true}, - {"gridRow", true}, - {"gridRowEnd", true}, - {"gridRowGap", true}, - {"gridRowStart", true}, - {"gridTemplate", true}, - {"gridTemplateAreas", true}, - {"gridTemplateColumns", true}, - {"gridTemplateRows", true}, - {"height", true}, - {"hyphens", true}, - {"imageOrientation", true}, - {"imageRendering", true}, - {"inherits", true}, - {"initialValue", true}, - {"inlineSize", true}, - {"inset", true}, - {"insetBlock", true}, - {"insetBlockEnd", true}, - {"insetBlockStart", true}, - {"insetInline", true}, - {"insetInlineEnd", true}, - {"insetInlineStart", true}, - {"isolation", true}, - {"justifyContent", true}, - {"justifyItems", true}, - {"justifySelf", true}, - {"left", true}, - {"letterSpacing", true}, - {"lightingColor", true}, - {"lineBreak", true}, - {"lineGapOverride", true}, - {"lineHeight", true}, - {"listStyle", true}, - {"listStyleImage", true}, - {"listStylePosition", true}, - {"listStyleType", true}, - {"margin", true}, - {"marginBlock", true}, - {"marginBlockEnd", true}, - {"marginBlockStart", true}, - {"marginBottom", true}, - {"marginInline", true}, - {"marginInlineEnd", true}, - {"marginInlineStart", true}, - {"marginLeft", true}, - {"marginRight", true}, - {"marginTop", true}, - {"marker", true}, - {"markerEnd", true}, - {"markerMid", true}, - {"markerStart", true}, - {"mask", true}, - {"maskType", true}, - {"maxBlockSize", true}, - {"maxHeight", true}, - {"maxInlineSize", true}, - {"maxWidth", true}, - {"maxZoom", true}, - {"minBlockSize", true}, - {"minHeight", true}, - {"minInlineSize", true}, - {"minWidth", true}, - {"minZoom", true}, - {"mixBlendMode", true}, - {"negative", true}, - {"objectFit", true}, - {"objectPosition", true}, - {"offset", true}, - {"offsetDistance", true}, - {"offsetPath", true}, - {"offsetRotate", true}, - {"opacity", true}, - {"order", true}, - {"orientation", true}, - {"orphans", true}, - {"outline", true}, - {"outlineColor", true}, - {"outlineOffset", true}, - {"outlineStyle", true}, - {"outlineWidth", true}, - {"overflow", true}, - {"overflowAnchor", true}, - {"overflowClipMargin", true}, - {"overflowWrap", true}, - {"overflowX", true}, - {"overflowY", true}, - {"overscrollBehavior", true}, - {"overscrollBehaviorBlock", true}, - {"overscrollBehaviorInline", true}, - {"overscrollBehaviorX", true}, - {"overscrollBehaviorY", true}, - {"pad", true}, - {"padding", true}, - {"paddingBlock", true}, - {"paddingBlockEnd", true}, - {"paddingBlockStart", true}, - {"paddingBottom", true}, - {"paddingInline", true}, - {"paddingInlineEnd", true}, - {"paddingInlineStart", true}, - {"paddingLeft", true}, - {"paddingRight", true}, - {"paddingTop", true}, - {"page", true}, - {"pageBreakAfter", true}, - {"pageBreakBefore", true}, - {"pageBreakInside", true}, - {"pageOrientation", true}, - {"paintOrder", true}, - {"perspective", true}, - {"perspectiveOrigin", true}, - {"placeContent", true}, - {"placeItems", true}, - {"placeSelf", true}, - {"pointerEvents", true}, - {"position", true}, - {"prefix", true}, - {"quotes", true}, - {"r", true}, - {"range", true}, - {"resize", true}, - {"right", true}, - {"rowGap", true}, - {"rubyPosition", true}, - {"rx", true}, - {"ry", true}, - {"scrollBehavior", true}, - {"scrollMargin", true}, - {"scrollMarginBlock", true}, - {"scrollMarginBlockEnd", true}, - {"scrollMarginBlockStart", true}, - {"scrollMarginBottom", true}, - {"scrollMarginInline", true}, - {"scrollMarginInlineEnd", true}, - {"scrollMarginInlineStart", true}, - {"scrollMarginLeft", true}, - {"scrollMarginRight", true}, - {"scrollMarginTop", true}, - {"scrollPadding", true}, - {"scrollPaddingBlock", true}, - {"scrollPaddingBlockEnd", true}, - {"scrollPaddingBlockStart", true}, - {"scrollPaddingBottom", true}, - {"scrollPaddingInline", true}, - {"scrollPaddingInlineEnd", true}, - {"scrollPaddingInlineStart", true}, - {"scrollPaddingLeft", true}, - {"scrollPaddingRight", true}, - {"scrollPaddingTop", true}, - {"scrollSnapAlign", true}, - {"scrollSnapStop", true}, - {"scrollSnapType", true}, - {"scrollbarGutter", true}, - {"shapeImageThreshold", true}, - {"shapeMargin", true}, - {"shapeOutside", true}, - {"shapeRendering", true}, - {"size", true}, - {"sizeAdjust", true}, - {"speak", true}, - {"speakAs", true}, - {"src", true}, - {"stopColor", true}, - {"stopOpacity", true}, - {"stroke", true}, - {"strokeDasharray", true}, - {"strokeDashoffset", true}, - {"strokeLinecap", true}, - {"strokeLinejoin", true}, - {"strokeMiterlimit", true}, - {"strokeOpacity", true}, - {"strokeWidth", true}, - {"suffix", true}, - {"symbols", true}, - {"syntax", true}, - {"system", true}, - {"tabSize", true}, - {"tableLayout", true}, - {"textAlign", true}, - {"textAlignLast", true}, - {"textAnchor", true}, - {"textCombineUpright", true}, - {"textDecoration", true}, - {"textDecorationColor", true}, - {"textDecorationLine", true}, - {"textDecorationSkipInk", true}, - {"textDecorationStyle", true}, - {"textDecorationThickness", true}, - {"textEmphasis", true}, - {"textEmphasisColor", true}, - {"textEmphasisPosition", true}, - {"textEmphasisStyle", true}, - {"textIndent", true}, - {"textOrientation", true}, - {"textOverflow", true}, - {"textRendering", true}, - {"textShadow", true}, - {"textSizeAdjust", true}, - {"textTransform", true}, - {"textUnderlineOffset", true}, - {"textUnderlinePosition", true}, - {"top", true}, - {"touchAction", true}, - {"transform", true}, - {"transformBox", true}, - {"transformOrigin", true}, - {"transformStyle", true}, - {"transition", true}, - {"transitionDelay", true}, - {"transitionDuration", true}, - {"transitionProperty", true}, - {"transitionTimingFunction", true}, - {"unicodeBidi", true}, - {"unicodeRange", true}, - {"userSelect", true}, - {"userZoom", true}, - {"vectorEffect", true}, - {"verticalAlign", true}, - {"visibility", true}, - {"whiteSpace", true}, - {"widows", true}, - {"width", true}, - {"willChange", true}, - {"wordBreak", true}, - {"wordSpacing", true}, - {"wordWrap", true}, - {"writingMode", true}, - {"x", true}, - {"y", true}, - {"zIndex", true}, - {"zoom", true}}; - -} // namespace webf - -#endif // BRIDGE_BINDINGS_QJS_DOM_CSS_PROPERTY_LIST_H_ diff --git a/bridge/bindings/qjs/dom/custom_event.cc b/bridge/bindings/qjs/dom/custom_event.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/custom_event.h b/bridge/bindings/qjs/dom/custom_event.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/bindings/qjs/dom/document.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/document_fragment.cc b/bridge/bindings/qjs/dom/document_fragment.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/document_fragment.h b/bridge/bindings/qjs/dom/document_fragment.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/document_test.cc b/bridge/bindings/qjs/dom/document_test.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h deleted file mode 100644 index e69de29bb2..0000000000 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 e69de29bb2..0000000000 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 e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/event.cc b/bridge/bindings/qjs/dom/event.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/event.h b/bridge/bindings/qjs/dom/event.h deleted file mode 100644 index e69de29bb2..0000000000 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 e69de29bb2..0000000000 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 e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/bindings/qjs/dom/event_target.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/bindings/qjs/dom/event_target.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/event_target_test.cc b/bridge/bindings/qjs/dom/event_target_test.cc deleted file mode 100644 index e69de29bb2..0000000000 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 e69de29bb2..0000000000 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 e69de29bb2..0000000000 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 e69de29bb2..0000000000 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 e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/node.cc b/bridge/bindings/qjs/dom/node.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/node.h b/bridge/bindings/qjs/dom/node.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/style_declaration.cc b/bridge/bindings/qjs/dom/style_declaration.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/exception_message.cc b/bridge/bindings/qjs/exception_message.cc index 195b312b6c..935720127c 100644 --- a/bridge/bindings/qjs/exception_message.cc +++ b/bridge/bindings/qjs/exception_message.cc @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* 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 <vector> diff --git a/bridge/bindings/qjs/exception_message.h b/bridge/bindings/qjs/exception_message.h index 8abf01e3b4..29032dec45 100644 --- a/bridge/bindings/qjs/exception_message.h +++ b/bridge/bindings/qjs/exception_message.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index c9695df238..925877a472 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 kraken { diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index b314be12dc..c901134875 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_EXCEPTION_STATE_H #define KRAKENBRIDGE_EXCEPTION_STATE_H diff --git a/bridge/bindings/qjs/executing_context.cc b/bridge/bindings/qjs/executing_context.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/executing_context.h b/bridge/bindings/qjs/executing_context.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/garbage_collected.h b/bridge/bindings/qjs/garbage_collected.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/garbage_collected_test.cc b/bridge/bindings/qjs/garbage_collected_test.cc deleted file mode 100644 index 3badad68e9..0000000000 --- a/bridge/bindings/qjs/garbage_collected_test.cc +++ /dev/null @@ -1,587 +0,0 @@ -///* -// * Copyright (C) 2021 Alibaba Inc. All rights reserved. -// * Author: Kraken Team. -// */ -// -//#include "host_class.h" -//#include <unordered_map> -//#include "gtest/gtest.h" -//#include "kraken_test_env.h" -//#include "page.h" -// -// namespace kraken { -// -// class ParentClass : public HostClass { -// public: -// explicit ParentClass(ExecutionContext* context) : HostClass(context, "ParentClass") {} -// JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValueConst* argv) -// override { return HostClass::instanceConstructor(ctx, func_obj, this_val, argc, argv); -// } -// -// OBJECT_INSTANCE(ParentClass); -// -// static JSValue foo(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, -// 20); } -// -// private: -// ObjectFunction m_foo{m_context, m_prototypeObject, "foo", foo, 0}; -//}; -// -// class SampleClass; -// static JSClassID kSampleClassId{0}; -// -// class SampleClassInstance : public Instance { -// public: -// explicit SampleClassInstance(HostClass* sampleClass) : Instance(sampleClass, "SampleClass", nullptr, kSampleClassId, -// finalizer){}; -// -// private: -// static void finalizer(JSRuntime* rt, JSValue v) { -// auto* instance = static_cast<SampleClassInstance*>(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<SampleClass*>(JS_GetOpaque(func_obj, ExecutionContext::kHostClassClassId)); -// auto* instance = new SampleClassInstance(sampleClass); -// return instance->jsObject; -// } -// ~SampleClass() {} -// -// private: -// static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { return JS_NewFloat64(ctx, -// 10); } -// -// ObjectFunction m_f{m_context, m_prototypeObject, "f", f, 0}; -//}; -// -// TEST(HostClass, newInstance) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "10"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// auto& context = bridge->getContext(); -// auto* sampleObject = new SampleClass(context.get()); -// auto* parentObject = ParentClass::instance(context.get()); -// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); -// context->defineGlobalProperty("ParentClass", parentObject->jsObject); -// const char* code = "let obj = new SampleClass(1,2,3,4); console.log(obj.f())"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} -// -// TEST(HostClass, instanceOf) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "true"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// errorCalled = true; -// KRAKEN_LOG(VERBOSE) << errmsg; -// }); -// auto& context = bridge->getContext(); -// auto* sampleObject = new SampleClass(context.get()); -// auto* parentObject = ParentClass::instance(context.get()); -// // Test for C API -// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); -// context->defineGlobalProperty("ParentClass", parentObject->jsObject); -// JSValue args[] = {}; -// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); -// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, parentObject->jsObject); -// EXPECT_EQ(isInstanceof, true); -// JS_FreeValue(context->ctx(), object); -// -// // Test with Javascript -// const char* code = "let obj = new SampleClass(1,2,3,4); \n console.log(obj instanceof SampleClass)"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} -// -// TEST(HostClass, inheritance) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "20"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// errorCalled = true; -// KRAKEN_LOG(VERBOSE) << errmsg; -// }); -// auto& context = bridge->getContext(); -// auto* sampleObject = new SampleClass(context.get()); -// -// auto* parentObject = ParentClass::instance(context.get()); -// context->defineGlobalProperty("ParentClass", parentObject->jsObject); -// -// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); -// -// const char* code = -// "let obj = new SampleClass(1,2,3,4);\n" -// "console.log(obj.foo())"; -// context->evaluateJavaScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} -// -// TEST(HostClass, inherintanceInJavaScript) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "TEST 10 20"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// errorCalled = true; -// KRAKEN_LOG(VERBOSE) << errmsg; -// }); -// auto& context = bridge->getContext(); -// auto* sampleObject = new SampleClass(context.get()); -// -// auto* parentObject = ParentClass::instance(context.get()); -// context->defineGlobalProperty("ParentClass", parentObject->jsObject); -// -// context->defineGlobalProperty("SampleClass", sampleObject->jsObject); -// -// const char* code = R"( -// class Demo extends SampleClass { -// constructor(name) { -// super(); -// this.name = name; -// } -// -// getName() { -// return this.name.toUpperCase(); -// } -//} -// let demo = new Demo('test'); -// console.log(demo.getName(), demo.f(), demo.foo()); -//)"; -// context->evaluateJavaScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} -// -// TEST(HostClass, haveFunctionProtoMethods) { -// bool static errorCalled = false; -// bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "ƒ ()"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// errorCalled = true; -// KRAKEN_LOG(VERBOSE) << errmsg; -// }); -// auto& context = bridge->getContext(); -// auto* parentObject = ParentClass::instance(context.get()); -// context->defineGlobalProperty("ParentClass", parentObject->jsObject); -// -// const char* code = R"( -// class Demo extends ParentClass { -// constructor(name) { -// super(); -// this.name = name; -// } -// -// getName() { -// return this.name.toUpperCase(); -// } -//} -// console.log(Demo.call); -//)"; -// context->evaluateJavaScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} -// -// TEST(HostClass, multipleInstance) { -// bool static errorCalled = false; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// errorCalled = true; -// KRAKEN_LOG(VERBOSE) << errmsg; -// }); -// auto& context = bridge->getContext(); -// -// auto* parentObject = ParentClass::instance(context.get()); -// context->defineGlobalProperty("ParentClass", parentObject->jsObject); -// -// // Test for C API 1 -// { -// auto* sampleObject = new SampleClass(context.get()); -// context->defineGlobalProperty("SampleClass1", sampleObject->jsObject); -// JSValue args[] = {}; -// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); -// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); -// EXPECT_EQ(isInstanceof, true); -// JS_FreeValue(context->ctx(), object); -// } -// -// // Test for C API 2 -// { -// auto* sampleObject = new SampleClass(context.get()); -// context->defineGlobalProperty("SampleClass2", sampleObject->jsObject); -// JSValue args[] = {}; -// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); -// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); -// EXPECT_EQ(isInstanceof, true); -// JS_FreeValue(context->ctx(), object); -// } -// -// { -// auto* sampleObject = new SampleClass(context.get()); -// context->defineGlobalProperty("SampleClass3", sampleObject->jsObject); -// JSValue args[] = {}; -// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); -// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); -// EXPECT_EQ(isInstanceof, true); -// JS_FreeValue(context->ctx(), object); -// } -// -// { -// auto* sampleObject = new SampleClass(context.get()); -// context->defineGlobalProperty("SampleClass4", sampleObject->jsObject); -// JSValue args[] = {}; -// JSValue object = JS_CallConstructor(context->ctx(), sampleObject->jsObject, 0, args); -// bool isInstanceof = JS_IsInstanceOf(context->ctx(), object, sampleObject->jsObject); -// EXPECT_EQ(isInstanceof, true); -// JS_FreeValue(context->ctx(), object); -// } -// -// EXPECT_EQ(errorCalled, false); -//} -// -// std::once_flag kExoticClassOnceFlag; -// -// class ExoticClassInstance; -// class ExoticClass : public HostClass { -// public: -// static JSClassID exoticClassID; -// ExoticClass() = delete; -// explicit ExoticClass(ExecutionContext* context) : HostClass(context, "ExoticClass") { -// std::call_once(kExoticClassOnceFlag, []() { JS_NewClassID(&exoticClassID); }); -// } -// JSValue instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv); -// -// private: -// friend ExoticClassInstance; -//}; -// -// JSClassID ExoticClass::exoticClassID{0}; -// static bool exoticClassFreed = false; -// -// class ExoticClassInstance : public Instance { -// public: -// ExoticClassInstance() = delete; -// static JSClassExoticMethods methods; -// -// explicit ExoticClassInstance(ExoticClass* exoticClass) : Instance(exoticClass, "ExoticClass", &methods, -// ExoticClass::exoticClassID, finalizer){}; -// -// static JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver) { -// auto* instance = static_cast<ExoticClassInstance*>(JS_GetOpaque(obj, ExoticClass::exoticClassID)); -// auto* prototype = static_cast<ExoticClass*>(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<ExoticClassInstance*>(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<ExoticClassInstance*>(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<ExoticClassInstance*>(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<ExoticClassInstance*>(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<JSAtom, JSValue> m_properties; -// double classValue{100.0}; -//}; -// -// JSClassExoticMethods ExoticClassInstance::methods{nullptr, nullptr, nullptr, nullptr, nullptr, getProperty, -// setProperty}; -// -// JSValue ExoticClass::instanceConstructor(JSContext* ctx, JSValue func_obj, JSValue this_val, int argc, JSValue* argv) -// { -// return (new ExoticClassInstance(this))->jsObject; -//} -// -// TEST(HostClass, exoticClass) { -// bool static errorCalled = false; -// bool static logCalled = false; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "10"); -// }; -// -// auto& context = bridge->getContext(); -// auto* constructor = new ExoticClass(context.get()); -// context->defineGlobalProperty("ExoticClass", constructor->jsObject); -// -// std::string code = -// "globalThis.obj = new ExoticClass();" -// "var key = 'onclick'; " -// "var otherKey = 'o' + 'n' + 'c' + 'l' + 'i' + 'c' + 'k';" -// "obj[key] = function() {return 10;};" -// "console.log(obj[otherKey]());"; -// context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(logCalled, true); -//} -// -// TEST(HostClass, setExoticClassProperty) { -// bool static errorCalled = false; -// bool static logCalled = false; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "200"); -// }; -// -// auto& context = bridge->getContext(); -// auto* constructor = new ExoticClass(context.get()); -// context->defineGlobalProperty("ExoticClass", constructor->jsObject); -// -// std::string code = -// "var obj = new ExoticClass();" -// "obj.className = 200.0;" -// "console.log(obj.className);"; -// context->evaluateJavaScript(code.c_str(), code.size(), "vm://", 0); -// -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(exoticClassFreed, true); -// EXPECT_EQ(logCalled, true); -//} -// -//} // namespace kraken - -///* -// * Copyright (C) 2021 Alibaba Inc. All rights reserved. -// * Author: Kraken Team. -// */ -// -//#include "host_object.h" -//#include <gtest/gtest.h> -//#include "executing_context.h" -//#include "kraken_test_env.h" -//#include "page.h" -// -// namespace kraken { -// -// static bool isSampleFree = false; -// -// class SampleObject : public HostObject { -// public: -// explicit SampleObject(ExecutionContext* context) : HostObject(context, "SampleObject"){}; -// ~SampleObject() { isSampleFree = true; } -// -// private: -// class FooPropertyDescriptor { -// public: -// static JSValue getter(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { -// auto* sampleObject = static_cast<SampleObject*>(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<SampleObject*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// double f; -// JS_ToFloat64(ctx, &f, argv[0]); -// sampleObject->m_foo = f; -// return JS_NULL; -// } -// }; -// -// static JSValue f(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { -// double v; -// JS_ToFloat64(ctx, &v, argv[0]); -// return JS_NewFloat64(ctx, 10 + v); -// } -// -// double m_foo{0}; -// ObjectProperty m_width{m_context, jsObject, "foo", FooPropertyDescriptor::getter, FooPropertyDescriptor::setter}; -// ObjectFunction m_f{m_context, jsObject, "f", f, 1}; -//}; -// -// TEST(HostObject, defineProperty) { -// bool static logCalled = false; -// bool static errorCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// -// EXPECT_STREQ(message.c_str(), "{f: ƒ (), foo: 1}"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); -// auto& context = bridge->getContext(); -// auto* sampleObject = new SampleObject(context.get()); -// JSValue object = sampleObject->jsObject; -// context->defineGlobalProperty("o", object); -// const char* code = "o.foo++; console.log(o);"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(logCalled, true); -// EXPECT_EQ(errorCalled, false); -//} -// -// TEST(ObjectProperty, worksWithProxy) { -// bool static logCalled = false; -// bool static errorCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "0"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// auto& context = bridge->getContext(); -// auto* sampleObject = new SampleObject(context.get()); -// JSValue object = sampleObject->jsObject; -// context->defineGlobalProperty("o", object); -// std::string code = std::string(R"( -// let p = new Proxy(o, { -// get(target, key, receiver) { -// return Reflect.get(target, key, receiver); -// } -//}); -// console.log(p.foo); -//)"); -// bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); -// -// EXPECT_EQ(logCalled, true); -// EXPECT_EQ(errorCalled, false); -//} -// -// TEST(HostObject, defineFunction) { -// bool static logCalled = false; -// bool static errorCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "20"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// auto& context = bridge->getContext(); -// auto* sampleObject = new SampleObject(context.get()); -// JSValue object = sampleObject->jsObject; -// context->defineGlobalProperty("o", object); -// const char* code = "console.log(o.f(10))"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(logCalled, true); -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(isSampleFree, true); -//} -// -// class SampleExoticHostObject : public ExoticHostObject { -// public: -// explicit SampleExoticHostObject(ExecutionContext* context) : ExoticHostObject(context, "SampleObject"){}; -// ~SampleExoticHostObject() { isSampleFree = true; } -// -// JSValue getProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst receiver); -// int setProperty(JSContext* ctx, JSValueConst obj, JSAtom atom, JSValueConst value, JSValueConst receiver, int -// flags); -// -// private: -//}; -// -// JSValue SampleExoticHostObject::getProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue receiver) { -// return JS_NewFloat64(ctx, 100.0); -//} -// int SampleExoticHostObject::setProperty(JSContext* ctx, JSValue obj, JSAtom atom, JSValue value, JSValue receiver, -// int flags) { -// return 0; -//} -// -// TEST(ExoticHostObject, overriteGetterSetter) { -// bool static logCalled = false; -// bool static errorCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { -// logCalled = true; -// EXPECT_STREQ(message.c_str(), "100"); -// }; -// auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; -// errorCalled = true; -// }); -// auto& context = bridge->getContext(); -// auto* sampleObject = new SampleExoticHostObject(context.get()); -// JSValue object = sampleObject->jsObject; -// context->defineGlobalProperty("o", object); -// const char* code = "console.log(o.abc)"; -// bridge->evaluateScript(code, strlen(code), "vm://", 0); -// -// EXPECT_EQ(logCalled, true); -// EXPECT_EQ(errorCalled, false); -// EXPECT_EQ(isSampleFree, true); -//} -// -//} // namespace kraken diff --git a/bridge/bindings/qjs/generated_code_helper.h b/bridge/bindings/qjs/generated_code_helper.h index 9bf8d3bf52..0ffdbc0d36 100644 --- a/bridge/bindings/qjs/generated_code_helper.h +++ b/bridge/bindings/qjs/generated_code_helper.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_GENERATED_CODE_HELPER_H #define KRAKENBRIDGE_GENERATED_CODE_HELPER_H diff --git a/bridge/bindings/qjs/host_class.h b/bridge/bindings/qjs/host_class.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/host_class_test.cc b/bridge/bindings/qjs/host_class_test.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/host_object.cc b/bridge/bindings/qjs/host_object.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/host_object.h b/bridge/bindings/qjs/host_object.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/host_object_test.cc b/bridge/bindings/qjs/host_object_test.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/bindings/qjs/html_parser.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/html_parser.h b/bridge/bindings/qjs/html_parser.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 4f20bc1afb..c12d2e0b9c 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc index a15b9242b7..0a66bb91e2 100644 --- a/bridge/bindings/qjs/js_based_event_listener.cc +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* 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" diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index 5674320164..1f56446ab3 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index 235749cef7..cb692de888 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* 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" diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index 2783bb3971..d0dab922de 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index 0db61c2e43..d10cf9c967 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* 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_target.h" diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h index c04c4125db..9eacdcf083 100644 --- a/bridge/bindings/qjs/js_event_listener.h +++ b/bridge/bindings/qjs/js_event_listener.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc index 99ba8a44c5..25aee8da96 100644 --- a/bridge/bindings/qjs/member_installer.cc +++ b/bridge/bindings/qjs/member_installer.cc @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* 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 <quickjs/quickjs.h> diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h index d17b2231c4..a2d87c83b0 100644 --- a/bridge/bindings/qjs/member_installer.h +++ b/bridge/bindings/qjs/member_installer.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_MEMBER_INSTALLER_H #define KRAKENBRIDGE_MEMBER_INSTALLER_H diff --git a/bridge/bindings/qjs/module_manager.cc b/bridge/bindings/qjs/module_manager.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/module_manager.h b/bridge/bindings/qjs/module_manager.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index c8326788df..77c9a87967 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -1,7 +1,7 @@ /* - * Copyright (C) 2020-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* 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" diff --git a/bridge/bindings/qjs/native_string_utils.h b/bridge/bindings/qjs/native_string_utils.h index 0731ec811d..9d7a752f38 100644 --- a/bridge/bindings/qjs/native_string_utils.h +++ b/bridge/bindings/qjs/native_string_utils.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2020-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_NATIVE_STRING_UTILS_H #define KRAKENBRIDGE_NATIVE_STRING_UTILS_H diff --git a/bridge/bindings/qjs/native_value.cc b/bridge/bindings/qjs/native_value.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/pending_promises.cc b/bridge/bindings/qjs/pending_promises.cc index eb708d88bc..41af49e12d 100644 --- a/bridge/bindings/qjs/pending_promises.cc +++ b/bridge/bindings/qjs/pending_promises.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #include "pending_promises.h" #include "script_promise.h" diff --git a/bridge/bindings/qjs/pending_promises.h b/bridge/bindings/qjs/pending_promises.h index cfe83b8e1b..678794196d 100644 --- a/bridge/bindings/qjs/pending_promises.h +++ b/bridge/bindings/qjs/pending_promises.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ #define KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ diff --git a/bridge/bindings/qjs/qjs_patch_test.cc b/bridge/bindings/qjs/qjs_engine_patch_test.cc similarity index 100% rename from bridge/bindings/qjs/qjs_patch_test.cc rename to bridge/bindings/qjs/qjs_engine_patch_test.cc diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 934bfdae21..9b1ebfe982 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 <algorithm> #include "cppgc/gc_visitor.h" diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 81951f336c..9d1f1794cb 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_QJS_FUNCTION_H #define KRAKENBRIDGE_QJS_FUNCTION_H diff --git a/bridge/bindings/qjs/qjs_interface_bridge.cc b/bridge/bindings/qjs/qjs_interface_bridge.cc deleted file mode 100644 index 8c6e207cef..0000000000 --- a/bridge/bindings/qjs/qjs_interface_bridge.cc +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright (C) 2020-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - -#include "qjs_interface_bridge.h" -#include "core/executing_context.h" - -namespace kraken {} // namespace kraken diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index 8358d6caef..91d2e01a7d 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2020-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index c23eee774a..f2532d3722 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index 5a58cb2407..7e862c171a 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index a7b2e615aa..d18bc60034 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" #include "pending_promises.h" diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 72bee032d8..4ba9b76fa8 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 170b397006..8ae3184e87 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 <vector> #include "core/executing_context.h" diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 710d0a994f..427a429f41 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_SCRIPT_VALUE_H #define KRAKENBRIDGE_SCRIPT_VALUE_H diff --git a/bridge/bindings/qjs/script_value_test.cc b/bridge/bindings/qjs/script_value_test.cc index 505d2efd63..666f3628e4 100644 --- a/bridge/bindings/qjs/script_value_test.cc +++ b/bridge/bindings/qjs/script_value_test.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "script_value.h" #include <quickjs/quickjs.h> diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index b151b745f6..cfbd631c22 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* 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" diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index bed127e5f6..209907b9de 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_SCRIPT_WRAPPABLE_H #define KRAKENBRIDGE_SCRIPT_WRAPPABLE_H diff --git a/bridge/bindings/qjs/source_location.cc b/bridge/bindings/qjs/source_location.cc index 2cd5561492..93f0711b0b 100644 --- a/bridge/bindings/qjs/source_location.cc +++ b/bridge/bindings/qjs/source_location.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 kraken { diff --git a/bridge/bindings/qjs/source_location.h b/bridge/bindings/qjs/source_location.h index 792b2e43a1..5a7ce98f29 100644 --- a/bridge/bindings/qjs/source_location.h +++ b/bridge/bindings/qjs/source_location.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ #define KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ diff --git a/bridge/bindings/qjs/to_quickjs.h b/bridge/bindings/qjs/to_quickjs.h index 78f332753d..42dc8073df 100644 --- a/bridge/bindings/qjs/to_quickjs.h +++ b/bridge/bindings/qjs/to_quickjs.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ #define KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 2113a06c44..9bdf75f2aa 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_WRAPPER_TYPE_INFO_H #define KRAKENBRIDGE_WRAPPER_TYPE_INFO_H diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index 3163f091ec..8b8d891acc 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 <vector> #include "core/dom/element.h" diff --git a/bridge/core/css/legacy/css_style_declaration.h b/bridge/core/css/legacy/css_style_declaration.h index 2c51ded1cf..39ebcf2e0f 100644 --- a/bridge/core/css/legacy/css_style_declaration.h +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CSS_STYLE_DECLARATION_H #define KRAKENBRIDGE_CSS_STYLE_DECLARATION_H diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index 8bff8c2f71..77fd4185a6 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -1,6 +1,7 @@ /* - * 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_ diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc index 6d39a9249c..1395a34ec3 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/dom/binding_object.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "binding_object.h" #include "binding_call_methods.h" diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h index 682582396b..a1b544a3c7 100644 --- a/bridge/core/dom/binding_object.h +++ b/bridge/core/dom/binding_object.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ #define KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index 54b1bdd6dc..12d46c5aa7 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "character_data.h" #include "core/dom/document.h" diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index 1a4edacc99..18e22bf989 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CHARACTER_DATA_H #define KRAKENBRIDGE_CHARACTER_DATA_H diff --git a/bridge/core/dom/child_node_list.cc b/bridge/core/dom/child_node_list.cc index c29722e6f7..4bf32df89e 100644 --- a/bridge/core/dom/child_node_list.cc +++ b/bridge/core/dom/child_node_list.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "child_node_list.h" #include "bindings/qjs/cppgc/gc_visitor.h" diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h index 8abed0ddad..7f5e2404a6 100644 --- a/bridge/core/dom/child_node_list.h +++ b/bridge/core/dom/child_node_list.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ #define KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ diff --git a/bridge/core/dom/collection_index_cache.h b/bridge/core/dom/collection_index_cache.h index 63241e50c8..5c1e24070f 100644 --- a/bridge/core/dom/collection_index_cache.h +++ b/bridge/core/dom/collection_index_cache.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ #define KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc index db84b3f304..8a6d8b3abb 100644 --- a/bridge/core/dom/comment.cc +++ b/bridge/core/dom/comment.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/dom/comment.h b/bridge/core/dom/comment.h index 5156573832..cd661be157 100644 --- a/bridge/core/dom/comment.h +++ b/bridge/core/dom/comment.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_COMMENT_H #define KRAKENBRIDGE_COMMENT_H diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index b6b161ca97..e7b77479d7 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "container_node.h" #include "bindings/qjs/cppgc/garbage_collected.h" diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index eedeffe055..e2ea09fec3 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ #define KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 7697721dad..cdc6f7bc36 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #include "document.h" #include "bindings/qjs/exception_message.h" #include "core/dom/element.h" diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 38d9b92637..e67fedd5cb 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_DOCUMENT_H #define KRAKENBRIDGE_DOCUMENT_H diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index 3a36472c53..c3fdffbf4f 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index 2815ac17a0..7f312b9215 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_DOCUMENT_FRAGMENT_H #define KRAKENBRIDGE_DOCUMENT_FRAGMENT_H diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index aa6c66356f..a0476e821f 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 "kraken_test_env.h" diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 14a668fe85..e99392c36c 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #include "element.h" #include <utility> diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index e060c4b9c0..311e444653 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_ELEMENT_H #define KRAKENBRIDGE_ELEMENT_H diff --git a/bridge/core/dom/element_traversal.h b/bridge/core/dom/element_traversal.h index 774c7029ce..feb472c8a9 100644 --- a/bridge/core/dom/element_traversal.h +++ b/bridge/core/dom/element_traversal.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ #define KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ diff --git a/bridge/core/dom/empty_node_list.cc b/bridge/core/dom/empty_node_list.cc index 09ea1983c6..136688840f 100644 --- a/bridge/core/dom/empty_node_list.cc +++ b/bridge/core/dom/empty_node_list.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "empty_node_list.h" #include "core/dom/node.h" diff --git a/bridge/core/dom/empty_node_list.h b/bridge/core/dom/empty_node_list.h index 2e391cbf74..ce994211b0 100644 --- a/bridge/core/dom/empty_node_list.h +++ b/bridge/core/dom/empty_node_list.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ #define KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ diff --git a/bridge/core/dom/events/custom_event.cc b/bridge/core/dom/events/custom_event.cc index 50ef78d3b0..88569d977e 100644 --- a/bridge/core/dom/events/custom_event.cc +++ b/bridge/core/dom/events/custom_event.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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/native_value.h" #include "bindings/qjs/qjs_engine_patch.h" diff --git a/bridge/core/dom/events/custom_event.h b/bridge/core/dom/events/custom_event.h index dc9597e586..a73b9f3f6e 100644 --- a/bridge/core/dom/events/custom_event.h +++ b/bridge/core/dom/events/custom_event.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CUSTOM_EVENT_H #define KRAKENBRIDGE_CUSTOM_EVENT_H diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 5c569b6795..9a8cd19d57 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #include "event.h" #include "core/executing_context.h" #include "event_target.h" diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 88f4783549..0da3a262d4 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_EVENT_H #define KRAKENBRIDGE_EVENT_H diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h index c131c041e9..c6703cf671 100644 --- a/bridge/core/dom/events/event_listener.h +++ b/bridge/core/dom/events/event_listener.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index ad65dd90fd..b168c387a3 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 kraken { diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index d8b3f8486e..5e16fe8931 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ #define KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index e363ef4b59..3db880d603 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 "bindings/qjs/converter_impl.h" #include "event_type_names.h" diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 58cc9059f8..1762e188f3 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H diff --git a/bridge/core/dom/events/event_target_impl.cc b/bridge/core/dom/events/event_target_impl.cc index 05a255735c..012c10cc11 100644 --- a/bridge/core/dom/events/event_target_impl.cc +++ b/bridge/core/dom/events/event_target_impl.cc @@ -1,6 +1,5 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 index e01a2795b4..34266f14b7 100644 --- a/bridge/core/dom/events/event_target_impl.h +++ b/bridge/core/dom/events/event_target_impl.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index 75f6e91e60..40fb06c4ec 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 "kraken_test_env.h" diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc index 3085cbd841..ad529a881d 100644 --- a/bridge/core/dom/events/registered_eventListener.cc +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 "qjs_add_event_listener_options.h" diff --git a/bridge/core/dom/events/registered_eventListener.h b/bridge/core/dom/events/registered_eventListener.h index ac995f6d58..97ad179151 100644 --- a/bridge/core/dom/events/registered_eventListener.h +++ b/bridge/core/dom/events/registered_eventListener.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc index d73046c91b..c96b740773 100644 --- a/bridge/core/dom/frame_request_callback_collection.cc +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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> diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index c06b9c5b85..577dae2a11 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ #define KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc index 016bad6cd4..71cd02e75f 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.cc +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "bounding_client_rect.h" #include "core/executing_context.h" diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index e5ae417dea..8476dc1d8b 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ #define KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 6d622fcce1..8fcf278d82 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "element_attributes.h" #include "bindings/qjs/exception_state.h" diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index 1857d8c9e2..d0e0bfc28a 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ #define KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ diff --git a/bridge/core/dom/legacy/space_split_string.cc b/bridge/core/dom/legacy/space_split_string.cc index a53fa721e1..5b55dd9341 100644 --- a/bridge/core/dom/legacy/space_split_string.cc +++ b/bridge/core/dom/legacy/space_split_string.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "space_split_string.h" diff --git a/bridge/core/dom/legacy/space_split_string.h b/bridge/core/dom/legacy/space_split_string.h index d7e40492a1..8f28867ae9 100644 --- a/bridge/core/dom/legacy/space_split_string.h +++ b/bridge/core/dom/legacy/space_split_string.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ #define KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 417b3943aa..36e72c271d 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "node.h" #include <unordered_map> diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 3a835110fb..bd0d1c5973 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_NODE_H #define KRAKENBRIDGE_NODE_H diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index 6029a5a6d1..2c2381b8ec 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "node_data.h" #include "bindings/qjs/cppgc/garbage_collected.h" diff --git a/bridge/core/dom/node_data.h b/bridge/core/dom/node_data.h index a1a4864a2a..0f50ffc0bc 100644 --- a/bridge/core/dom/node_data.h +++ b/bridge/core/dom/node_data.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ #define KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h index 10133a6687..57f04f93e8 100644 --- a/bridge/core/dom/node_list.h +++ b/bridge/core/dom/node_list.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ #define KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ diff --git a/bridge/core/dom/node_traversal.cc b/bridge/core/dom/node_traversal.cc index 4e4956d6f0..6d2ba1d588 100644 --- a/bridge/core/dom/node_traversal.cc +++ b/bridge/core/dom/node_traversal.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "node_traversal.h" diff --git a/bridge/core/dom/node_traversal.h b/bridge/core/dom/node_traversal.h index 8f166e3f35..e00001f6a4 100644 --- a/bridge/core/dom/node_traversal.h +++ b/bridge/core/dom/node_traversal.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ #define KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index 16dcbafcca..01581e64b5 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "scripted_animation_controller.h" #include "frame_request_callback_collection.h" diff --git a/bridge/core/dom/scripted_animation_controller.h b/bridge/core/dom/scripted_animation_controller.h index 75832c1767..c2dc617edd 100644 --- a/bridge/core/dom/scripted_animation_controller.h +++ b/bridge/core/dom/scripted_animation_controller.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ diff --git a/bridge/core/dom/text.cc b/bridge/core/dom/text.cc index 31053d3796..d7d636c834 100644 --- a/bridge/core/dom/text.cc +++ b/bridge/core/dom/text.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "text.h" #include "document.h" diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h index 3fd3a4dca1..dab789d438 100644 --- a/bridge/core/dom/text.h +++ b/bridge/core/dom/text.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_TEXT_H_ #define KRAKENBRIDGE_CORE_DOM_TEXT_H_ diff --git a/bridge/core/dom/tree_scope.cc b/bridge/core/dom/tree_scope.cc index 3c12ba9362..fb47959292 100644 --- a/bridge/core/dom/tree_scope.cc +++ b/bridge/core/dom/tree_scope.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "tree_scope.h" #include "document.h" diff --git a/bridge/core/dom/tree_scope.h b/bridge/core/dom/tree_scope.h index 59580fb592..66c410d0a3 100644 --- a/bridge/core/dom/tree_scope.h +++ b/bridge/core/dom/tree_scope.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ #define KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc index f15d37f2c9..7ba0ced506 100644 --- a/bridge/core/events/error_event.cc +++ b/bridge/core/events/error_event.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #include "error_event.h" namespace kraken { diff --git a/bridge/core/events/error_event.h b/bridge/core/events/error_event.h index 0f49ef5149..3fcd008a91 100644 --- a/bridge/core/events/error_event.h +++ b/bridge/core/events/error_event.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index 6f2323706a..708c200041 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "message_event.h" diff --git a/bridge/core/events/message_event.h b/bridge/core/events/message_event.h index c6f17aa893..ef5f8e2c49 100644 --- a/bridge/core/events/message_event.h +++ b/bridge/core/events/message_event.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ #define KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 85b2b0faff..1823a83893 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 "built_in_string.h" #include "core/dom/document.h" diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 8295b3ac27..585e93fdbd 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_JS_CONTEXT_H #define KRAKENBRIDGE_JS_CONTEXT_H diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc index c968f2bd09..a2e5597908 100644 --- a/bridge/core/executing_context_data.cc +++ b/bridge/core/executing_context_data.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/executing_context_data.h b/bridge/core/executing_context_data.h index ad88b2dcd0..687f9cd1a5 100644 --- a/bridge/core/executing_context_data.h +++ b/bridge/core/executing_context_data.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CONTEXT_DATA_H #define KRAKENBRIDGE_CONTEXT_DATA_H diff --git a/bridge/core/fileapi/array_buffer_data.h b/bridge/core/fileapi/array_buffer_data.h index e90a8722c0..c98540cf7d 100644 --- a/bridge/core/fileapi/array_buffer_data.h +++ b/bridge/core/fileapi/array_buffer_data.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ #define KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index cb2d91b6cf..61869a69b1 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 30db15ed3e..c30fbb19b5 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_BLOB_H #define KRAKENBRIDGE_BLOB_H diff --git a/bridge/core/fileapi/blob_part.cc b/bridge/core/fileapi/blob_part.cc index a042475df1..d19cbf53eb 100644 --- a/bridge/core/fileapi/blob_part.cc +++ b/bridge/core/fileapi/blob_part.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/fileapi/blob_part.h b/bridge/core/fileapi/blob_part.h index b734bcb802..65a09882ba 100644 --- a/bridge/core/fileapi/blob_part.h +++ b/bridge/core/fileapi/blob_part.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ #define KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ diff --git a/bridge/core/fileapi/blob_property_bag.cc b/bridge/core/fileapi/blob_property_bag.cc index 422d205041..09b32363a5 100644 --- a/bridge/core/fileapi/blob_property_bag.cc +++ b/bridge/core/fileapi/blob_property_bag.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 kraken { diff --git a/bridge/core/fileapi/blob_property_bag.h b/bridge/core/fileapi/blob_property_bag.h index 452417d009..2595827eee 100644 --- a/bridge/core/fileapi/blob_property_bag.h +++ b/bridge/core/fileapi/blob_property_bag.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ #define KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 9b9039b4e1..1ae522d09d 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index 461d0c0915..a4ad5dc4fe 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index 8d3f92bd8c..da5c763230 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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> diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index 225dd75fbb..9097f6f99d 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_DOM_TIMER_H #define KRAKENBRIDGE_DOM_TIMER_H diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index c043b25c95..6623a5de78 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/frame/legacy/location.cc b/bridge/core/frame/legacy/location.cc index 2767efeecf..f3d9bcdeca 100644 --- a/bridge/core/frame/legacy/location.cc +++ b/bridge/core/frame/legacy/location.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/frame/legacy/location.h b/bridge/core/frame/legacy/location.h index 86c2be89d5..02fdf744b5 100644 --- a/bridge/core/frame/legacy/location.h +++ b/bridge/core/frame/legacy/location.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_LOCATION_H #define KRAKENBRIDGE_LOCATION_H diff --git a/bridge/core/frame/module_callback.cc b/bridge/core/frame/module_callback.cc index 552f3ceb05..c225796c5b 100644 --- a/bridge/core/frame/module_callback.cc +++ b/bridge/core/frame/module_callback.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 kraken { diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h index 2399f8f1b0..9eb01b686d 100644 --- a/bridge/core/frame/module_callback.h +++ b/bridge/core/frame/module_callback.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_MODULE_CALLBACK_H #define KRAKENBRIDGE_MODULE_CALLBACK_H diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc index 835ae00022..faf2513bce 100644 --- a/bridge/core/frame/module_callback_coordinator.cc +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #include "module_callback_coordinator.h" namespace kraken { diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_callback_coordinator.h index f858bd93ee..02b04ccf8f 100644 --- a/bridge/core/frame/module_callback_coordinator.h +++ b/bridge/core/frame/module_callback_coordinator.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H #define KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H diff --git a/bridge/core/frame/module_listener.cc b/bridge/core/frame/module_listener.cc index 2ef03d4fe0..1cfd753e4f 100644 --- a/bridge/core/frame/module_listener.cc +++ b/bridge/core/frame/module_listener.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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> diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h index e0efce08b1..9d2980aaa0 100644 --- a/bridge/core/frame/module_listener.h +++ b/bridge/core/frame/module_listener.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_MODULE_LISTENER_H #define KRAKENBRIDGE_MODULE_LISTENER_H diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc index c7714c0809..1b1d56db1c 100644 --- a/bridge/core/frame/module_listener_container.cc +++ b/bridge/core/frame/module_listener_container.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" namespace kraken { diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h index 615dd8bda0..21b0c53a8c 100644 --- a/bridge/core/frame/module_listener_container.h +++ b/bridge/core/frame/module_listener_container.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H #define KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 63f6e3101d..be9db7b7bb 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 "module_callback.h" diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index 360b5c896f..bbaf8ea2da 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_MODULE_MANAGER_H #define KRAKENBRIDGE_MODULE_MANAGER_H diff --git a/bridge/core/frame/screen.cc b/bridge/core/frame/screen.cc index 278fb039a7..2d02ae06bc 100644 --- a/bridge/core/frame/screen.cc +++ b/bridge/core/frame/screen.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "screen.h" #include "core/frame/window.h" diff --git a/bridge/core/frame/screen.h b/bridge/core/frame/screen.h index aee755fe78..85bf487a9d 100644 --- a/bridge/core/frame/screen.h +++ b/bridge/core/frame/screen.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_SCREEN_H #define KRAKENBRIDGE_SCREEN_H diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index 5854703375..c76f1f4654 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "window.h" #include "binding_call_methods.h" diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index 437632adca..5b35960f6d 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_WINDOW_H #define KRAKENBRIDGE_WINDOW_H diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 35ce9fdb53..7dabd525f3 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h index 657f061755..96381ecce0 100644 --- a/bridge/core/frame/window_or_worker_global_scope.h +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H #define KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H diff --git a/bridge/core/html/canvas/html_canvas_element.cc b/bridge/core/html/canvas/html_canvas_element.cc index 751a45642c..a7c8145be3 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 "html_names.h" diff --git a/bridge/core/html/canvas/html_canvas_element.h b/bridge/core/html/canvas/html_canvas_element.h index a1731a0790..480b3a55df 100644 --- a/bridge/core/html/canvas/html_canvas_element.h +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_input_element.cc b/bridge/core/html/forms/html_input_element.cc index b854bd64ee..801854cd61 100644 --- a/bridge/core/html/forms/html_input_element.cc +++ b/bridge/core/html/forms/html_input_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h index e83c6e4355..d07be595eb 100644 --- a/bridge/core/html/forms/html_input_element.h +++ b/bridge/core/html/forms/html_input_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_textarea_element.cc b/bridge/core/html/forms/html_textarea_element.cc index 6c9523d5ec..fe337d0f6c 100644 --- a/bridge/core/html/forms/html_textarea_element.cc +++ b/bridge/core/html/forms/html_textarea_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h index b985795c48..a271a1db7b 100644 --- a/bridge/core/html/forms/html_textarea_element.h +++ b/bridge/core/html/forms/html_textarea_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ diff --git a/bridge/core/html/html_anchor_element.cc b/bridge/core/html/html_anchor_element.cc index f13705e0bf..c820fee5aa 100644 --- a/bridge/core/html/html_anchor_element.cc +++ b/bridge/core/html/html_anchor_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h index 40e2c5c9d3..a8689b459d 100644 --- a/bridge/core/html/html_anchor_element.h +++ b/bridge/core/html/html_anchor_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H #define KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H diff --git a/bridge/core/html/html_body_element.cc b/bridge/core/html/html_body_element.cc index d543f07f0a..3068664033 100644 --- a/bridge/core/html/html_body_element.cc +++ b/bridge/core/html/html_body_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index da08da4383..7a540f9a84 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ diff --git a/bridge/core/html/html_collection.cc b/bridge/core/html/html_collection.cc index c4e421a942..b179fada08 100644 --- a/bridge/core/html/html_collection.cc +++ b/bridge/core/html/html_collection.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "html_collection.h" diff --git a/bridge/core/html/html_collection.h b/bridge/core/html/html_collection.h index 638f65c2b4..3d7b70df4f 100644 --- a/bridge/core/html/html_collection.h +++ b/bridge/core/html/html_collection.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ diff --git a/bridge/core/html/html_div_element.cc b/bridge/core/html/html_div_element.cc index b43750987d..c7054aa418 100644 --- a/bridge/core/html/html_div_element.cc +++ b/bridge/core/html/html_div_element.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "html_div_element.h" #include "html_names.h" diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h index f09ffc7629..b4d6a89493 100644 --- a/bridge/core/html/html_div_element.h +++ b/bridge/core/html/html_div_element.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ diff --git a/bridge/core/html/html_element.cc b/bridge/core/html/html_element.cc index 7fb68b04aa..0e24510a9f 100644 --- a/bridge/core/html/html_element.cc +++ b/bridge/core/html/html_element.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "html_element.h" diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index e3549ff9ea..7da8b4a571 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_head_element.cc b/bridge/core/html/html_head_element.cc index 1014e0dbc2..22e2a1f57a 100644 --- a/bridge/core/html/html_head_element.cc +++ b/bridge/core/html/html_head_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/html/html_head_element.h b/bridge/core/html/html_head_element.h index a1f6ae6e52..107e0592a1 100644 --- a/bridge/core/html/html_head_element.h +++ b/bridge/core/html/html_head_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ diff --git a/bridge/core/html/html_html_element.cc b/bridge/core/html/html_html_element.cc index 2fb735034e..a471530521 100644 --- a/bridge/core/html/html_html_element.cc +++ b/bridge/core/html/html_html_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/html/html_html_element.h b/bridge/core/html/html_html_element.h index 41ca13ab2a..6a38b0b0d9 100644 --- a/bridge/core/html/html_html_element.h +++ b/bridge/core/html/html_html_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index ad1792d841..fa52941ecc 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index bf03c34813..b6e41cd0e1 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ diff --git a/bridge/core/html/html_script_element.cc b/bridge/core/html/html_script_element.cc index 63e62b9044..cb4894dce2 100644 --- a/bridge/core/html/html_script_element.cc +++ b/bridge/core/html/html_script_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h index 6998bff6c7..4fd3e64c0b 100644 --- a/bridge/core/html/html_script_element.h +++ b/bridge/core/html/html_script_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index 20c893e200..2081b38715 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index 3673758e61..a12980767e 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H #define KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H diff --git a/bridge/core/html/html_unknown_element.cc b/bridge/core/html/html_unknown_element.cc index 708d82558f..ec9e5d6df5 100644 --- a/bridge/core/html/html_unknown_element.cc +++ b/bridge/core/html/html_unknown_element.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" namespace kraken { diff --git a/bridge/core/html/html_unknown_element.h b/bridge/core/html/html_unknown_element.h index f3cd657b3f..2b69966d8c 100644 --- a/bridge/core/html/html_unknown_element.h +++ b/bridge/core/html/html_unknown_element.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ diff --git a/bridge/core/html/parser/html_parser.cc b/bridge/core/html/parser/html_parser.cc index 7bde226c92..ebe6fca85e 100644 --- a/bridge/core/html/parser/html_parser.cc +++ b/bridge/core/html/parser/html_parser.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 <utility> diff --git a/bridge/core/html/parser/html_parser.h b/bridge/core/html/parser/html_parser.h index 629e4c9f63..d0f66e03ea 100644 --- a/bridge/core/html/parser/html_parser.h +++ b/bridge/core/html/parser/html_parser.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_HTML_PARSER_H #define KRAKENBRIDGE_HTML_PARSER_H diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 366b955122..b9a6fd55c2 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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> diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 717f0e841c..174051db87 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 "binding_call_methods.h" #include "built_in_string.h" diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h index e3b7f407a2..10e93953d5 100644 --- a/bridge/core/script_state.h +++ b/bridge/core/script_state.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ #define KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ diff --git a/bridge/dart_methods.cc b/bridge/dart_methods.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/foundation/ascii_types.h b/bridge/foundation/ascii_types.h index 4242b5e8df..568cfbfeba 100644 --- a/bridge/foundation/ascii_types.h +++ b/bridge/foundation/ascii_types.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ #define KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ diff --git a/bridge/foundation/casting.h b/bridge/foundation/casting.h index 603e981266..2719d4acb6 100644 --- a/bridge/foundation/casting.h +++ b/bridge/foundation/casting.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_FOUNDATION_CASTING_H_ #define KRAKENBRIDGE_FOUNDATION_CASTING_H_ diff --git a/bridge/foundation/closure.h b/bridge/foundation/closure.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/foundation/colors.h b/bridge/foundation/colors.h index 373a6f3b86..feca25c5fd 100644 --- a/bridge/foundation/colors.h +++ b/bridge/foundation/colors.h @@ -1,6 +1,7 @@ /* - * 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/logging.cc b/bridge/foundation/logging.cc index ab9fea3ef7..06cdb2fa79 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -1,6 +1,7 @@ /* - * 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> diff --git a/bridge/foundation/logging.h b/bridge/foundation/logging.h index 3367453dca..c2127a8797 100644 --- a/bridge/foundation/logging.h +++ b/bridge/foundation/logging.h @@ -1,6 +1,7 @@ /* - * 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_ diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h index 35baeddaeb..c64dfd9054 100644 --- a/bridge/foundation/macros.h +++ b/bridge/foundation/macros.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2019 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_MACROS_H #define KRAKENBRIDGE_MACROS_H diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc index af7d8975c0..65f68be9de 100644 --- a/bridge/foundation/native_string.cc +++ b/bridge/foundation/native_string.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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> diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h index 024a15c05b..1cdb15a1dd 100644 --- a/bridge/foundation/native_string.h +++ b/bridge/foundation/native_string.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_NATIVE_STRING_H #define KRAKENBRIDGE_NATIVE_STRING_H diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h index 49ae7c4542..813af8b689 100644 --- a/bridge/foundation/native_type.h +++ b/bridge/foundation/native_type.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ #define KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 60ff30ff70..9c7d50055b 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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" diff --git a/bridge/foundation/native_value_converter.cc b/bridge/foundation/native_value_converter.cc index 2825bab773..b85a76e4f9 100644 --- a/bridge/foundation/native_value_converter.cc +++ b/bridge/foundation/native_value_converter.cc @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "native_value_converter.h" diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 77691dd6c2..661b7a34f1 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ #define KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ diff --git a/bridge/foundation/ref_counted_internal.h b/bridge/foundation/ref_counted_internal.h index fa71095b7c..9f93c2076a 100644 --- a/bridge/foundation/ref_counted_internal.h +++ b/bridge/foundation/ref_counted_internal.h @@ -1,6 +1,7 @@ /* - * 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 // found in the LICENSE file. diff --git a/bridge/foundation/ref_counter.h b/bridge/foundation/ref_counter.h index ae8f77f980..f86ee34ba9 100644 --- a/bridge/foundation/ref_counter.h +++ b/bridge/foundation/ref_counter.h @@ -1,6 +1,7 @@ /* - * 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 // found in the LICENSE file. diff --git a/bridge/foundation/ref_ptr.h b/bridge/foundation/ref_ptr.h index 0419f0388b..53f97ff264 100644 --- a/bridge/foundation/ref_ptr.h +++ b/bridge/foundation/ref_ptr.h @@ -1,6 +1,7 @@ /* - * 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 // found in the LICENSE file. diff --git a/bridge/foundation/ref_ptr_internal.h b/bridge/foundation/ref_ptr_internal.h index b5be8004de..13d8601aa9 100644 --- a/bridge/foundation/ref_ptr_internal.h +++ b/bridge/foundation/ref_ptr_internal.h @@ -1,6 +1,7 @@ /* - * 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 // found in the LICENSE file. diff --git a/bridge/foundation/string_view.cc b/bridge/foundation/string_view.cc index bd59577be3..c513c0f4f4 100644 --- a/bridge/foundation/string_view.cc +++ b/bridge/foundation/string_view.cc @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* 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 kraken { diff --git a/bridge/foundation/string_view.h b/bridge/foundation/string_view.h index a8b2871618..6cd3afa34f 100644 --- a/bridge/foundation/string_view.h +++ b/bridge/foundation/string_view.h @@ -1,8 +1,7 @@ /* - * Copyright (C) 2021 Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ - +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ #define KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index cbe561d231..1526bac28b 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -1,6 +1,7 @@ /* - * 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 "core/dart_methods.h" diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 22f2f0cdf4..9515d5b2e2 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -1,6 +1,7 @@ /* - * 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_ diff --git a/bridge/foundation/ui_task_queue.h b/bridge/foundation/ui_task_queue.h index d09121eeb9..d3a3c27ce3 100644 --- a/bridge/foundation/ui_task_queue.h +++ b/bridge/foundation/ui_task_queue.h @@ -1,6 +1,7 @@ /* - * 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 #define BRIDGE_UI_TASK_QUEUE_H diff --git a/bridge/include/webf_bridge.h b/bridge/include/webf_bridge.h index 9ae7e03de0..3ca7cbefef 100644 --- a/bridge/include/webf_bridge.h +++ b/bridge/include/webf_bridge.h @@ -1,6 +1,7 @@ /* - * 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 @@ -8,7 +9,7 @@ #include <thread> #define WEBF_EXPORT_C extern "C" __attribute__((visibility("default"))) __attribute__((used)) -#define WEBF_EXPORT_C __attribute__((__visibility__("default"))) +#define WEBF_EXPORT __attribute__((__visibility__("default"))) WEBF_EXPORT_C std::thread::id getUIThreadId(); @@ -56,7 +57,7 @@ void invokeModuleEvent(int32_t contextId, WEBF_EXPORT_C void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length); WEBF_EXPORT_C -KrakenInfo* getKrakenInfo(); +WebFInfo* getWebFInfo(); WEBF_EXPORT_C void dispatchUITask(int32_t contextId, void* context, void* callback); WEBF_EXPORT_C diff --git a/bridge/include/webf_bridge_test.h b/bridge/include/webf_bridge_test.h index 5aaa97043e..0ccd15b5c1 100644 --- a/bridge/include/webf_bridge_test.h +++ b/bridge/include/webf_bridge_test.h @@ -1,15 +1,16 @@ /* - * 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 "kraken_bridge.h" +#include "webf_bridge.h" WEBF_EXPORT_C void initTestFramework(int32_t contextId); -KRAKEN_EXPORT_C +WEBF_EXPORT_C int8_t evaluateTestScripts(int32_t contextId, void* code, const char* bundleFilename, int startLine); using ExecuteCallback = void* (*)(int32_t contextId, void* status); @@ -17,7 +18,7 @@ using ExecuteCallback = void* (*)(int32_t contextId, void* status); WEBF_EXPORT_C void executeTest(int32_t contextId, ExecuteCallback executeCallback); -KRAKEN_EXPORT_C +WEBF_EXPORT_C void registerTestEnvDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length); #endif diff --git a/bridge/page.cc b/bridge/page.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/page_test.cc b/bridge/page_test.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/page_test.h b/bridge/page_test.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/polyfill/src/bridge.ts b/bridge/polyfill/src/bridge.ts index e69de29bb2..680ddcafba 100644 --- a/bridge/polyfill/src/bridge.ts +++ b/bridge/polyfill/src/bridge.ts @@ -0,0 +1,13 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* 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; +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_print__: (log: string, level?: string) => void; +export const webfPrint = __webf_print__; \ No newline at end of file diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl index 608dfed39a..49d45329ed 100644 --- a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 "<%= blob.filename %>.h" #include "foundation/native_value_converter.h" diff --git a/bridge/scripts/code_generator/static/idl_templates/base.h.tpl b/bridge/scripts/code_generator/static/idl_templates/base.h.tpl index a865cd3923..4d14e70f06 100644 --- a/bridge/scripts/code_generator/static/idl_templates/base.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/base.h.tpl @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_<%= blob.filename.toUpperCase() %>_H #define KRAKENBRIDGE_<%= blob.filename.toUpperCase() %>_H diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl index 7705df96da..d5a3809af6 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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. +*/ // Generated from template: // code_generator/src/json/templates/element_factory.cc.tmp diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl index 6aaf25e7eb..e96d34ad8f 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl @@ -1,6 +1,7 @@ /* - * Copyright (C) 2021-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 KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ #define KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index 65b78390c3..d026e91889 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -1,7 +1,7 @@ /* - * Copyright (C) 2020-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #include "kraken_test_context.h" #include "bindings/qjs/member_installer.h" diff --git a/bridge/test/kraken_test_context.h b/bridge/test/kraken_test_context.h index ba3512a037..3dd35cb858 100644 --- a/bridge/test/kraken_test_context.h +++ b/bridge/test/kraken_test_context.h @@ -1,7 +1,7 @@ /* - * Copyright (C) 2020-present Alibaba Inc. All rights reserved. - * Author: Kraken Team. - */ +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ #ifndef KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H #define KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H diff --git a/bridge/webf_bridge.cc b/bridge/webf_bridge.cc index 70122900f2..7899c46e28 100644 --- a/bridge/webf_bridge.cc +++ b/bridge/webf_bridge.cc @@ -1,6 +1,7 @@ /* - * 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 <atomic> #include <cassert> diff --git a/bridge/webf_bridge_test.cc b/bridge/webf_bridge_test.cc index a7a6709b34..d338bb2f0b 100644 --- a/bridge/webf_bridge_test.cc +++ b/bridge/webf_bridge_test.cc @@ -1,6 +1,7 @@ /* - * 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 <atomic> #include "bindings/qjs/native_string_utils.h" From 25da74e3bbe9fae493ac11d8009f9938b45d8f7f Mon Sep 17 00:00:00 2001 From: openwebf-bot <openwebf@openwebf.com> Date: Tue, 9 Aug 2022 02:58:56 +0000 Subject: [PATCH 144/498] Committing clang-format changes --- bridge/bindings/qjs/atomic_string.cc | 4 ++-- bridge/bindings/qjs/atomic_string.h | 6 +++--- bridge/bindings/qjs/atomic_string_test.cc | 6 +++--- bridge/bindings/qjs/binding_initializer.cc | 6 +++--- bridge/bindings/qjs/binding_initializer.h | 6 +++--- bridge/bindings/qjs/converter.h | 6 +++--- bridge/bindings/qjs/converter_impl.h | 6 +++--- bridge/bindings/qjs/cppgc/garbage_collected.h | 6 +++--- bridge/bindings/qjs/cppgc/gc_visitor.cc | 6 +++--- bridge/bindings/qjs/cppgc/gc_visitor.h | 6 +++--- bridge/bindings/qjs/cppgc/member.h | 4 ++-- bridge/bindings/qjs/cppgc/mutation_scope.cc | 4 ++-- bridge/bindings/qjs/cppgc/mutation_scope.h | 4 ++-- bridge/bindings/qjs/dictionary_base.cc | 6 +++--- bridge/bindings/qjs/dictionary_base.h | 6 +++--- bridge/bindings/qjs/exception_message.cc | 6 +++--- bridge/bindings/qjs/exception_message.h | 6 +++--- bridge/bindings/qjs/exception_state.cc | 6 +++--- bridge/bindings/qjs/exception_state.h | 6 +++--- bridge/bindings/qjs/generated_code_helper.h | 6 +++--- bridge/bindings/qjs/idl_type.h | 6 +++--- bridge/bindings/qjs/js_based_event_listener.cc | 6 +++--- bridge/bindings/qjs/js_based_event_listener.h | 6 +++--- bridge/bindings/qjs/js_event_handler.cc | 6 +++--- bridge/bindings/qjs/js_event_handler.h | 6 +++--- bridge/bindings/qjs/js_event_listener.cc | 6 +++--- bridge/bindings/qjs/js_event_listener.h | 6 +++--- bridge/bindings/qjs/member_installer.cc | 6 +++--- bridge/bindings/qjs/member_installer.h | 6 +++--- bridge/bindings/qjs/native_string_utils.cc | 6 +++--- bridge/bindings/qjs/native_string_utils.h | 6 +++--- bridge/bindings/qjs/pending_promises.cc | 6 +++--- bridge/bindings/qjs/pending_promises.h | 6 +++--- bridge/bindings/qjs/qjs_engine_patch_test.cc | 2 +- bridge/bindings/qjs/qjs_function.cc | 6 +++--- bridge/bindings/qjs/qjs_function.h | 6 +++--- bridge/bindings/qjs/qjs_interface_bridge.h | 6 +++--- bridge/bindings/qjs/script_promise.cc | 6 +++--- bridge/bindings/qjs/script_promise.h | 6 +++--- bridge/bindings/qjs/script_promise_resolver.cc | 6 +++--- bridge/bindings/qjs/script_promise_resolver.h | 6 +++--- bridge/bindings/qjs/script_value.cc | 6 +++--- bridge/bindings/qjs/script_value.h | 6 +++--- bridge/bindings/qjs/script_value_test.cc | 6 +++--- bridge/bindings/qjs/script_wrappable.cc | 6 +++--- bridge/bindings/qjs/script_wrappable.h | 6 +++--- bridge/bindings/qjs/source_location.cc | 6 +++--- bridge/bindings/qjs/source_location.h | 6 +++--- bridge/bindings/qjs/to_quickjs.h | 6 +++--- bridge/bindings/qjs/wrapper_type_info.h | 6 +++--- bridge/core/css/legacy/css_style_declaration.cc | 6 +++--- bridge/core/css/legacy/css_style_declaration.h | 6 +++--- bridge/core/dart_methods.h | 6 +++--- bridge/core/dom/binding_object.cc | 6 +++--- bridge/core/dom/binding_object.h | 6 +++--- bridge/core/dom/character_data.cc | 6 +++--- bridge/core/dom/character_data.h | 6 +++--- bridge/core/dom/child_node_list.cc | 6 +++--- bridge/core/dom/child_node_list.h | 6 +++--- bridge/core/dom/collection_index_cache.h | 6 +++--- bridge/core/dom/comment.cc | 6 +++--- bridge/core/dom/comment.h | 6 +++--- bridge/core/dom/container_node.cc | 6 +++--- bridge/core/dom/container_node.h | 6 +++--- bridge/core/dom/document.cc | 6 +++--- bridge/core/dom/document.h | 6 +++--- bridge/core/dom/document_fragment.cc | 6 +++--- bridge/core/dom/document_fragment.h | 6 +++--- bridge/core/dom/document_test.cc | 6 +++--- bridge/core/dom/element.cc | 6 +++--- bridge/core/dom/element.h | 6 +++--- bridge/core/dom/element_traversal.h | 6 +++--- bridge/core/dom/empty_node_list.cc | 6 +++--- bridge/core/dom/empty_node_list.h | 6 +++--- bridge/core/dom/events/custom_event.cc | 6 +++--- bridge/core/dom/events/custom_event.h | 6 +++--- bridge/core/dom/events/event.cc | 6 +++--- bridge/core/dom/events/event.h | 6 +++--- bridge/core/dom/events/event_listener.h | 6 +++--- bridge/core/dom/events/event_listener_map.cc | 6 +++--- bridge/core/dom/events/event_listener_map.h | 6 +++--- bridge/core/dom/events/event_target.cc | 6 +++--- bridge/core/dom/events/event_target.h | 6 +++--- bridge/core/dom/events/event_target_impl.cc | 6 +++--- bridge/core/dom/events/event_target_impl.h | 6 +++--- bridge/core/dom/events/event_target_test.cc | 6 +++--- bridge/core/dom/events/registered_eventListener.cc | 6 +++--- bridge/core/dom/events/registered_eventListener.h | 6 +++--- bridge/core/dom/frame_request_callback_collection.cc | 6 +++--- bridge/core/dom/frame_request_callback_collection.h | 6 +++--- bridge/core/dom/legacy/bounding_client_rect.cc | 6 +++--- bridge/core/dom/legacy/bounding_client_rect.h | 6 +++--- bridge/core/dom/legacy/element_attributes.cc | 6 +++--- bridge/core/dom/legacy/element_attributes.h | 6 +++--- bridge/core/dom/legacy/space_split_string.cc | 6 +++--- bridge/core/dom/legacy/space_split_string.h | 6 +++--- bridge/core/dom/node.cc | 6 +++--- bridge/core/dom/node.h | 6 +++--- bridge/core/dom/node_data.cc | 6 +++--- bridge/core/dom/node_data.h | 6 +++--- bridge/core/dom/node_list.h | 6 +++--- bridge/core/dom/node_traversal.cc | 6 +++--- bridge/core/dom/node_traversal.h | 6 +++--- bridge/core/dom/scripted_animation_controller.cc | 6 +++--- bridge/core/dom/scripted_animation_controller.h | 6 +++--- bridge/core/dom/text.cc | 6 +++--- bridge/core/dom/text.h | 6 +++--- bridge/core/dom/tree_scope.cc | 6 +++--- bridge/core/dom/tree_scope.h | 6 +++--- bridge/core/events/error_event.cc | 6 +++--- bridge/core/events/error_event.h | 6 +++--- bridge/core/events/message_event.cc | 6 +++--- bridge/core/events/message_event.h | 6 +++--- bridge/core/executing_context.cc | 6 +++--- bridge/core/executing_context.h | 6 +++--- bridge/core/executing_context_data.cc | 6 +++--- bridge/core/executing_context_data.h | 6 +++--- bridge/core/fileapi/array_buffer_data.h | 6 +++--- bridge/core/fileapi/blob.cc | 6 +++--- bridge/core/fileapi/blob.h | 6 +++--- bridge/core/fileapi/blob_part.cc | 6 +++--- bridge/core/fileapi/blob_part.h | 6 +++--- bridge/core/fileapi/blob_property_bag.cc | 6 +++--- bridge/core/fileapi/blob_property_bag.h | 6 +++--- bridge/core/frame/console.cc | 6 +++--- bridge/core/frame/console.h | 6 +++--- bridge/core/frame/dom_timer.cc | 6 +++--- bridge/core/frame/dom_timer.h | 6 +++--- bridge/core/frame/dom_timer_coordinator.cc | 6 +++--- bridge/core/frame/legacy/location.cc | 6 +++--- bridge/core/frame/legacy/location.h | 6 +++--- bridge/core/frame/module_callback.cc | 6 +++--- bridge/core/frame/module_callback.h | 6 +++--- bridge/core/frame/module_callback_coordinator.cc | 6 +++--- bridge/core/frame/module_callback_coordinator.h | 6 +++--- bridge/core/frame/module_listener.cc | 6 +++--- bridge/core/frame/module_listener.h | 6 +++--- bridge/core/frame/module_listener_container.cc | 6 +++--- bridge/core/frame/module_listener_container.h | 6 +++--- bridge/core/frame/module_manager.cc | 6 +++--- bridge/core/frame/module_manager.h | 6 +++--- bridge/core/frame/screen.cc | 6 +++--- bridge/core/frame/screen.h | 6 +++--- bridge/core/frame/window.cc | 6 +++--- bridge/core/frame/window.h | 6 +++--- bridge/core/frame/window_or_worker_global_scope.cc | 6 +++--- bridge/core/frame/window_or_worker_global_scope.h | 6 +++--- bridge/core/html/canvas/html_canvas_element.cc | 6 +++--- bridge/core/html/canvas/html_canvas_element.h | 6 +++--- bridge/core/html/forms/html_input_element.cc | 6 +++--- bridge/core/html/forms/html_input_element.h | 6 +++--- bridge/core/html/forms/html_textarea_element.cc | 6 +++--- bridge/core/html/forms/html_textarea_element.h | 6 +++--- bridge/core/html/html_anchor_element.cc | 6 +++--- bridge/core/html/html_anchor_element.h | 6 +++--- bridge/core/html/html_body_element.cc | 6 +++--- bridge/core/html/html_body_element.h | 6 +++--- bridge/core/html/html_collection.cc | 6 +++--- bridge/core/html/html_collection.h | 6 +++--- bridge/core/html/html_div_element.cc | 6 +++--- bridge/core/html/html_div_element.h | 6 +++--- bridge/core/html/html_element.cc | 6 +++--- bridge/core/html/html_element.h | 6 +++--- bridge/core/html/html_head_element.cc | 6 +++--- bridge/core/html/html_head_element.h | 6 +++--- bridge/core/html/html_html_element.cc | 6 +++--- bridge/core/html/html_html_element.h | 6 +++--- bridge/core/html/html_image_element.cc | 6 +++--- bridge/core/html/html_image_element.h | 6 +++--- bridge/core/html/html_script_element.cc | 6 +++--- bridge/core/html/html_script_element.h | 6 +++--- bridge/core/html/html_template_element.cc | 6 +++--- bridge/core/html/html_template_element.h | 6 +++--- bridge/core/html/html_unknown_element.cc | 6 +++--- bridge/core/html/html_unknown_element.h | 6 +++--- bridge/core/html/parser/html_parser.cc | 6 +++--- bridge/core/html/parser/html_parser.h | 6 +++--- bridge/core/page.cc | 6 +++--- bridge/core/script_state.cc | 6 +++--- bridge/core/script_state.h | 6 +++--- bridge/foundation/ascii_types.h | 6 +++--- bridge/foundation/casting.h | 6 +++--- bridge/foundation/colors.h | 6 +++--- bridge/foundation/logging.cc | 6 +++--- bridge/foundation/logging.h | 6 +++--- bridge/foundation/macros.h | 6 +++--- bridge/foundation/native_string.cc | 6 +++--- bridge/foundation/native_string.h | 6 +++--- bridge/foundation/native_type.h | 6 +++--- bridge/foundation/native_value.cc | 6 +++--- bridge/foundation/native_value_converter.cc | 6 +++--- bridge/foundation/native_value_converter.h | 6 +++--- bridge/foundation/ref_counted_internal.h | 6 +++--- bridge/foundation/ref_counter.h | 6 +++--- bridge/foundation/ref_ptr.h | 6 +++--- bridge/foundation/ref_ptr_internal.h | 6 +++--- bridge/foundation/string_view.cc | 6 +++--- bridge/foundation/string_view.h | 6 +++--- bridge/foundation/ui_command_buffer.cc | 6 +++--- bridge/foundation/ui_command_buffer.h | 6 +++--- bridge/foundation/ui_task_queue.h | 6 +++--- bridge/include/webf_bridge.h | 6 +++--- bridge/include/webf_bridge_test.h | 6 +++--- bridge/test/kraken_test_context.cc | 6 +++--- bridge/test/kraken_test_context.h | 6 +++--- bridge/webf_bridge.cc | 6 +++--- bridge/webf_bridge_test.cc | 6 +++--- 207 files changed, 615 insertions(+), 615 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index d7a9019d36..dfb19a848d 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "atomic_string.h" diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 3d2b1aff9e..de6b9e27b0 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ #define KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ diff --git a/bridge/bindings/qjs/atomic_string_test.cc b/bridge/bindings/qjs/atomic_string_test.cc index 8c1c9db9ac..2cfec0afc4 100644 --- a/bridge/bindings/qjs/atomic_string_test.cc +++ b/bridge/bindings/qjs/atomic_string_test.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "atomic_string.h" #include <quickjs/quickjs.h> diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 40820da636..b819455811 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "binding_initializer.h" #include "core/executing_context.h" diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h index 05a957773c..4cd629ce50 100644 --- a/bridge/bindings/qjs/binding_initializer.h +++ b/bridge/bindings/qjs/binding_initializer.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDING_INITIALIZER_H #define KRAKENBRIDGE_BINDING_INITIALIZER_H diff --git a/bridge/bindings/qjs/converter.h b/bridge/bindings/qjs/converter.h index 5592aead3a..9eb8a53a78 100644 --- a/bridge/bindings/qjs/converter.h +++ b/bridge/bindings/qjs/converter.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CONVERTER_H #define KRAKENBRIDGE_CONVERTER_H diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 17139dea79..8d485e7208 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ #define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/cppgc/garbage_collected.h b/bridge/bindings/qjs/cppgc/garbage_collected.h index 0bb2cc4658..f0d49cc1ca 100644 --- a/bridge/bindings/qjs/cppgc/garbage_collected.h +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_GARBAGE_COLLECTED_H #define KRAKENBRIDGE_GARBAGE_COLLECTED_H diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.cc b/bridge/bindings/qjs/cppgc/gc_visitor.cc index 5d7595bee2..77532f054b 100644 --- a/bridge/bindings/qjs/cppgc/gc_visitor.cc +++ b/bridge/bindings/qjs/cppgc/gc_visitor.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "gc_visitor.h" #include "bindings/qjs/script_wrappable.h" diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.h b/bridge/bindings/qjs/cppgc/gc_visitor.h index ce1a3562c3..cf2ff7b16d 100644 --- a/bridge/bindings/qjs/cppgc/gc_visitor.h +++ b/bridge/bindings/qjs/cppgc/gc_visitor.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_GC_VISITOR_H #define KRAKENBRIDGE_GC_VISITOR_H diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index 10c3926791..7d09766746 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.cc b/bridge/bindings/qjs/cppgc/mutation_scope.cc index e947e02c09..3f3b0a8c4a 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.cc +++ b/bridge/bindings/qjs/cppgc/mutation_scope.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "mutation_scope.h" diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.h b/bridge/bindings/qjs/cppgc/mutation_scope.h index a68e11f98e..73044d7a83 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.h +++ b/bridge/bindings/qjs/cppgc/mutation_scope.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ diff --git a/bridge/bindings/qjs/dictionary_base.cc b/bridge/bindings/qjs/dictionary_base.cc index 9dd0898d0c..20d60b6a59 100644 --- a/bridge/bindings/qjs/dictionary_base.cc +++ b/bridge/bindings/qjs/dictionary_base.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "dictionary_base.h" diff --git a/bridge/bindings/qjs/dictionary_base.h b/bridge/bindings/qjs/dictionary_base.h index 3473d4033b..eed3755864 100644 --- a/bridge/bindings/qjs/dictionary_base.h +++ b/bridge/bindings/qjs/dictionary_base.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ diff --git a/bridge/bindings/qjs/exception_message.cc b/bridge/bindings/qjs/exception_message.cc index 935720127c..dd08a05763 100644 --- a/bridge/bindings/qjs/exception_message.cc +++ b/bridge/bindings/qjs/exception_message.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "exception_message.h" #include <vector> diff --git a/bridge/bindings/qjs/exception_message.h b/bridge/bindings/qjs/exception_message.h index 29032dec45..03915b83f0 100644 --- a/bridge/bindings/qjs/exception_message.h +++ b/bridge/bindings/qjs/exception_message.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index 925877a472..fc402e05c5 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "exception_state.h" namespace kraken { diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index c901134875..8f79084055 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_EXCEPTION_STATE_H #define KRAKENBRIDGE_EXCEPTION_STATE_H diff --git a/bridge/bindings/qjs/generated_code_helper.h b/bridge/bindings/qjs/generated_code_helper.h index 0ffdbc0d36..c9d0a39ba7 100644 --- a/bridge/bindings/qjs/generated_code_helper.h +++ b/bridge/bindings/qjs/generated_code_helper.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_GENERATED_CODE_HELPER_H #define KRAKENBRIDGE_GENERATED_CODE_HELPER_H diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index c12d2e0b9c..900321b29c 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc index 0a66bb91e2..4656c626cd 100644 --- a/bridge/bindings/qjs/js_based_event_listener.cc +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "js_based_event_listener.h" diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index 1f56446ab3..dacf177552 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index cb692de888..dd6c63d54a 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "js_event_handler.h" #include "bindings/qjs/converter_impl.h" diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index d0dab922de..b7faba292b 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index d10cf9c967..e0cf1125ee 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "js_event_listener.h" #include "core/dom/events/event_target.h" diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h index 9eacdcf083..1605831f53 100644 --- a/bridge/bindings/qjs/js_event_listener.h +++ b/bridge/bindings/qjs/js_event_listener.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc index 25aee8da96..4f6993d6c2 100644 --- a/bridge/bindings/qjs/member_installer.cc +++ b/bridge/bindings/qjs/member_installer.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "member_installer.h" #include <quickjs/quickjs.h> diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h index a2d87c83b0..3de0c3c6cc 100644 --- a/bridge/bindings/qjs/member_installer.h +++ b/bridge/bindings/qjs/member_installer.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_MEMBER_INSTALLER_H #define KRAKENBRIDGE_MEMBER_INSTALLER_H diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index 77c9a87967..14dba23c31 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "native_string_utils.h" #include "bindings/qjs/qjs_engine_patch.h" diff --git a/bridge/bindings/qjs/native_string_utils.h b/bridge/bindings/qjs/native_string_utils.h index 9d7a752f38..cc9ef58956 100644 --- a/bridge/bindings/qjs/native_string_utils.h +++ b/bridge/bindings/qjs/native_string_utils.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_NATIVE_STRING_UTILS_H #define KRAKENBRIDGE_NATIVE_STRING_UTILS_H diff --git a/bridge/bindings/qjs/pending_promises.cc b/bridge/bindings/qjs/pending_promises.cc index 41af49e12d..246bff459d 100644 --- a/bridge/bindings/qjs/pending_promises.cc +++ b/bridge/bindings/qjs/pending_promises.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "pending_promises.h" #include "script_promise.h" diff --git a/bridge/bindings/qjs/pending_promises.h b/bridge/bindings/qjs/pending_promises.h index 678794196d..edf131507c 100644 --- a/bridge/bindings/qjs/pending_promises.h +++ b/bridge/bindings/qjs/pending_promises.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ #define KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ diff --git a/bridge/bindings/qjs/qjs_engine_patch_test.cc b/bridge/bindings/qjs/qjs_engine_patch_test.cc index 9959d73bb8..d753b034eb 100644 --- a/bridge/bindings/qjs/qjs_engine_patch_test.cc +++ b/bridge/bindings/qjs/qjs_engine_patch_test.cc @@ -3,9 +3,9 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +#include "qjs_engine_patch.h" #include <codecvt> #include "gtest/gtest.h" -#include "qjs_engine_patch.h" TEST(JS_ToUnicode, asciiWords) { JSRuntime* runtime = JS_NewRuntime(); diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 9b1ebfe982..90ecac34fb 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "qjs_function.h" #include <algorithm> #include "cppgc/gc_visitor.h" diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 9d1f1794cb..3cfb39e0b4 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_QJS_FUNCTION_H #define KRAKENBRIDGE_QJS_FUNCTION_H diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index 91d2e01a7d..5d346ab95b 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index f2532d3722..c830791a90 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "script_promise.h" #include "qjs_engine_patch.h" diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index 7e862c171a..d5b537a119 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ #define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index d18bc60034..a4d8bd1cf8 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "script_promise_resolver.h" #include "core/executing_context.h" #include "pending_promises.h" diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 4ba9b76fa8..338c453abb 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 8ae3184e87..4ce5c2f993 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "script_value.h" #include <vector> #include "core/executing_context.h" diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 427a429f41..277dd91cd6 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_SCRIPT_VALUE_H #define KRAKENBRIDGE_SCRIPT_VALUE_H diff --git a/bridge/bindings/qjs/script_value_test.cc b/bridge/bindings/qjs/script_value_test.cc index 666f3628e4..be100070ac 100644 --- a/bridge/bindings/qjs/script_value_test.cc +++ b/bridge/bindings/qjs/script_value_test.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "script_value.h" #include <quickjs/quickjs.h> diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index cfbd631c22..9d7f848721 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "script_wrappable.h" #include "core/executing_context.h" diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 209907b9de..0c1cc9b629 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_SCRIPT_WRAPPABLE_H #define KRAKENBRIDGE_SCRIPT_WRAPPABLE_H diff --git a/bridge/bindings/qjs/source_location.cc b/bridge/bindings/qjs/source_location.cc index 93f0711b0b..7b1bc4b573 100644 --- a/bridge/bindings/qjs/source_location.cc +++ b/bridge/bindings/qjs/source_location.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "source_location.h" namespace kraken { diff --git a/bridge/bindings/qjs/source_location.h b/bridge/bindings/qjs/source_location.h index 5a7ce98f29..ca21842dd5 100644 --- a/bridge/bindings/qjs/source_location.h +++ b/bridge/bindings/qjs/source_location.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ #define KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ diff --git a/bridge/bindings/qjs/to_quickjs.h b/bridge/bindings/qjs/to_quickjs.h index 42dc8073df..9e3c599ccc 100644 --- a/bridge/bindings/qjs/to_quickjs.h +++ b/bridge/bindings/qjs/to_quickjs.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ #define KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 9bdf75f2aa..61b407153f 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_WRAPPER_TYPE_INFO_H #define KRAKENBRIDGE_WRAPPER_TYPE_INFO_H diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index 8b8d891acc..2c3336cc7d 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "css_style_declaration.h" #include <vector> #include "core/dom/element.h" diff --git a/bridge/core/css/legacy/css_style_declaration.h b/bridge/core/css/legacy/css_style_declaration.h index 39ebcf2e0f..79b0e26196 100644 --- a/bridge/core/css/legacy/css_style_declaration.h +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CSS_STYLE_DECLARATION_H #define KRAKENBRIDGE_CSS_STYLE_DECLARATION_H diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index 77fd4185a6..8d4ebb4e5e 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_ diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc index 1395a34ec3..8cff37c8aa 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/dom/binding_object.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "binding_object.h" #include "binding_call_methods.h" diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h index a1b544a3c7..3dd2e8bf08 100644 --- a/bridge/core/dom/binding_object.h +++ b/bridge/core/dom/binding_object.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ #define KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index 12d46c5aa7..97b1073956 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "character_data.h" #include "core/dom/document.h" diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index 18e22bf989..4dbd00deef 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CHARACTER_DATA_H #define KRAKENBRIDGE_CHARACTER_DATA_H diff --git a/bridge/core/dom/child_node_list.cc b/bridge/core/dom/child_node_list.cc index 4bf32df89e..07c3e778d9 100644 --- a/bridge/core/dom/child_node_list.cc +++ b/bridge/core/dom/child_node_list.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "child_node_list.h" #include "bindings/qjs/cppgc/gc_visitor.h" diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h index 7f5e2404a6..da44e9087a 100644 --- a/bridge/core/dom/child_node_list.h +++ b/bridge/core/dom/child_node_list.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ #define KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ diff --git a/bridge/core/dom/collection_index_cache.h b/bridge/core/dom/collection_index_cache.h index 5c1e24070f..0cffa5d41e 100644 --- a/bridge/core/dom/collection_index_cache.h +++ b/bridge/core/dom/collection_index_cache.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ #define KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc index 8a6d8b3abb..7044d8de9d 100644 --- a/bridge/core/dom/comment.cc +++ b/bridge/core/dom/comment.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "comment.h" #include "built_in_string.h" #include "document.h" diff --git a/bridge/core/dom/comment.h b/bridge/core/dom/comment.h index cd661be157..922f94ef50 100644 --- a/bridge/core/dom/comment.h +++ b/bridge/core/dom/comment.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_COMMENT_H #define KRAKENBRIDGE_COMMENT_H diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index e7b77479d7..a5ae108cdb 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "container_node.h" #include "bindings/qjs/cppgc/garbage_collected.h" diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index e2ea09fec3..8b5306f9ef 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ #define KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index cdc6f7bc36..80a75e847b 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "document.h" #include "bindings/qjs/exception_message.h" #include "core/dom/element.h" diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index e67fedd5cb..b387f8a9de 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_DOCUMENT_H #define KRAKENBRIDGE_DOCUMENT_H diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index c3fdffbf4f..d1e5a390b2 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "document_fragment.h" #include "document.h" #include "events/event_target.h" diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index 7f312b9215..91e120c8b5 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_DOCUMENT_FRAGMENT_H #define KRAKENBRIDGE_DOCUMENT_FRAGMENT_H diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index a0476e821f..14be2a5ec4 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "gtest/gtest.h" #include "kraken_test_env.h" diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index e99392c36c..2a43f0d827 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "element.h" #include <utility> diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 311e444653..6988b2284c 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_ELEMENT_H #define KRAKENBRIDGE_ELEMENT_H diff --git a/bridge/core/dom/element_traversal.h b/bridge/core/dom/element_traversal.h index feb472c8a9..808c0a8ebe 100644 --- a/bridge/core/dom/element_traversal.h +++ b/bridge/core/dom/element_traversal.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ #define KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ diff --git a/bridge/core/dom/empty_node_list.cc b/bridge/core/dom/empty_node_list.cc index 136688840f..588343fc2a 100644 --- a/bridge/core/dom/empty_node_list.cc +++ b/bridge/core/dom/empty_node_list.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "empty_node_list.h" #include "core/dom/node.h" diff --git a/bridge/core/dom/empty_node_list.h b/bridge/core/dom/empty_node_list.h index ce994211b0..6c8b48a796 100644 --- a/bridge/core/dom/empty_node_list.h +++ b/bridge/core/dom/empty_node_list.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ #define KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ diff --git a/bridge/core/dom/events/custom_event.cc b/bridge/core/dom/events/custom_event.cc index 88569d977e..19d246b9b6 100644 --- a/bridge/core/dom/events/custom_event.cc +++ b/bridge/core/dom/events/custom_event.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "custom_event.h" #include "bindings/qjs/native_value.h" #include "bindings/qjs/qjs_engine_patch.h" diff --git a/bridge/core/dom/events/custom_event.h b/bridge/core/dom/events/custom_event.h index a73b9f3f6e..ecb2570e4b 100644 --- a/bridge/core/dom/events/custom_event.h +++ b/bridge/core/dom/events/custom_event.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CUSTOM_EVENT_H #define KRAKENBRIDGE_CUSTOM_EVENT_H diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 9a8cd19d57..10afe78291 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "event.h" #include "core/executing_context.h" #include "event_target.h" diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 0da3a262d4..ab163b08f2 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_EVENT_H #define KRAKENBRIDGE_EVENT_H diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h index c6703cf671..67f1be90b3 100644 --- a/bridge/core/dom/events/event_listener.h +++ b/bridge/core/dom/events/event_listener.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index b168c387a3..9dc4bdff09 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "event_listener_map.h" namespace kraken { diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index 5e16fe8931..c3173847a1 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ #define KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 3db880d603..cb0f1cc61e 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "event_target.h" #include "bindings/qjs/converter_impl.h" #include "event_type_names.h" diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 1762e188f3..84a8b8113f 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_EVENT_TARGET_H #define KRAKENBRIDGE_EVENT_TARGET_H diff --git a/bridge/core/dom/events/event_target_impl.cc b/bridge/core/dom/events/event_target_impl.cc index 012c10cc11..9901e96e28 100644 --- a/bridge/core/dom/events/event_target_impl.cc +++ b/bridge/core/dom/events/event_target_impl.cc @@ -1,5 +1,5 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "event_target_impl.h" diff --git a/bridge/core/dom/events/event_target_impl.h b/bridge/core/dom/events/event_target_impl.h index 34266f14b7..c1f0378eb8 100644 --- a/bridge/core/dom/events/event_target_impl.h +++ b/bridge/core/dom/events/event_target_impl.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index 40fb06c4ec..48f3558585 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "event_target.h" #include "gtest/gtest.h" #include "kraken_test_env.h" diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc index ad529a881d..5a1afdaa4d 100644 --- a/bridge/core/dom/events/registered_eventListener.cc +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "registered_eventListener.h" #include "qjs_add_event_listener_options.h" diff --git a/bridge/core/dom/events/registered_eventListener.h b/bridge/core/dom/events/registered_eventListener.h index 97ad179151..a9c5873c72 100644 --- a/bridge/core/dom/events/registered_eventListener.h +++ b/bridge/core/dom/events/registered_eventListener.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc index c96b740773..82d025f0ae 100644 --- a/bridge/core/dom/frame_request_callback_collection.cc +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "frame_request_callback_collection.h" #include <utility> diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index 577dae2a11..accfb434c4 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ #define KRAKENBRIDGE_BINDINGS_QJS_BOM_FRAME_REQUEST_CALLBACK_COLLECTION_H_ diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc index 71cd02e75f..cc17634fd5 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.cc +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "bounding_client_rect.h" #include "core/executing_context.h" diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index 8476dc1d8b..89ebec3e5d 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ #define KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 8fcf278d82..c0c321cc55 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "element_attributes.h" #include "bindings/qjs/exception_state.h" diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index d0e0bfc28a..b8abe6f38e 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ #define KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ diff --git a/bridge/core/dom/legacy/space_split_string.cc b/bridge/core/dom/legacy/space_split_string.cc index 5b55dd9341..a0fb15e980 100644 --- a/bridge/core/dom/legacy/space_split_string.cc +++ b/bridge/core/dom/legacy/space_split_string.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "space_split_string.h" diff --git a/bridge/core/dom/legacy/space_split_string.h b/bridge/core/dom/legacy/space_split_string.h index 8f28867ae9..eae4450c9f 100644 --- a/bridge/core/dom/legacy/space_split_string.h +++ b/bridge/core/dom/legacy/space_split_string.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ #define KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 36e72c271d..576cb49a12 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "node.h" #include <unordered_map> diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index bd0d1c5973..9547e4b9ec 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_NODE_H #define KRAKENBRIDGE_NODE_H diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index 2c2381b8ec..bd1b7e0f84 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "node_data.h" #include "bindings/qjs/cppgc/garbage_collected.h" diff --git a/bridge/core/dom/node_data.h b/bridge/core/dom/node_data.h index 0f50ffc0bc..cafa800baf 100644 --- a/bridge/core/dom/node_data.h +++ b/bridge/core/dom/node_data.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ #define KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h index 57f04f93e8..92878f5147 100644 --- a/bridge/core/dom/node_list.h +++ b/bridge/core/dom/node_list.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ #define KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ diff --git a/bridge/core/dom/node_traversal.cc b/bridge/core/dom/node_traversal.cc index 6d2ba1d588..2a0efc762a 100644 --- a/bridge/core/dom/node_traversal.cc +++ b/bridge/core/dom/node_traversal.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "node_traversal.h" diff --git a/bridge/core/dom/node_traversal.h b/bridge/core/dom/node_traversal.h index e00001f6a4..1f08789700 100644 --- a/bridge/core/dom/node_traversal.h +++ b/bridge/core/dom/node_traversal.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ #define KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index 01581e64b5..50926508ca 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "scripted_animation_controller.h" #include "frame_request_callback_collection.h" diff --git a/bridge/core/dom/scripted_animation_controller.h b/bridge/core/dom/scripted_animation_controller.h index c2dc617edd..c9f87b92d0 100644 --- a/bridge/core/dom/scripted_animation_controller.h +++ b/bridge/core/dom/scripted_animation_controller.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ #define KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ diff --git a/bridge/core/dom/text.cc b/bridge/core/dom/text.cc index d7d636c834..cbec1f787f 100644 --- a/bridge/core/dom/text.cc +++ b/bridge/core/dom/text.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "text.h" #include "document.h" diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h index dab789d438..6ebac35670 100644 --- a/bridge/core/dom/text.h +++ b/bridge/core/dom/text.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_TEXT_H_ #define KRAKENBRIDGE_CORE_DOM_TEXT_H_ diff --git a/bridge/core/dom/tree_scope.cc b/bridge/core/dom/tree_scope.cc index fb47959292..a6e2989989 100644 --- a/bridge/core/dom/tree_scope.cc +++ b/bridge/core/dom/tree_scope.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "tree_scope.h" #include "document.h" diff --git a/bridge/core/dom/tree_scope.h b/bridge/core/dom/tree_scope.h index 66c410d0a3..45e810ca76 100644 --- a/bridge/core/dom/tree_scope.h +++ b/bridge/core/dom/tree_scope.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ #define KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc index 7ba0ced506..88bc00466f 100644 --- a/bridge/core/events/error_event.cc +++ b/bridge/core/events/error_event.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "error_event.h" namespace kraken { diff --git a/bridge/core/events/error_event.h b/bridge/core/events/error_event.h index 3fcd008a91..7dc0631676 100644 --- a/bridge/core/events/error_event.h +++ b/bridge/core/events/error_event.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ #define KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index 708c200041..8249c98cde 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "message_event.h" diff --git a/bridge/core/events/message_event.h b/bridge/core/events/message_event.h index ef5f8e2c49..113229de34 100644 --- a/bridge/core/events/message_event.h +++ b/bridge/core/events/message_event.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ #define KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 1823a83893..d86380e4a7 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "executing_context.h" #include "built_in_string.h" #include "core/dom/document.h" diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 585e93fdbd..d0ec0ba5c5 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_JS_CONTEXT_H #define KRAKENBRIDGE_JS_CONTEXT_H diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc index a2e5597908..ce8962fc99 100644 --- a/bridge/core/executing_context_data.cc +++ b/bridge/core/executing_context_data.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "executing_context_data.h" #include "executing_context.h" diff --git a/bridge/core/executing_context_data.h b/bridge/core/executing_context_data.h index 687f9cd1a5..5e7ad73710 100644 --- a/bridge/core/executing_context_data.h +++ b/bridge/core/executing_context_data.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CONTEXT_DATA_H #define KRAKENBRIDGE_CONTEXT_DATA_H diff --git a/bridge/core/fileapi/array_buffer_data.h b/bridge/core/fileapi/array_buffer_data.h index c98540cf7d..4bf861f3b8 100644 --- a/bridge/core/fileapi/array_buffer_data.h +++ b/bridge/core/fileapi/array_buffer_data.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ #define KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 61869a69b1..554deeeb11 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "blob.h" #include <string> #include "bindings/qjs/script_promise_resolver.h" diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index c30fbb19b5..9c10a9ad93 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_BLOB_H #define KRAKENBRIDGE_BLOB_H diff --git a/bridge/core/fileapi/blob_part.cc b/bridge/core/fileapi/blob_part.cc index d19cbf53eb..d628974d7f 100644 --- a/bridge/core/fileapi/blob_part.cc +++ b/bridge/core/fileapi/blob_part.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "blob_part.h" #include "qjs_blob.h" diff --git a/bridge/core/fileapi/blob_part.h b/bridge/core/fileapi/blob_part.h index 65a09882ba..af135002dc 100644 --- a/bridge/core/fileapi/blob_part.h +++ b/bridge/core/fileapi/blob_part.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ #define KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ diff --git a/bridge/core/fileapi/blob_property_bag.cc b/bridge/core/fileapi/blob_property_bag.cc index 09b32363a5..f13ba7f92c 100644 --- a/bridge/core/fileapi/blob_property_bag.cc +++ b/bridge/core/fileapi/blob_property_bag.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "blob_property_bag.h" namespace kraken { diff --git a/bridge/core/fileapi/blob_property_bag.h b/bridge/core/fileapi/blob_property_bag.h index 2595827eee..686561a6cc 100644 --- a/bridge/core/fileapi/blob_property_bag.h +++ b/bridge/core/fileapi/blob_property_bag.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ #define KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 1ae522d09d..5b95381335 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "console.h" #include <sstream> #include "built_in_string.h" diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index a4ad5dc4fe..fbe769394e 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKE_CONSOLE_H #define KRAKE_CONSOLE_H diff --git a/bridge/core/frame/dom_timer.cc b/bridge/core/frame/dom_timer.cc index da5c763230..8fc6e0acbc 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "dom_timer.h" #include <utility> diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index 9097f6f99d..d97d6144d9 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_DOM_TIMER_H #define KRAKENBRIDGE_DOM_TIMER_H diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 6623a5de78..12534c1aae 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "dom_timer_coordinator.h" #include "core/dart_methods.h" #include "core/executing_context.h" diff --git a/bridge/core/frame/legacy/location.cc b/bridge/core/frame/legacy/location.cc index f3d9bcdeca..6ac3aaf1fa 100644 --- a/bridge/core/frame/legacy/location.cc +++ b/bridge/core/frame/legacy/location.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "location.h" #include "core/executing_context.h" diff --git a/bridge/core/frame/legacy/location.h b/bridge/core/frame/legacy/location.h index 02fdf744b5..91a612c75e 100644 --- a/bridge/core/frame/legacy/location.h +++ b/bridge/core/frame/legacy/location.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_LOCATION_H #define KRAKENBRIDGE_LOCATION_H diff --git a/bridge/core/frame/module_callback.cc b/bridge/core/frame/module_callback.cc index c225796c5b..c2fec46e06 100644 --- a/bridge/core/frame/module_callback.cc +++ b/bridge/core/frame/module_callback.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "module_callback.h" namespace kraken { diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h index 9eb01b686d..9a730a660b 100644 --- a/bridge/core/frame/module_callback.h +++ b/bridge/core/frame/module_callback.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_MODULE_CALLBACK_H #define KRAKENBRIDGE_MODULE_CALLBACK_H diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc index faf2513bce..df319f5424 100644 --- a/bridge/core/frame/module_callback_coordinator.cc +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "module_callback_coordinator.h" namespace kraken { diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_callback_coordinator.h index 02b04ccf8f..98f548af9d 100644 --- a/bridge/core/frame/module_callback_coordinator.h +++ b/bridge/core/frame/module_callback_coordinator.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H #define KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H diff --git a/bridge/core/frame/module_listener.cc b/bridge/core/frame/module_listener.cc index 1cfd753e4f..00dff2a8dd 100644 --- a/bridge/core/frame/module_listener.cc +++ b/bridge/core/frame/module_listener.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "module_listener.h" #include <utility> diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h index 9d2980aaa0..33740c1dc7 100644 --- a/bridge/core/frame/module_listener.h +++ b/bridge/core/frame/module_listener.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_MODULE_LISTENER_H #define KRAKENBRIDGE_MODULE_LISTENER_H diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc index 1b1d56db1c..340d132e9c 100644 --- a/bridge/core/frame/module_listener_container.cc +++ b/bridge/core/frame/module_listener_container.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "module_listener_container.h" namespace kraken { diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h index 21b0c53a8c..a577ad5a91 100644 --- a/bridge/core/frame/module_listener_container.h +++ b/bridge/core/frame/module_listener_container.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H #define KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index be9db7b7bb..3125b61299 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "module_manager.h" #include "core/executing_context.h" #include "module_callback.h" diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index bbaf8ea2da..afc8aefe57 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_MODULE_MANAGER_H #define KRAKENBRIDGE_MODULE_MANAGER_H diff --git a/bridge/core/frame/screen.cc b/bridge/core/frame/screen.cc index 2d02ae06bc..10f579546f 100644 --- a/bridge/core/frame/screen.cc +++ b/bridge/core/frame/screen.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "screen.h" #include "core/frame/window.h" diff --git a/bridge/core/frame/screen.h b/bridge/core/frame/screen.h index 85bf487a9d..8844dea9e6 100644 --- a/bridge/core/frame/screen.h +++ b/bridge/core/frame/screen.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_SCREEN_H #define KRAKENBRIDGE_SCREEN_H diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index c76f1f4654..d0f64c5d56 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "window.h" #include "binding_call_methods.h" diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index 5b35960f6d..c841c9b82d 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_WINDOW_H #define KRAKENBRIDGE_WINDOW_H diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 7dabd525f3..463a3cd64b 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "window_or_worker_global_scope.h" #include "core/frame/dom_timer.h" diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h index 96381ecce0..dd27fe62d3 100644 --- a/bridge/core/frame/window_or_worker_global_scope.h +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H #define KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H diff --git a/bridge/core/html/canvas/html_canvas_element.cc b/bridge/core/html/canvas/html_canvas_element.cc index a7c8145be3..1a866984d3 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_canvas_element.h" #include "html_names.h" diff --git a/bridge/core/html/canvas/html_canvas_element.h b/bridge/core/html/canvas/html_canvas_element.h index 480b3a55df..cd0cb27c58 100644 --- a/bridge/core/html/canvas/html_canvas_element.h +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_input_element.cc b/bridge/core/html/forms/html_input_element.cc index 801854cd61..3d9b870a48 100644 --- a/bridge/core/html/forms/html_input_element.cc +++ b/bridge/core/html/forms/html_input_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_input_element.h" #include "html_names.h" diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h index d07be595eb..b387075b40 100644 --- a/bridge/core/html/forms/html_input_element.h +++ b/bridge/core/html/forms/html_input_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_textarea_element.cc b/bridge/core/html/forms/html_textarea_element.cc index fe337d0f6c..f933df2431 100644 --- a/bridge/core/html/forms/html_textarea_element.cc +++ b/bridge/core/html/forms/html_textarea_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_textarea_element.h" #include "html_names.h" diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h index a271a1db7b..10ea01a135 100644 --- a/bridge/core/html/forms/html_textarea_element.h +++ b/bridge/core/html/forms/html_textarea_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ diff --git a/bridge/core/html/html_anchor_element.cc b/bridge/core/html/html_anchor_element.cc index c820fee5aa..6abe4f74e1 100644 --- a/bridge/core/html/html_anchor_element.cc +++ b/bridge/core/html/html_anchor_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_anchor_element.h" #include "html_names.h" diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h index a8689b459d..18e1d838a0 100644 --- a/bridge/core/html/html_anchor_element.h +++ b/bridge/core/html/html_anchor_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H #define KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H diff --git a/bridge/core/html/html_body_element.cc b/bridge/core/html/html_body_element.cc index 3068664033..ea30da52d7 100644 --- a/bridge/core/html/html_body_element.cc +++ b/bridge/core/html/html_body_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_body_element.h" #include "html_names.h" diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index 7a540f9a84..ab3f8e522a 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ diff --git a/bridge/core/html/html_collection.cc b/bridge/core/html/html_collection.cc index b179fada08..ae3e9ce3c1 100644 --- a/bridge/core/html/html_collection.cc +++ b/bridge/core/html/html_collection.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_collection.h" diff --git a/bridge/core/html/html_collection.h b/bridge/core/html/html_collection.h index 3d7b70df4f..416d73fb54 100644 --- a/bridge/core/html/html_collection.h +++ b/bridge/core/html/html_collection.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ diff --git a/bridge/core/html/html_div_element.cc b/bridge/core/html/html_div_element.cc index c7054aa418..4008d73e86 100644 --- a/bridge/core/html/html_div_element.cc +++ b/bridge/core/html/html_div_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_div_element.h" #include "html_names.h" diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h index b4d6a89493..85e00d9dae 100644 --- a/bridge/core/html/html_div_element.h +++ b/bridge/core/html/html_div_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ diff --git a/bridge/core/html/html_element.cc b/bridge/core/html/html_element.cc index 0e24510a9f..74e7091354 100644 --- a/bridge/core/html/html_element.cc +++ b/bridge/core/html/html_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_element.h" diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index 7da8b4a571..4c8e892284 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_head_element.cc b/bridge/core/html/html_head_element.cc index 22e2a1f57a..38906f4ae9 100644 --- a/bridge/core/html/html_head_element.cc +++ b/bridge/core/html/html_head_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_head_element.h" #include "html_names.h" diff --git a/bridge/core/html/html_head_element.h b/bridge/core/html/html_head_element.h index 107e0592a1..c4446b46dd 100644 --- a/bridge/core/html/html_head_element.h +++ b/bridge/core/html/html_head_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ diff --git a/bridge/core/html/html_html_element.cc b/bridge/core/html/html_html_element.cc index a471530521..d4b965cd53 100644 --- a/bridge/core/html/html_html_element.cc +++ b/bridge/core/html/html_html_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_html_element.h" #include "html_names.h" diff --git a/bridge/core/html/html_html_element.h b/bridge/core/html/html_html_element.h index 6a38b0b0d9..d44fb4dd03 100644 --- a/bridge/core/html/html_html_element.h +++ b/bridge/core/html/html_html_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index fa52941ecc..6b10466bf0 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_image_element.h" #include "html_names.h" diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index b6e41cd0e1..1442c898ec 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ diff --git a/bridge/core/html/html_script_element.cc b/bridge/core/html/html_script_element.cc index cb4894dce2..e92aea1768 100644 --- a/bridge/core/html/html_script_element.cc +++ b/bridge/core/html/html_script_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_script_element.h" #include "html_names.h" diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h index 4fd3e64c0b..bf24f988e0 100644 --- a/bridge/core/html/html_script_element.h +++ b/bridge/core/html/html_script_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index 2081b38715..15d67ccd7e 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_template_element.h" #include "core/dom/document_fragment.h" #include "html_names.h" diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index a12980767e..4561612323 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H #define KRAKENBRIDGE_HTML_TEMPLATE_ELEMENT_H diff --git a/bridge/core/html/html_unknown_element.cc b/bridge/core/html/html_unknown_element.cc index ec9e5d6df5..8359cb77bd 100644 --- a/bridge/core/html/html_unknown_element.cc +++ b/bridge/core/html/html_unknown_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_unknown_element.h" namespace kraken { diff --git a/bridge/core/html/html_unknown_element.h b/bridge/core/html/html_unknown_element.h index 2b69966d8c..6ee3edb426 100644 --- a/bridge/core/html/html_unknown_element.h +++ b/bridge/core/html/html_unknown_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ #define KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ diff --git a/bridge/core/html/parser/html_parser.cc b/bridge/core/html/parser/html_parser.cc index ebe6fca85e..df326e0809 100644 --- a/bridge/core/html/parser/html_parser.cc +++ b/bridge/core/html/parser/html_parser.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 <utility> diff --git a/bridge/core/html/parser/html_parser.h b/bridge/core/html/parser/html_parser.h index d0f66e03ea..0f672511f5 100644 --- a/bridge/core/html/parser/html_parser.h +++ b/bridge/core/html/parser/html_parser.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_HTML_PARSER_H #define KRAKENBRIDGE_HTML_PARSER_H diff --git a/bridge/core/page.cc b/bridge/core/page.cc index b9a6fd55c2..f64c297b15 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 <atomic> #include <unordered_map> diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 174051db87..4c190c45e7 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "script_state.h" #include "binding_call_methods.h" #include "built_in_string.h" diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h index 10e93953d5..60dc1fffd2 100644 --- a/bridge/core/script_state.h +++ b/bridge/core/script_state.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ #define KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ diff --git a/bridge/foundation/ascii_types.h b/bridge/foundation/ascii_types.h index 568cfbfeba..9a3b1dc5d9 100644 --- a/bridge/foundation/ascii_types.h +++ b/bridge/foundation/ascii_types.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ #define KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ diff --git a/bridge/foundation/casting.h b/bridge/foundation/casting.h index 2719d4acb6..93b9999938 100644 --- a/bridge/foundation/casting.h +++ b/bridge/foundation/casting.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_FOUNDATION_CASTING_H_ #define KRAKENBRIDGE_FOUNDATION_CASTING_H_ diff --git a/bridge/foundation/colors.h b/bridge/foundation/colors.h index feca25c5fd..5da319bf3a 100644 --- a/bridge/foundation/colors.h +++ b/bridge/foundation/colors.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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/logging.cc b/bridge/foundation/logging.cc index 06cdb2fa79..29d86f070c 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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> diff --git a/bridge/foundation/logging.h b/bridge/foundation/logging.h index c2127a8797..17247bb760 100644 --- a/bridge/foundation/logging.h +++ b/bridge/foundation/logging.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_ diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h index c64dfd9054..3bba11ef77 100644 --- a/bridge/foundation/macros.h +++ b/bridge/foundation/macros.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_MACROS_H #define KRAKENBRIDGE_MACROS_H diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc index 65f68be9de..d2e744e7ed 100644 --- a/bridge/foundation/native_string.cc +++ b/bridge/foundation/native_string.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "native_string.h" #include <string> diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h index 1cdb15a1dd..6e792943b3 100644 --- a/bridge/foundation/native_string.h +++ b/bridge/foundation/native_string.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_NATIVE_STRING_H #define KRAKENBRIDGE_NATIVE_STRING_H diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h index 813af8b689..ff2272da2a 100644 --- a/bridge/foundation/native_type.h +++ b/bridge/foundation/native_type.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ #define KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 9c7d50055b..adc386203a 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "native_value.h" #include "bindings/qjs/qjs_engine_patch.h" #include "bindings/qjs/script_value.h" diff --git a/bridge/foundation/native_value_converter.cc b/bridge/foundation/native_value_converter.cc index b85a76e4f9..1cd4416d08 100644 --- a/bridge/foundation/native_value_converter.cc +++ b/bridge/foundation/native_value_converter.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "native_value_converter.h" diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 661b7a34f1..56de3e7717 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ #define KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ diff --git a/bridge/foundation/ref_counted_internal.h b/bridge/foundation/ref_counted_internal.h index 9f93c2076a..ea89328167 100644 --- a/bridge/foundation/ref_counted_internal.h +++ b/bridge/foundation/ref_counted_internal.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 // found in the LICENSE file. diff --git a/bridge/foundation/ref_counter.h b/bridge/foundation/ref_counter.h index f86ee34ba9..8455b07a1a 100644 --- a/bridge/foundation/ref_counter.h +++ b/bridge/foundation/ref_counter.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 // found in the LICENSE file. diff --git a/bridge/foundation/ref_ptr.h b/bridge/foundation/ref_ptr.h index 53f97ff264..e351bd5cbd 100644 --- a/bridge/foundation/ref_ptr.h +++ b/bridge/foundation/ref_ptr.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 // found in the LICENSE file. diff --git a/bridge/foundation/ref_ptr_internal.h b/bridge/foundation/ref_ptr_internal.h index 13d8601aa9..d1a5a3d675 100644 --- a/bridge/foundation/ref_ptr_internal.h +++ b/bridge/foundation/ref_ptr_internal.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 // found in the LICENSE file. diff --git a/bridge/foundation/string_view.cc b/bridge/foundation/string_view.cc index c513c0f4f4..9dd2e63bf0 100644 --- a/bridge/foundation/string_view.cc +++ b/bridge/foundation/string_view.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "string_view.h" namespace kraken { diff --git a/bridge/foundation/string_view.h b/bridge/foundation/string_view.h index 6cd3afa34f..ba0d505030 100644 --- a/bridge/foundation/string_view.h +++ b/bridge/foundation/string_view.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ #define KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 1526bac28b..109cf942b6 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "core/dart_methods.h" diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 9515d5b2e2..30c4bb0323 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_ diff --git a/bridge/foundation/ui_task_queue.h b/bridge/foundation/ui_task_queue.h index d3a3c27ce3..15f4dc8d2e 100644 --- a/bridge/foundation/ui_task_queue.h +++ b/bridge/foundation/ui_task_queue.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 #define BRIDGE_UI_TASK_QUEUE_H diff --git a/bridge/include/webf_bridge.h b/bridge/include/webf_bridge.h index 3ca7cbefef..d8aff01f83 100644 --- a/bridge/include/webf_bridge.h +++ b/bridge/include/webf_bridge.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 diff --git a/bridge/include/webf_bridge_test.h b/bridge/include/webf_bridge_test.h index 0ccd15b5c1..03bcee26dc 100644 --- a/bridge/include/webf_bridge_test.h +++ b/bridge/include/webf_bridge_test.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/kraken_test_context.cc index d026e91889..e24c195eb7 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/kraken_test_context.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "kraken_test_context.h" #include "bindings/qjs/member_installer.h" diff --git a/bridge/test/kraken_test_context.h b/bridge/test/kraken_test_context.h index 3dd35cb858..687d4dd8b4 100644 --- a/bridge/test/kraken_test_context.h +++ b/bridge/test/kraken_test_context.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H #define KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H diff --git a/bridge/webf_bridge.cc b/bridge/webf_bridge.cc index 7899c46e28..1b693fa71f 100644 --- a/bridge/webf_bridge.cc +++ b/bridge/webf_bridge.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 <atomic> #include <cassert> diff --git a/bridge/webf_bridge_test.cc b/bridge/webf_bridge_test.cc index d338bb2f0b..9750bb965f 100644 --- a/bridge/webf_bridge_test.cc +++ b/bridge/webf_bridge_test.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 <atomic> #include "bindings/qjs/native_string_utils.h" From fbc90cb84454feed00c3fabb712441a0281f6e5c Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sat, 6 Aug 2022 11:44:39 +0800 Subject: [PATCH 145/498] fix: set webf flutter requirement. --- webf/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webf/pubspec.yaml b/webf/pubspec.yaml index cb31fe39b0..68dddb614b 100644 --- a/webf/pubspec.yaml +++ b/webf/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://openwebf.com environment: sdk: ">=2.17.5 <3.0.0" - flutter: ">=3.0.0" + flutter: ">=3.0.2 <=3.0.5" dependencies: flutter: From 1c2a528c268a05196f4abc827d4cc728bb7db268 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sat, 6 Aug 2022 11:45:21 +0800 Subject: [PATCH 146/498] fix: fix example bundle name. --- .../specs/css/css-position/bottom-applies.ts | 2 +- webf/CHANGELOG.md | 8 +++++++- webf/android/src/main/AndroidManifest.xml | 2 +- webf/example/README.md | 4 ++-- webf/example/android/app/src/debug/AndroidManifest.xml | 2 +- webf/example/android/app/src/main/AndroidManifest.xml | 2 +- webf/example/android/app/src/profile/AndroidManifest.xml | 2 +- webf/example/ios/Runner/Info.plist | 2 +- webf/example/lib/main.dart | 3 ++- webf/example/linux/my_application.cc | 4 ++-- webf/example/macos/Runner.xcodeproj/project.pbxproj | 6 +++--- .../xcshareddata/xcschemes/Runner.xcscheme | 8 ++++---- webf/example/macos/Runner/Base.lproj/MainMenu.xib | 4 ++-- webf/example/pubspec.yaml | 1 + webf/pubspec.yaml | 2 +- 15 files changed, 30 insertions(+), 22 deletions(-) diff --git a/integration_tests/specs/css/css-position/bottom-applies.ts b/integration_tests/specs/css/css-position/bottom-applies.ts index 71faa2abbb..f7ea005a14 100644 --- a/integration_tests/specs/css/css-position/bottom-applies.ts +++ b/integration_tests/specs/css/css-position/bottom-applies.ts @@ -30,7 +30,7 @@ describe('bottom-applies', () => { ); BODY.appendChild(div); - await snapshot(0.1); + await snapshot(0.5); }); it('to-009', async () => { let p; diff --git a/webf/CHANGELOG.md b/webf/CHANGELOG.md index c60b9c4bee..3743236dfe 100644 --- a/webf/CHANGELOG.md +++ b/webf/CHANGELOG.md @@ -1,8 +1,14 @@ +## 0.12.0+2 + +**Bug Fixed** + +* Add Flutter version requirement at pubspec.yaml. + ## 0.12.0+1 **Bug Fixed** -* Fix Apple silicon platform build error. +* Fix Apple silicon platform build error. ## 0.12.0 diff --git a/webf/android/src/main/AndroidManifest.xml b/webf/android/src/main/AndroidManifest.xml index a105d3b735..81fd8d632d 100644 --- a/webf/android/src/main/AndroidManifest.xml +++ b/webf/android/src/main/AndroidManifest.xml @@ -1,3 +1,3 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.openkraken.kraken"> + package="com.openwebf.webf"> </manifest> diff --git a/webf/example/README.md b/webf/example/README.md index 55ccb69c38..e952c8dbf1 100644 --- a/webf/example/README.md +++ b/webf/example/README.md @@ -1,6 +1,6 @@ -# kraken_example +# webf_example -Demonstrates how to use the kraken plugin. +Demonstrates how to use the webf plugin. ## Getting Started diff --git a/webf/example/android/app/src/debug/AndroidManifest.xml b/webf/example/android/app/src/debug/AndroidManifest.xml index b19f16d8ee..caa5f72cfb 100644 --- a/webf/example/android/app/src/debug/AndroidManifest.xml +++ b/webf/example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.openkraken.kraken_example"> + package="com.openwebf.webf_example"> <!-- Flutter needs it to communicate with the running application to allow setting breakpoints, to provide hot reload, etc. --> diff --git a/webf/example/android/app/src/main/AndroidManifest.xml b/webf/example/android/app/src/main/AndroidManifest.xml index 051e85236a..77093c1f4f 100644 --- a/webf/example/android/app/src/main/AndroidManifest.xml +++ b/webf/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.openkraken.kraken_example"> + package="com.openwebf.webf_example"> <!-- io.flutter.app.FlutterApplication is an android.app.Application that calls FlutterMain.startInitialization(this); in its onCreate method. In most cases you can leave this as-is, but you if you want to provide diff --git a/webf/example/android/app/src/profile/AndroidManifest.xml b/webf/example/android/app/src/profile/AndroidManifest.xml index b19f16d8ee..caa5f72cfb 100644 --- a/webf/example/android/app/src/profile/AndroidManifest.xml +++ b/webf/example/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,5 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.openkraken.kraken_example"> + package="com.openwebf.webf_example"> <!-- Flutter needs it to communicate with the running application to allow setting breakpoints, to provide hot reload, etc. --> diff --git a/webf/example/ios/Runner/Info.plist b/webf/example/ios/Runner/Info.plist index 9fff96c6e3..7f5b0033f1 100644 --- a/webf/example/ios/Runner/Info.plist +++ b/webf/example/ios/Runner/Info.plist @@ -11,7 +11,7 @@ <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> <key>CFBundleName</key> - <string>kraken_example</string> + <string>webf_example</string> <key>CFBundlePackageType</key> <string>APPL</string> <key>CFBundleShortVersionString</key> diff --git a/webf/example/lib/main.dart b/webf/example/lib/main.dart index 34dca7aaca..4d2db3713a 100644 --- a/webf/example/lib/main.dart +++ b/webf/example/lib/main.dart @@ -6,9 +6,10 @@ import 'package:flutter/material.dart'; import 'package:webf/webf.dart'; import 'package:webf/devtools.dart'; +import 'package:webf_websocket/webf_websocket.dart'; void main() { - // KrakenWebsocket.initialize(); + WebFWebSocket.initialize(); runApp(MyApp()); } diff --git a/webf/example/linux/my_application.cc b/webf/example/linux/my_application.cc index 60034bd386..d52a8b63f5 100644 --- a/webf/example/linux/my_application.cc +++ b/webf/example/linux/my_application.cc @@ -40,12 +40,12 @@ static void my_application_activate(GApplication* application) { if (use_header_bar) { GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "kraken_example"); + gtk_header_bar_set_title(header_bar, "webf_example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { - gtk_window_set_title(window, "kraken_example"); + gtk_window_set_title(window, "webf_example"); } gtk_window_set_default_size(window, 1280, 720); diff --git a/webf/example/macos/Runner.xcodeproj/project.pbxproj b/webf/example/macos/Runner.xcodeproj/project.pbxproj index f057c310ea..b92f909c91 100644 --- a/webf/example/macos/Runner.xcodeproj/project.pbxproj +++ b/webf/example/macos/Runner.xcodeproj/project.pbxproj @@ -55,7 +55,7 @@ /* Begin PBXFileReference section */ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; - 33CC10ED2044A3C60003C045 /* kraken_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = kraken_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* webf.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = kraken_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; @@ -112,7 +112,7 @@ 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* kraken_example.app */, + 33CC10ED2044A3C60003C045 /* webf_example.app */, ); name = Products; sourceTree = "<group>"; @@ -193,7 +193,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* kraken_example.app */; + productReference = 33CC10ED2044A3C60003C045 /* webf_example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ diff --git a/webf/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/webf/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index bc166cc31a..35ce3f9ef3 100644 --- a/webf/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/webf/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "33CC10EC2044A3C60003C045" - BuildableName = "kraken_example.app" + BuildableName = "webf_example.app" BlueprintName = "Runner" ReferencedContainer = "container:Runner.xcodeproj"> </BuildableReference> @@ -31,7 +31,7 @@ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "33CC10EC2044A3C60003C045" - BuildableName = "kraken_example.app" + BuildableName = "webf_example.app" BlueprintName = "Runner" ReferencedContainer = "container:Runner.xcodeproj"> </BuildableReference> @@ -64,7 +64,7 @@ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "33CC10EC2044A3C60003C045" - BuildableName = "kraken_example.app" + BuildableName = "webf_example.app" BlueprintName = "Runner" ReferencedContainer = "container:Runner.xcodeproj"> </BuildableReference> @@ -81,7 +81,7 @@ <BuildableReference BuildableIdentifier = "primary" BlueprintIdentifier = "33CC10EC2044A3C60003C045" - BuildableName = "kraken_example.app" + BuildableName = "webf_example.app" BlueprintName = "Runner" ReferencedContainer = "container:Runner.xcodeproj"> </BuildableReference> diff --git a/webf/example/macos/Runner/Base.lproj/MainMenu.xib b/webf/example/macos/Runner/Base.lproj/MainMenu.xib index f4ff424280..2480da8680 100644 --- a/webf/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/webf/example/macos/Runner/Base.lproj/MainMenu.xib @@ -13,7 +13,7 @@ </customObject> <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> <customObject id="-3" userLabel="Application" customClass="NSObject"/> - <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="kraken_example" customModuleProvider="target"> + <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="webf_example" customModuleProvider="target"> <connections> <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/> <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/> @@ -326,7 +326,7 @@ </items> <point key="canvasLocation" x="142" y="-258"/> </menu> - <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="kraken_example" customModuleProvider="target"> + <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="webf_example" customModuleProvider="target"> <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> <rect key="contentRect" x="335" y="390" width="375" height="812"/> <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1047"/> diff --git a/webf/example/pubspec.yaml b/webf/example/pubspec.yaml index 0b21596d47..b59174cf93 100644 --- a/webf/example/pubspec.yaml +++ b/webf/example/pubspec.yaml @@ -12,6 +12,7 @@ dependencies: flutter: sdk: flutter webf: ^0.10.0 + webf_websocket: ^1.0.0 # When depending on this package from a real application, # you should remove the following overrides. diff --git a/webf/pubspec.yaml b/webf/pubspec.yaml index 68dddb614b..d6e865a48d 100644 --- a/webf/pubspec.yaml +++ b/webf/pubspec.yaml @@ -1,6 +1,6 @@ name: webf description: A W3C standard compliant Web rendering engine based on Flutter. -version: 0.12.0+1 +version: 0.12.0+2 homepage: https://openwebf.com environment: From b84a8c5b89d37384a683403b8d5e87bf210b1fc5 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sat, 6 Aug 2022 12:39:28 +0800 Subject: [PATCH 147/498] fix: fix plugin test. --- .../lib/custom/custom_object_element.dart | 159 ------------------ integration_tests/lib/plugin.dart | 44 ++--- integration_tests/pubspec.yaml | 4 +- integration_tests/specs/plugins/object.ts | 19 --- integration_tests/specs/plugins/video.ts | 42 ----- 5 files changed, 18 insertions(+), 250 deletions(-) delete mode 100644 integration_tests/lib/custom/custom_object_element.dart delete mode 100644 integration_tests/specs/plugins/object.ts delete mode 100644 integration_tests/specs/plugins/video.ts diff --git a/integration_tests/lib/custom/custom_object_element.dart b/integration_tests/lib/custom/custom_object_element.dart deleted file mode 100644 index ec58b44567..0000000000 --- a/integration_tests/lib/custom/custom_object_element.dart +++ /dev/null @@ -1,159 +0,0 @@ -// /* -// * Copyright (C) 2019-present The Kraken authors. All rights reserved. -// */ - -// import 'dart:async'; - -// import 'package:flutter/rendering.dart'; -// import 'package:webf/dom.dart'; -// // import 'package:kraken_video_player/video_player.dart'; - -// class CustomObjectElement implements ObjectElementClient { -// ObjectElementHost objectElementHost; - -// CustomObjectElement(this.objectElementHost); - -// VideoPlayerController? controller; - -// String? _src; - -// String? get src => _src; - -// set src(String? value) { -// if (_src != value) { -// bool needDispose = _src != null; -// _src = value; - -// if (needDispose && controller != null) { -// controller!.dispose().then((_) { -// _createVideoBox(); -// }); -// } else { -// _createVideoBox(); -// } -// } -// } - -// Future<int> createVideoPlayer(String src) { -// Completer<int> completer = new Completer(); - -// if (src.startsWith('//') || -// src.startsWith('http://') || -// src.startsWith('https://')) { -// controller = VideoPlayerController.network( -// src.startsWith('//') ? 'https:' + src : src); -// } else if (src.startsWith('file://')) { -// controller = VideoPlayerController.file(src); -// } else { -// // Fallback to asset video -// controller = VideoPlayerController.asset(src); -// } - -// _src = src; - -// controller!.initialize().then((int textureId) { -// completer.complete(textureId); -// }); - -// return completer.future; -// } - -// void addVideoBox(int textureId) { -// TextureBox box = TextureBox(textureId: textureId); -// objectElementHost.updateChildTextureBox(box); -// controller?.play(); -// } - -// void _createVideoBox() { -// createVideoPlayer(_src!).then((textureId) { -// addVideoBox(textureId); -// _dispatchCustomEvent(); -// }); -// } - -// void _dispatchCustomEvent() { -// CustomEvent event = -// CustomEvent('customevent', CustomEventInit(detail: 'hello world')); -// objectElementHost.dispatchEvent(event); -// } - -// @override -// dynamic handleJSCall(String name, List args) { -// switch (name) { -// case 'play': -// controller!.play(); -// break; -// case 'pause': -// controller!.pause(); -// break; -// } -// } - -// @override -// void setProperty(String key, value) { -// if (key == 'src') { -// src = value.toString(); -// } else if (key == 'data') { -// src = value.toString(); -// } else if (key == 'loop') { -// controller!.setLooping(value == 'true' ? true : false); -// } else if (key == 'currentTime') { -// controller!.seekTo(Duration(seconds: int.parse(value))); -// } -// } - -// @override -// dynamic getProperty(String key) { -// switch (key) { -// case 'loop': -// return controller!.value.isLooping; -// case 'currentTime': -// return controller!.value.position.inSeconds; -// case 'src': -// return _src; -// case 'videoWidth': -// return controller!.value.size!.width; -// case 'videoHeight': -// return controller!.value.size!.height; -// } -// } - -// @override -// void removeProperty(String key) {} - -// @override -// void setStyle(key, value) {} - -// @override -// Future<dynamic> initElementClient(Map<String, dynamic> properties) async { -// return null; -// } - -// @override -// void dispose() { -// objectElementHost.updateChildTextureBox(null); -// controller?.pause(); -// controller?.dispose(); -// controller = null; -// } - -// @override -// void didAttachRenderer() {} - -// @override -// void didDetachRenderer() { -// controller?.pause(); -// controller?.dispose(); -// controller = null; -// } - -// @override -// void willAttachRenderer() {} - -// @override -// void willDetachRenderer() {} -// } - -// ObjectElementClient customObjectElementFactory(ObjectElementHost host) { -// return CustomObjectElement(host); -// } diff --git a/integration_tests/lib/plugin.dart b/integration_tests/lib/plugin.dart index 39d68d8048..1028876ea8 100644 --- a/integration_tests/lib/plugin.dart +++ b/integration_tests/lib/plugin.dart @@ -1,34 +1,30 @@ /* - * Copyright (C) 2022-present The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:kraken/bridge.dart'; -import 'package:kraken/css.dart'; -import 'package:kraken/dom.dart'; -import 'package:kraken/foundation.dart'; -import 'package:kraken/widget.dart'; +import 'package:webf/bridge.dart'; +import 'package:webf/css.dart'; +import 'package:webf/dom.dart'; +import 'package:webf/foundation.dart'; +import 'package:webf/widget.dart'; import 'package:ansicolor/ansicolor.dart'; import 'package:path/path.dart' as path; -import 'package:kraken_websocket/kraken_websocket.dart'; -import 'package:kraken_video_player/kraken_video_player.dart'; -import 'package:kraken_webview/kraken_webview.dart'; +import 'package:webf_websocket/webf_websocket.dart'; import 'bridge/from_native.dart'; import 'bridge/to_native.dart'; -import 'custom/custom_object_element.dart'; 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['KRAKEN_TEST_DIR'] ?? __dirname; const int KRAKEN_NUM = 1; -Map<int, Kraken> krakenMap = Map(); +Map<int, WebF> krakenMap = Map(); // Test for UriParser. class IntegrationTestUriParser extends UriParser { @@ -45,12 +41,9 @@ class IntegrationTestUriParser extends UriParser { // By CLI: `KRAKEN_ENABLE_TEST=true flutter run` void main() async { // Overrides library name. - KrakenDynamicLibrary.libName = 'libkraken_test'; + WebFDynamicLibrary.libName = 'libkraken_test'; - KrakenWebsocket.initialize(); - KrakenVideoPlayer.initialize(); - KrakenWebView.initialize(); - setObjectElementFactory(customObjectElementFactory); + WebFWebSocket.initialize(); // FIXME: This is a workaround for testcase ParagraphElement.defaultStyle = { @@ -63,19 +56,15 @@ void main() async { 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() - } + {'filename': path.basename(specs.path), 'filepath': specs.path, 'code': specs.readAsStringSync()} ]; List<Widget> widgets = []; for (int i = 0; i < KRAKEN_NUM; i++) { - var kraken = krakenMap[i] = Kraken( + var kraken = krakenMap[i] = WebF( viewportWidth: 360, viewportHeight: 640, - bundle: KrakenBundle.fromContent('console.log("Starting Plugin tests...")'), + bundle: WebFBundle.fromContent('console.log("Starting Plugin tests...")'), disableViewportWidthAssertion: true, disableViewportHeightAssertion: true, uriParser: IntegrationTestUriParser(), @@ -94,7 +83,9 @@ void main() async { ), )); - WidgetsBinding.instance!.addPostFrameCallback((_) async { + WidgetsBinding.instance.addPostFrameCallback((_) async { + registerDartTestMethodsToCpp(); + List<Future<String>> testResults = []; for (int i = 0; i < widgets.length; i++) { @@ -103,7 +94,6 @@ void main() async { addJSErrorListener(contextId, (String err) { print(err); }); - registerDartTestMethodsToCpp(contextId); Map<String, String> payload = allSpecsPayload[i]; diff --git a/integration_tests/pubspec.yaml b/integration_tests/pubspec.yaml index db48adb09f..797498ac59 100644 --- a/integration_tests/pubspec.yaml +++ b/integration_tests/pubspec.yaml @@ -26,9 +26,7 @@ dependencies: flutter: sdk: flutter image: ^3.0.2 -# kraken_video_player: ^2.4.0 -# kraken_websocket: ^2.0.0 -# kraken_webview: ^2.5.0 + webf_websocket: ^1.0.0 waterfall_flow: ^3.0.1 image_compare: ^1.1.2 diff --git a/integration_tests/specs/plugins/object.ts b/integration_tests/specs/plugins/object.ts deleted file mode 100644 index 6c5d0ed653..0000000000 --- a/integration_tests/specs/plugins/object.ts +++ /dev/null @@ -1,19 +0,0 @@ -describe('Tags object', () => { - it('basic', done => { - - const objectElement = document.createElement('object'); - setElementStyle(objectElement, { - width: '750px', - height: '400px', - }); - - setAttributes(objectElement, { - data: 'https://videocdn.taobao.com/oss/ali-video/1fa0c3345eb3433b8af7e995e2013cea/1458900536/video.mp4', - }); - document.body.appendChild(objectElement); - - setTimeout(async () => { - done(); - }, 3000); - }); -}); diff --git a/integration_tests/specs/plugins/video.ts b/integration_tests/specs/plugins/video.ts deleted file mode 100644 index 62b180be2c..0000000000 --- a/integration_tests/specs/plugins/video.ts +++ /dev/null @@ -1,42 +0,0 @@ -describe('Tags video', () => { - it('basic', done => { - const container1 = document.createElement('div'); - setElementStyle(container1, { - height: '500px', - }); - - document.body.appendChild(container1); - - const video = document.createElement('video'); - setElementStyle(video, { - width: '750px', - height: '400px', - }); - - setAttributes(video, { - autoPlay: true, - src: - 'https://videocdn.taobao.com/oss/ali-video/1fa0c3345eb3433b8af7e995e2013cea/1458900536/video.mp4', - }); - - video.addEventListener('canplay', () => { - done(); - }); - - container1.appendChild(video); - - const pauseBtn = document.createElement('div'); - pauseBtn.appendChild(document.createTextNode('pause button')); - pauseBtn.addEventListener('click', () => { - video.pause(); - }); - container1.appendChild(pauseBtn); - - const playBtn = document.createElement('div'); - playBtn.appendChild(document.createTextNode('playBtn button')); - playBtn.addEventListener('click', () => { - video.play(); - }); - container1.appendChild(playBtn); - }); -}); From 85fd346e2655d4f5b84add426ec2f95fb138721d Mon Sep 17 00:00:00 2001 From: "zhanwen.zw" <zhanwen.zw@alibaba-inc.com> Date: Wed, 27 Apr 2022 16:20:02 +0800 Subject: [PATCH 148/498] test: add flexbox tests of blink. --- .../specs/css/css-flexbox/baseline-for.ts | 244 ++ .../specs/css/css-flexbox/columns-auto.ts | 761 +++++ .../specs/css/css-flexbox/definite-main.ts | 322 +++ .../specs/css/css-flexbox/flex-algorithm.ts | 2501 +++++++++++++++++ .../specs/css/css-flexbox/flex-align.ts | 866 ++++++ .../specs/css/css-flexbox/flex-flow.ts | 205 ++ .../specs/css/css-flexbox/flex-item.ts | 58 + .../css/css-flexbox/flex-justify-content.ts | 857 ++++++ .../specs/css/css-flexbox/flex-no-flex.ts | 91 + .../specs/css/css-flexbox/flexbox-baseline.ts | 1512 ++++++++++ .../specs/css/css-flexbox/flexbox-width.ts | 57 + .../specs/css/css-flexbox/multiline-column.ts | 312 ++ .../css/css-flexbox/multiline-reverse-wrap.ts | 694 +++++ .../specs/css/css-flexbox/nested-stretch.ts | 239 ++ .../specs/css/css-flexbox/overflow-auto.ts | 325 +++ .../css-flexbox/overflow-keep-scrollpos.ts | 71 + .../css/css-flexbox/percentage-heights.ts | 346 +++ .../css/css-flexbox/resize-min-content.ts | 49 + .../specs/css/css-flexbox/style-change.ts | 93 + 19 files changed, 9603 insertions(+) create mode 100644 integration_tests/specs/css/css-flexbox/baseline-for.ts create mode 100644 integration_tests/specs/css/css-flexbox/columns-auto.ts create mode 100644 integration_tests/specs/css/css-flexbox/definite-main.ts create mode 100644 integration_tests/specs/css/css-flexbox/flex-algorithm.ts create mode 100644 integration_tests/specs/css/css-flexbox/flex-justify-content.ts create mode 100644 integration_tests/specs/css/css-flexbox/flex-no-flex.ts create mode 100644 integration_tests/specs/css/css-flexbox/flexbox-baseline.ts create mode 100644 integration_tests/specs/css/css-flexbox/flexbox-width.ts create mode 100644 integration_tests/specs/css/css-flexbox/multiline-reverse-wrap.ts create mode 100644 integration_tests/specs/css/css-flexbox/nested-stretch.ts create mode 100644 integration_tests/specs/css/css-flexbox/overflow-auto.ts create mode 100644 integration_tests/specs/css/css-flexbox/overflow-keep-scrollpos.ts create mode 100644 integration_tests/specs/css/css-flexbox/percentage-heights.ts create mode 100644 integration_tests/specs/css/css-flexbox/resize-min-content.ts create mode 100644 integration_tests/specs/css/css-flexbox/style-change.ts diff --git a/integration_tests/specs/css/css-flexbox/baseline-for.ts b/integration_tests/specs/css/css-flexbox/baseline-for.ts new file mode 100644 index 0000000000..c1b54e036d --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/baseline-for.ts @@ -0,0 +1,244 @@ +/*auto generated*/ +describe('baseline-for', () => { + it('empty-line-expected', async () => { + let b; + let b_1; + let b_2; + let p; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`abc +`), + (b = createElement( + 'span', + { + class: 'b', + style: { + display: 'inline-block', + border: 'rgba(255,0,0,0.5) solid 1px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('span', { + contenteditable: 'true', + style: { + display: 'inline-block', + background: '#ddf', + 'min-width': '20px', + margin: '2px', + 'box-sizing': 'border-box', + }, + }), + ] + )), + (b_1 = createElement( + 'span', + { + class: 'b', + style: { + display: 'inline-block', + border: 'rgba(255,0,0,0.5) solid 1px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'span', + { + contenteditable: 'true', + style: { + display: 'inline-block', + background: '#ddf', + 'min-width': '20px', + margin: '2px', + 'box-sizing': 'border-box', + }, + }, + [createText(`a`)] + ), + ] + )), + (b_2 = createElement( + 'span', + { + class: 'b', + style: { + display: 'inline-block', + border: 'rgba(255,0,0,0.5) solid 1px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'span', + { + contenteditable: 'true', + style: { + display: 'inline-block', + background: '#ddf', + 'min-width': '20px', + margin: '2px', + 'box-sizing': 'border-box', + }, + }, + [createText(`a`)] + ), + createElement('span', { + contenteditable: 'true', + style: { + display: 'inline-block', + background: '#ddf', + 'min-width': '20px', + margin: '2px', + 'box-sizing': 'border-box', + }, + }), + createElement( + 'span', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`non-editable`)] + ), + ] + )), + createText(` +def`), + ] + ); + BODY.appendChild(p); + + await matchViewportSnapshot(); + }); + it('empty-line', async () => { + let flex; + let flex_1; + let flex_2; + let p; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`abc +`), + (flex = createElement( + 'span', + { + class: 'flex', + style: { + display: 'inline-flex', + border: 'rgba(255,0,0,0.5) solid 1px', + 'align-items': 'baseline', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('span', { + contenteditable: 'true', + style: { + display: 'inline-block', + background: '#ddf', + 'min-width': '20px', + margin: '2px', + 'box-sizing': 'border-box', + }, + }), + ] + )), + (flex_1 = createElement( + 'span', + { + class: 'flex', + style: { + display: 'inline-flex', + border: 'rgba(255,0,0,0.5) solid 1px', + 'align-items': 'baseline', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'span', + { + contenteditable: 'true', + style: { + display: 'inline-block', + background: '#ddf', + 'min-width': '20px', + margin: '2px', + 'box-sizing': 'border-box', + }, + }, + [createText(`a`)] + ), + ] + )), + (flex_2 = createElement( + 'span', + { + class: 'flex', + style: { + display: 'inline-flex', + border: 'rgba(255,0,0,0.5) solid 1px', + 'align-items': 'baseline', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'span', + { + contenteditable: 'true', + style: { + display: 'inline-block', + background: '#ddf', + 'min-width': '20px', + margin: '2px', + 'box-sizing': 'border-box', + }, + }, + [createText(`a`)] + ), + createElement('span', { + contenteditable: 'true', + style: { + display: 'inline-block', + background: '#ddf', + 'min-width': '20px', + margin: '2px', + 'box-sizing': 'border-box', + }, + }), + createElement( + 'span', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`non-editable`)] + ), + ] + )), + createText(` +def`), + ] + ); + BODY.appendChild(p); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/columns-auto.ts b/integration_tests/specs/css/css-flexbox/columns-auto.ts new file mode 100644 index 0000000000..a43af29387 --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/columns-auto.ts @@ -0,0 +1,761 @@ +/*auto generated*/ +describe('columns-auto', () => { + it('size', async () => { + let log; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let flexbox_4; + let flexbox_5; + let flexbox_6; + let flexbox_7; + let flexbox_8; + let flexbox_9; + let flexbox_10; + let childDiv; + let childDiv_1; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox column horizontal', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + width: '400px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '0', + style: { + width: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 0 10px', + }, + }), + createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '10', + style: { + width: '100%', + 'background-color': 'green', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + createElement( + 'div', + { + 'data-expected-height': '10', + 'data-offset-y': '20', + style: { + width: '100%', + 'background-color': 'red', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '20', + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox column horizontal', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + width: '400px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '0', + 'data-offset-y': '0', + style: { + width: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1', + }, + }), + createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '0', + style: { + width: '100%', + 'background-color': 'green', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + createElement( + 'div', + { + 'data-expected-height': '10', + 'data-offset-y': '10', + style: { + width: '100%', + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '1 auto', + }, + }, + [ + createElement('div', { + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + createElement( + 'div', + { + 'data-expected-height': '10', + 'data-offset-y': '20', + style: { + width: '100%', + 'background-color': 'orange', + 'box-sizing': 'border-box', + 'min-height': '0', + flex: '1', + }, + }, + [ + (childDiv = createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '20', + class: 'child-div', + style: { + width: '100%', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + height: '10px', + }, + })), + ] + ), + ] + ); + flexbox_2 = createElement( + 'div', + { + class: 'flexbox column horizontal', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + width: '400px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '10', + style: { + width: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 0 10px', + 'margin-top': '10px', + }, + }), + createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '20', + style: { + width: '100%', + 'background-color': 'green', + 'box-sizing': 'border-box', + height: '10px', + 'margin-bottom': '20px', + }, + }), + createElement( + 'div', + { + 'data-expected-height': '20', + 'data-offset-y': '50', + style: { + width: '100%', + 'background-color': 'red', + 'box-sizing': 'border-box', + 'padding-top': '10px', + }, + }, + [ + createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '60', + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + ] + ); + flexbox_3 = createElement( + 'div', + { + class: 'flexbox column horizontal justify-content-space-between', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + '-webkit-justify-content': 'space-between', + 'justify-content': 'space-between', + 'background-color': '#aaa', + position: 'relative', + width: '400px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '10', + style: { + width: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 0 10px', + 'margin-top': '10px', + }, + }), + createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '20', + style: { + width: '100%', + 'background-color': 'green', + 'box-sizing': 'border-box', + height: '10px', + 'margin-bottom': '20px', + }, + }), + createElement( + 'div', + { + 'data-expected-height': '20', + 'data-offset-y': '50', + style: { + width: '100%', + 'background-color': 'red', + 'box-sizing': 'border-box', + 'padding-top': '10px', + }, + }, + [ + createElement('div', { + 'data-expected-height': '10', + 'data-offset-y': '60', + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + ] + ); + flexbox_4 = createElement( + 'div', + { + class: 'flexbox column horizontal', + 'data-expected-height': '20', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + width: '400px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-height': '10', + 'data-offset-y': '0', + style: { + width: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '0 1 auto', + }, + }, + [ + createElement('div', { + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + createElement( + 'div', + { + 'data-expected-height': '10', + 'data-offset-y': '10', + style: { + width: '100%', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '0 2 auto', + }, + }, + [ + createElement('div', { + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + ] + ); + flexbox_5 = createElement( + 'div', + { + class: 'flexbox column horizontal', + 'data-expected-height': '20', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + width: '400px', + 'box-sizing': 'border-box', + 'min-height': '10px', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-height': '10', + 'data-offset-y': '0', + style: { + width: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '0 1 auto', + }, + }, + [ + createElement('div', { + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + createElement( + 'div', + { + 'data-expected-height': '10', + 'data-offset-y': '10', + style: { + width: '100%', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '0 2 auto', + }, + }, + [ + createElement('div', { + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + ] + ); + flexbox_6 = createElement( + 'div', + { + class: 'flexbox column horizontal', + 'data-expected-height': '17', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + width: '400px', + 'box-sizing': 'border-box', + 'min-height': '5px', + 'max-height': '17px', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-height': '9', + 'data-offset-y': '0', + style: { + width: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'min-height': '0', + flex: '0 1 auto', + }, + }, + [ + createElement('div', { + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + createElement( + 'div', + { + 'data-expected-height': '8', + 'data-offset-y': '9', + style: { + width: '100%', + 'background-color': 'green', + 'box-sizing': 'border-box', + 'min-height': '0', + flex: '0 2 auto', + }, + }, + [ + createElement('div', { + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + ] + ); + flexbox_7 = createElement( + 'div', + { + class: 'flexbox column horizontal', + 'data-expected-height': '33', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + width: '400px', + 'box-sizing': 'border-box', + 'min-height': '5px', + 'max-height': '30px', + 'padding-top': '1px', + 'padding-bottom': '2px', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-height': '15', + 'data-offset-y': '1', + style: { + width: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'min-height': '0', + flex: '0 1 auto', + }, + }, + [ + createElement('div', { + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '20px', + }, + }), + ] + ), + createElement( + 'div', + { + 'data-expected-height': '15', + 'data-offset-y': '16', + style: { + width: '100%', + 'background-color': 'green', + 'box-sizing': 'border-box', + 'min-height': '0', + flex: '0 1 auto', + }, + }, + [ + createElement('div', { + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '20px', + }, + }), + ] + ), + ] + ); + flexbox_8 = createElement( + 'div', + { + class: 'flexbox column horizontal', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + width: '400px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-client-height': '10', + 'data-offset-y': '0', + style: { + width: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + overflow: 'scroll', + }, + }, + [ + createElement('div', { + 'data-expected-height': '10', + style: { + width: '100%', + 'box-sizing': 'border-box', + height: '10px', + }, + }), + ] + ), + ] + ); + flexbox_9 = createElement( + 'div', + { + class: 'flexbox column vertical', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + 'writing-mode': 'vertical-rl', + height: '50px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '10', + 'data-offset-x': '20', + style: { + height: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 0 10px', + }, + }), + createElement('div', { + 'data-expected-width': '10', + 'data-offset-x': '10', + style: { + height: '100%', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '10px', + }, + }), + createElement( + 'div', + { + 'data-expected-width': '10', + 'data-offset-x': '0', + style: { + height: '100%', + 'background-color': 'red', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '10', + 'data-offset-x': '0', + style: { + height: '100%', + 'box-sizing': 'border-box', + width: '10px', + }, + }), + ] + ), + ] + ); + flexbox_10 = createElement( + 'div', + { + class: 'flexbox column vertical', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + 'writing-mode': 'vertical-rl', + height: '50px', + 'box-sizing': 'border-box', + 'margin-left': '100px', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-width': '50', + 'data-offset-x': '20', + style: { + height: '100%', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'min-width': '0', + flex: '1', + }, + }, + [ + (childDiv_1 = createElement('div', { + 'data-expected-width': '50', + 'data-offset-x': '20', + class: 'child-div', + style: { + height: '100%', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + width: '50px', + }, + })), + ] + ), + createElement('div', { + 'data-expected-width': '0', + 'data-offset-x': '20', + style: { + height: '100%', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1', + }, + }), + createElement('div', { + 'data-expected-width': '10', + 'data-offset-x': '10', + style: { + height: '100%', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '10px', + }, + }), + createElement( + 'div', + { + 'data-expected-width': '10', + 'data-offset-x': '0', + style: { + height: '100%', + 'background-color': 'orange', + 'box-sizing': 'border-box', + flex: '1 auto', + }, + }, + [ + createElement('div', { + style: { + height: '100%', + 'box-sizing': 'border-box', + width: '10px', + }, + }), + ] + ), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + BODY.appendChild(flexbox_4); + BODY.appendChild(flexbox_5); + BODY.appendChild(flexbox_6); + BODY.appendChild(flexbox_7); + BODY.appendChild(flexbox_8); + BODY.appendChild(flexbox_9); + BODY.appendChild(flexbox_10); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/definite-main.ts b/integration_tests/specs/css/css-flexbox/definite-main.ts new file mode 100644 index 0000000000..be083249a5 --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/definite-main.ts @@ -0,0 +1,322 @@ +/*auto generated*/ +describe('definite-main', () => { + it('size', async () => { + let log; + let p; + let p_1; + let p_2; + let p_3; + let rect; + let rect_1; + let rect_2; + let rect_3; + let rect_4; + let rect_5; + let rect_6; + let rect_7; + let flexOne; + let flexOne_1; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`Simple case of percentage resolution:`)] + ); + flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + border: '3px solid black', + 'box-sizing': 'border-box', + width: '300px', + }, + }, + [ + (flexOne = createElement( + 'div', + { + class: 'flex-one', + 'data-expected-width': '250', + style: { + '-webkit-flex': '1', + flex: '1', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-width': '125', + style: { + overflow: 'hidden', + 'box-sizing': 'border-box', + width: '50%', + }, + }, + [ + (rect = createElement('div', { + class: 'rect', + style: { + width: '50px', + height: '50px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + })), + ] + ), + ] + )), + (rect_1 = createElement('div', { + class: 'rect flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + width: '50px', + height: '50px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + })), + ] + ); + p_1 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`auto flex-basis. However, as this is a width, we follow regular width +rules and resolve the percentage:`), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + border: '3px solid black', + 'box-sizing': 'border-box', + width: '300px', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-width': '50', + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-width': '25', + style: { + overflow: 'hidden', + 'box-sizing': 'border-box', + width: '50%', + }, + }, + [ + (rect_2 = createElement('div', { + class: 'rect', + style: { + width: '50px', + height: '50px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + })), + ] + ), + ] + ), + (rect_3 = createElement('div', { + class: 'rect flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + width: '50px', + height: '50px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + })), + ] + ); + p_2 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`Simple case of percentage resolution, columns:`)] + ); + flexbox_2 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + border: '3px solid black', + 'box-sizing': 'border-box', + height: '300px', + }, + }, + [ + (flexOne_1 = createElement( + 'div', + { + class: 'flex-one', + 'data-expected-height': '250', + style: { + '-webkit-flex': '1', + flex: '1', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-height': '125', + style: { + overflow: 'hidden', + 'box-sizing': 'border-box', + height: '50%', + }, + }, + [ + (rect_4 = createElement('div', { + class: 'rect', + style: { + width: '50px', + height: '50px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + })), + ] + ), + ] + )), + (rect_5 = createElement('div', { + class: 'rect flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + width: '50px', + height: '50px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + })), + ] + ); + p_3 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`auto flex-basis. This is still definite.`)] + ); + flexbox_3 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + border: '3px solid black', + 'box-sizing': 'border-box', + height: '300px', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-height': '50', + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-height': '25', + style: { + overflow: 'hidden', + 'box-sizing': 'border-box', + height: '50%', + }, + }, + [ + (rect_6 = createElement('div', { + class: 'rect', + style: { + width: '50px', + height: '50px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + })), + ] + ), + ] + ), + (rect_7 = createElement('div', { + class: 'rect flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + width: '50px', + height: '50px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + })), + ] + ); + BODY.appendChild(log); + BODY.appendChild(p); + BODY.appendChild(flexbox); + BODY.appendChild(p_1); + BODY.appendChild(flexbox_1); + BODY.appendChild(p_2); + BODY.appendChild(flexbox_2); + BODY.appendChild(p_3); + BODY.appendChild(flexbox_3); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/flex-algorithm.ts b/integration_tests/specs/css/css-flexbox/flex-algorithm.ts new file mode 100644 index 0000000000..10b1d7718e --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/flex-algorithm.ts @@ -0,0 +1,2501 @@ +/*auto generated*/ +describe('flex-algorithm', () => { + it('min-max', async () => { + let log; + let flex100; + let flex100_1; + let flex100_2; + let flex100_3; + let flex100_4; + let flex100_5; + let flex100_6; + let flex100_7; + let flex100_8; + let flex100_9; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let flexbox_4; + let flexbox_5; + let flexbox_6; + let flexbox_7; + let flexbox_8; + let flexbox_9; + let flexbox_10; + let flexbox_11; + let flex1; + let flex1_1; + let flex1_2; + let flex1_3; + let flex1_4; + let flex1_5; + let flex1_6; + let flex3; + let flex2; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100 = createElement('div', { + 'data-expected-width': '100', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + 'max-width': '100px', + }, + })), + (flex100_1 = createElement('div', { + 'data-expected-width': '250', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + (flex100_2 = createElement('div', { + 'data-expected-width': '250', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100_3 = createElement('div', { + 'data-expected-width': '50', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + 'max-width': '50px', + }, + })), + createElement('div', { + 'data-expected-width': '300', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '4 0 0', + 'max-width': '300px', + }, + }), + createElement('div', { + 'data-expected-width': '250', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '1 0 0', + }, + }), + ] + ); + flexbox_2 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100_4 = createElement('div', { + 'data-expected-width': '100', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + 'max-width': '100px', + }, + })), + createElement('div', { + 'data-expected-width': '300', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 0 200px', + 'max-width': '300px', + }, + }), + (flex100_5 = createElement('div', { + 'data-expected-width': '200', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_3 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '350', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 1 400px', + 'min-width': '350px', + }, + }), + createElement('div', { + 'data-expected-width': '250', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 1 400px', + }, + }), + ] + ); + flexbox_4 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '350', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 1 400px', + 'min-width': '350px', + }, + }), + createElement('div', { + 'data-expected-width': '300', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '2 0 300px', + 'max-width': '300px', + }, + }), + (flex100_6 = createElement('div', { + 'data-expected-width': '0', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_5 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100_7 = createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '0', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + margin: '0 auto', + 'max-width': '100px', + }, + })), + createElement('div', { + 'data-expected-width': '333', + 'data-offset-x': '100', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '2 0 0', + }, + }), + (flex100_8 = createElement('div', { + 'data-expected-width': '167', + 'data-offset-x': '433', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_6 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100_9 = createElement('div', { + 'data-expected-width': '500', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + 'min-width': '300px', + }, + })), + createElement('div', { + 'data-expected-width': '100', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 0 50%', + 'max-width': '100px', + }, + }), + ] + ); + flexbox_7 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '200px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1 = createElement('div', { + 'data-expected-width': '150', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + 'min-width': '150px', + }, + })), + (flex1_1 = createElement('div', { + 'data-expected-width': '50', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1', + 'box-sizing': 'border-box', + 'max-width': '90px', + }, + })), + ] + ); + flexbox_8 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '200px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_2 = createElement('div', { + 'data-expected-width': '150', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + 'min-width': '120px', + }, + })), + (flex1_3 = createElement('div', { + 'data-expected-width': '50', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1', + 'box-sizing': 'border-box', + 'max-width': '50px', + }, + })), + ] + ); + flexbox_9 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '200px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_4 = createElement('div', { + 'data-expected-width': '100', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + 'min-width': '100px', + }, + })), + (flex3 = createElement('div', { + 'data-expected-width': '100', + class: 'flex3', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '3', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_10 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '200px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '150', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 50px', + 'min-width': '100px', + }, + }), + createElement('div', { + 'data-expected-width': '50', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 100px', + 'max-width': '50px', + }, + }), + ] + ); + flexbox_11 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_5 = createElement('div', { + 'data-expected-width': '80', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + (flex2 = createElement('div', { + 'data-expected-width': '160', + class: 'flex2', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '2', + 'box-sizing': 'border-box', + }, + })), + (flex1_6 = createElement('div', { + 'data-expected-width': '360', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1', + 'box-sizing': 'border-box', + 'min-width': '360px', + }, + })), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + BODY.appendChild(flexbox_4); + BODY.appendChild(flexbox_5); + BODY.appendChild(flexbox_6); + BODY.appendChild(flexbox_7); + BODY.appendChild(flexbox_8); + BODY.appendChild(flexbox_9); + BODY.appendChild(flexbox_10); + BODY.appendChild(flexbox_11); + + await matchViewportSnapshot(); + }); + it('with-margins', async () => { + let log; + let flex100; + let flex100_1; + let flex100_2; + let flex100_3; + let flex100_4; + let flex100_5; + let flex100_6; + let flex100_7; + let flex100_8; + let flex100_9; + let flexNone; + let flexNone_1; + let flexNone_2; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let flexbox_4; + let flexbox_5; + let flexbox_6; + let flexbox_7; + let flexbox_8; + let flexbox_9; + let flexbox_10; + let flexbox_11; + let flex4; + let flex1; + let flex1_1; + let flex200; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100 = createElement('div', { + 'data-expected-width': '200', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + (flexNone = createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '250', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + margin: '0 50px', + }, + })), + (flex100_1 = createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '400', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_1 = createElement( + 'div', + { + 'data-expected-height': '120', + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100_2 = createElement('div', { + 'data-expected-width': '200', + 'data-offset-y': '50', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + margin: '50px 0', + }, + })), + (flexNone_1 = createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '250', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + margin: '0 50px', + }, + })), + (flex100_3 = createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '400', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_2 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100_4 = createElement('div', { + 'data-expected-width': '200', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + (flexNone_2 = createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '200', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '200px', + margin: '0 auto', + }, + })), + (flex100_5 = createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '400', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_3 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100_6 = createElement('div', { + 'data-expected-width': '100', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + createElement('div', { + 'data-expected-width': '300', + 'data-offset-x': '100', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '2 0 100px', + 'margin-left': 'auto', + }, + }), + (flex100_7 = createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '400', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + 'margin-right': '100px', + }, + })), + ] + ); + flexbox_4 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '150', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 1 300px', + }, + }), + createElement('div', { + 'data-expected-width': '300', + 'data-offset-x': '150', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 0 300px', + margin: '0 auto', + }, + }), + createElement('div', { + 'data-expected-width': '150', + 'data-offset-x': '450', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '1 1 300px', + }, + }), + ] + ); + flexbox_5 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '300', + 'data-offset-x': '150', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '0 0 300px', + margin: '0 auto', + }, + }), + ] + ); + flexbox_6 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '700', + 'data-offset-x': '0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '0 0 700px', + margin: '0 auto', + }, + }), + ] + ); + flexbox_7 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '600', + 'data-offset-x': '0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 0 300px', + margin: '0 auto', + }, + }), + ] + ); + flexbox_8 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex4 = createElement( + 'div', + { + 'data-expected-width': '600', + 'data-offset-x': '0', + class: 'flex4', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '4', + 'box-sizing': 'border-box', + margin: '0 auto', + }, + }, + [ + createElement('div', { + style: { + height: '100%', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + )), + ] + ); + flexbox_9 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + margin: '100px', + }, + }, + [ + (flex1 = createElement('div', { + 'data-expected-width': '300', + 'data-offset-x': '0', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + margin: '0 auto', + }, + })), + (flex1_1 = createElement('div', { + 'data-expected-width': '300', + 'data-offset-x': '300', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1', + 'box-sizing': 'border-box', + margin: '0 auto', + }, + })), + ] + ); + flexbox_10 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + padding: '100px', + }, + }, + [ + createElement('div', { + 'data-expected-width': '300', + 'data-offset-x': '100', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 0 0px', + margin: '0 auto', + }, + }), + createElement('div', { + 'data-expected-width': '300', + 'data-offset-x': '400', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 0 0px', + margin: '0 auto', + }, + }), + ] + ); + flexbox_11 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100_8 = createElement('div', { + 'data-expected-width': '75', + 'data-offset-x': '0', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + margin: '0 auto', + }, + })), + (flex200 = createElement('div', { + 'data-expected-width': '350', + 'data-offset-x': '75', + class: 'flex2-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '2 0 0px', + 'box-sizing': 'border-box', + padding: '0 100px', + }, + })), + (flex100_9 = createElement('div', { + 'data-expected-width': '75', + 'data-offset-x': '525', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + 'margin-left': '100px', + }, + })), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + BODY.appendChild(flexbox_4); + BODY.appendChild(flexbox_5); + BODY.appendChild(flexbox_6); + BODY.appendChild(flexbox_7); + BODY.appendChild(flexbox_8); + BODY.appendChild(flexbox_9); + BODY.appendChild(flexbox_10); + BODY.appendChild(flexbox_11); + + await matchViewportSnapshot(); + }); + + it('algorithm', async () => { + let log; + let flex1; + let flex1_1; + let flex1_2; + let flex1_3; + let flex1_4; + let flex1_5; + let flex1_6; + let flex1_7; + let flex1_8; + let flex1_9; + let flex1_10; + let flex1_11; + let flex1_12; + let flex1_13; + let flex1_14; + let flex1_15; + let flex1_16; + let flex1_17; + let flex1_18; + let flex1_19; + let flex1_20; + let flex1_21; + let flex1_22; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let flexbox_4; + let flexbox_5; + let flexbox_6; + let flexbox_7; + let flexbox_8; + let flexbox_9; + let flexbox_10; + let flexbox_11; + let flexbox_12; + let flexbox_13; + let flexbox_14; + let flexbox_15; + let flexbox_16; + let flexbox_17; + let flexbox_18; + let flexbox_19; + let flexbox_20; + let flexbox_21; + let flexbox_22; + let flexbox_23; + let flexbox_24; + let flexbox_25; + let flex3; + let flex3_1; + let flex2; + let flex2_1; + let flex2_2; + let flex2_3; + let flex2_4; + let flex2_5; + let flexNone; + let flexNone_1; + let flexNone_2; + let flexNone_3; + let flexNone_4; + let flexNone_5; + let flexNone_6; + let flexNone_7; + let flex100; + let flex100_1; + let flex100_2; + let flex100_3; + let flex100_4; + let flexAuto; + let flexAuto_1; + let flexAuto_2; + let div; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1 = createElement('div', { + 'data-expected-width': '200', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + (flex1_1 = createElement('div', { + 'data-expected-width': '200', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + (flex1_2 = createElement('div', { + 'data-expected-width': '200', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '200', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '.5', + }, + }), + createElement('div', { + 'data-expected-width': '200', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '.5', + }, + }), + createElement('div', { + 'data-expected-width': '200', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '.5', + }, + }), + ] + ); + flexbox_2 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex3 = createElement('div', { + 'data-expected-width': '300', + class: 'flex3', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '3', + 'box-sizing': 'border-box', + }, + })), + (flex2 = createElement('div', { + 'data-expected-width': '200', + class: 'flex2', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '2', + 'box-sizing': 'border-box', + }, + })), + (flex1_3 = createElement('div', { + 'data-expected-width': '100', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_3 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_4 = createElement('div', { + 'data-expected-width': '250', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + (flex1_5 = createElement('div', { + 'data-expected-width': '250', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + (flexNone = createElement('div', { + 'data-expected-width': '100', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + }, + })), + ] + ); + flexbox_4 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_6 = createElement('div', { + 'data-expected-width': '150', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + (flex1_7 = createElement('div', { + 'data-expected-width': '150', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + (flexNone_1 = createElement('div', { + 'data-expected-width': '300', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '50%', + }, + })), + ] + ); + flexbox_5 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_8 = createElement('div', { + 'data-expected-width': '150', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + createElement('div', { + 'data-expected-width': '350', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 200px', + }, + }), + (flexNone_2 = createElement('div', { + 'data-expected-width': '100', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + }, + })), + ] + ); + flexbox_6 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_9 = createElement('div', { + 'data-expected-width': '100', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + createElement('div', { + 'data-expected-width': '400', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '2 33.333333%', + }, + }), + (flexNone_3 = createElement('div', { + 'data-expected-width': '100', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + }, + })), + ] + ); + flexbox_7 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '200', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 1 300px', + }, + }), + createElement('div', { + 'data-expected-width': '200', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '2 1 300px', + }, + }), + createElement('div', { + 'data-expected-width': '200', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '3 1 300px', + }, + }), + ] + ); + flexbox_8 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '250', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 1 300px', + }, + }), + createElement('div', { + 'data-expected-width': '150', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '2 3 300px', + }, + }), + (flexNone_4 = createElement('div', { + 'data-expected-width': '200', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '200px', + }, + })), + ] + ); + flexbox_9 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '50', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 1 100px', + }, + }), + createElement('div', { + 'data-expected-width': '250', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 1 500px', + }, + }), + (flexNone_5 = createElement('div', { + 'data-expected-width': '300', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '300px', + }, + })), + ] + ); + flexbox_10 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '50', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 1 100px', + }, + }), + createElement('div', { + 'data-expected-width': '250', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 1 500px', + 'margin-right': '300px', + }, + }), + ] + ); + flexbox_11 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '50', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 1 100px', + }, + }), + createElement('div', { + 'data-expected-width': '550', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 1 500px', + 'padding-left': '300px', + }, + }), + ] + ); + flexbox_12 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '50', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 1 100px', + }, + }), + createElement('div', { + 'data-expected-width': '550', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 1 500px', + 'border-left': '200px dashed orange', + 'border-right': '100px dashed orange', + }, + }), + ] + ); + flexbox_13 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '600', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '0 100000000000000000000000000000000000000 600px', + }, + }), + createElement('div', { + 'data-expected-width': '600', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '0 100000000000000000000000000000000000000 600px', + }, + }), + ] + ); + flexbox_14 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '600', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '100000000000000000000000000000000000000 0 600px', + }, + }), + createElement('div', { + 'data-expected-width': '600', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '0 100000000000000000000000000000000000000 600px', + }, + }), + createElement('div', { + 'data-expected-width': '33554428', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '1 1 100000000000000000000000000000000000000px', + }, + }), + ] + ); + flexbox_15 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_10 = createElement('div', { + 'data-expected-width': '250', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + 'border-left': '150px solid black', + }, + })), + (flex100 = createElement('div', { + 'data-expected-width': '250', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1 0 0px', + 'box-sizing': 'border-box', + 'border-right': '150px solid orange', + }, + })), + (flex100_1 = createElement('div', { + 'data-expected-width': '100', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_16 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '300', + style: { + height: '20px', + border: '100px solid black', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '100px', + flex: 'none', + }, + }), + (flex2_1 = createElement('div', { + 'data-expected-width': '200', + class: 'flex2', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '2', + 'box-sizing': 'border-box', + }, + })), + (flex1_11 = createElement('div', { + 'data-expected-width': '100', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_17 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_12 = createElement('div', { + 'data-expected-width': '250', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + 'padding-left': '150px', + }, + })), + (flex100_2 = createElement('div', { + 'data-expected-width': '250', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1 0 0px', + 'box-sizing': 'border-box', + 'padding-right': '150px', + }, + })), + (flex100_3 = createElement('div', { + 'data-expected-width': '100', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_18 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flexNone_6 = createElement('div', { + 'data-expected-width': '300', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '100px', + padding: '100px', + }, + })), + (flex2_2 = createElement('div', { + 'data-expected-width': '200', + class: 'flex2', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '2', + 'box-sizing': 'border-box', + }, + })), + (flex1_13 = createElement('div', { + 'data-expected-width': '100', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_19 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_14 = createElement('div', { + 'data-expected-width': '200', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + 'padding-left': '25%', + }, + })), + (flex3_1 = createElement('div', { + 'data-expected-width': '150', + class: 'flex3', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '3', + 'box-sizing': 'border-box', + }, + })), + (flexNone_7 = createElement('div', { + 'data-expected-width': '250', + class: 'flex-none', + style: { + '-webkit-flex': 'none', + flex: 'none', + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + 'padding-right': '25%', + }, + })), + ] + ); + flexbox_20 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_15 = createElement('div', { + 'data-expected-width': '200', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + 'padding-left': '50px', + 'border-right': '50px solid black', + }, + })), + (flex2_3 = createElement('div', { + 'data-expected-width': '250', + class: 'flex2', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '2', + 'box-sizing': 'border-box', + 'border-right': '50px solid orange', + }, + })), + (flex1_16 = createElement('div', { + 'data-expected-width': '150', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1', + 'box-sizing': 'border-box', + 'padding-right': '50px', + }, + })), + ] + ); + flexbox_21 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex1_17 = createElement( + 'div', + { + 'data-expected-width': '120', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + style: { + height: '100%', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + )), + (flex2_4 = createElement('div', { + 'data-expected-width': '240', + class: 'flex2', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '2', + 'box-sizing': 'border-box', + }, + })), + (flex2_5 = createElement('div', { + 'data-expected-width': '240', + class: 'flex2', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '2', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_22 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex100_4 = createElement( + 'div', + { + 'data-expected-width': '200', + class: 'flex1-0-0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1 0 0px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + style: { + height: '100%', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + )), + (flex1_18 = createElement('div', { + 'data-expected-width': '200', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + (flex1_19 = createElement('div', { + 'data-expected-width': '200', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + ] + ); + flexbox_23 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + }, + }, + [ + (flexAuto = createElement( + 'div', + { + 'data-expected-width': '200', + class: 'flex-auto', + style: { + '-webkit-flex': 'auto', + flex: 'auto', + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + )), + (flexAuto_1 = createElement('div', { + 'data-expected-width': '100', + class: 'flex-auto', + style: { + '-webkit-flex': 'auto', + flex: 'auto', + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + })), + (flexAuto_2 = createElement( + 'div', + { + 'data-expected-width': '300', + class: 'flex-auto', + style: { + '-webkit-flex': 'auto', + flex: 'auto', + height: '20px', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '200px', + }, + }), + ] + )), + ] + ); + flexbox_24 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + height: '60px', + 'flex-flow': 'row wrap', + position: 'relative', + }, + }, + [ + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + position: 'absolute', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '700px', + }, + }), + ] + ); + div = createElement( + 'div', + { + 'data-expected-width': '830', + style: { + 'box-sizing': 'border-box', + border: '1px 10px solid', + display: 'inline-block', + }, + }, + [ + (flexbox_25 = createElement( + 'div', + { + 'data-expected-width': '700', + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'box-sizing': 'border-box', + 'padding-left': '10px', + 'padding-right': '20px', + 'border-left': '1px 30px solid', + 'border-right': '1px 40px solid', + 'margin-left': '50px', + 'margin-right': '60px', + }, + }, + [ + (flex1_20 = createElement('div', { + 'data-offset-x': '100', + 'data-expected-width': '200', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'blue', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + (flex1_21 = createElement('div', { + 'data-offset-x': '300', + 'data-expected-width': '200', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'green', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + (flex1_22 = createElement('div', { + 'data-offset-x': '500', + 'data-expected-width': '200', + class: 'flex1', + style: { + height: '20px', + border: '0', + 'background-color': 'red', + flex: '1', + 'box-sizing': 'border-box', + }, + })), + ] + )), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + BODY.appendChild(flexbox_4); + BODY.appendChild(flexbox_5); + BODY.appendChild(flexbox_6); + BODY.appendChild(flexbox_7); + BODY.appendChild(flexbox_8); + BODY.appendChild(flexbox_9); + BODY.appendChild(flexbox_10); + BODY.appendChild(flexbox_11); + BODY.appendChild(flexbox_12); + BODY.appendChild(flexbox_13); + BODY.appendChild(flexbox_14); + BODY.appendChild(flexbox_15); + BODY.appendChild(flexbox_16); + BODY.appendChild(flexbox_17); + BODY.appendChild(flexbox_18); + BODY.appendChild(flexbox_19); + BODY.appendChild(flexbox_20); + BODY.appendChild(flexbox_21); + BODY.appendChild(flexbox_22); + BODY.appendChild(flexbox_23); + BODY.appendChild(flexbox_24); + BODY.appendChild(flexbox_25); + BODY.appendChild(div); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/flex-align.ts b/integration_tests/specs/css/css-flexbox/flex-align.ts index 714423c804..5b330feefc 100644 --- a/integration_tests/specs/css/css-flexbox/flex-align.ts +++ b/integration_tests/specs/css/css-flexbox/flex-align.ts @@ -447,4 +447,870 @@ describe('flex-align', () => { await snapshot(); }); + + it('column', async () => { + let log; + let flexbox; + let flexbox_1; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + width: '600px', + height: '240px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-offset-x': '0', + 'data-expected-width': '600', + 'data-expected-height': '40', + style: { + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-expected-width': '600', + 'data-expected-height': '40', + style: { + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1', + 'align-self': 'stretch', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-expected-width': '20', + 'data-expected-height': '40', + style: { + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '1', + 'align-self': 'flex-start', + width: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '580', + 'data-expected-width': '20', + 'data-expected-height': '40', + style: { + 'background-color': 'yellow', + 'box-sizing': 'border-box', + flex: '1', + 'align-self': 'flex-end', + width: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '290', + 'data-expected-width': '20', + 'data-expected-height': '40', + style: { + 'background-color': 'purple', + 'box-sizing': 'border-box', + flex: '1', + 'align-self': 'center', + width: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-expected-width': '20', + 'data-expected-height': '40', + style: { + 'background-color': 'orange', + 'box-sizing': 'border-box', + flex: '1', + 'align-self': 'baseline', + width: '20px', + }, + }), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox column vertical', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + width: '600px', + height: '240px', + 'background-color': '#aaa', + position: 'relative', + 'writing-mode': 'vertical-lr', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-offset-y': '0', + 'data-expected-width': '100', + 'data-expected-height': '240', + style: { + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1', + }, + }), + createElement('div', { + 'data-offset-y': '0', + 'data-expected-width': '100', + 'data-expected-height': '240', + style: { + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1', + 'align-self': 'stretch', + }, + }), + createElement('div', { + 'data-offset-y': '0', + 'data-expected-width': '100', + 'data-expected-height': '20', + style: { + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '1', + 'align-self': 'flex-start', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-y': '220', + 'data-expected-width': '100', + 'data-expected-height': '20', + style: { + 'background-color': 'yellow', + 'box-sizing': 'border-box', + flex: '1', + 'align-self': 'flex-end', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-y': '110', + 'data-expected-width': '100', + 'data-expected-height': '20', + style: { + 'background-color': 'purple', + 'box-sizing': 'border-box', + flex: '1', + 'align-self': 'center', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-y': '0', + 'data-expected-width': '100', + 'data-expected-height': '20', + style: { + 'background-color': 'orange', + 'box-sizing': 'border-box', + flex: '1', + 'align-self': 'baseline', + height: '20px', + }, + }), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + + await matchViewportSnapshot(); + }); + it('max', async () => { + let log; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '50', + style: { + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 0 0', + 'max-height': '100px', + }, + }), + createElement('div', { + 'data-expected-height': '50', + style: { + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 0 0', + height: '50px', + }, + }), + createElement('div', { + 'data-expected-height': '25', + style: { + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '1 0 0', + 'max-height': '25px', + }, + }), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + width: '200px', + }, + }, + [ + createElement('div', { + 'data-expected-width': '150', + style: { + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 0 20px', + 'max-width': '150px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + style: { + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 0 20px', + width: '100px', + }, + }), + createElement('div', { + 'data-expected-width': '200', + style: { + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '1 0 20px', + }, + }), + ] + ); + flexbox_2 = createElement( + 'div', + { + class: 'flexbox vertical-rl', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'writing-mode': 'vertical-rl', + 'box-sizing': 'border-box', + height: '60px', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + style: { + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 0 20px', + 'max-width': '110px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + style: { + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 0 20px', + width: '100px', + }, + }), + createElement('div', { + 'data-expected-width': '50', + style: { + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '1 0 20px', + 'max-width': '50px', + }, + }), + ] + ); + flexbox_3 = createElement( + 'div', + { + class: 'flexbox column vertical-rl', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + 'writing-mode': 'vertical-rl', + 'box-sizing': 'border-box', + height: '50px', + }, + }, + [ + createElement('div', { + 'data-expected-height': '50', + style: { + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '1 0 100px', + 'max-height': '100px', + }, + }), + createElement('div', { + 'data-expected-height': '50', + style: { + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '1 0 100px', + height: '50px', + }, + }), + createElement('div', { + 'data-expected-height': '25', + style: { + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + flex: '1 0 100px', + 'max-height': '25px', + }, + }), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + + await matchViewportSnapshot(); + }); + it('percent-height', async () => { + let log; + let flexOne; + let flexOne_1; + let flexbox; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + width: '600px', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + height: '50%', + }, + }, + [ + (flexOne = createElement('div', { + 'data-expected-height': '50', + 'data-offset-y': '0', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + height: '50px', + }, + })), + (flexOne_1 = createElement('div', { + 'data-expected-height': '300', + 'data-offset-y': '0', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + })), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + + BODY.style.height = '600px'; + + await matchViewportSnapshot(); + }); + it('stretch', async () => { + let log; + let absolute; + let absolute_1; + let absolute_2; + let absolute_3; + let absolute_4; + let absolute_5; + let flexOne; + let flexOne_1; + let flexOne_2; + let flexOne_3; + let flexOne_4; + let flexOne_5; + let flexOne_6; + let flexOne_7; + let flexOne_8; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let flexbox_4; + let div; + let div_1; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + width: '600px', + }, + }, + [ + (flexOne = createElement( + 'div', + { + 'data-expected-height': '100', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + position: 'relative', + }, + }, + [ + (absolute = createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '50', + class: 'absolute', + style: { + border: '0', + position: 'absolute', + width: '50px', + height: '50px', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + bottom: '0', + }, + })), + ] + )), + (flexOne_1 = createElement('div', { + 'data-expected-height': '100', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + height: '100px', + }, + })), + (flexOne_2 = createElement( + 'div', + { + 'data-expected-height': '100', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + position: 'relative', + }, + }, + [ + (absolute_1 = createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '50', + class: 'absolute', + style: { + border: '0', + position: 'absolute', + width: '50px', + height: '50px', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + bottom: '0', + }, + })), + ] + )), + ] + ); + div = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + 'writing-mode': 'vertical-rl', + }, + }, + [ + (flexbox_1 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + height: '200px', + }, + }, + [ + (flexOne_3 = createElement( + 'div', + { + 'data-expected-width': '100', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + position: 'relative', + }, + }, + [ + (absolute_2 = createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '0', + class: 'absolute', + style: { + border: '0', + position: 'absolute', + width: '50px', + height: '50px', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + left: '0', + }, + })), + ] + )), + (flexOne_4 = createElement('div', { + 'data-expected-width': '100', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + }, + })), + (flexOne_5 = createElement( + 'div', + { + 'data-expected-width': '100', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + position: 'relative', + }, + }, + [ + (absolute_3 = createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '0', + class: 'absolute', + style: { + border: '0', + position: 'absolute', + width: '50px', + height: '50px', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + left: '0', + }, + })), + ] + )), + ] + )), + ] + ); + div_1 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + 'writing-mode': 'vertical-lr', + }, + }, + [ + (flexbox_2 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + height: '200px', + }, + }, + [ + (flexOne_6 = createElement( + 'div', + { + 'data-expected-width': '100', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + position: 'relative', + }, + }, + [ + (absolute_4 = createElement('div', { + 'data-offset-x': '50', + 'data-offset-y': '0', + class: 'absolute', + style: { + border: '0', + position: 'absolute', + width: '50px', + height: '50px', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + right: '0', + }, + })), + ] + )), + (flexOne_7 = createElement('div', { + 'data-expected-width': '100', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + }, + })), + (flexOne_8 = createElement( + 'div', + { + 'data-expected-width': '100', + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + border: '0', + 'background-color': 'red', + 'box-sizing': 'border-box', + position: 'relative', + }, + }, + [ + (absolute_5 = createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '0', + class: 'absolute', + style: { + border: '0', + position: 'absolute', + width: '50px', + height: '50px', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + left: '0', + }, + })), + ] + )), + ] + )), + ] + ); + flexbox_3 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + height: '50px', + width: '600px', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-height': '50', + style: { + border: '0', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + width: '300px', + }, + }, + [ + createElement('div', { + 'data-expected-height': '60', + style: { + border: '0', + 'box-sizing': 'border-box', + height: '60px', + width: '10px', + 'background-color': 'orange', + }, + }), + ] + ), + ] + ); + flexbox_4 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + width: '100px', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-width': '100', + 'data-expected-height': '50', + style: { + border: '0', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '200', + style: { + border: '0', + 'box-sizing': 'border-box', + height: '50px', + width: '200px', + 'background-color': 'orange', + }, + }), + ] + ), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + BODY.appendChild(flexbox_4); + BODY.appendChild(div); + BODY.appendChild(div_1); + + await matchViewportSnapshot(); + }); }); diff --git a/integration_tests/specs/css/css-flexbox/flex-flow.ts b/integration_tests/specs/css/css-flexbox/flex-flow.ts index d3857c97a4..569d57f0f0 100644 --- a/integration_tests/specs/css/css-flexbox/flex-flow.ts +++ b/integration_tests/specs/css/css-flexbox/flex-flow.ts @@ -1363,4 +1363,209 @@ describe('flexbox flex-flow', () => { await snapshot(); }) + + it('auto-margins-no-available-space', async () => { + let log; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let container; + let container_1; + let container_2; + let container_3; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + container = createElement( + 'div', + { + class: 'container', + style: { + position: 'relative', + 'background-color': 'pink', + outline: '1px solid black', + display: 'inline-block', + 'box-sizing': 'border-box', + }, + }, + [ + (flexbox = createElement( + 'div', + { + class: 'flexbox row', + style: { + display: 'flex', + '-webkit-flex-direction': 'row', + 'flex-direction': 'row', + 'background-color': 'grey', + width: '100px', + height: '100px', + margin: '20px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-offset-x': '60', + 'data-offset-y': '20', + style: { + flex: 'none', + 'background-color': 'blue', + margin: 'auto', + 'box-sizing': 'border-box', + width: '20px', + height: '120px', + }, + }), + ] + )), + ] + ); + container_1 = createElement( + 'div', + { + class: 'container', + style: { + position: 'relative', + 'background-color': 'pink', + outline: '1px solid black', + display: 'inline-block', + 'box-sizing': 'border-box', + }, + }, + [ + (flexbox_1 = createElement( + 'div', + { + class: 'flexbox row-reverse', + style: { + display: 'flex', + '-webkit-flex-direction': 'row-reverse', + 'flex-direction': 'row-reverse', + 'background-color': 'grey', + width: '100px', + height: '100px', + margin: '20px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-offset-x': '60', + 'data-offset-y': '20', + style: { + flex: 'none', + 'background-color': 'blue', + margin: 'auto', + 'box-sizing': 'border-box', + width: '20px', + height: '120px', + }, + }), + ] + )), + ] + ); + container_2 = createElement( + 'div', + { + class: 'container', + style: { + position: 'relative', + 'background-color': 'pink', + outline: '1px solid black', + display: 'inline-block', + 'box-sizing': 'border-box', + }, + }, + [ + (flexbox_2 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + 'background-color': 'grey', + width: '100px', + height: '100px', + margin: '20px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-offset-x': '20', + 'data-offset-y': '60', + style: { + flex: 'none', + 'background-color': 'blue', + margin: 'auto', + 'box-sizing': 'border-box', + width: '120px', + height: '20px', + }, + }), + ] + )), + ] + ); + container_3 = createElement( + 'div', + { + class: 'container', + style: { + position: 'relative', + 'background-color': 'pink', + outline: '1px solid black', + display: 'inline-block', + 'box-sizing': 'border-box', + }, + }, + [ + (flexbox_3 = createElement( + 'div', + { + class: 'flexbox column-reverse', + style: { + display: 'flex', + '-webkit-flex-direction': 'column-reverse', + 'flex-direction': 'column-reverse', + 'background-color': 'grey', + width: '100px', + height: '100px', + margin: '20px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-offset-x': '20', + 'data-offset-y': '60', + style: { + flex: 'none', + 'background-color': 'blue', + margin: 'auto', + 'box-sizing': 'border-box', + width: '120px', + height: '20px', + }, + }), + ] + )), + ] + ); + BODY.appendChild(log); + BODY.appendChild(container); + BODY.appendChild(container_1); + BODY.appendChild(container_2); + BODY.appendChild(container_3); + + await matchViewportSnapshot(); + }); }); diff --git a/integration_tests/specs/css/css-flexbox/flex-item.ts b/integration_tests/specs/css/css-flexbox/flex-item.ts index beda003b67..3bfb0dc6ef 100644 --- a/integration_tests/specs/css/css-flexbox/flex-item.ts +++ b/integration_tests/specs/css/css-flexbox/flex-item.ts @@ -297,4 +297,62 @@ describe('flex-item', () => { await snapshot(); }); + + it('child-overflow', async () => { + let log; + let flexbox; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + 'data-expected-height': '0', + 'data-offset-x': '0', + 'data-offset-y': '0', + class: 'flexbox', + style: { + display: 'flex', + position: 'relative', + 'flex-flow': 'column', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + 'data-expected-height': '0', + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + height: '0', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '20', + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + width: '20px', + height: '20px', + 'line-height': '0px', + 'background-color': 'blue', + 'box-sizing': 'border-box', + }, + }), + ] + ), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + + await matchViewportSnapshot(); + }); }); diff --git a/integration_tests/specs/css/css-flexbox/flex-justify-content.ts b/integration_tests/specs/css/css-flexbox/flex-justify-content.ts new file mode 100644 index 0000000000..5eb2a44876 --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/flex-justify-content.ts @@ -0,0 +1,857 @@ +/*auto generated*/ +describe('flex-justify', () => { + it('content', async () => { + let log; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let flexbox_4; + let flexbox_5; + let flexbox_6; + let flexbox_7; + let flexbox_8; + let flexbox_9; + let flexbox_10; + let flexbox_11; + let flexbox_12; + let flexbox_13; + let flexbox_14; + let flexbox_15; + let flexbox_16; + let flexbox_17; + let flexbox_18; + let flexbox_19; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '0', + style: { + height: '20px', + border: '0', + flex: '1 0 0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'max-width': '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '100', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '200', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'flex-end', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '300', + style: { + height: '20px', + border: '0', + flex: '0 0 100px', + 'background-color': 'blue', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '400', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '500', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + ); + flexbox_2 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'center', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '150', + style: { + height: '20px', + border: '0', + flex: '1 0 0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'max-width': '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '250', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '350', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + ); + flexbox_3 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'center', + }, + }, + [ + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '0', + style: { + height: '20px', + border: '0', + flex: '1 100px', + 'background-color': 'blue', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '200', + style: { + height: '20px', + border: '0', + flex: '1 100px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '400', + style: { + height: '20px', + border: '0', + flex: '1 100px', + 'background-color': 'red', + 'box-sizing': 'border-box', + }, + }), + ] + ); + flexbox_4 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'center', + }, + }, + [ + createElement('div', { + 'data-expected-width': '800', + 'data-offset-x': '-100', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '800px', + }, + }), + ] + ); + flexbox_5 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-between', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '0', + style: { + height: '20px', + border: '0', + flex: '1 0 0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'max-width': '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '250', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '500', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + ); + flexbox_6 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-between', + }, + }, + [ + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '0', + style: { + height: '20px', + border: '0', + flex: '1 100px', + 'background-color': 'blue', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '200', + style: { + height: '20px', + border: '0', + flex: '1 100px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '400', + style: { + height: '20px', + border: '0', + flex: '1 100px', + 'background-color': 'red', + 'box-sizing': 'border-box', + }, + }), + ] + ); + flexbox_7 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-between', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '0', + style: { + height: '20px', + border: '0', + flex: '1 0 0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'max-width': '100px', + }, + }), + ] + ); + flexbox_8 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-around', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '50', + style: { + height: '20px', + border: '0', + flex: '1 0 0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'max-width': '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '250', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '450', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + ); + flexbox_9 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-around', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '250', + style: { + height: '20px', + border: '0', + flex: '1 0 0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'max-width': '100px', + }, + }), + ] + ); + flexbox_10 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-around', + }, + }, + [ + createElement('div', { + 'data-expected-width': '800', + 'data-offset-x': '-100', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '800px', + }, + }), + ] + ); + flexbox_11 = createElement('div', { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-around', + }, + }); + flexbox_12 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-evenly', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '75', + style: { + height: '20px', + border: '0', + flex: '1 0 0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'max-width': '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '250', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '425', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + ); + flexbox_13 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-evenly', + }, + }, + [ + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '0', + style: { + height: '20px', + border: '0', + flex: '1 100px', + 'background-color': 'blue', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '200', + style: { + height: '20px', + border: '0', + flex: '1 100px', + 'background-color': 'green', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '400', + style: { + height: '20px', + border: '0', + flex: '1 100px', + 'background-color': 'red', + 'box-sizing': 'border-box', + }, + }), + ] + ); + flexbox_14 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-evenly', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '250', + style: { + height: '20px', + border: '0', + flex: '1 0 0', + 'background-color': 'blue', + 'box-sizing': 'border-box', + 'max-width': '100px', + }, + }), + ] + ); + flexbox_15 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-evenly', + }, + }, + [ + createElement('div', { + 'data-expected-width': '800', + 'data-offset-x': '-100', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '800px', + }, + }), + ] + ); + flexbox_16 = createElement('div', { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'space-evenly', + }, + }); + flexbox_17 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'flex-end', + }, + }, + [ + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '0', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'blue', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '100', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '100px', + 'margin-right': 'auto', + }, + }), + createElement('div', { + 'data-expected-width': '100', + 'data-offset-x': '500', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '100px', + }, + }), + ] + ); + flexbox_18 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'flex-end', + }, + }, + [ + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '0', + style: { + height: '20px', + border: '0', + flex: '0 1 300px', + 'background-color': 'blue', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '200', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '200px', + }, + }), + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '400', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '200px', + }, + }), + ] + ); + flexbox_19 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '600px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + 'justify-content': 'flex-end', + }, + }, + [ + createElement('div', { + 'data-expected-width': '300', + 'data-offset-x': '-100', + style: { + height: '20px', + border: '0', + flex: '1 0 300px', + 'background-color': 'blue', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '200', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'green', + 'box-sizing': 'border-box', + width: '200px', + }, + }), + createElement('div', { + 'data-expected-width': '200', + 'data-offset-x': '400', + style: { + height: '20px', + border: '0', + flex: 'none', + 'background-color': 'red', + 'box-sizing': 'border-box', + width: '200px', + }, + }), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + BODY.appendChild(flexbox_4); + BODY.appendChild(flexbox_5); + BODY.appendChild(flexbox_6); + BODY.appendChild(flexbox_7); + BODY.appendChild(flexbox_8); + BODY.appendChild(flexbox_9); + BODY.appendChild(flexbox_10); + BODY.appendChild(flexbox_11); + BODY.appendChild(flexbox_12); + BODY.appendChild(flexbox_13); + BODY.appendChild(flexbox_14); + BODY.appendChild(flexbox_15); + BODY.appendChild(flexbox_16); + BODY.appendChild(flexbox_17); + BODY.appendChild(flexbox_18); + BODY.appendChild(flexbox_19); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/flex-no-flex.ts b/integration_tests/specs/css/css-flexbox/flex-no-flex.ts new file mode 100644 index 0000000000..a9921b5c34 --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/flex-no-flex.ts @@ -0,0 +1,91 @@ +/*auto generated*/ +describe('flex-no', () => { + it('flex', async () => { + let log; + let flexbox; + let flexbox_1; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox row', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + width: '200px', + height: '200px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-width': '50', + style: { + 'background-color': 'blue', + flex: 'none', + width: '50px', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '150', + style: { + 'background-color': 'green', + flex: '1 auto', + 'box-sizing': 'border-box', + }, + }), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-direction': 'column', + width: '200px', + height: '200px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '50', + style: { + 'background-color': 'blue', + flex: 'none', + height: '50px', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-height': '150', + style: { + 'background-color': 'green', + flex: '1 auto', + 'box-sizing': 'border-box', + }, + }), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + + function runTest() { + document.body.className = 'noflex'; + checkLayout('.flexbox'); + } + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/flexbox-baseline.ts b/integration_tests/specs/css/css-flexbox/flexbox-baseline.ts new file mode 100644 index 0000000000..529dc610cb --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/flexbox-baseline.ts @@ -0,0 +1,1512 @@ +/*auto generated*/ +describe('flexbox-baseline', () => { + it('baseline', async () => { + let inlineFlexbox; + let inlineFlexbox_1; + let inlineFlexbox_2; + let inlineFlexbox_3; + let inlineFlexbox_4; + let inlineFlexbox_5; + let inlineFlexbox_6; + let inlineFlexbox_7; + let inlineFlexbox_8; + let inlineFlexbox_9; + let inlineFlexbox_10; + let inlineFlexbox_11; + let inlineFlexbox_12; + let div; + let div_1; + let div_2; + let div_3; + let div_4; + let div_5; + let div_6; + let div_7; + let div_8; + let div_9; + let div_10; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let flexitemWithScrollbar; + let flexboxWithScrollbar; + div = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + (inlineFlexbox = createElement( + 'div', + { + class: 'inline-flexbox', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'box-sizing': 'border-box', + height: '50px', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + 'align-self': 'flex-end', + }, + }, + [createText(`below`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + 'align-self': 'baseline', + 'margin-top': '15px', + }, + }, + [createText(`baseline`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + 'align-self': 'flex-start', + }, + }, + [createText(`above`)] + ), + ] + )), + createText(` +after text +`), + ] + ); + div_1 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + (inlineFlexbox_1 = createElement( + 'div', + { + class: 'inline-flexbox', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'box-sizing': 'border-box', + height: '40px', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + 'align-self': 'flex-end', + }, + }, + [createText(`baseline`)] + ), + createElement('div', { + style: { + 'box-sizing': 'border-box', + 'align-self': 'baseline', + 'writing-mode': 'vertical-rl', + }, + }), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + 'align-self': 'flex-start', + }, + }, + [createText(`above`)] + ), + ] + )), + createText(` +after text +`), + ] + ); + div_2 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + (inlineFlexbox_2 = createElement( + 'div', + { + class: 'inline-flexbox', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'h2', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`h2 baseline`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`above`)] + ), + ] + )), + createText(` +after text +`), + ] + ); + div_3 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + (inlineFlexbox_3 = createElement( + 'div', + { + class: 'inline-flexbox', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`baseline`)] + ), + createElement( + 'h2', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`h2 below`)] + ), + ] + )), + createText(` +after text +`), + ] + ); + div_4 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +should align with the middle +`), + (inlineFlexbox_4 = createElement( + 'div', + { + class: 'inline-flexbox', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'box-sizing': 'border-box', + width: '40px', + height: '40px', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + 'writing-mode': 'vertical-rl', + height: '20px', + width: '40px', + 'border-bottom': '1px solid black', + }, + }), + ] + )), + createText(` +of the grey flexbox +`), + ] + ); + div_5 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +should align with the bottom +`), + (inlineFlexbox_5 = createElement('div', { + class: 'inline-flexbox', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'box-sizing': 'border-box', + width: '30px', + height: '30px', + }, + })), + createText(` +of the grey flexbox +`), + ] + ); + div_6 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + (inlineFlexbox_6 = createElement( + 'div', + { + class: 'inline-flexbox column', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'flex-flow': 'column', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`baseline`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`below`)] + ), + ] + )), + createText(` +after text +`), + ] + ); + div_7 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + (inlineFlexbox_7 = createElement( + 'div', + { + class: 'inline-flexbox column-reverse', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'flex-flow': 'column-reverse', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`baseline`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`above`)] + ), + ] + )), + createText(` +after text +`), + ] + ); + div_8 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +should align with the middle +`), + (inlineFlexbox_8 = createElement( + 'div', + { + class: 'inline-flexbox column', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'flex-flow': 'column', + 'box-sizing': 'border-box', + width: '40px', + height: '40px', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + 'writing-mode': 'vertical-rl', + width: '40px', + height: '20px', + 'border-bottom': '1px solid black', + }, + }), + createElement('div', { + style: { + 'box-sizing': 'border-box', + 'writing-mode': 'vertical-rl', + width: '40px', + height: '20px', + }, + }), + ] + )), + createText(` +of the grey flexbox +`), + ] + ); + div_9 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +should align with the bottom +`), + (inlineFlexbox_9 = createElement('div', { + class: 'inline-flexbox column', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'flex-flow': 'column', + 'box-sizing': 'border-box', + width: '30px', + height: '30px', + }, + })), + createText(` +of the grey flexbox +`), + ] + ); + div_10 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + position: 'absolute', + top: '0', + left: '400px', + width: '360px', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + (inlineFlexbox_10 = createElement( + 'div', + { + class: 'inline-flexbox', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + position: 'absolute', + }, + }, + [createText(`absolute`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + 'margin-top': '30px', + }, + }, + [createText(`baseline`)] + ), + ] + )), + createText(` +after text +`), + ] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + (inlineFlexbox_11 = createElement( + 'div', + { + class: 'inline-flexbox', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'box-sizing': 'border-box', + height: '40px', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`baseline`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + 'align-self': 'baseline', + 'margin-top': 'auto', + }, + }, + [createText(`below`)] + ), + ] + )), + createText(` +after text +`), + ] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + display: 'inline-block', + }, + }, + [ + (inlineFlexbox_12 = createElement( + 'div', + { + class: 'inline-flexbox', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'box-sizing': 'border-box', + height: '40px', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`above`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + 'align-self': 'baseline', + 'margin-top': '10px', + }, + }, + [createText(`baseline`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`above`)] + ), + ] + )), + createText(` +after +`), + ] + ), + createText(` +text +`), + ] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + display: 'inline-block', + }, + }, + [ + (flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + 'background-color': 'grey', + 'margin-top': '10px', + 'box-sizing': 'border-box', + height: '30px', + }, + }, + [ + createText(` + baseline +`), + ] + )), + ] + ), + createText(` +after text +`), + ] + ), + createElement( + 'table', + { + style: { + 'box-sizing': 'border-box', + 'background-color': 'lightgrey', + 'margin-top': '5px', + }, + }, + [ + createElement( + 'tbody', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'tr', + { + style: { + 'box-sizing': 'border-box', + height: '50px', + }, + }, + [ + createElement( + 'td', + { + style: { + 'box-sizing': 'border-box', + 'vertical-align': 'bottom', + }, + }, + [createText(`bottom`)] + ), + createElement( + 'td', + { + style: { + 'box-sizing': 'border-box', + 'vertical-align': 'baseline', + }, + }, + [createText(`baseline`)] + ), + createElement( + 'td', + { + style: { + 'box-sizing': 'border-box', + 'vertical-align': 'top', + }, + }, + [createText(`top`)] + ), + createElement( + 'td', + { + style: { + 'box-sizing': 'border-box', + 'vertical-align': 'baseline', + }, + }, + [ + (flexbox_1 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + 'background-color': 'grey', + 'margin-top': '10px', + 'flex-flow': 'column', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`baseline`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`below`)] + ), + ] + )), + ] + ), + createElement( + 'td', + { + style: { + 'box-sizing': 'border-box', + 'vertical-align': 'baseline', + }, + }, + [ + (flexbox_2 = createElement( + 'div', + { + class: 'flexbox column-reverse', + style: { + display: 'flex', + 'background-color': 'grey', + 'margin-top': '10px', + 'flex-flow': 'column-reverse', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`baseline`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`above`)] + ), + ] + )), + ] + ), + ] + ), + ] + ), + ] + ), + createElement( + 'table', + { + style: { + 'box-sizing': 'border-box', + 'background-color': 'lightgrey', + 'margin-top': '5px', + }, + }, + [ + createElement( + 'tbody', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'tr', + { + style: { + 'box-sizing': 'border-box', + height: '50px', + }, + }, + [ + createElement( + 'td', + { + style: { + 'box-sizing': 'border-box', + 'vertical-align': 'bottom', + }, + }, + [createText(`bottom`)] + ), + createElement( + 'td', + { + style: { + 'box-sizing': 'border-box', + 'vertical-align': 'baseline', + }, + }, + [createText(`baseline`)] + ), + createElement( + 'td', + { + style: { + 'box-sizing': 'border-box', + 'vertical-align': 'top', + }, + }, + [createText(`top`)] + ), + createElement( + 'td', + { + style: { + 'box-sizing': 'border-box', + 'vertical-align': 'baseline', + }, + }, + [ + (flexbox_3 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + 'background-color': 'grey', + 'margin-top': '10px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'h2', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`h2 baseline`)] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`above`)] + ), + ] + )), + ] + ), + ] + ), + ] + ), + ] + ), + createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + (flexboxWithScrollbar = createElement( + 'div', + { + id: 'flexbox-with-scrollbar', + class: 'inline-flexbox', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + 'margin-top': '5px', + 'box-sizing': 'border-box', + height: '65px', + width: 'auto', + }, + }, + [ + (flexitemWithScrollbar = createElement( + 'div', + { + id: 'flexitem-with-scrollbar', + style: { + 'box-sizing': 'border-box', + 'align-self': 'baseline', + 'padding-top': '15px', + height: '50px', + 'overflow-y': 'scroll', + }, + }, + [ + createText(` + The baseline is based on`), + createElement('br', { + style: { + 'box-sizing': 'border-box', + }, + }), + createText(` + the non-scrolled position;`), + createElement('br', { + style: { + 'box-sizing': 'border-box', + }, + }), + createText(` + this won't line up. + `), + ] + )), + ] + )), + createText(` +after text +`), + ] + ), + ] + ); + BODY.appendChild(div); + BODY.appendChild(div_1); + BODY.appendChild(div_2); + BODY.appendChild(div_3); + BODY.appendChild(div_4); + BODY.appendChild(div_5); + BODY.appendChild(div_6); + BODY.appendChild(div_7); + BODY.appendChild(div_8); + BODY.appendChild(div_9); + BODY.appendChild(div_10); + + document.getElementById('flexitem-with-scrollbar').scrollTop = 999; + document.getElementById('flexbox-with-scrollbar').style.width = 'auto'; + + await matchViewportSnapshot(); + }); + it('margins', async () => { + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let flexbox_4; + let flexbox_5; + let flexbox_6; + let border; + let div; + let div_1; + let div_2; + let div_3; + let div_4; + let div_5; + let flexOne; + let flexOne_1; + let flexOne_2; + let flexOne_3; + let inlineBlock; + let inlineBlock_1; + let inlineBlock_2; + let inlineBlock_3; + let inlineBlock_4; + let inlineBlock_5; + let inlineFlexbox; + let inlineFlexbox_1; + div = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +before text +`), + (border = createElement( + 'div', + { + class: 'border', + style: { + border: '11px solid pink', + 'box-sizing': 'border-box', + display: 'inline-block', + 'background-color': 'lightgrey', + }, + }, + [ + (flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + 'background-color': 'lightgrey', + 'box-sizing': 'border-box', + height: '30px', + 'margin-top': '7px', + 'padding-top': '10px', + }, + }, + [ + createText(` + baseline +`), + ] + )), + ] + )), + createText(` +after text +`), + ] + ); + div_1 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +Should align +`), + (inlineBlock = createElement( + 'div', + { + class: 'inline-block border', + style: { + display: 'inline-block', + border: '11px solid pink', + 'box-sizing': 'border-box', + }, + }, + [ + (flexbox_1 = createElement( + 'div', + { + class: 'flexbox padding', + style: { + display: 'flex', + 'background-color': 'pink', + padding: '13px', + 'box-sizing': 'border-box', + width: '50px', + height: '50px', + }, + }, + [ + (flexOne = createElement('div', { + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + 'background-color': 'lightgrey', + }, + })), + ] + )), + ] + )), + createText(` +with the +`), + (inlineBlock_1 = createElement( + 'div', + { + class: 'inline-block margin', + style: { + display: 'inline-block', + margin: '8px 0', + 'box-sizing': 'border-box', + }, + }, + [ + (flexbox_2 = createElement( + 'div', + { + class: 'flexbox border', + style: { + display: 'flex', + 'background-color': 'pink', + border: '11px solid pink', + 'box-sizing': 'border-box', + width: '50px', + height: '50px', + }, + }, + [ + (flexOne_1 = createElement('div', { + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + 'background-color': 'lightgrey', + }, + })), + ] + )), + ] + )), + createText(` +bottom of +`), + (inlineBlock_2 = createElement( + 'div', + { + class: 'inline-block padding', + style: { + display: 'inline-block', + padding: '13px', + 'box-sizing': 'border-box', + 'padding-left': '0', + 'padding-right': '0', + }, + }, + [ + (flexbox_3 = createElement( + 'div', + { + class: 'flexbox margin border', + style: { + display: 'flex', + 'background-color': 'pink', + border: '11px solid pink', + margin: '8px 0', + 'box-sizing': 'border-box', + width: '50px', + height: '50px', + }, + }, + [ + (flexOne_2 = createElement('div', { + class: 'flex-one', + style: { + '-webkit-flex': '1', + flex: '1', + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + 'background-color': 'lightgrey', + }, + })), + ] + )), + ] + )), + createText(` +the grey box. +`), + ] + ); + div_2 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +Should align with the +`), + (inlineBlock_3 = createElement( + 'div', + { + class: 'inline-block', + style: { + display: 'inline-block', + 'box-sizing': 'border-box', + }, + }, + [ + (flexbox_4 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + 'background-color': 'white', + 'box-sizing': 'border-box', + }, + }, + [ + (flexOne_3 = createElement('div', { + class: 'flex-one border padding margin', + style: { + '-webkit-flex': '1', + flex: '1', + border: '11px solid pink', + padding: '13px', + margin: '8px 0', + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + 'background-color': 'lightgrey', + }, + })), + ] + )), + ] + )), + createText(` +bottom of the pink box. +`), + ] + ); + div_3 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +Should align 8px +`), + (inlineFlexbox = createElement('div', { + class: 'inline-flexbox margin border', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + border: '11px solid pink', + margin: '8px 0', + 'box-sizing': 'border-box', + width: '30px', + height: '30px', + }, + })), + createText(` +below the bottom +`), + (inlineFlexbox_1 = createElement('div', { + class: 'inline-flexbox margin border padding', + style: { + display: 'inline-flex', + 'background-color': 'lightgrey', + border: '11px solid pink', + padding: '13px', + margin: '8px 0', + 'box-sizing': 'border-box', + }, + })), + createText(` +of the pink box. +`), + ] + ); + div_4 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +Should align with the bottom +`), + (inlineBlock_4 = createElement( + 'div', + { + class: 'inline-block border margin padding', + style: { + display: 'inline-block', + border: '11px solid pink', + padding: '13px', + margin: '8px 0', + 'box-sizing': 'border-box', + 'background-color': 'pink', + }, + }, + [ + (flexbox_5 = createElement( + 'div', + { + class: 'flexbox border margin padding', + style: { + display: 'flex', + 'background-color': 'pink', + border: '11px solid pink', + padding: '13px', + margin: '8px 0', + 'box-sizing': 'border-box', + width: '50px', + height: '50px', + }, + }, + [ + createElement('div', { + style: { + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + width: '200px', + overflow: 'scroll', + 'background-color': 'lightgrey', + 'margin-top': '4px', + 'border-top': '9px solid pink', + }, + }), + ] + )), + ] + )), + createText(` +of the horizontal scrollbar. +`), + ] + ); + div_5 = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(` +Should align 10px below the +`), + (inlineBlock_5 = createElement( + 'div', + { + class: 'inline-block', + style: { + display: 'inline-block', + 'box-sizing': 'border-box', + 'background-color': 'pink', + }, + }, + [ + (flexbox_6 = createElement( + 'div', + { + class: 'flexbox', + style: { + display: 'flex', + 'background-color': 'pink', + 'box-sizing': 'border-box', + width: '50px', + height: '50px', + }, + }, + [ + createElement('div', { + style: { + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + width: '200px', + overflow: 'scroll', + 'background-color': 'lightgrey', + 'padding-bottom': '10px', + 'border-bottom': '10px solid pink', + }, + }), + ] + )), + ] + )), + createText(` +of the horizontal scrollbar, if one is visible. +`), + ] + ); + BODY.appendChild(div); + BODY.appendChild(div_1); + BODY.appendChild(div_2); + BODY.appendChild(div_3); + BODY.appendChild(div_4); + BODY.appendChild(div_5); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/flexbox-width.ts b/integration_tests/specs/css/css-flexbox/flexbox-width.ts new file mode 100644 index 0000000000..ee74d81080 --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/flexbox-width.ts @@ -0,0 +1,57 @@ +/*auto generated*/ +describe('flexbox-width', () => { + it('with-overflow-auto', async () => { + let log; + let overflow; + let target; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + target = createElement( + 'div', + { + id: 'target', + class: 'flexbox', + 'data-expected-width': '47', + style: { + display: 'inline-flex', + border: '5px solid green', + position: 'relative', + height: '50px', + 'box-sizing': 'border-box', + }, + }, + [ + (overflow = createElement( + 'div', + { + class: 'overflow', + style: { + border: '1px solid red', + overflow: 'auto', + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + height: '100px', + width: '20px', + }, + }), + ] + )), + ] + ); + BODY.appendChild(log); + BODY.appendChild(target); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/multiline-column.ts b/integration_tests/specs/css/css-flexbox/multiline-column.ts index 123e7354d5..d9e5f71bf9 100644 --- a/integration_tests/specs/css/css-flexbox/multiline-column.ts +++ b/integration_tests/specs/css/css-flexbox/multiline-column.ts @@ -229,4 +229,316 @@ describe('multiline-column', () => { await snapshot(); }); + + it('auto', async () => { + let log; + let p; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText( + `Test to make sure that multiline columns break at the right places when auto sized.` + ), + ] + ); + flexbox = createElement( + 'div', + { + 'data-expected-width': '200', + 'data-expected-height': '80', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-flow': 'column wrap', + 'margin-top': '20px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + width: '200px', + }, + }, + [ + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + border: '0', + flex: 'none', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '20', + style: { + border: '0', + flex: 'none', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '40', + style: { + border: '0', + flex: 'none', + 'background-color': 'pink', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '60', + style: { + border: '0', + flex: 'none', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + ] + ); + flexbox_1 = createElement( + 'div', + { + 'data-expected-width': '200', + 'data-expected-height': '40', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-flow': 'column wrap', + 'margin-top': '20px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + width: '200px', + 'max-height': '50px', + }, + }, + [ + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + border: '0', + flex: 'none', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '20', + style: { + border: '0', + flex: 'none', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '50', + 'data-offset-y': '0', + style: { + border: '0', + flex: 'none', + 'background-color': 'pink', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '50', + 'data-offset-y': '20', + style: { + border: '0', + flex: 'none', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + ] + ); + flexbox_2 = createElement( + 'div', + { + 'data-expected-width': '200', + 'data-expected-height': '50', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-flow': 'column wrap', + 'margin-top': '20px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + width: '200px', + height: '50px', + }, + }, + [ + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + border: '0', + flex: 'none', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '20', + style: { + border: '0', + flex: 'none', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '50', + 'data-offset-y': '0', + style: { + border: '0', + flex: 'none', + 'background-color': 'pink', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '50', + 'data-offset-y': '20', + style: { + border: '0', + flex: 'none', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + ] + ); + flexbox_3 = createElement( + 'div', + { + 'data-expected-width': '200', + 'data-expected-height': '30', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-flow': 'column wrap', + 'margin-top': '20px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + width: '200px', + height: '50px', + 'max-height': '30px', + }, + }, + [ + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + border: '0', + flex: 'none', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '50', + 'data-offset-y': '0', + style: { + border: '0', + flex: 'none', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '100', + 'data-offset-y': '0', + style: { + border: '0', + flex: 'none', + 'background-color': 'pink', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '150', + 'data-offset-y': '0', + style: { + border: '0', + flex: 'none', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + width: '50px', + height: '20px', + }, + }), + ] + ); + BODY.appendChild(log); + BODY.appendChild(p); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + + await matchViewportSnapshot(); + }); }); diff --git a/integration_tests/specs/css/css-flexbox/multiline-reverse-wrap.ts b/integration_tests/specs/css/css-flexbox/multiline-reverse-wrap.ts new file mode 100644 index 0000000000..f72b017fb3 --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/multiline-reverse-wrap.ts @@ -0,0 +1,694 @@ +/*auto generated*/ +describe('multiline-reverse', () => { + xit('wrap-baseline', async () => { + let flexbox; + let flexbox_1; + let flexbox_2; + flexbox = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '200px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-wrap': 'wrap-reverse', + 'align-items': 'baseline', + 'margin-bottom': '10px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + flex: '1 0 100px', + }, + }, + [ + createText(`first`), + createElement('br', { + style: { + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + }, + }), + createText(`first`), + createElement('br', { + style: { + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + }, + }), + createText(`first`), + ] + ), + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + flex: '1 0 100px', + }, + }, + [createText(`second`)] + ), + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'pink', + 'box-sizing': 'border-box', + flex: '1 0 100px', + 'margin-top': '5px', + }, + }, + [createText(`third`)] + ), + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + flex: '1 0 100px', + }, + }, + [ + createText(`fourth`), + createElement('br', { + style: { + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + }, + }), + createText(`fourth`), + ] + ), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '200px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-wrap': 'wrap-reverse', + 'align-items': 'baseline', + 'margin-bottom': '10px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + flex: '1 0 100px', + }, + }, + [ + createText(`first`), + createElement('br', { + style: { + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + }, + }), + createText(`first`), + createElement('br', { + style: { + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + }, + }), + createText(`first`), + ] + ), + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + flex: '1 0 100px', + }, + }, + [createText(`second`)] + ), + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'pink', + 'box-sizing': 'border-box', + flex: '1 0 100px', + }, + }, + [createText(`third`)] + ), + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + flex: '1 0 100px', + 'margin-bottom': '5px', + }, + }, + [ + createText(`fourth`), + createElement('br', { + style: { + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + }, + }), + createText(`fourth`), + ] + ), + ] + ); + flexbox_2 = createElement( + 'div', + { + class: 'flexbox', + style: { + width: '300px', + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-wrap': 'wrap-reverse', + 'align-items': 'baseline', + 'margin-bottom': '10px', + 'box-sizing': 'border-box', + }, + }, + [ + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + flex: '1 0 100px', + 'align-self': 'flex-start', + height: '100px', + }, + }, + [createText(`first`)] + ), + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + flex: '1 0 100px', + }, + }, + [createText(`second`)] + ), + createElement( + 'div', + { + style: { + border: '0', + 'background-color': 'pink', + 'box-sizing': 'border-box', + flex: '1 0 100px', + }, + }, + [ + createText(`third`), + createElement('br', { + style: { + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + }, + }), + createText(`third`), + ] + ), + ] + ); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + + await matchViewportSnapshot(); + }); + it('wrap-overflow', async () => { + let log; + let p; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let flexbox_4; + let flexbox_5; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`Test to make sure that wrap-reverse starts at the cross start edge if +sizing is not auto.`), + ] + ); + flexbox = createElement( + 'div', + { + 'data-expected-width': '200', + 'data-expected-height': '35', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-wrap': 'wrap-reverse', + 'margin-top': '20px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + width: '200px', + height: '35px', + }, + }, + [ + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '15', + style: { + border: '0', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '100', + 'data-offset-y': '25', + style: { + border: '0', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '5', + style: { + border: '0', + 'background-color': 'pink', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '100', + 'data-offset-y': '-5', + style: { + border: '0', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '20px', + }, + }), + ] + ); + flexbox_1 = createElement( + 'div', + { + 'data-expected-width': '200', + 'data-expected-height': '35', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-wrap': 'wrap-reverse', + 'margin-top': '20px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + width: '200px', + 'max-height': '35px', + }, + }, + [ + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '15', + style: { + border: '0', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '100', + 'data-offset-y': '25', + style: { + border: '0', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '5', + style: { + border: '0', + 'background-color': 'pink', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '100', + 'data-offset-y': '-5', + style: { + border: '0', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '20px', + }, + }), + ] + ); + flexbox_2 = createElement( + 'div', + { + 'data-expected-width': '200', + 'data-expected-height': '50', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-wrap': 'wrap-reverse', + 'margin-top': '20px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + width: '200px', + 'min-height': '50px', + }, + }, + [ + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '30', + style: { + border: '0', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '100', + 'data-offset-y': '40', + style: { + border: '0', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '0', + 'data-offset-y': '20', + style: { + border: '0', + 'background-color': 'pink', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '100', + 'data-offset-y': '10', + style: { + border: '0', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + flex: '1 100px', + height: '20px', + }, + }), + ] + ); + flexbox_3 = createElement( + 'div', + { + 'data-expected-width': '35', + 'data-expected-height': '200', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-wrap': 'wrap-reverse', + 'margin-top': '20px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + 'flex-direction': 'column', + height: '200px', + width: '35px', + }, + }, + [ + createElement('div', { + 'data-offset-x': '15', + 'data-offset-y': '0', + style: { + border: '0', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '25', + 'data-offset-y': '100', + style: { + border: '0', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '5', + 'data-offset-y': '0', + style: { + border: '0', + 'background-color': 'pink', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '-5', + 'data-offset-y': '100', + style: { + border: '0', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '20px', + }, + }), + ] + ); + flexbox_4 = createElement( + 'div', + { + 'data-expected-width': '35', + 'data-expected-height': '200', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-wrap': 'wrap-reverse', + 'margin-top': '20px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + 'flex-direction': 'column', + height: '200px', + 'max-width': '35px', + }, + }, + [ + createElement('div', { + 'data-offset-x': '15', + 'data-offset-y': '0', + style: { + border: '0', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '25', + 'data-offset-y': '100', + style: { + border: '0', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '5', + 'data-offset-y': '0', + style: { + border: '0', + 'background-color': 'pink', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '-5', + 'data-offset-y': '100', + style: { + border: '0', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '20px', + }, + }), + ] + ); + flexbox_5 = createElement( + 'div', + { + 'data-expected-width': '600', + 'data-expected-height': '200', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-wrap': 'wrap-reverse', + 'margin-top': '20px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + 'flex-direction': 'column', + height: '200px', + 'min-width': '50px', + 'max-width': '600px', + }, + }, + [ + createElement('div', { + 'data-offset-x': '580', + 'data-offset-y': '0', + style: { + border: '0', + 'background-color': 'lightblue', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '20px', + }, + }), + createElement('div', { + 'data-offset-x': '590', + 'data-offset-y': '100', + style: { + border: '0', + 'background-color': 'lightgreen', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '570', + 'data-offset-y': '0', + style: { + border: '0', + 'background-color': 'pink', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '10px', + }, + }), + createElement('div', { + 'data-offset-x': '560', + 'data-offset-y': '100', + style: { + border: '0', + 'background-color': 'yellow', + 'box-sizing': 'border-box', + flex: '1 100px', + width: '20px', + }, + }), + ] + ); + BODY.appendChild(log); + BODY.appendChild(p); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + BODY.appendChild(flexbox_4); + BODY.appendChild(flexbox_5); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/nested-stretch.ts b/integration_tests/specs/css/css-flexbox/nested-stretch.ts new file mode 100644 index 0000000000..727eea596a --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/nested-stretch.ts @@ -0,0 +1,239 @@ +/*auto generated*/ +describe('nested', () => { + it('stretch', async () => { + let log; + let flex; + let flex_1; + let flex_2; + let flex_3; + let flex_4; + let column; + let column_1; + let column_2; + let flexbox; + let flexbox_1; + log = createElement('div', { + id: 'log', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox row', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + display: 'flex', + 'flex-direction': 'row', + 'box-sizing': 'border-box', + width: '600px', + }, + }, + [ + (column = createElement( + 'div', + { + 'data-expected-width': '290', + 'data-expected-height': '220', + class: 'column flex', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + display: 'flex', + 'flex-direction': 'column', + flex: '1 0 auto', + 'box-sizing': 'border-box', + }, + }, + [ + (flex = createElement('div', { + 'data-expected-width': '270', + 'data-expected-height': '95', + class: 'flex', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + flex: '1 0 auto', + 'box-sizing': 'border-box', + }, + })), + (flex_1 = createElement('div', { + 'data-expected-width': '270', + 'data-expected-height': '95', + class: 'flex', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + flex: '1 0 auto', + 'box-sizing': 'border-box', + }, + })), + ] + )), + (column_1 = createElement( + 'div', + { + 'data-expected-width': '290', + 'data-expected-height': '220', + class: 'column flex', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + display: 'flex', + 'flex-direction': 'column', + flex: '1 0 auto', + 'box-sizing': 'border-box', + }, + }, + [ + (flex_2 = createElement('div', { + 'data-expected-width': '270', + 'data-expected-height': '60', + class: 'flex', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + flex: '1 0 auto', + 'box-sizing': 'border-box', + height: '50px', + }, + })), + (flex_3 = createElement('div', { + 'data-expected-width': '270', + 'data-expected-height': '60', + class: 'flex', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + flex: '1 0 auto', + 'box-sizing': 'border-box', + height: '50px', + }, + })), + (flex_4 = createElement('div', { + 'data-expected-width': '270', + 'data-expected-height': '60', + class: 'flex', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + flex: '1 0 auto', + 'box-sizing': 'border-box', + height: '50px', + }, + })), + ] + )), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox column', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + display: 'flex', + 'flex-direction': 'column', + 'box-sizing': 'border-box', + width: '600px', + height: '300px', + position: 'relative', + }, + }, + [ + (column_2 = createElement( + 'div', + { + 'data-expected-width': '590', + 'data-expected-height': '250', + class: 'column flex', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + display: 'flex', + 'flex-direction': 'column', + flex: '1 0 auto', + 'box-sizing': 'border-box', + 'justify-content': 'flex-end', + }, + }, + [ + createElement('div', { + 'data-offset-y': '180', + 'data-expected-width': '570', + 'data-expected-height': '30', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + 'box-sizing': 'border-box', + height: '20px', + flex: 'none', + }, + }), + createElement('div', { + 'data-offset-y': '220', + 'data-expected-width': '570', + 'data-expected-height': '30', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + 'box-sizing': 'border-box', + height: '20px', + flex: 'none', + }, + }), + ] + )), + createElement('div', { + 'data-expected-width': '590', + 'data-expected-height': '30', + style: { + border: '1px solid black', + background: 'hsla(210,100%,90%, .8)', + padding: '5px', + margin: '5px', + 'box-sizing': 'border-box', + height: '20px', + flex: 'none', + }, + }), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/overflow-auto.ts b/integration_tests/specs/css/css-flexbox/overflow-auto.ts new file mode 100644 index 0000000000..fd3d220ad5 --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/overflow-auto.ts @@ -0,0 +1,325 @@ +/*auto generated*/ +describe('overflow-auto', () => { + it('intrinsic-size', async () => { + let log; + let overflow; + let overflow_1; + let overflow_2; + let overflow_3; + let inlineFlexbox; + let inlineFlexbox_1; + let inlineFlexbox_2; + let inlineFlexbox_3; + let measure; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + inlineFlexbox = createElement( + 'div', + { + class: 'inline-flexbox column to-be-checked', + 'check-width': '', + 'check-accounts-scrollbar': '', + style: { + display: 'inline-flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + border: '5px solid green', + position: 'relative', + height: '50px', + 'box-sizing': 'border-box', + }, + }, + [ + (overflow = createElement( + 'div', + { + class: 'overflow', + style: { + border: '1px solid red', + overflow: 'auto', + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + width: '20px', + height: '100px', + }, + }), + ] + )), + ] + ); + inlineFlexbox_1 = createElement( + 'div', + { + class: 'inline-flexbox column-reverse to-be-checked', + 'check-width': '', + 'check-accounts-scrollbar': '', + style: { + display: 'inline-flex', + '-webkit-flex-direction': 'column-reverse', + 'flex-direction': 'column-reverse', + border: '5px solid green', + position: 'relative', + height: '50px', + 'box-sizing': 'border-box', + }, + }, + [ + (overflow_1 = createElement( + 'div', + { + class: 'overflow', + style: { + border: '1px solid red', + overflow: 'auto', + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + width: '20px', + height: '100px', + }, + }), + ] + )), + ] + ); + inlineFlexbox_2 = createElement( + 'div', + { + class: 'inline-flexbox column to-be-checked', + 'check-width': '', + 'check-accounts-scrollbar': '', + style: { + display: 'inline-flex', + '-webkit-flex-direction': 'column', + 'flex-direction': 'column', + border: '5px solid green', + position: 'relative', + height: '50px', + 'box-sizing': 'border-box', + }, + }, + [ + (overflow_2 = createElement( + 'div', + { + class: 'overflow align-self-baseline', + style: { + '-webkit-align-self': 'baseline', + 'align-self': 'baseline', + border: '1px solid red', + overflow: 'auto', + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + width: '20px', + height: '100px', + }, + }), + ] + )), + ] + ); + inlineFlexbox_3 = createElement( + 'div', + { + class: 'inline-flexbox column-reverse to-be-checked', + 'check-width': '', + 'check-accounts-scrollbar': '', + style: { + display: 'inline-flex', + '-webkit-flex-direction': 'column-reverse', + 'flex-direction': 'column-reverse', + border: '5px solid green', + position: 'relative', + height: '50px', + 'box-sizing': 'border-box', + }, + }, + [ + (overflow_3 = createElement( + 'div', + { + class: 'overflow align-self-baseline', + style: { + '-webkit-align-self': 'baseline', + 'align-self': 'baseline', + border: '1px solid red', + overflow: 'auto', + 'min-width': '0', + 'min-height': '0', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + width: '20px', + height: '100px', + }, + }), + ] + )), + ] + ); + measure = createElement( + 'div', + { + id: 'measure', + style: { + 'box-sizing': 'border-box', + height: '100px', + width: '100px', + display: 'inline-block', + overflow: 'auto', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + 'min-height': '300px', + }, + }), + ] + ); + BODY.appendChild(log); + BODY.appendChild(inlineFlexbox); + BODY.appendChild(inlineFlexbox_1); + BODY.appendChild(inlineFlexbox_2); + BODY.appendChild(inlineFlexbox_3); + BODY.appendChild(measure); + + await matchViewportSnapshot(); + }); + it('resizes-correctly', async () => { + let rect; + let vbox; + let hflex; + let inner; + let div; + let measure; + hflex = createElement( + 'div', + { + class: 'hflex', + style: { + display: 'flex', + 'flex-direction': 'row', + 'max-height': '200px', + margin: '10px 0 10px 0', + 'box-sizing': 'border-box', + }, + }, + [ + (vbox = createElement( + 'div', + { + class: 'vbox', + style: { + 'overflow-y': 'auto', + 'max-height': '200px', + 'box-sizing': 'border-box', + }, + }, + [ + (rect = createElement('div', { + class: 'rect', + style: { + 'min-height': '300px', + 'min-width': '100px', + 'background-color': 'green', + display: 'inline-block', + 'box-sizing': 'border-box', + }, + })), + ] + )), + ] + ); + div = createElement( + 'div', + { + style: { + 'box-sizing': 'border-box', + display: 'flex', + width: '100px', + height: '100px', + }, + }, + [ + (inner = createElement( + 'div', + { + id: 'inner', + style: { + 'box-sizing': 'border-box', + flex: 'none', + height: '100px', + overflow: 'auto', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + width: '100px', + height: '150px', + 'background-color': 'green', + }, + }), + ] + )), + ] + ); + measure = createElement( + 'div', + { + id: 'measure', + style: { + 'box-sizing': 'border-box', + height: '100px', + width: '100px', + display: 'inline-box', + overflow: 'auto', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + 'min-height': '300px', + }, + }), + ] + ); + BODY.appendChild(hflex); + BODY.appendChild(div); + BODY.appendChild(measure); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/overflow-keep-scrollpos.ts b/integration_tests/specs/css/css-flexbox/overflow-keep-scrollpos.ts new file mode 100644 index 0000000000..443a06e742 --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/overflow-keep-scrollpos.ts @@ -0,0 +1,71 @@ +/*auto generated*/ +describe('overflow-keep', () => { + it('scrollpos', async () => { + let log; + let flex; + let sidebar; + let container; + let console; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + container = createElement( + 'div', + { + class: 'container flexbox', + style: { + display: 'flex', + width: '100px', + height: '100px', + 'box-sizing': 'border-box', + }, + }, + [ + (flex = createElement( + 'div', + { + class: 'flex overflow-auto flexbox', + style: { + display: 'flex', + overflow: 'auto', + flex: '1', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + style: { + 'box-sizing': 'border-box', + height: '400px', + }, + }), + ] + )), + (sidebar = createElement( + 'div', + { + id: 'sidebar', + style: { + 'box-sizing': 'border-box', + }, + }, + [createText(`foo`)] + )), + ] + ); + console = createElement('div', { + id: 'console', + style: { + 'box-sizing': 'border-box', + }, + }); + BODY.appendChild(log); + BODY.appendChild(container); + BODY.appendChild(console); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/percentage-heights.ts b/integration_tests/specs/css/css-flexbox/percentage-heights.ts new file mode 100644 index 0000000000..12f97596d3 --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/percentage-heights.ts @@ -0,0 +1,346 @@ +/*auto generated*/ +describe('percentage', () => { + it('heights', async () => { + let log; + let flexbox; + let flexbox_1; + let flexbox_2; + let flexbox_3; + let flexbox_4; + let flexbox_5; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-flow': 'column wrap', + width: '100px', + height: '100px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '40', + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + 'background-color': 'blue', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-height': '40', + 'data-offset-x': '0', + 'data-offset-y': '40', + style: { + 'background-color': 'green', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-height': '40', + 'data-offset-x': '40', + 'data-offset-y': '0', + style: { + 'background-color': 'red', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + ] + ); + flexbox_1 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-flow': 'column wrap', + width: '100px', + height: '100px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '40', + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + 'background-color': 'blue', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + 'margin-bottom': '10%', + }, + }), + createElement('div', { + 'data-expected-height': '40', + 'data-offset-x': '40', + 'data-offset-y': '0', + style: { + 'background-color': 'green', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + 'margin-bottom': '20%', + }, + }), + createElement('div', { + 'data-expected-height': '40', + 'data-offset-x': '40', + 'data-offset-y': '60', + style: { + 'background-color': 'red', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + ] + ); + flexbox_2 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-flow': 'column wrap', + width: '100px', + height: '100px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + }, + }, + [ + createElement('div', { + 'data-expected-height': '20', + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + 'background-color': 'blue', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + flex: '1', + 'min-height': '0', + 'max-height': '20%', + }, + }), + createElement('div', { + 'data-expected-height': '40', + 'data-offset-x': '0', + 'data-offset-y': '20', + style: { + 'background-color': 'green', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-height': '40', + 'data-offset-x': '0', + 'data-offset-y': '60', + style: { + 'background-color': 'red', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + ] + ); + flexbox_3 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-flow': 'column wrap', + width: '100px', + height: '100px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + 'writing-mode': 'vertical-rl', + }, + }, + [ + createElement('div', { + 'data-expected-width': '40', + 'data-offset-x': '60', + 'data-offset-y': '0', + style: { + 'background-color': 'blue', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '40', + 'data-offset-x': '20', + 'data-offset-y': '0', + style: { + 'background-color': 'green', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '40', + 'data-offset-x': '60', + 'data-offset-y': '40', + style: { + 'background-color': 'red', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + ] + ); + flexbox_4 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-flow': 'column wrap', + width: '100px', + height: '100px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + 'writing-mode': 'vertical-rl', + }, + }, + [ + createElement('div', { + 'data-expected-width': '40', + 'data-offset-x': '60', + 'data-offset-y': '0', + style: { + 'background-color': 'blue', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + 'margin-bottom': '10%', + }, + }), + createElement('div', { + 'data-expected-width': '40', + 'data-offset-x': '20', + 'data-offset-y': '0', + style: { + 'background-color': 'green', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + 'margin-bottom': '20%', + }, + }), + createElement('div', { + 'data-expected-width': '40', + 'data-offset-x': '60', + 'data-offset-y': '60', + style: { + 'background-color': 'red', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + ] + ); + flexbox_5 = createElement( + 'div', + { + class: 'flexbox column', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'flex-flow': 'column wrap', + width: '100px', + height: '100px', + 'align-content': 'flex-start', + 'box-sizing': 'border-box', + 'writing-mode': 'vertical-rl', + }, + }, + [ + createElement('div', { + 'data-expected-width': '20', + 'data-offset-x': '80', + 'data-offset-y': '0', + style: { + 'background-color': 'blue', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + flex: '1', + 'min-width': '0', + 'max-width': '20%', + }, + }), + createElement('div', { + 'data-expected-width': '40', + 'data-offset-x': '40', + 'data-offset-y': '0', + style: { + 'background-color': 'green', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + createElement('div', { + 'data-expected-width': '40', + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + 'background-color': 'red', + width: '40%', + height: '40%', + 'box-sizing': 'border-box', + }, + }), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + BODY.appendChild(flexbox_4); + BODY.appendChild(flexbox_5); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/resize-min-content.ts b/integration_tests/specs/css/css-flexbox/resize-min-content.ts new file mode 100644 index 0000000000..08fcbbc596 --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/resize-min-content.ts @@ -0,0 +1,49 @@ +/*auto generated*/ +describe('resize-min', () => { + it('content-flexbox', async () => { + let log; + let content; + let flexbox; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + flexbox = createElement( + 'div', + { + class: 'flexbox column justify-content-center align-items-center', + 'data-expected-height': '100', + style: { + 'min-height': 'min-content', + background: 'green', + height: '100%', + 'box-sizing': 'border-box', + }, + }, + [ + (content = createElement('div', { + id: 'content', + 'data-expected-height': '100', + style: { + height: '1000px', + 'max-height': '100%', + 'box-sizing': 'border-box', + }, + })), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.style.height = '100%'; + document.documentElement.style.height = '100%'; + + document.body.offsetHeight; + document.documentElement.style.height = '100px'; + + checkLayout('.flexbox'); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-flexbox/style-change.ts b/integration_tests/specs/css/css-flexbox/style-change.ts new file mode 100644 index 0000000000..69e11d27db --- /dev/null +++ b/integration_tests/specs/css/css-flexbox/style-change.ts @@ -0,0 +1,93 @@ +/*auto generated*/ +describe('style', () => { + it('change', async () => { + let log; + let p; + let a; + let b; + let flexbox; + log = createElement('div', { + id: 'log', + style: { + 'box-sizing': 'border-box', + }, + }); + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText( + `This test verifies that changing order, align-content, align-items, align-self, or justify-content will relayout.` + ), + ] + ); + flexbox = createElement( + 'div', + { + id: 'flexbox', + class: 'flexbox', + style: { + display: 'flex', + 'background-color': '#aaa', + position: 'relative', + 'box-sizing': 'border-box', + width: '300px', + height: '300px', + }, + }, + [ + (a = createElement('div', { + id: 'a', + 'data-offset-x': '0', + 'data-offset-y': '0', + style: { + 'background-color': 'blue', + 'box-sizing': 'border-box', + flex: '0 0 auto', + width: '100px', + height: '100px', + }, + })), + (b = createElement('div', { + id: 'b', + 'data-offset-x': '100', + 'data-offset-y': '0', + style: { + 'background-color': 'green', + 'box-sizing': 'border-box', + flex: '0 0 auto', + width: '100px', + height: '100px', + }, + })), + ] + ); + BODY.appendChild(log); + BODY.appendChild(p); + BODY.appendChild(flexbox); + + (() => { + var flexbox = document.getElementById('flexbox'); + var aDiv = document.getElementById('a'); + var bDiv = document.getElementById('b'); + + flexbox.style.justifyContent = 'flex-end'; + + flexbox.style.alignItems = 'flex-end'; + + bDiv.style.order = -1; + + aDiv.style.alignSelf = 'flex-start'; + + flexbox.style.width = '100px'; + flexbox.style.flexWrap = 'wrap'; + flexbox.style.alignContent = 'flex-end'; + })(); + + await matchViewportSnapshot(); + }); +}); From 35a298de88c689852bbbc0abc6421d108c37dd79 Mon Sep 17 00:00:00 2001 From: "zhanwen.zw" <zhanwen.zw@alibaba-inc.com> Date: Thu, 12 May 2022 19:36:20 +0800 Subject: [PATCH 149/498] test: update tests --- .../specs/css/css-flexbox/baseline-for.ts | 7 +- .../specs/css/css-flexbox/columns-auto.ts | 895 +++++++++--------- .../specs/css/css-flexbox/definite-main.ts | 346 ++++--- 3 files changed, 647 insertions(+), 601 deletions(-) diff --git a/integration_tests/specs/css/css-flexbox/baseline-for.ts b/integration_tests/specs/css/css-flexbox/baseline-for.ts index c1b54e036d..392ea5d2ec 100644 --- a/integration_tests/specs/css/css-flexbox/baseline-for.ts +++ b/integration_tests/specs/css/css-flexbox/baseline-for.ts @@ -1,6 +1,7 @@ /*auto generated*/ describe('baseline-for', () => { - it('empty-line-expected', async () => { + // @TODO: Height of empty inline-block div should equal to font height. + xit('empty-line-expected', async () => { let b; let b_1; let b_2; @@ -117,7 +118,7 @@ def`), ); BODY.appendChild(p); - await matchViewportSnapshot(); + await snapshot(); }); it('empty-line', async () => { let flex; @@ -239,6 +240,6 @@ def`), ); BODY.appendChild(p); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/columns-auto.ts b/integration_tests/specs/css/css-flexbox/columns-auto.ts index a43af29387..2243a97740 100644 --- a/integration_tests/specs/css/css-flexbox/columns-auto.ts +++ b/integration_tests/specs/css/css-flexbox/columns-auto.ts @@ -1,6 +1,6 @@ /*auto generated*/ describe('columns-auto', () => { - it('size', async () => { + it("size 001", async () => { let log; let flexbox; let flexbox_1; @@ -9,72 +9,67 @@ describe('columns-auto', () => { let flexbox_4; let flexbox_5; let flexbox_6; - let flexbox_7; let flexbox_8; - let flexbox_9; - let flexbox_10; - let childDiv; - let childDiv_1; - log = createElement('div', { - id: 'log', + log = createElement("div", { + id: "log", style: { - 'box-sizing': 'border-box', + "box-sizing": "border-box", }, }); flexbox = createElement( - 'div', + "div", { - class: 'flexbox column horizontal', + class: "flexbox column horizontal", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - 'background-color': '#aaa', - position: 'relative', - width: '400px', - 'box-sizing': 'border-box', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "background-color": "#aaa", + position: "relative", + width: "400px", + "box-sizing": "border-box", }, }, [ - createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '0', + createElement("div", { + "data-expected-height": "10", + "data-offset-y": "0", style: { - width: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - flex: '1 0 10px', + width: "100%", + "background-color": "blue", + "box-sizing": "border-box", + flex: "1 0 10px", }, }), - createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '10', + createElement("div", { + "data-expected-height": "10", + "data-offset-y": "10", style: { - width: '100%', - 'background-color': 'green', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "background-color": "green", + "box-sizing": "border-box", + height: "10px", }, }), createElement( - 'div', + "div", { - 'data-expected-height': '10', - 'data-offset-y': '20', + "data-expected-height": "10", + "data-offset-y": "20", style: { - width: '100%', - 'background-color': 'red', - 'box-sizing': 'border-box', + width: "100%", + "background-color": "red", + "box-sizing": "border-box", }, }, [ - createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '20', + createElement("div", { + "data-expected-height": "10", + "data-offset-y": "20", style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] @@ -82,85 +77,85 @@ describe('columns-auto', () => { ] ); flexbox_1 = createElement( - 'div', + "div", { - class: 'flexbox column horizontal', + class: "flexbox column horizontal", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - 'background-color': '#aaa', - position: 'relative', - width: '400px', - 'box-sizing': 'border-box', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "background-color": "#aaa", + position: "relative", + width: "400px", + "box-sizing": "border-box", }, }, [ - createElement('div', { - 'data-expected-height': '0', - 'data-offset-y': '0', + createElement("div", { + "data-expected-height": "0", + "data-offset-y": "0", style: { - width: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - flex: '1', + width: "100%", + "background-color": "blue", + "box-sizing": "border-box", + flex: "1", }, }), - createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '0', + createElement("div", { + "data-expected-height": "10", + "data-offset-y": "0", style: { - width: '100%', - 'background-color': 'green', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "background-color": "green", + "box-sizing": "border-box", + height: "10px", }, }), createElement( - 'div', + "div", { - 'data-expected-height': '10', - 'data-offset-y': '10', + "data-expected-height": "10", + "data-offset-y": "10", style: { - width: '100%', - 'background-color': 'red', - 'box-sizing': 'border-box', - flex: '1 auto', + width: "100%", + "background-color": "red", + "box-sizing": "border-box", + flex: "1 auto", }, }, [ - createElement('div', { + createElement("div", { style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] ), createElement( - 'div', + "div", { - 'data-expected-height': '10', - 'data-offset-y': '20', + "data-expected-height": "10", + "data-offset-y": "20", style: { - width: '100%', - 'background-color': 'orange', - 'box-sizing': 'border-box', - 'min-height': '0', - flex: '1', + width: "100%", + "background-color": "orange", + "box-sizing": "border-box", + "min-height": "0", + flex: "1", }, }, [ - (childDiv = createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '20', - class: 'child-div', + (childDiv = createElement("div", { + "data-expected-height": "10", + "data-offset-y": "20", + class: "child-div", style: { - width: '100%', - 'background-color': 'yellow', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "background-color": "yellow", + "box-sizing": "border-box", + height: "10px", }, })), ] @@ -168,62 +163,62 @@ describe('columns-auto', () => { ] ); flexbox_2 = createElement( - 'div', + "div", { - class: 'flexbox column horizontal', + class: "flexbox column horizontal", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - 'background-color': '#aaa', - position: 'relative', - width: '400px', - 'box-sizing': 'border-box', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "background-color": "#aaa", + position: "relative", + width: "400px", + "box-sizing": "border-box", }, }, [ - createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '10', + createElement("div", { + "data-expected-height": "10", + "data-offset-y": "10", style: { - width: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - flex: '1 0 10px', - 'margin-top': '10px', + width: "100%", + "background-color": "blue", + "box-sizing": "border-box", + flex: "1 0 10px", + "margin-top": "10px", }, }), - createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '20', + createElement("div", { + "data-expected-height": "10", + "data-offset-y": "20", style: { - width: '100%', - 'background-color': 'green', - 'box-sizing': 'border-box', - height: '10px', - 'margin-bottom': '20px', + width: "100%", + "background-color": "green", + "box-sizing": "border-box", + height: "10px", + "margin-bottom": "20px", }, }), createElement( - 'div', + "div", { - 'data-expected-height': '20', - 'data-offset-y': '50', + "data-expected-height": "20", + "data-offset-y": "50", style: { - width: '100%', - 'background-color': 'red', - 'box-sizing': 'border-box', - 'padding-top': '10px', + width: "100%", + "background-color": "red", + "box-sizing": "border-box", + "padding-top": "10px", }, }, [ - createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '60', + createElement("div", { + "data-expected-height": "10", + "data-offset-y": "60", style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] @@ -231,64 +226,64 @@ describe('columns-auto', () => { ] ); flexbox_3 = createElement( - 'div', + "div", { - class: 'flexbox column horizontal justify-content-space-between', + class: "flexbox column horizontal justify-content-space-between", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - '-webkit-justify-content': 'space-between', - 'justify-content': 'space-between', - 'background-color': '#aaa', - position: 'relative', - width: '400px', - 'box-sizing': 'border-box', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "-webkit-justify-content": "space-between", + "justify-content": "space-between", + "background-color": "#aaa", + position: "relative", + width: "400px", + "box-sizing": "border-box", }, }, [ - createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '10', + createElement("div", { + "data-expected-height": "10", + "data-offset-y": "10", style: { - width: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - flex: '1 0 10px', - 'margin-top': '10px', + width: "100%", + "background-color": "blue", + "box-sizing": "border-box", + flex: "1 0 10px", + "margin-top": "10px", }, }), - createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '20', + createElement("div", { + "data-expected-height": "10", + "data-offset-y": "20", style: { - width: '100%', - 'background-color': 'green', - 'box-sizing': 'border-box', - height: '10px', - 'margin-bottom': '20px', + width: "100%", + "background-color": "green", + "box-sizing": "border-box", + height: "10px", + "margin-bottom": "20px", }, }), createElement( - 'div', + "div", { - 'data-expected-height': '20', - 'data-offset-y': '50', + "data-expected-height": "20", + "data-offset-y": "50", style: { - width: '100%', - 'background-color': 'red', - 'box-sizing': 'border-box', - 'padding-top': '10px', + width: "100%", + "background-color": "red", + "box-sizing": "border-box", + "padding-top": "10px", }, }, [ - createElement('div', { - 'data-expected-height': '10', - 'data-offset-y': '60', + createElement("div", { + "data-expected-height": "10", + "data-offset-y": "60", style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] @@ -296,61 +291,61 @@ describe('columns-auto', () => { ] ); flexbox_4 = createElement( - 'div', + "div", { - class: 'flexbox column horizontal', - 'data-expected-height': '20', + class: "flexbox column horizontal", + "data-expected-height": "20", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - 'background-color': '#aaa', - position: 'relative', - width: '400px', - 'box-sizing': 'border-box', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "background-color": "#aaa", + position: "relative", + width: "400px", + "box-sizing": "border-box", }, }, [ createElement( - 'div', + "div", { - 'data-expected-height': '10', - 'data-offset-y': '0', + "data-expected-height": "10", + "data-offset-y": "0", style: { - width: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - flex: '0 1 auto', + width: "100%", + "background-color": "blue", + "box-sizing": "border-box", + flex: "0 1 auto", }, }, [ - createElement('div', { + createElement("div", { style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] ), createElement( - 'div', + "div", { - 'data-expected-height': '10', - 'data-offset-y': '10', + "data-expected-height": "10", + "data-offset-y": "10", style: { - width: '100%', - 'background-color': 'green', - 'box-sizing': 'border-box', - flex: '0 2 auto', + width: "100%", + "background-color": "green", + "box-sizing": "border-box", + flex: "0 2 auto", }, }, [ - createElement('div', { + createElement("div", { style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] @@ -358,62 +353,62 @@ describe('columns-auto', () => { ] ); flexbox_5 = createElement( - 'div', + "div", { - class: 'flexbox column horizontal', - 'data-expected-height': '20', + class: "flexbox column horizontal", + "data-expected-height": "20", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - 'background-color': '#aaa', - position: 'relative', - width: '400px', - 'box-sizing': 'border-box', - 'min-height': '10px', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "background-color": "#aaa", + position: "relative", + width: "400px", + "box-sizing": "border-box", + "min-height": "10px", }, }, [ createElement( - 'div', + "div", { - 'data-expected-height': '10', - 'data-offset-y': '0', + "data-expected-height": "10", + "data-offset-y": "0", style: { - width: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - flex: '0 1 auto', + width: "100%", + "background-color": "blue", + "box-sizing": "border-box", + flex: "0 1 auto", }, }, [ - createElement('div', { + createElement("div", { style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] ), createElement( - 'div', + "div", { - 'data-expected-height': '10', - 'data-offset-y': '10', + "data-expected-height": "10", + "data-offset-y": "10", style: { - width: '100%', - 'background-color': 'green', - 'box-sizing': 'border-box', - flex: '0 2 auto', + width: "100%", + "background-color": "green", + "box-sizing": "border-box", + flex: "0 2 auto", }, }, [ - createElement('div', { + createElement("div", { style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] @@ -421,341 +416,359 @@ describe('columns-auto', () => { ] ); flexbox_6 = createElement( - 'div', + "div", { - class: 'flexbox column horizontal', - 'data-expected-height': '17', + class: "flexbox column horizontal", + "data-expected-height": "17", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - 'background-color': '#aaa', - position: 'relative', - width: '400px', - 'box-sizing': 'border-box', - 'min-height': '5px', - 'max-height': '17px', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "background-color": "#aaa", + position: "relative", + width: "400px", + "box-sizing": "border-box", + "min-height": "5px", + "max-height": "17px", }, }, [ createElement( - 'div', + "div", { - 'data-expected-height': '9', - 'data-offset-y': '0', + "data-expected-height": "9", + "data-offset-y": "0", style: { - width: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - 'min-height': '0', - flex: '0 1 auto', + width: "100%", + "background-color": "blue", + "box-sizing": "border-box", + "min-height": "0", + flex: "0 1 auto", }, }, [ - createElement('div', { + createElement("div", { style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] ), createElement( - 'div', + "div", { - 'data-expected-height': '8', - 'data-offset-y': '9', + "data-expected-height": "8", + "data-offset-y": "9", style: { - width: '100%', - 'background-color': 'green', - 'box-sizing': 'border-box', - 'min-height': '0', - flex: '0 2 auto', + width: "100%", + "background-color": "green", + "box-sizing": "border-box", + "min-height": "0", + flex: "0 2 auto", }, }, [ - createElement('div', { + createElement("div", { style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] ), ] ); - flexbox_7 = createElement( - 'div', + flexbox_8 = createElement( + "div", { - class: 'flexbox column horizontal', - 'data-expected-height': '33', + class: "flexbox column horizontal", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - 'background-color': '#aaa', - position: 'relative', - width: '400px', - 'box-sizing': 'border-box', - 'min-height': '5px', - 'max-height': '30px', - 'padding-top': '1px', - 'padding-bottom': '2px', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "background-color": "#aaa", + position: "relative", + width: "400px", + "box-sizing": "border-box", }, }, [ createElement( - 'div', + "div", { - 'data-expected-height': '15', - 'data-offset-y': '1', + "data-expected-client-height": "10", + "data-offset-y": "0", style: { - width: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - 'min-height': '0', - flex: '0 1 auto', + width: "100%", + "background-color": "blue", + "box-sizing": "border-box", + overflow: "scroll", }, }, [ - createElement('div', { + createElement("div", { + "data-expected-height": "10", style: { - width: '100%', - 'box-sizing': 'border-box', - height: '20px', + width: "100%", + "box-sizing": "border-box", + height: "10px", }, }), ] ), + ] + ); + BODY.appendChild(log); + BODY.appendChild(flexbox); + BODY.appendChild(flexbox_1); + BODY.appendChild(flexbox_2); + BODY.appendChild(flexbox_3); + BODY.appendChild(flexbox_4); + BODY.appendChild(flexbox_5); + BODY.appendChild(flexbox_6); + BODY.appendChild(flexbox_8); + + await snapshot(); + }); + + xit("size 002", async () => { + let flexbox_7; + flexbox_7 = createElement( + "div", + { + class: "flexbox column horizontal", + "data-expected-height": "33", + style: { + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "background-color": "#aaa", + position: "relative", + width: "400px", + "box-sizing": "border-box", + "min-height": "5px", + "max-height": "30px", + "padding-top": "1px", + "padding-bottom": "2px", + }, + }, + [ createElement( - 'div', + "div", { - 'data-expected-height': '15', - 'data-offset-y': '16', + "data-expected-height": "15", + "data-offset-y": "1", style: { - width: '100%', - 'background-color': 'green', - 'box-sizing': 'border-box', - 'min-height': '0', - flex: '0 1 auto', + width: "100%", + "background-color": "blue", + "box-sizing": "border-box", + "min-height": "0", + flex: "0 1 auto", }, }, [ - createElement('div', { + createElement("div", { style: { - width: '100%', - 'box-sizing': 'border-box', - height: '20px', + width: "100%", + "box-sizing": "border-box", + height: "20px", }, }), ] ), - ] - ); - flexbox_8 = createElement( - 'div', - { - class: 'flexbox column horizontal', - style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - 'background-color': '#aaa', - position: 'relative', - width: '400px', - 'box-sizing': 'border-box', - }, - }, - [ createElement( - 'div', + "div", { - 'data-expected-client-height': '10', - 'data-offset-y': '0', + "data-expected-height": "15", + "data-offset-y": "16", style: { - width: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - overflow: 'scroll', + width: "100%", + "background-color": "green", + "box-sizing": "border-box", + "min-height": "0", + flex: "0 1 auto", }, }, [ - createElement('div', { - 'data-expected-height': '10', + createElement("div", { style: { - width: '100%', - 'box-sizing': 'border-box', - height: '10px', + width: "100%", + "box-sizing": "border-box", + height: "20px", }, }), ] ), ] ); + BODY.appendChild(flexbox_7); + + await snapshot(); + }); + + xit("size 003", async () => { + let flexbox_9; flexbox_9 = createElement( - 'div', + "div", { - class: 'flexbox column vertical', + class: "flexbox column vertical", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - 'background-color': '#aaa', - position: 'relative', - 'writing-mode': 'vertical-rl', - height: '50px', - 'box-sizing': 'border-box', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "background-color": "#aaa", + position: "relative", + height: "50px", + "box-sizing": "border-box", }, }, [ - createElement('div', { - 'data-expected-width': '10', - 'data-offset-x': '20', + createElement("div", { + "data-expected-width": "10", + "data-offset-x": "20", style: { - height: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - flex: '1 0 10px', + height: "100%", + "background-color": "blue", + "box-sizing": "border-box", + flex: "1 0 10px", }, }), - createElement('div', { - 'data-expected-width': '10', - 'data-offset-x': '10', + createElement("div", { + "data-expected-width": "10", + "data-offset-x": "10", style: { - height: '100%', - 'background-color': 'green', - 'box-sizing': 'border-box', - width: '10px', + height: "100%", + "background-color": "green", + "box-sizing": "border-box", + width: "10px", }, }), createElement( - 'div', + "div", { - 'data-expected-width': '10', - 'data-offset-x': '0', + "data-expected-width": "10", + "data-offset-x": "0", style: { - height: '100%', - 'background-color': 'red', - 'box-sizing': 'border-box', + height: "100%", + "background-color": "red", + "box-sizing": "border-box", }, }, [ - createElement('div', { - 'data-expected-width': '10', - 'data-offset-x': '0', + createElement("div", { + "data-expected-width": "10", + "data-offset-x": "0", style: { - height: '100%', - 'box-sizing': 'border-box', - width: '10px', + height: "100%", + "box-sizing": "border-box", + width: "10px", }, }), ] ), ] ); + BODY.appendChild(flexbox_9); + + await snapshot(); + }); + + xit("size 004", async () => { + let flexbox_10; + let childDiv; + let childDiv_1; flexbox_10 = createElement( - 'div', + "div", { - class: 'flexbox column vertical', + class: "flexbox column vertical", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - 'background-color': '#aaa', - position: 'relative', - 'writing-mode': 'vertical-rl', - height: '50px', - 'box-sizing': 'border-box', - 'margin-left': '100px', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + "background-color": "#aaa", + position: "relative", + height: "50px", + "box-sizing": "border-box", + "margin-left": "100px", }, }, [ createElement( - 'div', + "div", { - 'data-expected-width': '50', - 'data-offset-x': '20', + "data-expected-width": "50", + "data-offset-x": "20", style: { - height: '100%', - 'background-color': 'blue', - 'box-sizing': 'border-box', - 'min-width': '0', - flex: '1', + height: "100%", + "background-color": "blue", + "box-sizing": "border-box", + "min-width": "0", + flex: "1", }, }, [ - (childDiv_1 = createElement('div', { - 'data-expected-width': '50', - 'data-offset-x': '20', - class: 'child-div', + (childDiv_1 = createElement("div", { + "data-expected-width": "50", + "data-offset-x": "20", + class: "child-div", style: { - height: '100%', - 'background-color': 'yellow', - 'box-sizing': 'border-box', - width: '50px', + height: "100%", + "background-color": "yellow", + "box-sizing": "border-box", + width: "50px", }, })), ] ), - createElement('div', { - 'data-expected-width': '0', - 'data-offset-x': '20', + createElement("div", { + "data-expected-width": "0", + "data-offset-x": "20", style: { - height: '100%', - 'background-color': 'green', - 'box-sizing': 'border-box', - flex: '1', + height: "100%", + "background-color": "green", + "box-sizing": "border-box", + flex: "1", }, }), - createElement('div', { - 'data-expected-width': '10', - 'data-offset-x': '10', + createElement("div", { + "data-expected-width": "10", + "data-offset-x": "10", style: { - height: '100%', - 'background-color': 'red', - 'box-sizing': 'border-box', - width: '10px', + height: "100%", + "background-color": "red", + "box-sizing": "border-box", + width: "10px", }, }), createElement( - 'div', + "div", { - 'data-expected-width': '10', - 'data-offset-x': '0', + "data-expected-width": "10", + "data-offset-x": "0", style: { - height: '100%', - 'background-color': 'orange', - 'box-sizing': 'border-box', - flex: '1 auto', + height: "100%", + "background-color": "orange", + "box-sizing": "border-box", + flex: "1 auto", }, }, [ - createElement('div', { + createElement("div", { style: { - height: '100%', - 'box-sizing': 'border-box', - width: '10px', + height: "100%", + "box-sizing": "border-box", + width: "10px", }, }), ] ), ] ); - BODY.appendChild(log); - BODY.appendChild(flexbox); - BODY.appendChild(flexbox_1); - BODY.appendChild(flexbox_2); - BODY.appendChild(flexbox_3); - BODY.appendChild(flexbox_4); - BODY.appendChild(flexbox_5); - BODY.appendChild(flexbox_6); - BODY.appendChild(flexbox_7); - BODY.appendChild(flexbox_8); - BODY.appendChild(flexbox_9); BODY.appendChild(flexbox_10); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/definite-main.ts b/integration_tests/specs/css/css-flexbox/definite-main.ts index be083249a5..3935091075 100644 --- a/integration_tests/specs/css/css-flexbox/definite-main.ts +++ b/integration_tests/specs/css/css-flexbox/definite-main.ts @@ -1,11 +1,8 @@ /*auto generated*/ describe('definite-main', () => { - it('size', async () => { - let log; + it("size 001", async () => { let p; - let p_1; - let p_2; - let p_3; + let flexbox; let rect; let rect_1; let rect_2; @@ -14,309 +11,344 @@ describe('definite-main', () => { let rect_5; let rect_6; let rect_7; - let flexOne; - let flexOne_1; - let flexbox; - let flexbox_1; - let flexbox_2; - let flexbox_3; - log = createElement('div', { - id: 'log', - style: { - 'box-sizing': 'border-box', - }, - }); p = createElement( - 'p', + "p", { style: { - 'box-sizing': 'border-box', + "box-sizing": "border-box", }, }, [createText(`Simple case of percentage resolution:`)] ); flexbox = createElement( - 'div', + "div", { - class: 'flexbox', + class: "flexbox", style: { - display: 'flex', - border: '3px solid black', - 'box-sizing': 'border-box', - width: '300px', + display: "flex", + border: "3px solid black", + "box-sizing": "border-box", + width: "300px", }, }, [ (flexOne = createElement( - 'div', + "div", { - class: 'flex-one', - 'data-expected-width': '250', + class: "flex-one", + "data-expected-width": "250", style: { - '-webkit-flex': '1', - flex: '1', - 'box-sizing': 'border-box', + "-webkit-flex": "1", + flex: "1", + "box-sizing": "border-box", }, }, [ createElement( - 'div', + "div", { - 'data-expected-width': '125', + "data-expected-width": "125", style: { - overflow: 'hidden', - 'box-sizing': 'border-box', - width: '50%', + overflow: "hidden", + "box-sizing": "border-box", + width: "50%", }, }, [ - (rect = createElement('div', { - class: 'rect', + (rect = createElement("div", { + class: "rect", style: { - width: '50px', - height: '50px', - 'background-color': 'green', - 'box-sizing': 'border-box', + width: "50px", + height: "50px", + "background-color": "green", + "box-sizing": "border-box", }, })), ] ), ] )), - (rect_1 = createElement('div', { - class: 'rect flex-none', + (rect_1 = createElement("div", { + class: "rect flex-none", style: { - '-webkit-flex': 'none', - flex: 'none', - width: '50px', - height: '50px', - 'background-color': 'green', - 'box-sizing': 'border-box', + "-webkit-flex": "none", + flex: "none", + width: "50px", + height: "50px", + "background-color": "green", + "box-sizing": "border-box", }, })), ] ); + + BODY.appendChild(p); + BODY.appendChild(flexbox); + + await snapshot(); + }); + + // @TODO: The width of parent with no width style is no determined by its child but its grand child? + xit("size 002", async () => { + let p_1; + let flexbox_1; + let rect; + let rect_1; + let rect_2; + let rect_3; + let rect_4; + let rect_5; + let rect_6; + let rect_7; p_1 = createElement( - 'p', + "p", { style: { - 'box-sizing': 'border-box', + "box-sizing": "border-box", }, }, [ createText(`auto flex-basis. However, as this is a width, we follow regular width -rules and resolve the percentage:`), + rules and resolve the percentage:`), ] ); flexbox_1 = createElement( - 'div', + "div", { - class: 'flexbox', + class: "flexbox", style: { - display: 'flex', - border: '3px solid black', - 'box-sizing': 'border-box', - width: '300px', + display: "flex", + border: "3px solid black", + "box-sizing": "border-box", + width: "300px", }, }, [ createElement( - 'div', + "div", { - 'data-expected-width': '50', + "data-expected-width": "50", style: { - 'box-sizing': 'border-box', + "box-sizing": "border-box", }, }, [ createElement( - 'div', + "div", { - 'data-expected-width': '25', + "data-expected-width": "25", style: { - overflow: 'hidden', - 'box-sizing': 'border-box', - width: '50%', + overflow: "hidden", + "box-sizing": "border-box", + width: "50%", }, }, [ - (rect_2 = createElement('div', { - class: 'rect', + (rect_2 = createElement("div", { + class: "rect", style: { - width: '50px', - height: '50px', - 'background-color': 'green', - 'box-sizing': 'border-box', + width: "50px", + height: "50px", + "background-color": "green", + "box-sizing": "border-box", }, })), ] ), ] ), - (rect_3 = createElement('div', { - class: 'rect flex-none', + (rect_3 = createElement("div", { + class: "rect flex-none", style: { - '-webkit-flex': 'none', - flex: 'none', - width: '50px', - height: '50px', - 'background-color': 'green', - 'box-sizing': 'border-box', + "-webkit-flex": "none", + flex: "none", + width: "50px", + height: "50px", + "background-color": "green", + "box-sizing": "border-box", }, })), ] ); + BODY.appendChild(p_1); + BODY.appendChild(flexbox_1); + + await snapshot(); + }); + + it("size 003", async () => { + let p_2; + let flexbox_2; + let rect; + let rect_1; + let rect_2; + let rect_3; + let rect_4; + let rect_5; + let rect_6; + let rect_7; p_2 = createElement( - 'p', + "p", { style: { - 'box-sizing': 'border-box', + "box-sizing": "border-box", }, }, [createText(`Simple case of percentage resolution, columns:`)] ); flexbox_2 = createElement( - 'div', + "div", { - class: 'flexbox column', + class: "flexbox column", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - border: '3px solid black', - 'box-sizing': 'border-box', - height: '300px', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + border: "3px solid black", + "box-sizing": "border-box", + height: "300px", }, }, [ (flexOne_1 = createElement( - 'div', + "div", { - class: 'flex-one', - 'data-expected-height': '250', + class: "flex-one", + "data-expected-height": "250", style: { - '-webkit-flex': '1', - flex: '1', - 'box-sizing': 'border-box', + "-webkit-flex": "1", + flex: "1", + "box-sizing": "border-box", }, }, [ createElement( - 'div', + "div", { - 'data-expected-height': '125', + "data-expected-height": "125", style: { - overflow: 'hidden', - 'box-sizing': 'border-box', - height: '50%', + overflow: "hidden", + "box-sizing": "border-box", + height: "50%", }, }, [ - (rect_4 = createElement('div', { - class: 'rect', + (rect_4 = createElement("div", { + class: "rect", style: { - width: '50px', - height: '50px', - 'background-color': 'green', - 'box-sizing': 'border-box', + width: "50px", + height: "50px", + "background-color": "green", + "box-sizing": "border-box", }, })), ] ), ] )), - (rect_5 = createElement('div', { - class: 'rect flex-none', + (rect_5 = createElement("div", { + class: "rect flex-none", style: { - '-webkit-flex': 'none', - flex: 'none', - width: '50px', - height: '50px', - 'background-color': 'green', - 'box-sizing': 'border-box', + "-webkit-flex": "none", + flex: "none", + width: "50px", + height: "50px", + "background-color": "green", + "box-sizing": "border-box", }, })), ] ); + BODY.appendChild(p_2); + BODY.appendChild(flexbox_2); + + await snapshot(); + }); + + // @TODO: The height of parent with no height style is no determined by its child but its grand child? + xit("size 004", async () => { + let p_3; + let flexbox_3; + let rect; + let rect_1; + let rect_2; + let rect_3; + let rect_4; + let rect_5; + let rect_6; + let rect_7; p_3 = createElement( - 'p', + "p", { style: { - 'box-sizing': 'border-box', + "box-sizing": "border-box", }, }, [createText(`auto flex-basis. This is still definite.`)] ); flexbox_3 = createElement( - 'div', + "div", { - class: 'flexbox column', + class: "flexbox column", style: { - display: 'flex', - '-webkit-flex-direction': 'column', - 'flex-direction': 'column', - border: '3px solid black', - 'box-sizing': 'border-box', - height: '300px', + display: "flex", + "-webkit-flex-direction": "column", + "flex-direction": "column", + border: "3px solid black", + "box-sizing": "border-box", + height: "300px", }, }, [ createElement( - 'div', + "div", { - 'data-expected-height': '50', + "data-expected-height": "50", style: { - 'box-sizing': 'border-box', + "box-sizing": "border-box", }, }, [ createElement( - 'div', + "div", { - 'data-expected-height': '25', + "data-expected-height": "25", style: { - overflow: 'hidden', - 'box-sizing': 'border-box', - height: '50%', + overflow: "hidden", + "box-sizing": "border-box", + height: "50%", }, }, [ - (rect_6 = createElement('div', { - class: 'rect', + (rect_6 = createElement("div", { + class: "rect", style: { - width: '50px', - height: '50px', - 'background-color': 'green', - 'box-sizing': 'border-box', + width: "50px", + height: "50px", + "background-color": "green", + "box-sizing": "border-box", }, })), ] ), ] ), - (rect_7 = createElement('div', { - class: 'rect flex-none', + (rect_7 = createElement("div", { + class: "rect flex-none", style: { - '-webkit-flex': 'none', - flex: 'none', - width: '50px', - height: '50px', - 'background-color': 'green', - 'box-sizing': 'border-box', + "-webkit-flex": "none", + flex: "none", + width: "50px", + height: "50px", + "background-color": "green", + "box-sizing": "border-box", }, })), ] ); - BODY.appendChild(log); - BODY.appendChild(p); - BODY.appendChild(flexbox); - BODY.appendChild(p_1); - BODY.appendChild(flexbox_1); - BODY.appendChild(p_2); - BODY.appendChild(flexbox_2); BODY.appendChild(p_3); BODY.appendChild(flexbox_3); - - await matchViewportSnapshot(); + + await snapshot(); }); }); From 9fbb534ff9f291fa070689d83567533379e6b6ef Mon Sep 17 00:00:00 2001 From: "zhanwen.zw" <zhanwen.zw@alibaba-inc.com> Date: Fri, 13 May 2022 11:08:07 +0800 Subject: [PATCH 150/498] test: add comment --- integration_tests/specs/css/css-flexbox/columns-auto.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integration_tests/specs/css/css-flexbox/columns-auto.ts b/integration_tests/specs/css/css-flexbox/columns-auto.ts index 2243a97740..edc0dd7128 100644 --- a/integration_tests/specs/css/css-flexbox/columns-auto.ts +++ b/integration_tests/specs/css/css-flexbox/columns-auto.ts @@ -534,6 +534,7 @@ describe('columns-auto', () => { await snapshot(); }); + // @TODO: max-width/max-height is not considered when calculating remaining space for flex-shrink. xit("size 002", async () => { let flexbox_7; flexbox_7 = createElement( @@ -609,6 +610,7 @@ describe('columns-auto', () => { await snapshot(); }); + // @TODO: Percentage size of child should not be considered in auto min content width/height. xit("size 003", async () => { let flexbox_9; flexbox_9 = createElement( @@ -676,6 +678,7 @@ describe('columns-auto', () => { await snapshot(); }); + // @TODO: Percentage size of child should not be considered in auto min content width/height. xit("size 004", async () => { let flexbox_10; let childDiv; From c57015e712c747d38eefef242cb0e3179b18c337 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sun, 7 Aug 2022 13:49:49 +0800 Subject: [PATCH 151/498] fix: fix remaining test specs. --- .../css-flexbox/baseline-for.ts.cedd9b411.png | Bin 0 -> 6036 bytes .../css-flexbox/columns-auto.ts.e2e5da4f1.png | Bin 0 -> 2434 bytes .../definite-main.ts.03f982261.png | Bin 0 -> 8310 bytes .../definite-main.ts.d060b4cf1.png | Bin 0 -> 7180 bytes .../flex-algorithm.ts.60c739121.png | Bin 0 -> 2517 bytes .../flex-algorithm.ts.98dd9ad91.png | Bin 0 -> 2461 bytes .../flex-algorithm.ts.a6872d481.png | Bin 0 -> 2497 bytes .../css-flexbox/flex-align.ts.598891fc1.png | Bin 0 -> 2427 bytes .../css-flexbox/flex-align.ts.8e5d62301.png | Bin 0 -> 2457 bytes .../css-flexbox/flex-align.ts.91857f5c1.png | Bin 0 -> 2388 bytes .../css-flexbox/flex-align.ts.b726f1f11.png | Bin 0 -> 2476 bytes .../css-flexbox/flex-flow.ts.22c0d7a31.png | Bin 0 -> 2561 bytes .../css-flexbox/flex-item.ts.860927ff1.png | Bin 0 -> 2368 bytes .../flex-justify-content.ts.bd6bf64c1.png | Bin 0 -> 2596 bytes .../css-flexbox/flex-no-flex.ts.84754dfa1.png | Bin 0 -> 2392 bytes .../flexbox-baseline.ts.ad53e4951.png | Bin 0 -> 57802 bytes .../flexbox-baseline.ts.bd340f511.png | Bin 0 -> 39746 bytes .../flexbox-width.ts.c30ac9e41.png | Bin 0 -> 2410 bytes .../multiline-column.ts.3ec3e2a31.png | Bin 0 -> 10607 bytes .../multiline-reverse-wrap.ts.7fd99dfa1.png | Bin 0 -> 11258 bytes .../nested-stretch.ts.ab2b666c1.png | Bin 0 -> 2749 bytes .../overflow-auto.ts.24e33ffc1.png | Bin 0 -> 2466 bytes .../overflow-auto.ts.ae8584f01.png | Bin 0 -> 2386 bytes .../overflow-keep-scrollpos.ts.280d1c921.png | Bin 0 -> 2871 bytes .../percentage-heights.ts.87871c4b1.png | Bin 0 -> 2598 bytes .../resize-min-content.ts.827375281.png | Bin 0 -> 2364 bytes .../css-flexbox/style-change.ts.8298040c1.png | Bin 0 -> 12593 bytes .../css-flexbox/style-change.ts.8298040c2.png | Bin 0 -> 12592 bytes .../specs/css/css-flexbox/flex-algorithm.ts | 6 +- .../specs/css/css-flexbox/flex-align.ts | 8 +- .../specs/css/css-flexbox/flex-flow.ts | 2 +- .../specs/css/css-flexbox/flex-item.ts | 2 +- .../css/css-flexbox/flex-justify-content.ts | 2 +- .../specs/css/css-flexbox/flex-no-flex.ts | 2 +- .../specs/css/css-flexbox/flexbox-baseline.ts | 279 +----------------- .../specs/css/css-flexbox/flexbox-width.ts | 2 +- .../specs/css/css-flexbox/multiline-column.ts | 2 +- .../css/css-flexbox/multiline-reverse-wrap.ts | 4 +- .../specs/css/css-flexbox/nested-stretch.ts | 2 +- .../specs/css/css-flexbox/overflow-auto.ts | 4 +- .../css-flexbox/overflow-keep-scrollpos.ts | 2 +- .../css/css-flexbox/percentage-heights.ts | 2 +- .../css/css-flexbox/resize-min-content.ts | 4 +- .../specs/css/css-flexbox/style-change.ts | 12 +- 44 files changed, 31 insertions(+), 304 deletions(-) create mode 100644 integration_tests/snapshots/css/css-flexbox/baseline-for.ts.cedd9b411.png create mode 100644 integration_tests/snapshots/css/css-flexbox/columns-auto.ts.e2e5da4f1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/definite-main.ts.03f982261.png create mode 100644 integration_tests/snapshots/css/css-flexbox/definite-main.ts.d060b4cf1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-algorithm.ts.60c739121.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-algorithm.ts.98dd9ad91.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-algorithm.ts.a6872d481.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-align.ts.598891fc1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-align.ts.8e5d62301.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-align.ts.91857f5c1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-align.ts.b726f1f11.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-flow.ts.22c0d7a31.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-item.ts.860927ff1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-justify-content.ts.bd6bf64c1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flex-no-flex.ts.84754dfa1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flexbox-baseline.ts.ad53e4951.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flexbox-baseline.ts.bd340f511.png create mode 100644 integration_tests/snapshots/css/css-flexbox/flexbox-width.ts.c30ac9e41.png create mode 100644 integration_tests/snapshots/css/css-flexbox/multiline-column.ts.3ec3e2a31.png create mode 100644 integration_tests/snapshots/css/css-flexbox/multiline-reverse-wrap.ts.7fd99dfa1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/nested-stretch.ts.ab2b666c1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/overflow-auto.ts.24e33ffc1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/overflow-auto.ts.ae8584f01.png create mode 100644 integration_tests/snapshots/css/css-flexbox/overflow-keep-scrollpos.ts.280d1c921.png create mode 100644 integration_tests/snapshots/css/css-flexbox/percentage-heights.ts.87871c4b1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/resize-min-content.ts.827375281.png create mode 100644 integration_tests/snapshots/css/css-flexbox/style-change.ts.8298040c1.png create mode 100644 integration_tests/snapshots/css/css-flexbox/style-change.ts.8298040c2.png diff --git a/integration_tests/snapshots/css/css-flexbox/baseline-for.ts.cedd9b411.png b/integration_tests/snapshots/css/css-flexbox/baseline-for.ts.cedd9b411.png new file mode 100644 index 0000000000000000000000000000000000000000..3ca5dd3e0d4449d8425bf5a41b52078a086a30ab GIT binary patch literal 6036 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/columns-auto.ts.e2e5da4f1.png b/integration_tests/snapshots/css/css-flexbox/columns-auto.ts.e2e5da4f1.png new file mode 100644 index 0000000000000000000000000000000000000000..b3ddaf0df000960d62b6d7e7e8e828f91f46b92f GIT binary patch literal 2434 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_n+YPZ!6KiaBrZZk%{TL4@_-`KgO;YKZomdFVAvJuh)rFu~WBJ;Jz} zpTV+zIX}Yz>qfQ?p^OF%Y}5+V?>js>1X3KCT{uk~ra0iB*4pL?F^NQ8U<hR56;Q=R z{ZnD-*tCFg5sQ?97cS}#(6&?pw#B{^WHKRS+4b+g=h!hb=<DF{8PvNgOgI1aBEo|^ zkNY|`C~)I(InY<Hs_f3cG-mj4s0nw7Vhv_Y!KmuA`F1P}`w2K7QzJ%9u6X_1X8C!B z1Kb20pLO<4?Jog_KZIiF+MUO0iVhQ4@kBH*5tQ1_fBr?1p(2q$T;fYrP`S0XoBidP z8sf$9BtRmJ0V=sN>n1P_a!|*IPyL-5Gzf(0srfAofrQ*`&&jxmP&C&IF}Yw(@lOxd z5p>0?qJDt|Cn-;p7)gtW)HkXGd%-#yE~DXszh)XuA#_L~E9%XJ^uvN%@B0GVQ4F50 KelF{r5}E+DtJ$0Y literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/definite-main.ts.03f982261.png b/integration_tests/snapshots/css/css-flexbox/definite-main.ts.03f982261.png new file mode 100644 index 0000000000000000000000000000000000000000..32ab0ed10cc92ed3308cec74726e2f5416050b3d GIT binary patch literal 8310 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/definite-main.ts.d060b4cf1.png b/integration_tests/snapshots/css/css-flexbox/definite-main.ts.d060b4cf1.png new file mode 100644 index 0000000000000000000000000000000000000000..f467cd9e8ff15d77b8ab43e59320e4aef39bb34e GIT binary patch literal 7180 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-algorithm.ts.60c739121.png b/integration_tests/snapshots/css/css-flexbox/flex-algorithm.ts.60c739121.png new file mode 100644 index 0000000000000000000000000000000000000000..96d5b6052ead2289de6ba14bf4366775a8525fca GIT binary patch literal 2517 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_sW0PZ!6KiaBrZ9?X??6mUH#*}T%?eCZsnv^foYO$wXlPYV_NbNHR} z{j~L^=a}ru-@atsv(f%CGsBNL4$Ll`CJs{ul)4i4dd^EhhWH|&yY!znv2_S#G-x!E zPgVKwGsHbOd+*b3ZgI-}X3km3m;c}+%kx=rAENXqa~a5E`9e(lk9|{9WMF0D6;Qpv z5J)a{Pi4){Z(o=c4zW^Z4$%Is*R9g4SW+sLCpl8Zhli{`Y<sl+300!<hvB`L=G)fS zzcgJha-=x5-hJ-t^x)sQnQ<S2DEH}#zh(V3S7WQI7zG|u?pF#^JyiRx?77CaOb@)* z+3tO?G=_rY1x=RsR9HHw5bkfcP6KA7rr9-N*XPB3P(FA6C2LJPx0Hg{0>(uw<WsK< zZ5wv)Iv!nDzR`Y}_y<`k#OdB^R+p6*ypq|^UQC7HBR6x8>La^c9MVD8zxCV~wiCDh zfBV96ZwBT0gTm|u)t&`xHhke&@u}S4B;|DtB~{+wjM}gAu1(ri`5mVsflV_8Pgg&e IbxsLQ0Jrd-!vFvP literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-algorithm.ts.98dd9ad91.png b/integration_tests/snapshots/css/css-flexbox/flex-algorithm.ts.98dd9ad91.png new file mode 100644 index 0000000000000000000000000000000000000000..53e12e4fb2d9871139713aeb65fbcd0f029c8f75 GIT binary patch literal 2461 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_n-dPZ!6KiaBrZ8v3yq3NScI-`#(6{_GZp#umSVIkWGsx-m&T%h#;9 zXDgfR@jJGRAC6xuyuW+;ep%**`9c{D8jWln<Wjrd$~{QD^NHE-7#pvE>IH^CCh{rM z;}O~41!^8_Q~Gd@a?_rwDIVB-x3c;Q<wjBHFR1p?+qMkzWs1MEIJEFeDR?blT*N{? zmDRWL+B-#tAEzky9mrkkrDyI6#~i9E4f=8>hq71%`8H4JkolcI$?DQ34pSVMT{tPA zX19cw0Nut=zQK_)gMjYX=~>}=yy}qrJGn#oHz^B$Q1m^Mc+JWF%IVH2>#e14<}t_} zr#vo9ukX6y>(o$xh;pN-lW0dxp|n&R4Z+b6B)?7^O+EBWJzv?v7byIcJ8uhYfH5$5 My85}Sb4q9e0Ngh<<^TWy literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-algorithm.ts.a6872d481.png b/integration_tests/snapshots/css/css-flexbox/flex-algorithm.ts.a6872d481.png new file mode 100644 index 0000000000000000000000000000000000000000..eead468e352ae3ca7737408600e738b1b88a1496 GIT binary patch literal 2497 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_sV-PZ!6KiaBrZZY&fvlyD2w-F$V2{5HAki{G}Fxi~fSG9Nqk;@oG; zjZONyKBrq9i^yikpY{2k?Zf#y>SA`Qf0+H#<o~O?+x_n`9<b&#ahT%3>_RRzxBt^~ zsRN4(9mzKWYW3XR><shs?atm6u3#6Z%u&R7=8EC5sNL=kSLb|x$61r8m(ifn$kri5 zKDFZV{A>na%FUtB8N_Q}k=(PjtiNH+^W^VL`wS=#^D8^68P@+`=Kpb&l~+LZ0z)7Z z`BZ7o#%u2cZ4~OY54G=Hz_^G-O2LZ)>YU~KyV4a7*PpKT{-HqypBf(fR4L;yftxZ{ zQRpdZYkw7!{hfv3-lHapvH|hwZ{(@8D(8Q&^zn_o3@ZvKFED>jHb`50m%D+B^7Kqe z4M1FBJ2Y*$QhERTJ6?u82PrSmC@c?$t@hG)atFMxf35w_@}kPFO#jFF7|JRRP(U4@ kQ~Xwr@xXj47-i46w?$j)(z{1hz-AbOr>mdKI;Vst03x-k!~g&Q literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-align.ts.598891fc1.png b/integration_tests/snapshots/css/css-flexbox/flex-align.ts.598891fc1.png new file mode 100644 index 0000000000000000000000000000000000000000..28112665a410f7aede0f269d02449b3a0270b0f3 GIT binary patch literal 2427 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_n-5PZ!6KiaBqu9n3v!AmHk#6=15R@pXsXF8S?;MHJ*&KQPYJO!(-} zU){6u8iT^~^zRG|`-C$ZG#c4Dga(wdI+nfWt}?@p&yLJ4oF)!a90r<_&F=a7<ly%? z{_~iBY_GY%5Xi(UpgQ2x^XhYVr5S!4Vjm2S0ftEFJ2{3AKmXcPy`Ob`@%OTigC+Ln z?q+9*`FzV(@#LThDPWF!C&+N;=K{t>EK&+y15WjQl*<0j!SHVIr0qS=-tjWjJZv85 zvS?sS!cp5vsEVwvMGdY*JSb|3QAgmdt?R$*NEeFy;S>Zmpcp(|{an^LB{Ts5Copkf literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-align.ts.8e5d62301.png b/integration_tests/snapshots/css/css-flexbox/flex-align.ts.8e5d62301.png new file mode 100644 index 0000000000000000000000000000000000000000..ffcd8ea5e0d122a867744e071d1959263a9edb18 GIT binary patch literal 2457 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_n-NPZ!6KiaBrZ9?X?76mWICbR%QV`Dex-dzzN4XA4tz?yj4(TIq+l z-Iw0#wZ&rg*S9Vg|KRp~R@?#Wzq8~R4m@X(Qt(>9xQJ@hD??j`ALa7jZEYLPiyWC< zI87X;I8c$g68Cz}OTj-j-~SrgKJ>PkfBq%spW`tX7y_Ai1ym`Z`b*!w<o)yXU#-7C zgAx_Y`@N=gnY+Wak2W(NH?egHWi)6sl26^Ti~YT9o$X$RP3KJaKFHod%?P7v?2~2K zx2Wen{R|JLa#J}elI0kxWrV?Gd)E5wxi1A7_Ru0jQZ;uEUBljPzBW^i@qo5CmCHL& zh}d4Uye!S|qh&OMP_LdEOxbhPE!1s;kQSc*7&iV=%>I6$D;U_kV(@hJb6Mw<&;$TN CKalJI literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-align.ts.91857f5c1.png b/integration_tests/snapshots/css/css-flexbox/flex-align.ts.91857f5c1.png new file mode 100644 index 0000000000000000000000000000000000000000..8453dae2d459f88552ddd6796cd2b406b185b0ab GIT binary patch literal 2388 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_q9Qo-U3d6?5KR+sMmg$iU(#9jN>@;C#LPmSnDmh8KH1j74L%?6N!- z#r#0t=J`9;>AT*_F&rpo;uTQ6zz{g-sqc?1%JUh1oP4~s`ruZZj0TNHwhp0DYBUH& zQvuB~!it^OqISD8d^p)mGb={3@n}glT8fXB;*^%+Rm@xU_?EX8I`RWsMhu>=elF{r G5}E)*m)=$Y literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-align.ts.b726f1f11.png b/integration_tests/snapshots/css/css-flexbox/flex-align.ts.b726f1f11.png new file mode 100644 index 0000000000000000000000000000000000000000..a7af18cdc9496ad1dbd6ee9879112d27474d2e2a GIT binary patch literal 2476 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_sVBPZ!6KiaBqu9n8ENAmSQmyIAY4&%W-DDte!mGSqM240?AgGJDNI zM$g&1`;y-?SG?P}O#Fj<^-MX21I0|d0;(4n0tY?y{9bm|VbyE5E(<g0J#Av^5XxxK zXdD8R)pGWN&BtfO9q9eG{w1sU$C-9R%JtW8U6yXxzjYaVfx5Vf!xRT*7tT>?ga^SD zOWOrm?@etV3cb#)V%&b~GPB+%<GSsqcP?OD#3H5OH590GFBwC#-@fF$Ua{sS>z$LY z|6Yt43Z>wnFQ$is1GD$b|Bm+GcW)2N18#Zw{ma$=1nUiv+6GuFuGo6N-`19CQ`}#< tGlh;rr@UUf_xn6K#sletC8$@}D_xOqoLW8a0<gu!;OXk;vd$@?2>{|5mC67B literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-flow.ts.22c0d7a31.png b/integration_tests/snapshots/css/css-flexbox/flex-flow.ts.22c0d7a31.png new file mode 100644 index 0000000000000000000000000000000000000000..eaf5dc90121f0ccc03d0d205d3a79a18aac1d1c8 GIT binary patch literal 2561 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_sX6o-U3d6?5L+-B@_rLBJvK(BVllx-GkvBBvB-EUU6w&ZTnxgq3vs zp=#DQIShYpzL~bs?ik~P_zJ7sy$82{%YMzuP%+<u*@e@@VG5blmBMcae*dzmeiE|% zcIl@*zpqqojy*qpZTOGHzrJrW-@Jc%>P)M524&xxR&h%ycr9RD#4<`zI|#BQUnl46 zJ@S9c9RJ|wy<baT+5GTa_CJ63s;R&*_ogh&vm}%A*VlYh)xVeUM0nn_eV#ka^)ebX z8reF8$fs63zQ(;F|NGa<*P<1VtM=vWWy;?{nKK7l%Ia&Metm5@!vk4r1lF5IU{HPY z*!KV&R3E0tTwn-f;uTP(fVzBNrRDsGG^Ls|tdto9bjQ}7uZGL`Z04u+DX01TjpA!h zooU@a?dJW{_oxu5eVf_u<$b@Y{IluxPov65cg)rIMzYHnJ5p3=kz3ed>eEfG?O8kh zfpjIXL}h%_MtK<r%x@Kw1r$zD9xaq44+>M)sCG*8=V%BHvk?3$-Tm3Y`Rmd1Yk|!; N22WQ%mvv4FO#uEsrd9v| literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-item.ts.860927ff1.png b/integration_tests/snapshots/css/css-flexbox/flex-item.ts.860927ff1.png new file mode 100644 index 0000000000000000000000000000000000000000..500c0d3eea423ab146d9c81d82fe9425791d7d28 GIT binary patch literal 2368 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_q8-o-U3d6?5L+-N<>sfQP|RHRb=*xvW7_e<ti^Y+2<#ciqgbX^Zz> zX88BoZZ8AFeHJMNuLX>Y$fe}$Sr})<Twn-f;uTOGrAC8bG!=|ygwe8Kv^X5C5k_l6 d8rO#U>Y`kK-bH?R0Bi*@c)I$ztaD0e0ss|W?6m*@ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-justify-content.ts.bd6bf64c1.png b/integration_tests/snapshots/css/css-flexbox/flex-justify-content.ts.bd6bf64c1.png new file mode 100644 index 0000000000000000000000000000000000000000..981ad3d26ff7d29df6e5fbb68328ec660daa408a GIT binary patch literal 2596 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_sV^o-U3d6?5L+J(zpjLBuuCD8Tfc`<nKzJ--UAC+iBcG>SDnkFrZY zdsH|f{?FYvx6Pir9pAY=?!Zx-aQz3b43E9yjDL0g6(hr+*$&JuoF)!a$fT~=9s8iB z_(50Zc<t8~{ijWA9YPrm8ja*ryVlAbsQ#6ifBfm(->+EL7p{~0Aocm={fCsBd~18| zxvR<wuVe3hdK_KkNRhWe!Bsg~K;aZCuYl?WhCn9rsnTfM2K(NR5w?xyB{g!Z)e8*m zjs@S}xqxvIi<E*F1=P3P*~MQuPFQ`v%KhVD3}uc4`SG4gkL2TxRZIfZh>PPA`5)CD z7TyW>e{kq_m+tZBK!e2}C)<3T%T1ZDfr0jFTh)Qp70mqq9@lg)cfWsp@?I*$_S@TL zwqa}nPbg1^yMU3s_u*}ezTdAnV}9JOz4x(l&AbCge@OV(KBwGtSSr5*%;)UIl&A8y z$vpW&hh|%YUEA}q;+3^H`K1N4l)17m_wwu(21h;0iV9#!v*PwE#y?L%eodOUTD;(^ zL^5?sA!sO`mY%3Xg&3!>NQ7$Nb#`YJ)0Cg=iL#W(0J+8es6Mi5%F&=34LTep=au|p XPJZS-vihySmL7wrtDnm{r-UW|PbcuC literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flex-no-flex.ts.84754dfa1.png b/integration_tests/snapshots/css/css-flexbox/flex-no-flex.ts.84754dfa1.png new file mode 100644 index 0000000000000000000000000000000000000000..00675166af39d9dfcde6abd9964d51a14941ada2 GIT binary patch literal 2392 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_n+hPZ!6KiaBqu9Ta3T<Y94~t(31<EXeT3R_3nY8~5N_*=N^S@A&qT z_0EMo?+WGqEIa;^k)cNY0z)7ZuYl?(H5vp%CKYVGKFiXUX;lon`p*T7i&&%-yoLfb z@Aa+a<_<UJ$Q_XX?8xlGY2q-&VU!x-LGX6w%*)CQKb|zRbqHlNXf%#eqd_p53dqX{ aRm_(Ag-dcDxyl1uM+}~>elF{r5}E*Ws&6*{ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flexbox-baseline.ts.ad53e4951.png b/integration_tests/snapshots/css/css-flexbox/flexbox-baseline.ts.ad53e4951.png new file mode 100644 index 0000000000000000000000000000000000000000..5c641e0b5e4a7c82e159c759a2aca90ccfa6dbb6 GIT binary patch literal 57802 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ÐC*@ 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 I8}5z#E2G>{-v9sr literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flexbox-baseline.ts.bd340f511.png b/integration_tests/snapshots/css/css-flexbox/flexbox-baseline.ts.bd340f511.png new file mode 100644 index 0000000000000000000000000000000000000000..f7be12d1da5d0ce417c072798bb7372bc4f3e38b GIT binary patch literal 39746 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 G-~R$8cIbcr literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/flexbox-width.ts.c30ac9e41.png b/integration_tests/snapshots/css/css-flexbox/flexbox-width.ts.c30ac9e41.png new file mode 100644 index 0000000000000000000000000000000000000000..f45f2708905dfa215363df1d87f57b2d32cc572c GIT binary patch literal 2410 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_n+MPZ!6KiaBp@IdUCV5OHwS-C15-ygUDL>?NC1XN(yCu5Rxrev{lQ zz4rOq+srAp@8lR7l9_k~R4*_D0;#RCwK2a<XP@o9#`?p(y-_Z?XQN+Ttl{&WuNnVX zhRcA}K$Q*zb^E$~@xKBdhI_JXRqyu8F*lU2vtU@nBBkK9fDuGxt=oF-t~0}jlbmoN zsLUt@3!~AH7!8Th+<_=FM)SjHei+RU!#O{^Vm-P-uu1OJk`Q2fiNVv=&t;ucLK6W0 CkawH_ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/multiline-column.ts.3ec3e2a31.png b/integration_tests/snapshots/css/css-flexbox/multiline-column.ts.3ec3e2a31.png new file mode 100644 index 0000000000000000000000000000000000000000..21d3ce95bcc19f866c42164d69e74e8698fa00fe GIT binary patch literal 10607 zcmeHtXH=8<x^L_iacn43R6raNP(cweprT?dAPO={iHL}ZfV2R?5FN$_BaA^nN<aZc zdNn`-$sizNkQRDKP<jZ2mJlT&IZx)SyVtqv&R%!c*?Ycizi8sizdY?%UY?w{Fqd1Y zxDtUt$e~Uhzkon2xQRe4ytjNQd{dy<mkxg|@V#Ju43XQQ#DNcseUG86m&2dC%dbC1 zAhsb;$B$YECXTc4e%1n_bjo(m%RY-EzdgCu>0y4Zr#duv<Ya10m{#Y)Ece?Nv6Hs) z+RHa-KXpHvX)|!@m=(YCp6L;*_q%r<vwo`--O`p*n2lWds`BT}N0wLL`;J<!{#&C^ zo+^w--5{Vg*W2Mt8ony|3^&xf@+xNP#h>o3OZ2rsAbc%2oCOHP4z(u4)eZNTB2t(0 zR|L*{HV{Ze6GHx8Y&E+gSnx?J?=6)|O@Fk`RzpMMO3{sT>0D)F%vo)1ZAs%Zk~!0# zy7F4ZJbFnCFPWu$=KY+2>tJSP*1Az5Kuc}r=LE*j3UD5BTlODUJNfJRqeqWg$`76q zqNM^fne!%rFQiEwGBPrd$i&h(eK$gkE;^S?Ccot#8g9))n?BvV?^s^NY<$`*Q?11a zy`-Q=lDG1Vu#p!r_?+oUIj3eG@#|b(TH3%b%Q~8xUbePcYde;gRtbh0lcn#-MR*7C zfTn4)Zpc)4p4i{dJk8l)`}U(9XnX}#STS(|iQdCS5IKKbF(U&-c%G1uHJprhOgQM# zl98UY8#R}XpG#j)B@7MS@L-b89XN16IoI~`<qY^hoD^)QntUHv4?)2V=A{~^C{xbF z>WuTt3$hpa4LqN?J8XRV+&P<Dw=$QmQQCz%jY3Vu5;5IwyS20!PYu{_)6)YW$( zkq(xJr;c|n@#xGydhyNKsSd-5@|j}i+BG>2luNOW*bH?}<uX-kES5Y}AII?ACOiEm z9eY^O(z2sQ0i8WB5}qNjgNBtIaDukeXV3EU@DlNNiEhj($+ue5-LL^YMw<~p6FI<s zBYokzm#nOwv2j8N2cJ5P)2B|Izi@$YqaiUP)jaYc5ign@mk4AJ63*x}kG5qQ_q=d# z&$fc?Z!{)pqcXFy7)W7HDnwdJ_VMv)?d;qv*dHGs&!4Q4=Z<FOT{?E`=XLAX`!^zm z-_p^TE|+)%FUQWH_FRW7KHtxym&!sPyivdT^NW3E@<{$~1Oh=!f9!(Bnn=aIuhY}H zq4Fk^UQGWE2L)0C(~CClMX#<t&hnvaXXfTc1lfJ?QBGRll^($Ij|~y`tD2YNbSwB5 zs=3^eP65fwygOIVy?u{^f1Ayf;+xMJj3*A`vx{6C*97Oxbrv{dhvHoQrfL*KOu@sg zo=rR!#kibG5=KEP;7-0)mXU)Y>NS}9aTdjv!{J=Xzk2_}uUk^#0I%P?8NI(ur6~y+ z>^Cpy5S6&}lzL}oXY+OG%sjZN{iE6z_&<H$p|g03x`swZX=!Oy!I=voAygg8*nVlC zTX1=>Xz*Q!<3%V4ow9*-sDfU6e{<Mddo4plLkoiY?c4m=0|yV12L`SyEMMGH9yHTa z=tA%LY|uNVUZ!y-=Fo7Wap2_3RZBy4)YPi9FBevC+_<sqa?y=4$Ui00RMLr@{C$xH zVJi|_OsCU*^Jo&XyL)rW30|01?j3@?qhpfnYbaq}YNUw|!T$8=Fgv(lu;lwN>)TX6 zQC&#sozG<r2F7UH4XGQVJmKm*7z6>8SDVlnm<p6K-(hlbvA$*<QZ!cNG@O^6?f3or z_uRK{KO(29*Xw9#)S!HrB#)U6l!XPxfaMqE+fYUztkbM>@UGdI`|4Gt;jnnzufrjr zOu;d*a-J7J^Ck4;l&}^3dte<4LhzUS%X-2#YW4KhgtOrCV#cET_Y#c!Z*>N(T(yd* zUJ^7jlwW}Ixqsw$mTx1{6K2c4aeBMr)~&3bGGB+ioBdv$|2Q}}Sf-g2n8KQ!?`jMg znx2`Nr+=!edlF=a@l&4aN$=|Ff|F{DO@E@(iD~)VghSpbs}?V8FzonRz{K{Js4#n_ z5@9~B2}^vkUUN=#T^5F^T2*1>cUv^B4LuR(n&eHezjP_aj_j8~Z)}vsM?+JSGBh;w znE8Q{tPcN{Me0QOrZN$a(x~k~)*s_H@XT|(Xqqw3p2H19Cy^7oJV+9<eDT6RvNJQ> z7y#Wj+wr6rUEvH<(eqZo9v2svvLoBRzy(;JqP1Agk;tL;JhZBJa3Irk?ebEn6X8d- z$kOyA?5AFKPNbX{?UYZ{&T~Ew9z19$>ztrX-8`XBTsj96!jI4sf582H!y&(s^9Fan z-5E~w-Foo$9YPPZx0)l$-~V0Iz|iM}j;~kWSSItj#SM3&G%YcN9_Wv%m|)|%$*)yE zK%H}P0;=I8P$)CW(jGMC%kK-MzmI&ROi;P%WeVuK&FCQyepuMb*u=!KmX;QK8=Gh- z00#=j5XDOh=B=P4!lO)77}@zlgfjP`+}2J0N)B@TT2=g*Znz8j<o?^8dkwJsk$e~S zED;%u>70K4d_!zp+!jG$VtBc>nuEEWW}ziz{mtPNRf^`ik&@mJCYt2hsG?cN3h2d2 z3eWEv*uQ`O`&_TKOdAV=2u`Ww%?uPqS(<!Lt6~^aREalV+C35$HJbPm?00SOId|@y zWcroaBl6X~BT}XDpH@CN=2S~i7bf(IclM2kS5xGl$Ht22ii(QjWl<_#wDQUKt0aXy zHlE1EY8ka;_1AVxqt{bMQo^k-U3xP*ddtSvHVaJ)=`L`7q|RaXN=Rw_c}@lf23)|< zR;Zh<t`oIs#l_ZcZf>V7EY{eWVEH$5E*G7InuWcs8u8x;V?|OLEU2!G|1`Aa(4Cir zs3<1Rehkih(akLbrte@P!!;SV6J-R6?8LGeC2!vBteE<+RWh!lr4^-)gD!4^#MwAF z<YZ><mU5c!FIvWrlzxjG|Bw(9^Tt#%6^R{r^++FP@I0L+4qvx!oe}$#m3YM5wB`G7 z@`O~0P91CTFUC|ZQ78`Ht->Y!Y7-#LSaSc!5m{r*Cohv%*~9sk@QmO$@bQP}=;$A{ zw9W(zc{b<HJ%!`FgM6S`s5sFMM^8BLg$ox_*a{Z>kPTT{dF3rq{NByhdpEZZ#-ek9 zTZrhg;jMk8_$@0)P5DfY*F@7RGeM7U<Bu8|CuGphnJ*}CuAO84M3|c$&vMT#I|)#> z%1(Ff@|!S!C5;Yc(xjV^3b&UPa{AXl{+2&jdG-93En6f~+dRjdI6YSY+cN}xr}vAs z?Ms{RMFkY-djAH);bJ>B4Ub1%IC0`c{jy8RCb%6s6U>r43&X+i$X|4S%0F^3-6GoW zZms_SO)8>kprF12IxEpuR@+hIj>6STLjlg4-O`Dhrlw`kIX8!81y59vh3w$y#KdeF zhmg*CN!#mk+$Z=9OL>uD46e&2r^GHTnNp}tQESKDvSFgBY>`gn;pxXJK?6VFgSM?b zziY#EujVbS?)kJL2F2HOH0+dUxR_xP9=OL3v!{aJas~$3l<Bx0q#mY9EB@Z*x-Qpb zmS@+!1&g3!Xz7A)SJ3EeVQ*k@iW*U+GIXgNLEVF0epw`(=y;}PF792IgI=y`jLAOe z(VaFu?K3zuGzu$t2<<YBf3CbrF(9gQzEp-1HVE#G=bAlQ<2Krwp$LzUa=B33>148z zM^EX#zB$=&-)WhY4FlN?noTlY)L?XmP<*{^-8i$vQcg}zf?<;VvtY?;I=7cbOA72; z0m#E42C$pqtTG6#9VFF|b^BjuG1sB1ou>!tx{F*RM@L6nTUt_JAKY}ECV42{eMge+ zAK|cUpC7qi3JoZj<K|_NNS9#Db(eal@?fT{X}oA-)8-i$kdQj>6MKy!{gAa$nWNvt z_DWy0Y|Th^QN!ruNmOle;o!i)DI-bqk0=Qvp@FSE+wMNk6D?7&f2nOp8u|VHA}6jV zkgy9P^H9yq@^X8E{<Z4m^HrweP?}iKfsVu`nXBTgp}fdEGPhU;M&7tNPP!4GVOxn; zug=rdfC;vu$=y=V@~M+;9UYI54qhUCVTF(#&e=~P>mqS;{ANhQHKp$gWGI7R<pLu? z>&16oy(U1Y>(N8XX39?loq}Sjm^T8|_;cmU(`U~3^U8*IuU%fvUl$u-I;C>o+_-`w zOy#NgYMNcUmQq+$<PTHBo!g$HoNH%quQY3JU>>Q`mf%IraH@LfA4vU)N1M8J!rTb= zL=BpO@#Y+()Vp6FSki-I+m(&{PEF9H)ctyT{^RdDMyupaVK`+Rd-dBIRg-MMI~#&H z{j+PL_rrzv?_J9N=@ng<X75V$kCApexwG9*tlG4vNk&1>=#(8VVPO(hu3V|_*>mVz zvWbK2ha|&ziFu=`w0!5e_}0Nm{~_^Aqb=}`P1;w|W*SY=6~V%GvOiZJ`0CqX!|`GU zf10k#O$pa&d9lx#53G0Bfde+FC!e;=3w!foVm7&4zdrK(qD3>LAq^-wY=i7`f$?$5 z?BQxzUCG9<srairL~Ta?UVZ)9oSzDgS(?rl!x-YX+j(&cYE{BHq?$rqwJrOvKMo5E zE56mXTTbcF-mwE;BK%zJFhZki(J&=l=rL|{e9oK@FAb0pf4G|+MX*#}B^x?jwrH#a zCJsI-F$%cI=n&59SlP8-zkcl>oGpuReVm>@FeWWyV>`&XRZvhFJ9q+#oT=CQp|PK3 zSy)&oqd{)J-&@d43CNohPUJPGn%CYn4f7LO4!j7D1uT)ZI~nS|E!|Spqq`X8RX+9b z&AEhjPLO$oQbw<2wnSDtHs{a3L<Z0Pg7Z5-_#@E|g!Kng+9x)joZIlow;=~jW6LQU zwMut|Nu=hkmvXKQCi>=<mL3`_hUgf$HYDcL1EJIA8MTqBs#D5zNLG*B)`RtEFBr5i zCu8<pUN`H#dtMqzRyuTN+bAWOZCsUOI@(vkC=Z<K%lBsh&|@$p=}cB0brk9b@9^V~ zKY9vA+r~{IHm!W{p8YO_n^TggF5?{?EiHYsV_p4l{r4YoEKF27=Vq6M9yJIN2bQ=o zc1rnAw;DWrs=awNxu?fovknq*QaK^L2)CsUH0AvGL`9XVo5*Om2Us-s;Tq-cL60pQ zqi&;TxGOd`gD0OVZp2VjaeOP_rw|zl7SbW1NZFLhXAvwCvm_&<PP)8fpe{-+SI@hz zI^R+^P`ymXNTg*21<jyGQd!=5*Qy^sX4b6dgzN{=v&PaZSe6oJ!=du|rrG{X57WB7 zL|;hoDrRkg(iuv?1m^ycZDWVxjr=pSK%S715P<{}99imZIMGR-po)3`zQlr*@!0?F zLj4sDHIfJOFd#uYSoczK2W~dK%vWf$yK==Ejm`r|G{;#pTU*nh%!u9}wpjJ5N2PUl zUwKzt{1z{jI9X0)W@h?*n{-+ddK6;J36-wzOJw9aQ2byHB~PMDVaR5rq#Wy=YcJWb zdTABAIbs0Vn~~qhZw-fM>K|Kr5q%{W8-Ptv?7SZO7wf746|n;3#tM0?K?Bh>R*a^P za={$*X<KLK+oPdNPv90Y4by>12qt>bvSrn4LdM!#TSoxaOurh`zV2tH!iJwee^%7L zdA`1JQ&wiCH9*CJMN4<>+qbWeqs)rj!IA_j`A*%L{BjiLTsFN|-Wu3R=rZ};hK6PI z<gn2($=1e8#ci*TEJ9EZ&ze43jOabTSP}8-j(_+P`iRXZUM~23*FrhONu1k#Ud9#- zv*G!=a3BdI(YB79S=&39jt;kzgQu{!C?Q0@;Xqz4fN`UA=|TRly4p>q((jFeZ~c+@ z%eI;*GSH<1SARzwFfa(<+yxo+>C>kcm_rsVA9nzumls)d=OrX{=b{x747@H)uq$ZA z6P1CJmCFRc42RB?=^Sr=eWZ5(%rsgutpJb~)LrbxSRuDZ1=y!PW~?)6*7a+Qrxx}@ zY|!e}iu$LVYa`?mep!I_a@cLVM^Cq-y&bZj%`9@MzPP8?&ws}r)+IRP%;rM?B$l6q zm?|CV4}OPlI5yDf45(KP<>V*ZK*f7ezrVp_&CEhUc8j;XzIgGX%((c);iT;i;_UH+ zXdw*DRvTM~jU-;Ogs<t~`}qL40oDK#Z_dW0ELpV)yarY>=3|lXum$T*m7L^Y*=S~# z^_44%3=fab39PcBt_P`Q{Sn6EeV|}Du{5y@MR$iT*cBi`v$vZ){8qH1*Sl6(!ttWb zlXA(UyYZ^{iQry_@kIH0%od<hb9I&>>qr*rTDG||zrs}eH}&z(0yB`(b6@50n@PM( zo2=(aNl>qt?{uT?+#D}U*V;<itmmSoWQOx@f=ouxBp|pQK;h2;g&s<#`g1RVF;Eh0 zxYNL(qgO{+d6B=r|J=A2e(OOszdgMR%AKl~o{QUGLqiS|1Cpfi6$ZYupd!^#sv+NA zwJ;cJk{N2EX?t8o^4)2awDaug(=8KtsVG10L$g_V-n;PHdODz|ZwVWl6*b^Vo5#e) z#tvKV=HMPUfk-GTEAy0zHCt6+)g6Wk%+-C*AlN12i(kl)QyP6Q(q!(fs7h7x1yFQ; zroFx0X!yPBE|q=PehC&&)v^E!i0V?VyfHb)!`}t-^%VNuWqbQEfXbe5UFrz0e5L`j zK1IdkZm0B9)fT-Qr8VISl=AY!`1f?6CEuL!qZ#Qv1dTId<~#tO;c%kpOQ^g!k6}-} zTV_YP9TyC2eg|8{>)}Yd==#_94<rZOk=~<FHxAp@-l>gH4tJv%DTutAW?#(BPvCob zFT#fM@bg)A<$(oSd-nMG4%JTy8v%ZZz72pgbtoT5q|-3S?o3*wk<rrfDGQ=e+r|wW zO25wxQ$Q&J6JP>&uHL77IIt<X$*+!}A+rEJ#~+}hY2&t8ysn~J<n%{139YNhRfJxK z9cg-%@9n3r_fF`a31IHCN<qgjC@4sRxosSzO|=ih91C!DoTaW3;H5%52+n}gcD*jD zc$yB5fDAi)S;_uJE!pMl>GsM5un40DFyq~Wy2d^Ok5x-Z_f-zuvhEaqU_)g(>CK&_ z{CAXZ_CDz8eruHqI&T_3OR@vsv06~Eg=mMLn2^kis;Ii(zCKdL5U$0$mA-ymvm~@K zAH7bd!D(JWGWTp6#AKcV7xPqb=&|2fZ8Y`p*o&}e;rxf>khu(B9nukFh3}guZ~Q`? zpJi4xP(tRhnsp%KPAFA@uuW7i!3stnr<;p-?vW+Q9U$8eMl{@bV}U|>gGHfi=u?fZ z6}`5285|>3^ecCDj2P&4&y!22YfsoH1PMg2xZTbn6-}R%$*1Dt;{2{+5ucL^U7a4U z=W0O_6crU!QHAXZ`!fp)3g~)e)kc&%c3{ZL7%?cE`5#}!CnU@VwWaBjm>-|1sX3km zRtE=r10>3WMQ-cO1rH^eqzUwj@H{Fi)y}KJ1}M$t)(L%!Q>U8n;#&MSy<w%pf#(4R zn*lDepd~jdC~!Myl6RO1c1~g8ek8xi%xQSyHU%?y=w|TQAU$Mot+l=?UR*%(>gCb0 z2R|QIiF7L|j}7VOMFQJEp~vz_<>SAB#|qrdA!smq-)He2)RHAju)si~lanXVmV5Lr zCi}Nv?k0I8ys;oE9$eCGDxEP6Fw)*&L3M1YOAhK{bY)j)8gczx(K{S3U*6(<qyOgm z8<>Oa+>uXX?~`P7U*<ugP*2iyXV2kqxX!d9HfH9=P0uVK^kAtt4X;?daI_;YnU+%v z)}BnjKzCz-W2>Vgk+UyC1X<`4<@HvnC*8n1>!#u;CUYZds22H<$wh#;XG(f}!q+Gr zdI^Hime5@xzqYae<#>>>X#XsPmKE6l^a`|P+N6h~F8kM}XH;vG&8z2V8`Ov-tl-w? zM8k`hFJC64tysVFCUmN@1p>U+6g$td6aBT39lH4MbWH!q%^Rcf@ro$4Qg{xuq#;1X z*vOkcb>o_rdb(zCI|uaq$;7VG)UMz9UmF*#8Xg|bN=rMApJOBsB+dvw|FUBJ`}P@< z9oM`q2Ur5IKWpOvo;8>;5h}<`P*&sY3Oj(tytMvTqDHCM(#G0)#0xJ<_z<a@=f-$* zg7|z!)GMW?^{eUygHDTn_Uu(<=rU1}1r1;mD;h63aJw^Kr*Xgc{>V@ntjaD7)^H9a z6`!C3W>?=_?$(*hfqKWx!$O>$om;oCUiUW^|KEypeDd-opdG*jpA!fv;9>3VJ7g3e z*PX!K-%`+DvlTh~Jg~8R?A$U&o9UWoWK*tRwoduvF^{go8oZH~vXq|v{{4GT2S-~^ z_kbG;vq>q~ea|Dj5=eN~phwBgB5U-W$|W)zM&?9=={v{Re4-C`?Y(I6DXt~DIOh5D zH1K_HvB>^c%6wjj@KaU4)AjW91ki;X0J~Q3ic=kJ^)+V>eFL7Op^RVp1!p3GZ?C+& zWdp8@T6=rsHXv=?xM_`+eP58%;QHV?$;=WjTO6+9*sJsD{m?x50MzQr(m;3h2E%hN z_8x`u!wTALt9Vd*&CT_#Po6wkxp?7tL<TsxqQ-kyvT}0fFG%C35^`-}Cd@h>`>F8y zlSE&zhK#_Sc2+za;7W{WgO?erU>x5Z`1nl5JAt|7fStO1)ct27XVhc-Hs&iyZ#O!4 z{N3(dyP9OeNmVlWsJqZ599|l9@&;FWQDkTF9d2j~pw1|_wYI)3EWEH@(}M5nC#jSV zd6}4~B)56*5gQwuD<z&pnY#hi!Msv|eJ3j*C<QLe1IEuwR;i<a>RWj=LRg{`60}f? z;5^gBV60C{7ixEN`;-miYAay`gEK)Cf)#J=;_@1NTpJLnq$GtBfH-0>uWS^AZc@mc zTlDkizf`Q=%#jV%B;$Zw5FbjQ_uhik7xv<5>YNxm&nJgS@}}bRK<s5Px%(|&s)#R+ z6~e{d_VrzZ@Vd<meRfj)N&)TUoOC&)ulh;6DV{2$KVxIJL6LKTBHa$aX<f>0io}bZ zSy*n4gnso<U1In|J-xwDSd=_pPs>hE4(T>j2#Vp*B!WDP3!T5Vu;ToUy0go{7WFI< zgZ%IX6TnGtQ3>($;qKKn>^)66rU%w?NF*?S#zLSIF))vrTcWorD)xZ2UPY=&Lq+xQ zISeK%_Y^ZnpsBLs^GG5*i{bTf5=DJP+(hq4b1G|kke=%4JDg}o!Q|i#bHKUjq9s;6 zk{8h_g$~Y!Yj!AK1c;P&@Lt?<2O;I8cRW(@z$clk1iKU1?YyAeGAd9CG_(h3X&0|E z(r@+g_#cc64ZC1?n=9LTdwX}z#l^*WHY6HKH~?jCowGA0(Hl6uK$ub@%vL7Hkzc*q zK6Y^N!UeHWQT>0gp}06s-td9Sgf1n`WK*~7@R@r0qczGGoSjoamUZNqzT=H%Gg#nD zg3@^B-jSOhYeG&bVj5llxK{T14hB52L1%17{abrGyDU();J$O9t0wO30Ucy?=gU1B zs~cp^1rLuLP{O^u^$XeV4CoC_Ts!|KVs`%*K(O7qx*cH97$<I@gFtnccqLna$XkOp zj=-$e2hCGh*n+=E(gO(0%*(R{%1#lZUPV;zFA{^_Vr^yh3h3)`Rs+Z*tKMHvigEon z1Z<e?5dRENDmVK-JjH^G^6tiGMWlGLip<TWSbXv)t<gtRfBI0BrrX^8&i#PPr|0=D zKnJZ%EtVpXgIk5mW$%<S-@ZK$CkJ_T7A{6Cj<q~28v|e|zLy+XiZGgax=J5$*JkhF z#{VH<ez=L)8+K&TJ*$7Na{l5YnH`^UL|E)<Z}02@T_kexT{#z5S0R7xq}&I!f7>Si zIO^y-<DWVi&X2<@6?+(Fh<l}dKm1Q+_7~6mkLCVc-wEby>NX;g4@-BoL?o#!L{x8# z{6Es~-?M>imCV@O`Vr2)YI7?m%SZ&GFU<T;wdTLHVf2?oUnFul;z;n}pJgxC|KCtO zAg|bmK&+6H{Vu@2Pz`S#wMuwgxJ`}R#z7?Vm(pFCJ<SZWBU<pXOI8!1Q_KIUn*Lwk z5IeqR(=2~;pPK&_%gAjeVbq<XMZd4;{~HQI<Iu3ZrTjuflgi&w2>)mxvlBs<^4oR7 zb_ekJYnx7EFc?eu1-)1QS?>O&d{IIlK!txhMQmrNJ&sJttCJw^yboLq?#BN*AAeF_ zaKwtGdCEPqj@_{@R`p(<g38gqcJ3AMLX;7TKor;hEn~!oOHEQSvmcX{(AvH}TMb)? zd0+phUz1dhChm37#IE@pQoZ|Ce*SN$j(fYnG!Mou|F-n67ArJzzwU|!zi<1i(fgOj z`u}G{{+o;BKga#^=J`)t{u7t~wA!Duw7)4Xt<pscYL4PA)Rrm1FHIs)CoGQV9=rPU Fe*vq4skZ<C literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/multiline-reverse-wrap.ts.7fd99dfa1.png b/integration_tests/snapshots/css/css-flexbox/multiline-reverse-wrap.ts.7fd99dfa1.png new file mode 100644 index 0000000000000000000000000000000000000000..e89d18043443e0e8a1957e955903e31c920efa15 GIT binary patch literal 11258 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 ImwpfU7o&^Yv;Y7A literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/nested-stretch.ts.ab2b666c1.png b/integration_tests/snapshots/css/css-flexbox/nested-stretch.ts.ab2b666c1.png new file mode 100644 index 0000000000000000000000000000000000000000..ca9435b396d0e6e2ef0e281c6c5c77dc7352ce9a GIT binary patch literal 2749 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_rKlPZ!6KiaBrZ8s;fQia1=1SI$4ksB=&;>A$u^OB@qRoJ*;}#T5(N zr+s{WYmTth%1Ju0k>|hui@#PH%g$i=-{u!9!w#PXjEh*L6uf{`oZjDp$Z0p<Gk&<D zSG&H@@IrFW$A1qW^EF7`i_`xfm~(_tK=lGcAQLZkYHN%Y|E*Q#2V%_ES6LsHfAF!# zf&peU(1ufue;ES#-mrHVJ!0q(%4pDNWW!GF+T~fXdV6^G!viwg^DO>P(W|urx}?x1 znwx#!wY<;e7yoj`5;Wtq+QYT~%7i(7{FK}KG5p=7D-QjJ#~yz9dtJZr_l<n%!Zl?l zJD9<~hI)Q*Qm>v~)MxmR@%!zk7vVcR7Bns|vsvF?*SZ=tXhD&O>i!%dCjZ>+nd>V1 z6)!M6F34I}EnUdq!fE0##etc0%6ETY>i)NJ`pf}FrWcMEe~yxDP*Uc@9fQEwU9oa@ z%kEy&{0IAUB;w8g>hv8xsn7gl`sT`iXDA5wtY5ua45n9S$FfLOWIeSO>_tgov<?Px zgO6l=d(YH8sM~#}PUV1n=x!5Wkbk!NT*&Z$^&M40p}spu=$+YFQ_lyLe_Ql)U+?<2 z?fDL!(-r?;tg~mbVJ4(|_3Rb~n`=imTj-fcH=IOGW3L|BbJiYZ{7?~|A5xfLe;`?s zK!S?BtFq_n?bTZg8rFY!3CevRb97}M+(R`LGs;lY@hA^1V~++1l>#I#%`RtqWSSk{ zgXq~(FAR@a%(-^6pXJ9iO48@D`ztox-}Q~_fmkqet<COtTo1zBWN=rQpvpAZoU>dy z`yRvgS#@H^AEw-uejr9j3$FSXWZdQakem6j)vN{GD;w?R#g?-cXcNlx;3DJNcE)#a zFE`fx->{GOgD9aeu-ohT<6G8Q=?B_(p6c*zFJ-<nUyz-=ss&g!UWv^MxtSXSbaeiF zzAwMqU$0}>*F?a)E1TB6&R^YdI;m9p!9~4wRq+{S43i0!NF<lTqZ&YMg)922J1Qlj TS18#5o1hGyu6{1-oD!M<8h}Rr literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/overflow-auto.ts.24e33ffc1.png b/integration_tests/snapshots/css/css-flexbox/overflow-auto.ts.24e33ffc1.png new file mode 100644 index 0000000000000000000000000000000000000000..4a308b8a00b898c46ab1965947a14f6c1664a6f7 GIT binary patch literal 2466 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_n+aPZ!6KiaBp@p3ZAF5OHwaeX9QW-=gwZO;vu;LNQ}+ju>|jcaHOW zzsoT^sfpdoz#u1_(V)@D)*&=XQ4j<xEU(=N*Kd0LX7_Ra(0dCQ7qLhwctNOF-(Eb3 zd(Jca^_}1GcJ=c4JI=oUc`MiN){g2@y9*x-E+qFX-&lG3k8FFTz{$OKGwh##G<|>O zzsiK*KMnt%3bQ**abR}gG#M!B%G>kC#q+Ex)~nn9NcksyUFKZ;^MB<H;b%4;+gBwR zlYZZ}ZpEigW|(I|etvfEww|EF1oP1ZML~iZ&61;~(r5-6%|N5|@_;o>elbom*Q?+t TUAQ0E&SLO%^>bP0l+XkKzepB+ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/overflow-auto.ts.ae8584f01.png b/integration_tests/snapshots/css/css-flexbox/overflow-auto.ts.ae8584f01.png new file mode 100644 index 0000000000000000000000000000000000000000..ade394e1f8aade5654f6dd5fc79c314d5f68e28b GIT binary patch literal 2386 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_q8lo-U3d6?5L+-pG5zfQP~Gx46IAb#cwf_rfO#a|jd!EVeb?%y8o8 zTsek@d?sE2)e8)PSgGo}r_~cr#9U*viNU1=tG-b|?BO{YE_4r<>a=HH1Q_=0T)?=9 zMM}X77xit|bpJyWcCKNr*nvw4KJBA&ctaN&Jm1cod8x=yQS8X<!fE0##bJ~h4T8~B hFq#plSr+_deDFr%#p}rPr-1Du22WQ%mvv4FO#p7cEo}e* literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/overflow-keep-scrollpos.ts.280d1c921.png b/integration_tests/snapshots/css/css-flexbox/overflow-keep-scrollpos.ts.280d1c921.png new file mode 100644 index 0000000000000000000000000000000000000000..370ae4d74ddd7d1f05ed68c224e10644c4ede7c3 GIT binary patch literal 2871 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_rK&o-U3d6?5L+QS6@jTB_lpd%u`tV2fuIOLy4iEeBQH4lA&2U9sPh zN9^FEf80r-^^Jiu5@g*BC5|K>P>2#?3%th3&U*8CL$OW!&bz;NB}|>(esuD?4|VsW zs>QmGCNZ42x%)OZ!-w~rCJs{^m|fo9e#=^C9>2eC?$)haD?dLwJIA6hY1;JZ<<+VW z^t~38zj$=C`}2#7i!CcYJc!v*;JBdi|7_cPkA76HkJ~$Ivbz7dv$M^gpPOsFdoSMy z@#lP1>~Z_+Y>kbdpPzsKsc*x6=Ei@X)AeF!tqxy*?#!82uU^futF1CKGfPW*_VV)b z;%{$mT9&-H@aE>`bies_w)KBL9<TZL=V$x-A2%%<X3ng?v#<8|w><ri);I3mo2MVY z@64}XzhZV4rG|xtrBzl|nwgtFKQq&KbN&B+J4;_*TeEK6x#j-zpWWMAy}A7Ty~xPO znKNfj*N^{qMdm;`cipYS?fl}#n<L`m|JRj0KGu8mj1JI%`Wd$6@9rd}iQT?^+j<uB zk7J_8^{aqxm}6C%)hBEH?eNc?#n02y(z3I&-HRFSb9~Xib?eqFW%s^6FaLaac-YwZ z^S8IRWzRC!C|}s0k(rrkSM%e;^3VP9_CWt$T<l(M!v3Mx;r}8~P<4yzKfAlT+<uOI z^|zd~v_5(JeTyv{@>za~$L_DIP5bxt)m@;nwA|d>clY*M^UK-H$iBYrY$G#!8PJfs zdG<^H%(bceQ(<V_-P_wMfA0PL{re-LqN2{6`F{TC>FMI{@9q7){uu)>mJj*w{oj6X zf4x1>Pn)-FG5P)N@ALhZKW|z-c<;M_anT(|W*2Dmk5Z!{F`5{F<=|*W7!88aR4|$m jKy9MYvVfLB@K^ZBww)c84GVzHVg?3JS3j3^P6<r_IuTeZ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/percentage-heights.ts.87871c4b1.png b/integration_tests/snapshots/css/css-flexbox/percentage-heights.ts.87871c4b1.png new file mode 100644 index 0000000000000000000000000000000000000000..80544fc47523e664acf492cd97a34c9cd9fc0263 GIT binary patch literal 2598 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_sUxo-U3d6?5L+J=puiO~BPLXo2Zt{@e4mEq~E}XCcpprs9`(Ym{9j z0)8xS-*a_e?)SZ|cW>L-KD@Z~n)`k0^Pd;LXSBZ_f0>!#hiM~Qhfqd?#(+{QlB?fe zmX?c+&#q!Rz#(qpFvWq{g>#^(D;xJ+`@+UhcaW7=K=lGcAQSo2*8JPG{{0nS?kyL$ z`SoRf-}B14FSZY!7dZ|BZ-T;FHaqsR^uCSD*co~XD0h?WW#)aw`{FJO%f+6a-+AEu z!=^!y#y~0j*7mpimb=?TzMeJjgZtd)mbQ!?)bQ@@>iPFR^qSf-B(!i#DR?blT*NZa z)T<r$FAL9$&8}iRz%zLAQ~viaZSLQ*=g<4_A_|z*Kgd5FEGffk+jqOY4=;Y*x=fua zS%5l)m+b3o-+YD#;xUw!KA^(u_qMy=zjV02udO<`*#G~_ih0l7?=#ow4W6`d&2;ZW z>Fg>7YLx$13~e7+KYwiss__2I<oF|}H_$a4w0>Ptd;8t*$^7d|Yd`J$KliiEa&-nl z%H0O4KILmaeEse||Ms=(zuq)6*pC)Ocv=uw>h6EHwPk9kFQ&Y%qfSQu%lt>pd0w`f Sgb=W;$KdJe=d#Wzp$PyuWA}Lg literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/resize-min-content.ts.827375281.png b/integration_tests/snapshots/css/css-flexbox/resize-min-content.ts.827375281.png new file mode 100644 index 0000000000000000000000000000000000000000..415b768d2de8935c7327c3410408258c8ac35f97 GIT binary patch literal 2364 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_q92o-U3d6?5KR+bDRzK;W=}_2l%==NSs~#SXBns<`z!b~D3?$~=CC z2R;iJ7qLhwc#Tq24T7xmXYR-`9+=P0E1-ITA&_a58V!QcR4|$mM$3ZH;&8M^pjK`8 al~H6qSCxk7p;f>Z5QC?ypUXO@geCx76Z#tf literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/style-change.ts.8298040c1.png b/integration_tests/snapshots/css/css-flexbox/style-change.ts.8298040c1.png new file mode 100644 index 0000000000000000000000000000000000000000..a894d41a4af11a093725acac6bbd67e1b5e359a1 GIT binary patch literal 12593 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-flexbox/style-change.ts.8298040c2.png b/integration_tests/snapshots/css/css-flexbox/style-change.ts.8298040c2.png new file mode 100644 index 0000000000000000000000000000000000000000..504e80f27fc90d4a4ef87b21434f93b285cbf2f9 GIT binary patch literal 12592 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-flexbox/flex-algorithm.ts b/integration_tests/specs/css/css-flexbox/flex-algorithm.ts index 10b1d7718e..111fab1570 100644 --- a/integration_tests/specs/css/css-flexbox/flex-algorithm.ts +++ b/integration_tests/specs/css/css-flexbox/flex-algorithm.ts @@ -575,7 +575,7 @@ describe('flex-algorithm', () => { BODY.appendChild(flexbox_10); BODY.appendChild(flexbox_11); - await matchViewportSnapshot(); + await snapshot(); }); it('with-margins', async () => { let log; @@ -1148,7 +1148,7 @@ describe('flex-algorithm', () => { BODY.appendChild(flexbox_10); BODY.appendChild(flexbox_11); - await matchViewportSnapshot(); + await snapshot(); }); it('algorithm', async () => { @@ -2496,6 +2496,6 @@ describe('flex-algorithm', () => { BODY.appendChild(flexbox_25); BODY.appendChild(div); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/flex-align.ts b/integration_tests/specs/css/css-flexbox/flex-align.ts index 5b330feefc..6f55301c66 100644 --- a/integration_tests/specs/css/css-flexbox/flex-align.ts +++ b/integration_tests/specs/css/css-flexbox/flex-align.ts @@ -637,7 +637,7 @@ describe('flex-align', () => { BODY.appendChild(flexbox); BODY.appendChild(flexbox_1); - await matchViewportSnapshot(); + await snapshot(); }); it('max', async () => { let log; @@ -841,7 +841,7 @@ describe('flex-align', () => { BODY.appendChild(flexbox_2); BODY.appendChild(flexbox_3); - await matchViewportSnapshot(); + await snapshot(); }); it('percent-height', async () => { let log; @@ -900,7 +900,7 @@ describe('flex-align', () => { BODY.style.height = '600px'; - await matchViewportSnapshot(); + await snapshot(); }); it('stretch', async () => { let log; @@ -1311,6 +1311,6 @@ describe('flex-align', () => { BODY.appendChild(div); BODY.appendChild(div_1); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/flex-flow.ts b/integration_tests/specs/css/css-flexbox/flex-flow.ts index 569d57f0f0..81f2e87fcd 100644 --- a/integration_tests/specs/css/css-flexbox/flex-flow.ts +++ b/integration_tests/specs/css/css-flexbox/flex-flow.ts @@ -1566,6 +1566,6 @@ describe('flexbox flex-flow', () => { BODY.appendChild(container_2); BODY.appendChild(container_3); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/flex-item.ts b/integration_tests/specs/css/css-flexbox/flex-item.ts index 3bfb0dc6ef..ce58eedaaf 100644 --- a/integration_tests/specs/css/css-flexbox/flex-item.ts +++ b/integration_tests/specs/css/css-flexbox/flex-item.ts @@ -353,6 +353,6 @@ describe('flex-item', () => { BODY.appendChild(log); BODY.appendChild(flexbox); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/flex-justify-content.ts b/integration_tests/specs/css/css-flexbox/flex-justify-content.ts index 5eb2a44876..03a2fc66bd 100644 --- a/integration_tests/specs/css/css-flexbox/flex-justify-content.ts +++ b/integration_tests/specs/css/css-flexbox/flex-justify-content.ts @@ -852,6 +852,6 @@ describe('flex-justify', () => { BODY.appendChild(flexbox_18); BODY.appendChild(flexbox_19); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/flex-no-flex.ts b/integration_tests/specs/css/css-flexbox/flex-no-flex.ts index a9921b5c34..08aff4bf64 100644 --- a/integration_tests/specs/css/css-flexbox/flex-no-flex.ts +++ b/integration_tests/specs/css/css-flexbox/flex-no-flex.ts @@ -86,6 +86,6 @@ describe('flex-no', () => { checkLayout('.flexbox'); } - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/flexbox-baseline.ts b/integration_tests/specs/css/css-flexbox/flexbox-baseline.ts index 529dc610cb..910a8c1a78 100644 --- a/integration_tests/specs/css/css-flexbox/flexbox-baseline.ts +++ b/integration_tests/specs/css/css-flexbox/flexbox-baseline.ts @@ -497,8 +497,8 @@ of the grey flexbox style: { 'box-sizing': 'border-box', position: 'absolute', - top: '0', - left: '400px', + top: '605px', + left: '0px', width: '360px', }, }, @@ -526,16 +526,6 @@ before text }, }, [ - createElement( - 'div', - { - style: { - 'box-sizing': 'border-box', - position: 'absolute', - }, - }, - [createText(`absolute`)] - ), createElement( 'div', { @@ -723,263 +713,6 @@ after text `), ] ), - createElement( - 'table', - { - style: { - 'box-sizing': 'border-box', - 'background-color': 'lightgrey', - 'margin-top': '5px', - }, - }, - [ - createElement( - 'tbody', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createElement( - 'tr', - { - style: { - 'box-sizing': 'border-box', - height: '50px', - }, - }, - [ - createElement( - 'td', - { - style: { - 'box-sizing': 'border-box', - 'vertical-align': 'bottom', - }, - }, - [createText(`bottom`)] - ), - createElement( - 'td', - { - style: { - 'box-sizing': 'border-box', - 'vertical-align': 'baseline', - }, - }, - [createText(`baseline`)] - ), - createElement( - 'td', - { - style: { - 'box-sizing': 'border-box', - 'vertical-align': 'top', - }, - }, - [createText(`top`)] - ), - createElement( - 'td', - { - style: { - 'box-sizing': 'border-box', - 'vertical-align': 'baseline', - }, - }, - [ - (flexbox_1 = createElement( - 'div', - { - class: 'flexbox column', - style: { - display: 'flex', - 'background-color': 'grey', - 'margin-top': '10px', - 'flex-flow': 'column', - 'box-sizing': 'border-box', - }, - }, - [ - createElement( - 'div', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`baseline`)] - ), - createElement( - 'div', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`below`)] - ), - ] - )), - ] - ), - createElement( - 'td', - { - style: { - 'box-sizing': 'border-box', - 'vertical-align': 'baseline', - }, - }, - [ - (flexbox_2 = createElement( - 'div', - { - class: 'flexbox column-reverse', - style: { - display: 'flex', - 'background-color': 'grey', - 'margin-top': '10px', - 'flex-flow': 'column-reverse', - 'box-sizing': 'border-box', - }, - }, - [ - createElement( - 'div', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`baseline`)] - ), - createElement( - 'div', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`above`)] - ), - ] - )), - ] - ), - ] - ), - ] - ), - ] - ), - createElement( - 'table', - { - style: { - 'box-sizing': 'border-box', - 'background-color': 'lightgrey', - 'margin-top': '5px', - }, - }, - [ - createElement( - 'tbody', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createElement( - 'tr', - { - style: { - 'box-sizing': 'border-box', - height: '50px', - }, - }, - [ - createElement( - 'td', - { - style: { - 'box-sizing': 'border-box', - 'vertical-align': 'bottom', - }, - }, - [createText(`bottom`)] - ), - createElement( - 'td', - { - style: { - 'box-sizing': 'border-box', - 'vertical-align': 'baseline', - }, - }, - [createText(`baseline`)] - ), - createElement( - 'td', - { - style: { - 'box-sizing': 'border-box', - 'vertical-align': 'top', - }, - }, - [createText(`top`)] - ), - createElement( - 'td', - { - style: { - 'box-sizing': 'border-box', - 'vertical-align': 'baseline', - }, - }, - [ - (flexbox_3 = createElement( - 'div', - { - class: 'flexbox', - style: { - display: 'flex', - 'background-color': 'grey', - 'margin-top': '10px', - 'box-sizing': 'border-box', - }, - }, - [ - createElement( - 'h2', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`h2 baseline`)] - ), - createElement( - 'div', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [createText(`above`)] - ), - ] - )), - ] - ), - ] - ), - ] - ), - ] - ), createElement( 'div', { @@ -1057,12 +790,8 @@ after text BODY.appendChild(div_7); BODY.appendChild(div_8); BODY.appendChild(div_9); - BODY.appendChild(div_10); - - document.getElementById('flexitem-with-scrollbar').scrollTop = 999; - document.getElementById('flexbox-with-scrollbar').style.width = 'auto'; - await matchViewportSnapshot(); + await snapshot(); }); it('margins', async () => { let flexbox; @@ -1507,6 +1236,6 @@ of the horizontal scrollbar, if one is visible. BODY.appendChild(div_4); BODY.appendChild(div_5); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/flexbox-width.ts b/integration_tests/specs/css/css-flexbox/flexbox-width.ts index ee74d81080..417670fce4 100644 --- a/integration_tests/specs/css/css-flexbox/flexbox-width.ts +++ b/integration_tests/specs/css/css-flexbox/flexbox-width.ts @@ -52,6 +52,6 @@ describe('flexbox-width', () => { BODY.appendChild(log); BODY.appendChild(target); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/multiline-column.ts b/integration_tests/specs/css/css-flexbox/multiline-column.ts index d9e5f71bf9..f0960cc4ac 100644 --- a/integration_tests/specs/css/css-flexbox/multiline-column.ts +++ b/integration_tests/specs/css/css-flexbox/multiline-column.ts @@ -539,6 +539,6 @@ describe('multiline-column', () => { BODY.appendChild(flexbox_2); BODY.appendChild(flexbox_3); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/multiline-reverse-wrap.ts b/integration_tests/specs/css/css-flexbox/multiline-reverse-wrap.ts index f72b017fb3..21229519d2 100644 --- a/integration_tests/specs/css/css-flexbox/multiline-reverse-wrap.ts +++ b/integration_tests/specs/css/css-flexbox/multiline-reverse-wrap.ts @@ -257,7 +257,7 @@ describe('multiline-reverse', () => { BODY.appendChild(flexbox_1); BODY.appendChild(flexbox_2); - await matchViewportSnapshot(); + await snapshot(); }); it('wrap-overflow', async () => { let log; @@ -689,6 +689,6 @@ sizing is not auto.`), BODY.appendChild(flexbox_4); BODY.appendChild(flexbox_5); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/nested-stretch.ts b/integration_tests/specs/css/css-flexbox/nested-stretch.ts index 727eea596a..262879d768 100644 --- a/integration_tests/specs/css/css-flexbox/nested-stretch.ts +++ b/integration_tests/specs/css/css-flexbox/nested-stretch.ts @@ -234,6 +234,6 @@ describe('nested', () => { BODY.appendChild(flexbox); BODY.appendChild(flexbox_1); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/overflow-auto.ts b/integration_tests/specs/css/css-flexbox/overflow-auto.ts index fd3d220ad5..13ff1ef3de 100644 --- a/integration_tests/specs/css/css-flexbox/overflow-auto.ts +++ b/integration_tests/specs/css/css-flexbox/overflow-auto.ts @@ -213,7 +213,7 @@ describe('overflow-auto', () => { BODY.appendChild(inlineFlexbox_3); BODY.appendChild(measure); - await matchViewportSnapshot(); + await snapshot(); }); it('resizes-correctly', async () => { let rect; @@ -320,6 +320,6 @@ describe('overflow-auto', () => { BODY.appendChild(div); BODY.appendChild(measure); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/overflow-keep-scrollpos.ts b/integration_tests/specs/css/css-flexbox/overflow-keep-scrollpos.ts index 443a06e742..122f9cfdb3 100644 --- a/integration_tests/specs/css/css-flexbox/overflow-keep-scrollpos.ts +++ b/integration_tests/specs/css/css-flexbox/overflow-keep-scrollpos.ts @@ -66,6 +66,6 @@ describe('overflow-keep', () => { BODY.appendChild(container); BODY.appendChild(console); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/percentage-heights.ts b/integration_tests/specs/css/css-flexbox/percentage-heights.ts index 12f97596d3..e3c452d9cd 100644 --- a/integration_tests/specs/css/css-flexbox/percentage-heights.ts +++ b/integration_tests/specs/css/css-flexbox/percentage-heights.ts @@ -341,6 +341,6 @@ describe('percentage', () => { BODY.appendChild(flexbox_4); BODY.appendChild(flexbox_5); - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/resize-min-content.ts b/integration_tests/specs/css/css-flexbox/resize-min-content.ts index 08fcbbc596..864e1a070f 100644 --- a/integration_tests/specs/css/css-flexbox/resize-min-content.ts +++ b/integration_tests/specs/css/css-flexbox/resize-min-content.ts @@ -42,8 +42,6 @@ describe('resize-min', () => { document.body.offsetHeight; document.documentElement.style.height = '100px'; - checkLayout('.flexbox'); - - await matchViewportSnapshot(); + await snapshot(); }); }); diff --git a/integration_tests/specs/css/css-flexbox/style-change.ts b/integration_tests/specs/css/css-flexbox/style-change.ts index 69e11d27db..01a8b198ce 100644 --- a/integration_tests/specs/css/css-flexbox/style-change.ts +++ b/integration_tests/specs/css/css-flexbox/style-change.ts @@ -70,24 +70,24 @@ describe('style', () => { BODY.appendChild(p); BODY.appendChild(flexbox); + + await snapshot(); + (() => { - var flexbox = document.getElementById('flexbox'); - var aDiv = document.getElementById('a'); - var bDiv = document.getElementById('b'); flexbox.style.justifyContent = 'flex-end'; flexbox.style.alignItems = 'flex-end'; - bDiv.style.order = -1; + a.style.order = -1; - aDiv.style.alignSelf = 'flex-start'; + b.style.alignSelf = 'flex-start'; flexbox.style.width = '100px'; flexbox.style.flexWrap = 'wrap'; flexbox.style.alignContent = 'flex-end'; })(); - await matchViewportSnapshot(); + await snapshot(); }); }); From 3bca017e8d9f2e835229203535227203520646d3 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sun, 7 Aug 2022 13:49:49 +0800 Subject: [PATCH 152/498] fix: fix remaining test specs. --- integration_tests/specs/css/css-flexbox/align-items.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/specs/css/css-flexbox/align-items.ts b/integration_tests/specs/css/css-flexbox/align-items.ts index aa157166ae..64346e62d4 100644 --- a/integration_tests/specs/css/css-flexbox/align-items.ts +++ b/integration_tests/specs/css/css-flexbox/align-items.ts @@ -1191,7 +1191,7 @@ describe('align-items', () => { ); document.body.appendChild(container); - await snapshot(0.1); + await snapshot(0.2); }); }); From 8abd6c2eb5784db94749103a0fa9b2866ee6ba48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=A4=A9=E6=88=90?= <dongtiangche@outlook.com> Date: Mon, 8 Aug 2022 22:08:33 +0800 Subject: [PATCH 153/498] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 45ee42993d..098023fe89 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -5,7 +5,7 @@ labels: bug --- -### What version of kraken are you using +### What version of webf are you using <!-- Version: main/0.12 etc. --> From 5df7986d0165a684948d03710056070c04e9290f Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Tue, 9 Aug 2022 17:29:33 +0800 Subject: [PATCH 154/498] fix: fix namespace and error defines. --- bridge/CMakeLists.txt | 40 +- bridge/bindings/qjs/atomic_string.cc | 4 +- bridge/bindings/qjs/atomic_string.h | 4 +- bridge/bindings/qjs/atomic_string_test.cc | 2 +- bridge/bindings/qjs/binding_initializer.cc | 4 +- bridge/bindings/qjs/binding_initializer.h | 4 +- bridge/bindings/qjs/converter.h | 4 +- bridge/bindings/qjs/converter_impl.h | 4 +- bridge/bindings/qjs/cppgc/garbage_collected.h | 4 +- bridge/bindings/qjs/cppgc/gc_visitor.cc | 4 +- bridge/bindings/qjs/cppgc/gc_visitor.h | 4 +- bridge/bindings/qjs/cppgc/local_handle.h | 4 +- bridge/bindings/qjs/cppgc/member.h | 4 +- bridge/bindings/qjs/cppgc/mutation_scope.cc | 4 +- bridge/bindings/qjs/cppgc/mutation_scope.h | 4 +- bridge/bindings/qjs/dictionary_base.cc | 4 +- bridge/bindings/qjs/dictionary_base.h | 4 +- bridge/bindings/qjs/exception_message.cc | 4 +- bridge/bindings/qjs/exception_message.h | 4 +- bridge/bindings/qjs/exception_state.cc | 4 +- bridge/bindings/qjs/exception_state.h | 4 +- bridge/bindings/qjs/idl_type.h | 4 +- .../bindings/qjs/js_based_event_listener.cc | 4 +- bridge/bindings/qjs/js_based_event_listener.h | 4 +- bridge/bindings/qjs/js_event_handler.cc | 4 +- bridge/bindings/qjs/js_event_handler.h | 4 +- bridge/bindings/qjs/js_event_listener.cc | 4 +- bridge/bindings/qjs/js_event_listener.h | 4 +- bridge/bindings/qjs/member_installer.cc | 4 +- bridge/bindings/qjs/member_installer.h | 4 +- bridge/bindings/qjs/native_string_utils.cc | 4 +- bridge/bindings/qjs/native_string_utils.h | 4 +- bridge/bindings/qjs/pending_promises.cc | 4 +- bridge/bindings/qjs/pending_promises.h | 4 +- bridge/bindings/qjs/qjs_function.cc | 4 +- bridge/bindings/qjs/qjs_function.h | 4 +- bridge/bindings/qjs/qjs_interface_bridge.h | 4 +- bridge/bindings/qjs/script_promise.cc | 4 +- bridge/bindings/qjs/script_promise.h | 4 +- .../bindings/qjs/script_promise_resolver.cc | 4 +- bridge/bindings/qjs/script_promise_resolver.h | 4 +- bridge/bindings/qjs/script_value.cc | 4 +- bridge/bindings/qjs/script_value.h | 4 +- bridge/bindings/qjs/script_value_test.cc | 2 +- bridge/bindings/qjs/script_wrappable.cc | 4 +- bridge/bindings/qjs/script_wrappable.h | 4 +- bridge/bindings/qjs/source_location.cc | 4 +- bridge/bindings/qjs/source_location.h | 4 +- bridge/bindings/qjs/to_quickjs.h | 4 +- bridge/bindings/qjs/wrapper_type_info.h | 5 +- .../core/css/legacy/css_style_declaration.cc | 4 +- .../core/css/legacy/css_style_declaration.h | 4 +- .../css/legacy/css_style_declaration_test.cc | 2 +- bridge/core/dart_methods.h | 4 +- bridge/core/dom/binding_object.cc | 6 +- bridge/core/dom/binding_object.h | 4 +- bridge/core/dom/character_data.cc | 4 +- bridge/core/dom/character_data.h | 4 +- bridge/core/dom/child_node_list.cc | 4 +- bridge/core/dom/child_node_list.h | 4 +- bridge/core/dom/collection_index_cache.h | 4 +- bridge/core/dom/comment.cc | 4 +- bridge/core/dom/comment.h | 4 +- bridge/core/dom/container_node.cc | 4 +- bridge/core/dom/container_node.h | 4 +- bridge/core/dom/document.cc | 4 +- bridge/core/dom/document.h | 4 +- bridge/core/dom/document_fragment.cc | 4 +- bridge/core/dom/document_fragment.h | 4 +- bridge/core/dom/document_test.cc | 14 +- bridge/core/dom/element.cc | 4 +- bridge/core/dom/element.h | 4 +- bridge/core/dom/element_test.cc | 2 +- bridge/core/dom/element_traversal.h | 4 +- bridge/core/dom/empty_node_list.cc | 4 +- bridge/core/dom/empty_node_list.h | 4 +- bridge/core/dom/events/custom_event.cc | 4 +- bridge/core/dom/events/custom_event.h | 4 +- bridge/core/dom/events/event.cc | 4 +- bridge/core/dom/events/event.h | 4 +- bridge/core/dom/events/event_listener.h | 4 +- bridge/core/dom/events/event_listener_map.cc | 4 +- bridge/core/dom/events/event_listener_map.h | 4 +- bridge/core/dom/events/event_target.cc | 6 +- bridge/core/dom/events/event_target.h | 4 +- bridge/core/dom/events/event_target_impl.h | 4 +- bridge/core/dom/events/event_target_test.cc | 44 +- .../dom/events/registered_eventListener.cc | 4 +- .../dom/events/registered_eventListener.h | 4 +- .../dom/frame_request_callback_collection.cc | 4 +- .../dom/frame_request_callback_collection.h | 4 +- .../core/dom/legacy/bounding_client_rect.cc | 4 +- bridge/core/dom/legacy/bounding_client_rect.h | 4 +- bridge/core/dom/legacy/element_attributes.cc | 4 +- bridge/core/dom/legacy/element_attributes.h | 4 +- bridge/core/dom/legacy/space_split_string.cc | 4 +- bridge/core/dom/legacy/space_split_string.h | 4 +- bridge/core/dom/node.cc | 4 +- bridge/core/dom/node.h | 4 +- bridge/core/dom/node_data.cc | 4 +- bridge/core/dom/node_data.h | 4 +- bridge/core/dom/node_list.h | 4 +- bridge/core/dom/node_test.cc | 2 +- bridge/core/dom/node_traversal.cc | 4 +- bridge/core/dom/node_traversal.h | 4 +- .../core/dom/scripted_animation_controller.cc | 4 +- .../core/dom/scripted_animation_controller.h | 4 +- bridge/core/dom/text.cc | 4 +- bridge/core/dom/text.h | 4 +- bridge/core/dom/traversal_range.h | 4 +- bridge/core/dom/tree_scope.cc | 4 +- bridge/core/dom/tree_scope.h | 4 +- bridge/core/events/close_event.d.ts | 8 +- bridge/core/events/error_event.cc | 4 +- bridge/core/events/error_event.h | 4 +- bridge/core/events/message_event.cc | 4 +- bridge/core/events/message_event.h | 4 +- bridge/core/events/touch_event.cc | 4 +- bridge/core/events/touch_event.h | 4 +- bridge/core/executing_context.cc | 38 +- bridge/core/executing_context.h | 12 +- bridge/core/executing_context_data.cc | 4 +- bridge/core/executing_context_data.h | 4 +- bridge/core/executing_context_test.cc | 8 +- bridge/core/fileapi/array_buffer_data.h | 4 +- bridge/core/fileapi/blob.cc | 4 +- bridge/core/fileapi/blob.h | 4 +- bridge/core/fileapi/blob_part.cc | 4 +- bridge/core/fileapi/blob_part.h | 4 +- bridge/core/fileapi/blob_property_bag.cc | 4 +- bridge/core/fileapi/blob_property_bag.h | 4 +- bridge/core/frame/console.cc | 4 +- bridge/core/frame/console.h | 4 +- bridge/core/frame/console_test.cc | 2 +- bridge/core/frame/dom_timer.cc | 4 +- bridge/core/frame/dom_timer.h | 4 +- bridge/core/frame/dom_timer_coordinator.cc | 4 +- bridge/core/frame/dom_timer_test.cc | 2 +- bridge/core/frame/legacy/location.cc | 6 +- bridge/core/frame/legacy/location.d.ts | 2 +- bridge/core/frame/legacy/location.h | 6 +- bridge/core/frame/module_callback.cc | 4 +- bridge/core/frame/module_callback.h | 6 +- .../core/frame/module_callback_coordinator.cc | 4 +- .../core/frame/module_callback_coordinator.h | 4 +- bridge/core/frame/module_listener.cc | 4 +- bridge/core/frame/module_listener.h | 6 +- .../core/frame/module_listener_container.cc | 4 +- bridge/core/frame/module_listener_container.h | 4 +- bridge/core/frame/module_manager.cc | 4 +- bridge/core/frame/module_manager.h | 4 +- bridge/core/frame/module_manager_test.cc | 4 +- bridge/core/frame/screen.cc | 4 +- bridge/core/frame/screen.h | 4 +- bridge/core/frame/window.cc | 4 +- bridge/core/frame/window.h | 4 +- .../frame/window_or_worker_global_scope.cc | 4 +- .../frame/window_or_worker_global_scope.h | 4 +- bridge/core/frame/window_test.cc | 8 +- .../core/html/canvas/html_canvas_element.cc | 4 +- bridge/core/html/canvas/html_canvas_element.h | 4 +- bridge/core/html/forms/html_input_element.cc | 4 +- bridge/core/html/forms/html_input_element.h | 4 +- .../core/html/forms/html_textarea_element.cc | 4 +- .../core/html/forms/html_textarea_element.h | 4 +- bridge/core/html/html_all_collection.cc | 4 +- bridge/core/html/html_all_collection.h | 4 +- bridge/core/html/html_anchor_element.cc | 4 +- bridge/core/html/html_anchor_element.h | 4 +- bridge/core/html/html_body_element.cc | 4 +- bridge/core/html/html_body_element.h | 4 +- bridge/core/html/html_collection.cc | 2 +- bridge/core/html/html_collection.h | 4 +- bridge/core/html/html_div_element.cc | 4 +- bridge/core/html/html_div_element.h | 4 +- bridge/core/html/html_element.cc | 2 +- bridge/core/html/html_element.h | 4 +- bridge/core/html/html_head_element.cc | 4 +- bridge/core/html/html_head_element.h | 4 +- bridge/core/html/html_html_element.cc | 4 +- bridge/core/html/html_html_element.h | 4 +- bridge/core/html/html_image_element.cc | 4 +- bridge/core/html/html_image_element.h | 4 +- bridge/core/html/html_script_element.cc | 4 +- bridge/core/html/html_script_element.h | 4 +- bridge/core/html/html_template_element.cc | 4 +- bridge/core/html/html_template_element.h | 4 +- bridge/core/html/html_unknown_element.cc | 4 +- bridge/core/html/html_unknown_element.h | 4 +- bridge/core/html/parser/html_parser.cc | 6 +- bridge/core/html/parser/html_parser.h | 4 +- bridge/core/page.cc | 60 +- bridge/core/page.h | 5 +- bridge/core/script_state.cc | 4 +- bridge/core/script_state.h | 4 +- bridge/core/timing/performance.cc | 1243 ++++++++--------- bridge/core/timing/performance.d.ts | 4 + bridge/core/timing/performance.h | 135 +- bridge/foundation/ascii_types.h | 4 +- bridge/foundation/casting.h | 4 +- bridge/foundation/inspector_task_queue.cc | 4 +- bridge/foundation/inspector_task_queue.h | 5 +- bridge/foundation/logging.cc | 4 +- bridge/foundation/logging.h | 16 +- bridge/foundation/native_string.cc | 4 +- bridge/foundation/native_string.h | 4 +- bridge/foundation/native_type.h | 4 +- bridge/foundation/native_value.cc | 4 +- bridge/foundation/native_value.h | 4 +- bridge/foundation/native_value_converter.cc | 4 +- bridge/foundation/native_value_converter.h | 4 +- bridge/foundation/ref_counter.h | 2 - bridge/foundation/string_view.cc | 4 +- bridge/foundation/string_view.h | 8 +- bridge/foundation/task_queue.cc | 4 +- bridge/foundation/task_queue.h | 4 +- bridge/foundation/ui_command_buffer.cc | 4 +- bridge/foundation/ui_command_buffer.h | 4 +- bridge/foundation/ui_task_queue.cc | 4 +- bridge/foundation/ui_task_queue.h | 4 +- bridge/include/webf_bridge.h | 3 - bridge/polyfill/src/bridge.ts | 3 + bridge/polyfill/src/index.ts | 60 +- bridge/polyfill/src/location.ts | 8 +- .../static/idl_templates/base.cc.tpl | 2 +- .../static/idl_templates/dictionary.h.tpl | 2 +- .../idl_templates/global_function.h.tpl | 2 +- .../static/idl_templates/interface.h.tpl | 2 +- .../json_templates/element_factory.cc.tpl | 5 +- .../json_templates/element_factory.h.tpl | 4 +- .../json_templates/element_type_helper.h.tpl | 3 +- .../static/json_templates/make_names.cc.tpl | 4 +- .../static/json_templates/make_names.h.tpl | 4 +- bridge/test/run_integration_test.cc | 4 +- bridge/test/test.cmake | 6 +- ...n_test_context.cc => webf_test_context.cc} | 14 +- ...ken_test_context.h => webf_test_context.h} | 12 +- bridge/test/webf_test_env.cc | 24 +- bridge/test/webf_test_env.h | 6 +- bridge/webf_bridge.cc | 38 +- bridge/webf_bridge_test.cc | 16 +- 241 files changed, 1330 insertions(+), 1349 deletions(-) create mode 100644 bridge/core/timing/performance.d.ts rename bridge/test/{kraken_test_context.cc => webf_test_context.cc} (96%) rename bridge/test/{kraken_test_context.h => webf_test_context.h} (84%) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 6ed1e16f1d..7192890616 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -358,25 +358,27 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") # core/html/html_image_element.h # core/html/html_script_element.cc # core/html/html_script_element.h - core/html/html_unknown_element.cc - core/html/html_unknown_element.h - # Legacy implements, should remove them in the future. - core/dom/legacy/space_split_string.cc - core/dom/legacy/space_split_string.h - core/dom/legacy/element_attributes.cc - core/dom/legacy/element_attributes.h - core/dom/legacy/bounding_client_rect.cc - core/dom/legacy/bounding_client_rect.h - -# core/dom/character_data.cc -# core/dom/character_data.h -# core/dom/comment.cc -# core/dom/comment.h -# core/dom/node.cc -# core/dom/node.h -# core/dom/events/custom_event.cc -# core/dom/events/custom_event.h -# core/dom/events/event.h + core/html/html_unknown_element.cc + core/html/html_unknown_element.h + # Legacy implements, should remove them in the future. + core/dom/legacy/space_split_string.cc + core/dom/legacy/space_split_string.h + core/dom/legacy/element_attributes.cc + core/dom/legacy/element_attributes.h + core/dom/legacy/bounding_client_rect.cc + core/dom/legacy/bounding_client_rect.h + core/timing/performance.cc + core/timing/performance.h + + # core/dom/character_data.cc + # core/dom/character_data.h + # core/dom/comment.cc + # core/dom/comment.h + # core/dom/node.cc + # core/dom/node.h + # core/dom/events/custom_event.cc + # core/dom/events/custom_event.h + # core/dom/events/event.h # core/dom/events/event.cc # core/dom/events/event_listener_map.cc # core/dom/events/event_listener_map.h diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index dfb19a848d..7415ef0b0f 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -6,7 +6,7 @@ #include "atomic_string.h" #include "built_in_string.h" -namespace kraken { +namespace webf { AtomicString AtomicString::Empty(JSContext* ctx) { AtomicString tmp = built_in_string::kempty_string; @@ -181,4 +181,4 @@ const AtomicString AtomicString::ToLowerSlow() const { return AtomicString(ctx_, str); } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index de6b9e27b0..9298d68f87 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -15,7 +15,7 @@ #include "native_string_utils.h" #include "qjs_engine_patch.h" -namespace kraken { +namespace webf { // An AtomicString instance represents a string, and multiple AtomicString // instances can share their string storage if the strings are @@ -89,6 +89,6 @@ class AtomicString { StringKind kind_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_ATOMIC_STRING_H_ diff --git a/bridge/bindings/qjs/atomic_string_test.cc b/bridge/bindings/qjs/atomic_string_test.cc index 2cfec0afc4..5f5be57f9b 100644 --- a/bridge/bindings/qjs/atomic_string_test.cc +++ b/bridge/bindings/qjs/atomic_string_test.cc @@ -12,7 +12,7 @@ #include "native_string_utils.h" #include "qjs_engine_patch.h" -using namespace kraken; +using namespace webf; using TestCallback = void (*)(JSContext* ctx); diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index b819455811..9fb95fdc68 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -35,7 +35,7 @@ #include "qjs_window.h" #include "qjs_window_or_worker_global_scope.h" -namespace kraken { +namespace webf { void InstallBindings(ExecutingContext* context) { // Must follow the inheritance order when install. @@ -72,4 +72,4 @@ void InstallBindings(ExecutingContext* context) { QJSElementAttributes::Install(context); } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/binding_initializer.h b/bridge/bindings/qjs/binding_initializer.h index 4cd629ce50..65789e30d0 100644 --- a/bridge/bindings/qjs/binding_initializer.h +++ b/bridge/bindings/qjs/binding_initializer.h @@ -8,12 +8,12 @@ #include <quickjs/quickjs.h> -namespace kraken { +namespace webf { class ExecutingContext; void InstallBindings(ExecutingContext* context); -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDING_INITIALIZER_H diff --git a/bridge/bindings/qjs/converter.h b/bridge/bindings/qjs/converter.h index 9eb8a53a78..9aaed3207b 100644 --- a/bridge/bindings/qjs/converter.h +++ b/bridge/bindings/qjs/converter.h @@ -8,7 +8,7 @@ #include <cassert> -namespace kraken { +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 @@ -25,6 +25,6 @@ struct ConverterBase { using ImplType = typename T::ImplType; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CONVERTER_H diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 8d485e7208..cbf8ad5a33 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -26,7 +26,7 @@ #include "js_event_listener.h" #include "native_string_utils.h" -namespace kraken { +namespace webf { template <typename T> struct is_shared_ptr : std::false_type {}; @@ -428,6 +428,6 @@ struct Converter<Window> : public ConverterBase<Window> { } }; -}; // namespace kraken +}; // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_IMPL_H_ diff --git a/bridge/bindings/qjs/cppgc/garbage_collected.h b/bridge/bindings/qjs/cppgc/garbage_collected.h index f0d49cc1ca..593ac0a8b0 100644 --- a/bridge/bindings/qjs/cppgc/garbage_collected.h +++ b/bridge/bindings/qjs/cppgc/garbage_collected.h @@ -13,7 +13,7 @@ #include "foundation/macros.h" #include "local_handle.h" -namespace kraken { +namespace webf { template <typename T> class MakeGarbageCollectedTrait; @@ -74,6 +74,6 @@ T* MakeGarbageCollected(Args&&... args) { return MakeLocal<T>(MakeGarbageCollectedTrait<T>::Allocate(std::forward<Args>(args)...)).Get(); } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_GARBAGE_COLLECTED_H diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.cc b/bridge/bindings/qjs/cppgc/gc_visitor.cc index 77532f054b..47fd9e2c43 100644 --- a/bridge/bindings/qjs/cppgc/gc_visitor.cc +++ b/bridge/bindings/qjs/cppgc/gc_visitor.cc @@ -5,10 +5,10 @@ #include "gc_visitor.h" #include "bindings/qjs/script_wrappable.h" -namespace kraken { +namespace webf { void GCVisitor::Trace(JSValue value) { JS_MarkValue(runtime_, value, markFunc_); } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/cppgc/gc_visitor.h b/bridge/bindings/qjs/cppgc/gc_visitor.h index cf2ff7b16d..4c336cc794 100644 --- a/bridge/bindings/qjs/cppgc/gc_visitor.h +++ b/bridge/bindings/qjs/cppgc/gc_visitor.h @@ -11,7 +11,7 @@ #include "foundation/macros.h" #include "member.h" -namespace kraken { +namespace webf { class ScriptWrappable; @@ -38,6 +38,6 @@ class GCVisitor final { friend class ScriptWrappable; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_GC_VISITOR_H diff --git a/bridge/bindings/qjs/cppgc/local_handle.h b/bridge/bindings/qjs/cppgc/local_handle.h index b93f6020e7..d14b55280a 100644 --- a/bridge/bindings/qjs/cppgc/local_handle.h +++ b/bridge/bindings/qjs/cppgc/local_handle.h @@ -12,7 +12,7 @@ #include "foundation/macros.h" #include "mutation_scope.h" -namespace kraken { +namespace webf { template <typename T> class LocalTrait; @@ -59,6 +59,6 @@ Local<T> MakeLocal(Args&&... args) { return LocalTrait<T>::Allocate(std::forward<Args>(args)...); } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_LOCAL_HANDLE_H_ diff --git a/bridge/bindings/qjs/cppgc/member.h b/bridge/bindings/qjs/cppgc/member.h index 7d09766746..9b41034ebb 100644 --- a/bridge/bindings/qjs/cppgc/member.h +++ b/bridge/bindings/qjs/cppgc/member.h @@ -13,7 +13,7 @@ #include "foundation/casting.h" #include "mutation_scope.h" -namespace kraken { +namespace webf { class ScriptWrappable; @@ -95,6 +95,6 @@ class Member { JSRuntime* runtime_{nullptr}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MEMBER_H_ diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.cc b/bridge/bindings/qjs/cppgc/mutation_scope.cc index 3f3b0a8c4a..0e6678df88 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.cc +++ b/bridge/bindings/qjs/cppgc/mutation_scope.cc @@ -6,7 +6,7 @@ #include "mutation_scope.h" #include "core/executing_context.h" -namespace kraken { +namespace webf { MemberMutationScope::MemberMutationScope(ExecutingContext* context) : context_(context) { context->SetMutationScope(*this); @@ -42,4 +42,4 @@ void MemberMutationScope::ApplyRecord() { } } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.h b/bridge/bindings/qjs/cppgc/mutation_scope.h index 73044d7a83..918985e01d 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.h +++ b/bridge/bindings/qjs/cppgc/mutation_scope.h @@ -10,7 +10,7 @@ #include <unordered_map> #include "foundation/macros.h" -namespace kraken { +namespace webf { class ExecutingContext; class ScriptWrappable; @@ -39,6 +39,6 @@ class MemberMutationScope { std::unordered_map<ScriptWrappable*, int> mutation_records_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_CPPGC_MUTATION_SCOPE_H_ diff --git a/bridge/bindings/qjs/dictionary_base.cc b/bridge/bindings/qjs/dictionary_base.cc index 20d60b6a59..28427ebe61 100644 --- a/bridge/bindings/qjs/dictionary_base.cc +++ b/bridge/bindings/qjs/dictionary_base.cc @@ -5,7 +5,7 @@ #include "dictionary_base.h" -namespace kraken { +namespace webf { JSValue DictionaryBase::toQuickJS(JSContext* ctx) const { JSValue object = JS_NewObject(ctx); @@ -15,4 +15,4 @@ JSValue DictionaryBase::toQuickJS(JSContext* ctx) const { return object; } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/dictionary_base.h b/bridge/bindings/qjs/dictionary_base.h index eed3755864..a27db0d0f3 100644 --- a/bridge/bindings/qjs/dictionary_base.h +++ b/bridge/bindings/qjs/dictionary_base.h @@ -8,7 +8,7 @@ #include "bindings/qjs/cppgc/garbage_collected.h" -namespace kraken { +namespace webf { // 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 @@ -34,6 +34,6 @@ class DictionaryBase { virtual bool FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dictionary) const = 0; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_DICTIONARY_BASE_H_ diff --git a/bridge/bindings/qjs/exception_message.cc b/bridge/bindings/qjs/exception_message.cc index dd08a05763..a3a1905376 100644 --- a/bridge/bindings/qjs/exception_message.cc +++ b/bridge/bindings/qjs/exception_message.cc @@ -6,7 +6,7 @@ #include "exception_message.h" #include <vector> -namespace kraken { +namespace webf { std::string ExceptionMessage::FormatString(const char* format, ...) { va_list args; @@ -50,4 +50,4 @@ std::string ExceptionMessage::ArgumentNullOrIncorrectType(int argument_index, co return FormatString("The %d argument provided is either null, or an invalid %s object.", argument_index, expect_type); } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/exception_message.h b/bridge/bindings/qjs/exception_message.h index 03915b83f0..b3a56413a9 100644 --- a/bridge/bindings/qjs/exception_message.h +++ b/bridge/bindings/qjs/exception_message.h @@ -8,7 +8,7 @@ #include <string> -namespace kraken { +namespace webf { class ExceptionMessage { public: @@ -20,6 +20,6 @@ class ExceptionMessage { private: }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_EXCEPTION_MESSAGE_H_ diff --git a/bridge/bindings/qjs/exception_state.cc b/bridge/bindings/qjs/exception_state.cc index fc402e05c5..a6262cfc9f 100644 --- a/bridge/bindings/qjs/exception_state.cc +++ b/bridge/bindings/qjs/exception_state.cc @@ -4,7 +4,7 @@ */ #include "exception_state.h" -namespace kraken { +namespace webf { void ExceptionState::ThrowException(JSContext* ctx, ErrorType type, const std::string& message) { switch (type) { @@ -42,4 +42,4 @@ JSValue ExceptionState::ToQuickJS() { return exception_; } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/exception_state.h b/bridge/bindings/qjs/exception_state.h index 8f79084055..df61a0e606 100644 --- a/bridge/bindings/qjs/exception_state.h +++ b/bridge/bindings/qjs/exception_state.h @@ -11,7 +11,7 @@ #define ASSERT_NO_EXCEPTION() ExceptionState().ReturnThis() -namespace kraken { +namespace webf { enum ErrorType { TypeError, InternalError, RangeError, ReferenceError, SyntaxError }; @@ -33,6 +33,6 @@ class ExceptionState { JSValue exception_{JS_NULL}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_EXCEPTION_STATE_H diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 900321b29c..a2d0368c64 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -9,7 +9,7 @@ #include <vector> #include "converter.h" -namespace kraken { +namespace webf { struct IDLTypeBase { using ImplType = void; @@ -67,6 +67,6 @@ struct IDLSequence final : public IDLTypeBase { using ImplType = typename std::vector<T>; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_CONVERTER_TS_TYPE_H_ diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc index 4656c626cd..fc4ce41650 100644 --- a/bridge/bindings/qjs/js_based_event_listener.cc +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -5,7 +5,7 @@ #include "js_based_event_listener.h" -namespace kraken { +namespace webf { // Implements step 2. of "inner invoke". // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke @@ -22,4 +22,4 @@ void JSBasedEventListener::Invoke(ExecutingContext* context, Event* event, Excep JSBasedEventListener::JSBasedEventListener() {} -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index dacf177552..c156db5c82 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -10,7 +10,7 @@ #include "core/dom/events/event_listener.h" #include "core/executing_context.h" -namespace kraken { +namespace webf { // |JSBasedEventListener| is the base class for JS-based event listeners, // i.e. EventListener and EventHandler in the standards. @@ -55,6 +55,6 @@ struct DowncastTraits<JSBasedEventListener> { static bool AllowFrom(const EventListener& event_listener) { return event_listener.IsJSBasedEventListener(); } }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index dd6c63d54a..e247fc97a2 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -9,7 +9,7 @@ #include "core/events/error_event.h" #include "event_type_names.h" -namespace kraken { +namespace webf { std::unique_ptr<JSEventHandler> JSEventHandler::CreateOrNull(JSContext* ctx, JSValue value, @@ -102,4 +102,4 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc void JSEventHandler::Trace(GCVisitor* visitor) const {} -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index b7faba292b..800a3aa37c 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -9,7 +9,7 @@ #include "foundation/casting.h" #include "js_based_event_listener.h" -namespace kraken { +namespace webf { // |JSEventHandler| implements EventHandler in the HTML standard. // https://html.spec.whatwg.org/C/#event-handler-attributes @@ -73,6 +73,6 @@ struct DowncastTraits<JSEventHandler> { } }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_HANDLER_H_ diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index e0cf1125ee..761e925edd 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -6,7 +6,7 @@ #include "js_event_listener.h" #include "core/dom/events/event_target.h" -namespace kraken { +namespace webf { JSEventListener::JSEventListener(std::shared_ptr<QJSFunction> listener) : event_listener_(listener) {} JSValue JSEventListener::GetListenerObject(EventTarget&) { @@ -29,4 +29,4 @@ void JSEventListener::Trace(GCVisitor* visitor) const { event_listener_->Trace(visitor); } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h index 1605831f53..a019b28697 100644 --- a/bridge/bindings/qjs/js_event_listener.h +++ b/bridge/bindings/qjs/js_event_listener.h @@ -9,7 +9,7 @@ #include "foundation/casting.h" #include "js_based_event_listener.h" -namespace kraken { +namespace webf { // |JSEventListener| implements EventListener in the DOM standard. // https://dom.spec.whatwg.org/#callbackdef-eventlistener @@ -51,6 +51,6 @@ struct DowncastTraits<JSEventListener> { } }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_JS_EVENT_LISTENER_H_ diff --git a/bridge/bindings/qjs/member_installer.cc b/bridge/bindings/qjs/member_installer.cc index 4f6993d6c2..24b38a1ee7 100644 --- a/bridge/bindings/qjs/member_installer.cc +++ b/bridge/bindings/qjs/member_installer.cc @@ -8,7 +8,7 @@ #include "core/executing_context.h" #include "qjs_engine_patch.h" -namespace kraken { +namespace webf { int combinePropFlags(JSPropFlag a, JSPropFlag b) { return a | b; @@ -82,4 +82,4 @@ void MemberInstaller::InstallFunctions(ExecutingContext* context, } } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/member_installer.h b/bridge/bindings/qjs/member_installer.h index 3de0c3c6cc..f9ec226fad 100644 --- a/bridge/bindings/qjs/member_installer.h +++ b/bridge/bindings/qjs/member_installer.h @@ -9,7 +9,7 @@ #include <quickjs/quickjs.h> #include <initializer_list> -namespace kraken { +namespace webf { class ExecutingContext; @@ -49,6 +49,6 @@ class MemberInstaller { static void InstallFunctions(ExecutingContext* context, JSValue root, std::initializer_list<FunctionConfig> config); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_MEMBER_INSTALLER_H diff --git a/bridge/bindings/qjs/native_string_utils.cc b/bridge/bindings/qjs/native_string_utils.cc index 14dba23c31..f28dfb2df6 100644 --- a/bridge/bindings/qjs/native_string_utils.cc +++ b/bridge/bindings/qjs/native_string_utils.cc @@ -6,7 +6,7 @@ #include "native_string_utils.h" #include "bindings/qjs/qjs_engine_patch.h" -namespace kraken { +namespace webf { std::unique_ptr<NativeString> jsValueToNativeString(JSContext* ctx, JSValue value) { bool isValueString = true; @@ -55,4 +55,4 @@ std::string jsValueToStdString(JSContext* ctx, JSValue& value) { return str; } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/native_string_utils.h b/bridge/bindings/qjs/native_string_utils.h index cc9ef58956..61503a2466 100644 --- a/bridge/bindings/qjs/native_string_utils.h +++ b/bridge/bindings/qjs/native_string_utils.h @@ -14,7 +14,7 @@ #include "foundation/native_string.h" -namespace kraken { +namespace webf { // Convert to string and return a full copy of NativeString from JSValue. std::unique_ptr<NativeString> jsValueToNativeString(JSContext* ctx, JSValue value); @@ -40,6 +40,6 @@ void fromUTF8(const std::string& source, std::basic_string<T, std::char_traits<T result = convertor.from_bytes(source); } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_NATIVE_STRING_UTILS_H diff --git a/bridge/bindings/qjs/pending_promises.cc b/bridge/bindings/qjs/pending_promises.cc index 246bff459d..7cb0189cb3 100644 --- a/bridge/bindings/qjs/pending_promises.cc +++ b/bridge/bindings/qjs/pending_promises.cc @@ -5,10 +5,10 @@ #include "pending_promises.h" #include "script_promise.h" -namespace kraken { +namespace webf { void PendingPromises::TrackPendingPromises(ScriptPromise&& promise) { promises_.emplace_back(promise); } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/pending_promises.h b/bridge/bindings/qjs/pending_promises.h index edf131507c..0bde392784 100644 --- a/bridge/bindings/qjs/pending_promises.h +++ b/bridge/bindings/qjs/pending_promises.h @@ -9,7 +9,7 @@ #include <vector> #include "script_promise.h" -namespace kraken { +namespace webf { class PendingPromises { public: @@ -20,6 +20,6 @@ class PendingPromises { std::vector<ScriptPromise> promises_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 90ecac34fb..13118b0723 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -6,7 +6,7 @@ #include <algorithm> #include "cppgc/gc_visitor.h" -namespace kraken { +namespace webf { bool QJSFunction::IsFunction(JSContext* ctx) { return JS_IsFunction(ctx, function_); @@ -34,4 +34,4 @@ void QJSFunction::Trace(GCVisitor* visitor) const { visitor->Trace(function_); } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 3cfb39e0b4..467b189f00 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -7,7 +7,7 @@ #include "script_value.h" -namespace kraken { +namespace webf { // https://webidl.spec.whatwg.org/#dfn-callback-interface // QJSFunction memory are auto managed by std::shared_ptr. @@ -39,6 +39,6 @@ class QJSFunction { JSValue function_{JS_NULL}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_QJS_FUNCTION_H diff --git a/bridge/bindings/qjs/qjs_interface_bridge.h b/bridge/bindings/qjs/qjs_interface_bridge.h index 5d346ab95b..a3d7aabb47 100644 --- a/bridge/bindings/qjs/qjs_interface_bridge.h +++ b/bridge/bindings/qjs/qjs_interface_bridge.h @@ -9,7 +9,7 @@ #include "core/executing_context.h" #include "script_wrappable.h" -namespace kraken { +namespace webf { template <class QJST, class T> class QJSInterfaceBridge { @@ -24,6 +24,6 @@ class QJSInterfaceBridge { }; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_QJS_INTERFACE_BRIDGE_H_ diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index c830791a90..c0ff4c76c0 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -5,7 +5,7 @@ #include "script_promise.h" #include "qjs_engine_patch.h" -namespace kraken { +namespace webf { ScriptPromise::ScriptPromise(JSContext* ctx, JSValue promise) : ctx_(ctx) { if (JS_IsUndefined(promise) || JS_IsNull(promise)) @@ -24,4 +24,4 @@ JSValue ScriptPromise::ToQuickJS() { void ScriptPromise::Trace(GCVisitor* visitor) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index d5b537a119..c16eff4a05 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -9,7 +9,7 @@ #include "foundation/macros.h" #include "script_value.h" -namespace kraken { +namespace webf { // ScriptPromise is the class for representing Promise values in C++ world. // ScriptPromise holds a Promise. @@ -31,6 +31,6 @@ class ScriptPromise final { ScriptValue promise_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_H_ diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index a4d8bd1cf8..edff510c0d 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -6,7 +6,7 @@ #include "core/executing_context.h" #include "pending_promises.h" -namespace kraken { +namespace webf { std::shared_ptr<ScriptPromiseResolver> ScriptPromiseResolver::Create(ExecutingContext* context) { return std::make_shared<ScriptPromiseResolver>(context); @@ -54,4 +54,4 @@ void ScriptPromiseResolver::ResolveOrRejectImmediately(JSValue value) { context_->DrainPendingPromiseJobs(); } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 338c453abb..ba60076d9d 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -9,7 +9,7 @@ #include "script_promise.h" #include "to_quickjs.h" -namespace kraken { +namespace webf { class ScriptPromiseResolver { public: @@ -65,6 +65,6 @@ class ScriptPromiseResolver { JSValue reject_func_{JS_NULL}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 4ce5c2f993..4d6e4c329c 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -11,7 +11,7 @@ #include "qjs_bounding_client_rect.h" #include "qjs_engine_patch.h" -namespace kraken { +namespace webf { ScriptValue ScriptValue::CreateErrorObject(JSContext* ctx, const char* errmsg) { JS_ThrowInternalError(ctx, "%s", errmsg); @@ -142,4 +142,4 @@ void ScriptValue::Trace(GCVisitor* visitor) { visitor->Trace(value_); } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 277dd91cd6..dd0b016132 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -13,7 +13,7 @@ #include "foundation/macros.h" #include "foundation/native_string.h" -namespace kraken { +namespace webf { class ExecutingContext; class WrapperTypeInfo; @@ -70,6 +70,6 @@ class ScriptValue final { JSValue value_{JS_NULL}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_SCRIPT_VALUE_H diff --git a/bridge/bindings/qjs/script_value_test.cc b/bridge/bindings/qjs/script_value_test.cc index be100070ac..f80136ae12 100644 --- a/bridge/bindings/qjs/script_value_test.cc +++ b/bridge/bindings/qjs/script_value_test.cc @@ -9,7 +9,7 @@ #include "atomic_string.h" #include "gtest/gtest.h" -using namespace kraken; +using namespace webf; using TestCallback = void (*)(JSContext* ctx); diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 9d7f848721..7148f6bf01 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -7,7 +7,7 @@ #include "core/executing_context.h" #include "cppgc/gc_visitor.h" -namespace kraken { +namespace webf { ScriptWrappable::ScriptWrappable(JSContext* ctx) : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), context_(ExecutingContext::From(ctx)) {} @@ -166,4 +166,4 @@ void ScriptWrappable::InitializeQuickJSObject() { JS_SetPrototype(ctx_, jsObject_, prototype); } -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 0c1cc9b629..1828c309d7 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -11,7 +11,7 @@ #include "foundation/macros.h" #include "wrapper_type_info.h" -namespace kraken { +namespace webf { class ScriptValue; class GCVisitor; @@ -84,6 +84,6 @@ Local<T>::~Local<T>() { } } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_SCRIPT_WRAPPABLE_H diff --git a/bridge/bindings/qjs/source_location.cc b/bridge/bindings/qjs/source_location.cc index 7b1bc4b573..26b66687a8 100644 --- a/bridge/bindings/qjs/source_location.cc +++ b/bridge/bindings/qjs/source_location.cc @@ -4,7 +4,7 @@ */ #include "source_location.h" -namespace kraken { +namespace webf { std::unique_ptr<SourceLocation> SourceLocation::Capture(const std::string& url, unsigned int line_number, @@ -17,4 +17,4 @@ SourceLocation::SourceLocation(const std::string& url, unsigned int line_number, SourceLocation::~SourceLocation() {} -} // namespace kraken +} // namespace webf diff --git a/bridge/bindings/qjs/source_location.h b/bridge/bindings/qjs/source_location.h index ca21842dd5..e82df9548f 100644 --- a/bridge/bindings/qjs/source_location.h +++ b/bridge/bindings/qjs/source_location.h @@ -8,7 +8,7 @@ #include <memory> #include <string> -namespace kraken { +namespace webf { class ExecutingContext; @@ -31,6 +31,6 @@ class SourceLocation { unsigned column_number_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_SOURCE_LOCATION_H_ diff --git a/bridge/bindings/qjs/to_quickjs.h b/bridge/bindings/qjs/to_quickjs.h index 9e3c599ccc..71d9fba352 100644 --- a/bridge/bindings/qjs/to_quickjs.h +++ b/bridge/bindings/qjs/to_quickjs.h @@ -12,7 +12,7 @@ #include "qjs_engine_patch.h" #include "script_wrappable.h" -namespace kraken { +namespace webf { // Arithmetic values inline JSValue toQuickJS(JSContext* ctx, double v) { @@ -50,6 +50,6 @@ inline JSValue toQuickJS(JSContext* ctx, ArrayBufferData data) { return JS_NewArrayBufferCopy(ctx, data.buffer, data.length); } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_TO_QUICKJS_H_ diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 61b407153f..8c8ed52a10 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -9,7 +9,7 @@ #include <cassert> #include "bindings/qjs/qjs_engine_patch.h" -namespace kraken { +namespace webf { // Define all built-in wrapper class id. enum { @@ -23,6 +23,7 @@ enum { JS_CLASS_NODE, JS_CLASS_ELEMENT, JS_CLASS_SCREEN, + JS_CLASS_PERFORMANCE, JS_CLASS_DOCUMENT, JS_CLASS_CHARACTER_DATA, JS_CLASS_TEXT, @@ -90,6 +91,6 @@ class WrapperTypeInfo final { StringPropertyCheckerHandler string_property_checker_handler_{nullptr}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_WRAPPER_TYPE_INFO_H diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index 2c3336cc7d..19e86e5647 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -7,7 +7,7 @@ #include "core/dom/element.h" #include "core/executing_context.h" -namespace kraken { +namespace webf { template <typename CharacterType> inline bool isASCIILower(CharacterType character) { @@ -153,4 +153,4 @@ AtomicString CSSStyleDeclaration::InternalRemoveProperty(std::string& name) { return return_value; } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/css/legacy/css_style_declaration.h b/bridge/core/css/legacy/css_style_declaration.h index 79b0e26196..6293524e53 100644 --- a/bridge/core/css/legacy/css_style_declaration.h +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -12,7 +12,7 @@ #include "bindings/qjs/script_value.h" #include "bindings/qjs/script_wrappable.h" -namespace kraken { +namespace webf { class Element; @@ -44,6 +44,6 @@ class CSSStyleDeclaration : public ScriptWrappable { int32_t owner_element_target_id_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_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 index e3bc4fe637..d0bb0cc0b0 100644 --- a/bridge/core/css/legacy/css_style_declaration_test.cc +++ b/bridge/core/css/legacy/css_style_declaration_test.cc @@ -7,7 +7,7 @@ #include "page.h" #include "webf_test_env.h" -using namespace kraken; +using namespace webf; TEST(CSSStyleDeclaration, setStyleData) { bool static errorCalled = false; diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index 8d4ebb4e5e..f26f975dee 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -16,7 +16,7 @@ #include "foundation/native_string.h" #define WEBF_EXPORT __attribute__((__visibility__("default"))) -namespace kraken { +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); @@ -94,6 +94,6 @@ struct DartMethodPointer { #endif }; -} // namespace kraken +} // namespace webf #endif diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/dom/binding_object.cc index 8cff37c8aa..039d71f2b5 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/dom/binding_object.cc @@ -9,7 +9,7 @@ #include "core/executing_context.h" #include "foundation/logging.h" -namespace kraken { +namespace webf { void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_object, NativeValue* return_value, @@ -42,7 +42,7 @@ NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, return Native_NewNull(); } - KRAKEN_LOG(VERBOSE) << " binding object_ " << &binding_object_; + WEBF_LOG(VERBOSE) << " binding object_ " << &binding_object_; NativeValue return_value = Native_NewNull(); binding_object_->invoke_bindings_methods_from_native(binding_object_, &return_value, method.ToNativeString().release(), argc, argv); @@ -63,4 +63,4 @@ NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, return InvokeBindingMethod(binding_call_methods::ksetPropertyMagic, 2, argv, exception_state); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/binding_object.h b/bridge/core/dom/binding_object.h index 3dd2e8bf08..3b9c9a71bf 100644 --- a/bridge/core/dom/binding_object.h +++ b/bridge/core/dom/binding_object.h @@ -10,7 +10,7 @@ #include "bindings/qjs/atomic_string.h" #include "foundation/native_value.h" -namespace kraken { +namespace webf { class BindingObject; class NativeBindingObject; @@ -72,6 +72,6 @@ class BindingObject { NativeBindingObject* binding_object_{new NativeBindingObject(this)}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_BINDING_OBJECT_H_ diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index 97b1073956..8931b0b64a 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -6,7 +6,7 @@ #include "character_data.h" #include "core/dom/document.h" -namespace kraken { +namespace webf { void CharacterData::setData(const AtomicString& data) { data_ = data; @@ -21,4 +21,4 @@ CharacterData::CharacterData(TreeScope& tree_scope, const AtomicString& text, No assert(type == kCreateOther || type == kCreateText); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index 4dbd00deef..ccaebfd6e5 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -8,7 +8,7 @@ #include "node.h" -namespace kraken { +namespace webf { class Document; @@ -34,6 +34,6 @@ struct DowncastTraits<CharacterData> { static bool AllowFrom(const Node& node) { return node.IsCharacterDataNode(); } }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CHARACTER_DATA_H diff --git a/bridge/core/dom/child_node_list.cc b/bridge/core/dom/child_node_list.cc index 07c3e778d9..112fc34553 100644 --- a/bridge/core/dom/child_node_list.cc +++ b/bridge/core/dom/child_node_list.cc @@ -6,7 +6,7 @@ #include "child_node_list.h" #include "bindings/qjs/cppgc/gc_visitor.h" -namespace kraken { +namespace webf { ChildNodeList::ChildNodeList(ContainerNode* parent) : parent_(parent), NodeList(parent->ctx()) {} ChildNodeList::~ChildNodeList() = default; @@ -47,4 +47,4 @@ void ChildNodeList::Trace(GCVisitor* visitor) const { NodeList::Trace(visitor); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h index da44e9087a..77ab83ff43 100644 --- a/bridge/core/dom/child_node_list.h +++ b/bridge/core/dom/child_node_list.h @@ -11,7 +11,7 @@ #include "core/dom/container_node.h" #include "core/dom/node_list.h" -namespace kraken { +namespace webf { class ExceptionState; @@ -48,6 +48,6 @@ class ChildNodeList : public NodeList { mutable CollectionIndexCache<ChildNodeList, Node> collection_index_cache_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_CHILD_NODE_LIST_H_ diff --git a/bridge/core/dom/collection_index_cache.h b/bridge/core/dom/collection_index_cache.h index 0cffa5d41e..c784d8eb28 100644 --- a/bridge/core/dom/collection_index_cache.h +++ b/bridge/core/dom/collection_index_cache.h @@ -11,7 +11,7 @@ #include "bindings/qjs/cppgc/gc_visitor.h" #include "foundation/macros.h" -namespace kraken { +namespace webf { template <typename Collection, typename NodeType> class CollectionIndexCache { @@ -189,6 +189,6 @@ inline NodeType* CollectionIndexCache<Collection, NodeType>::NodeAfterCachedNode return current_node; } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_COLLECTION_INDEX_CACHE_H_ diff --git a/bridge/core/dom/comment.cc b/bridge/core/dom/comment.cc index 7044d8de9d..d1f9cc7668 100644 --- a/bridge/core/dom/comment.cc +++ b/bridge/core/dom/comment.cc @@ -7,7 +7,7 @@ #include "document.h" #include "tree_scope.h" -namespace kraken { +namespace webf { Comment* Comment::Create(ExecutingContext* context, ExceptionState& exception_state) { return MakeGarbageCollected<Comment>(*context->document(), ConstructionType::kCreateOther); @@ -34,4 +34,4 @@ Node* Comment::Clone(Document& factory, CloneChildrenFlag flag) const { return Create(factory); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/comment.h b/bridge/core/dom/comment.h index 922f94ef50..6d69033ff6 100644 --- a/bridge/core/dom/comment.h +++ b/bridge/core/dom/comment.h @@ -7,7 +7,7 @@ #include "character_data.h" -namespace kraken { +namespace webf { class Comment : public CharacterData { DEFINE_WRAPPERTYPEINFO(); @@ -25,6 +25,6 @@ class Comment : public CharacterData { Node* Clone(Document&, CloneChildrenFlag) const override; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_COMMENT_H diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index a5ae108cdb..d0408b6613 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -10,7 +10,7 @@ #include "document_fragment.h" #include "node_traversal.h" -namespace kraken { +namespace webf { HTMLCollection* ContainerNode::Children() { // TODO: add children implements. @@ -450,4 +450,4 @@ void ContainerNode::Trace(GCVisitor* visitor) const { Node::Trace(visitor); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index 8b5306f9ef..ba3ccadd3a 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -11,7 +11,7 @@ #include "node.h" #include "node_list.h" -namespace kraken { +namespace webf { class HTMLCollection; @@ -120,6 +120,6 @@ struct DowncastTraits<ContainerNode> { static bool AllowFrom(const Node& node) { return node.IsContainerNode(); } }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_CONTAINER_NODE_H_ diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 80a75e847b..3b653baeda 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -14,7 +14,7 @@ #include "foundation/ascii_types.h" #include "html_element_factory.h" -namespace kraken { +namespace webf { Document* Document::Create(ExecutingContext* context, ExceptionState& exception_state) { return MakeGarbageCollected<Document>(context); @@ -208,4 +208,4 @@ void Document::Trace(GCVisitor* visitor) const { ContainerNode::Trace(visitor); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index b387f8a9de..58063872e0 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -14,7 +14,7 @@ #include "scripted_animation_controller.h" #include "tree_scope.h" -namespace kraken { +namespace webf { class HTMLBodyElement; class HTMLHeadElement; @@ -79,6 +79,6 @@ class Document : public ContainerNode, public TreeScope { ScriptAnimationController script_animation_controller_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_DOCUMENT_H diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index d1e5a390b2..3c9084f3b5 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -6,7 +6,7 @@ #include "document.h" #include "events/event_target.h" -namespace kraken { +namespace webf { DocumentFragment* DocumentFragment::Create(Document& document) { return MakeGarbageCollected<DocumentFragment>(&document, ConstructionType::kCreateDocumentFragment); @@ -48,4 +48,4 @@ bool DocumentFragment::ChildTypeAllowed(NodeType type) const { } } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/document_fragment.h b/bridge/core/dom/document_fragment.h index 91e120c8b5..58ebf3c38a 100644 --- a/bridge/core/dom/document_fragment.h +++ b/bridge/core/dom/document_fragment.h @@ -7,7 +7,7 @@ #include "container_node.h" -namespace kraken { +namespace webf { class DocumentFragment : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); @@ -40,6 +40,6 @@ struct DowncastTraits<DocumentFragment> { static bool AllowFrom(const Node& node) { return node.IsDocumentFragment(); } }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_DOCUMENT_FRAGMENT_H diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 14be2a5ec4..a23463599c 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -5,7 +5,7 @@ #include "gtest/gtest.h" #include "kraken_test_env.h" -using namespace kraken; +using namespace webf; TEST(Document, createElement) { bool static errorCalled = false; @@ -15,7 +15,7 @@ TEST(Document, createElement) { EXPECT_STREQ(message.c_str(), "<div/>"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; + WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->GetExecutingContext(); @@ -35,7 +35,7 @@ TEST(Document, body) { EXPECT_STREQ(message.c_str(), "<body/>"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; + WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->GetExecutingContext(); @@ -67,7 +67,7 @@ TEST(Document, createTextNode) { EXPECT_STREQ(message.c_str(), "<div>"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; + WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->GetExecutingContext(); @@ -91,7 +91,7 @@ TEST(Document, createComment) { EXPECT_STREQ(message.c_str(), "<div>"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; + WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->GetExecutingContext(); @@ -115,7 +115,7 @@ TEST(Document, instanceofNode) { EXPECT_STREQ(message.c_str(), "true true true"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; + WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->GetExecutingContext(); @@ -133,7 +133,7 @@ TEST(Document, FreedByOutOfScope) { logCalled = false; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; + WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->GetExecutingContext(); diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 2a43f0d827..8ef831ae0f 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -15,7 +15,7 @@ #include "core/html/parser/html_parser.h" #include "foundation/native_value_converter.h" -namespace kraken { +namespace webf { Element::Element(const AtomicString& tag_name, Document* document, Node::ConstructionType construction_type) : ContainerNode(document, construction_type), tag_name_(tag_name) { @@ -405,4 +405,4 @@ bool Element::ChildTypeAllowed(NodeType type) const { return false; } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 6988b2284c..77828bae2a 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -12,7 +12,7 @@ #include "legacy/element_attributes.h" #include "qjs_scroll_to_options.h" -namespace kraken { +namespace webf { class Element : public ContainerNode { DEFINE_WRAPPERTYPEINFO(); @@ -123,6 +123,6 @@ struct DowncastTraits<Element> { static bool AllowFrom(const Node& node) { return node.IsElementNode(); } }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_ELEMENT_H diff --git a/bridge/core/dom/element_test.cc b/bridge/core/dom/element_test.cc index ba4a4f4c41..0db75f9985 100644 --- a/bridge/core/dom/element_test.cc +++ b/bridge/core/dom/element_test.cc @@ -6,7 +6,7 @@ #include "core/dom/legacy/bounding_client_rect.h" #include "gtest/gtest.h" #include "webf_test_env.h" -using namespace kraken; +using namespace webf; TEST(Element, setAttribute) { bool static errorCalled = false; diff --git a/bridge/core/dom/element_traversal.h b/bridge/core/dom/element_traversal.h index 808c0a8ebe..f027564026 100644 --- a/bridge/core/dom/element_traversal.h +++ b/bridge/core/dom/element_traversal.h @@ -11,7 +11,7 @@ #include "node_traversal.h" #include "traversal_range.h" -namespace kraken { +namespace webf { class HasTagName { KRAKEN_STACK_ALLOCATED(); @@ -408,6 +408,6 @@ inline ElementType* Traversal<ElementType>::NextSibling(const Node& current, Mat return element; } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_ELEMENT_TRAVERSAL_H_ diff --git a/bridge/core/dom/empty_node_list.cc b/bridge/core/dom/empty_node_list.cc index 588343fc2a..ec56962c65 100644 --- a/bridge/core/dom/empty_node_list.cc +++ b/bridge/core/dom/empty_node_list.cc @@ -6,7 +6,7 @@ #include "empty_node_list.h" #include "core/dom/node.h" -namespace kraken { +namespace webf { EmptyNodeList::EmptyNodeList(Node* root_node) : owner_(root_node), NodeList(root_node->ctx()) {} @@ -16,4 +16,4 @@ Node* EmptyNodeList::VirtualOwnerNode() const { return &OwnerNode(); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/empty_node_list.h b/bridge/core/dom/empty_node_list.h index 6c8b48a796..ab96808fd0 100644 --- a/bridge/core/dom/empty_node_list.h +++ b/bridge/core/dom/empty_node_list.h @@ -8,7 +8,7 @@ #include "node_list.h" -namespace kraken { +namespace webf { class ExceptionState; @@ -29,6 +29,6 @@ class EmptyNodeList : public NodeList { Node* owner_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_EMPTY_NODE_LIST_H_ diff --git a/bridge/core/dom/events/custom_event.cc b/bridge/core/dom/events/custom_event.cc index 19d246b9b6..2e114450e2 100644 --- a/bridge/core/dom/events/custom_event.cc +++ b/bridge/core/dom/events/custom_event.cc @@ -8,7 +8,7 @@ #include <utility> -namespace kraken { +namespace webf { void bindCustomEvent(std::unique_ptr<ExecutionContext>& context) { JSValue constructor = context->contextData()->constructorForType(&customEventTypeInfo); @@ -130,4 +130,4 @@ IMPL_PROPERTY_GETTER(CustomEvent, detail)(JSContext* ctx, JSValue this_val, int return JS_DupValue(ctx, customEventInstance->m_detail); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/events/custom_event.h b/bridge/core/dom/events/custom_event.h index ecb2570e4b..925735dbea 100644 --- a/bridge/core/dom/events/custom_event.h +++ b/bridge/core/dom/events/custom_event.h @@ -7,7 +7,7 @@ #include "event.h" -namespace kraken { +namespace webf { void bindCustomEvent(ExecutionContext* context); @@ -72,6 +72,6 @@ auto customEventCreator = const WrapperTypeInfo customEventTypeInfo = {"CustomEvent", &eventTypeInfo, customEventCreator}; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CUSTOM_EVENT_H diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 10afe78291..ebaf7d8a82 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -6,7 +6,7 @@ #include "core/executing_context.h" #include "event_target.h" -namespace kraken { +namespace webf { Event* Event::From(ExecutingContext* context, NativeEvent* native_event) { AtomicString event_type = AtomicString::From(context->ctx(), native_event->type); @@ -160,4 +160,4 @@ void Event::SetHandlingPassive(PassiveMode mode) { void Event::Trace(GCVisitor* visitor) const {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index ab163b08f2..decf6b50ec 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -12,7 +12,7 @@ #include "foundation/native_string.h" #include "qjs_event_init.h" -namespace kraken { +namespace webf { class EventTarget; class ExceptionState; @@ -229,6 +229,6 @@ class Event : public ScriptWrappable { EventTarget* current_target_{nullptr}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_EVENT_H diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h index 67f1be90b3..17ab5c8559 100644 --- a/bridge/core/dom/events/event_listener.h +++ b/bridge/core/dom/events/event_listener.h @@ -8,7 +8,7 @@ #include "core/executing_context.h" #include "event.h" -namespace kraken { +namespace webf { class JSBasedEventListener; @@ -57,6 +57,6 @@ class EventListener { friend JSBasedEventListener; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index 9dc4bdff09..e45f099162 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -4,7 +4,7 @@ */ #include "event_listener_map.h" -namespace kraken { +namespace webf { EventListenerMap::EventListenerMap() {} @@ -121,4 +121,4 @@ void EventListenerMap::Trace(GCVisitor* visitor) const { } } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index c3173847a1..fe3256f7ba 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -14,7 +14,7 @@ #include "foundation/macros.h" #include "registered_eventListener.h" -namespace kraken { +namespace webf { class AddEventListenerOptions; class EventListenerOptions; @@ -55,6 +55,6 @@ class EventListenerMap final { std::vector<std::pair<AtomicString, std::unique_ptr<EventListenerVector>>> entries_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_DOM_EVENT_LISTENER_MAP_H_ diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index cb0f1cc61e..d534e2a3b1 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -14,7 +14,7 @@ #include "kraken_test_env.h" #endif -namespace kraken { +namespace webf { static std::atomic<int32_t> global_event_target_id{0}; @@ -260,6 +260,6 @@ void EventTargetWithInlineData::Trace(GCVisitor* visitor) const { data_.Trace(visitor); } -} // namespace kraken +} // namespace webf -// namespace kraken::binding::qjs +// namespace webf::binding::qjs diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 84a8b8113f..6ef48189b4 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -22,7 +22,7 @@ void TEST_invokeBindingMethod(void* nativePtr, void* returnValue, void* method, #define GetPropertyMagic "%g" #define SetPropertyMagic "%s" -namespace kraken { +namespace webf { enum class DispatchEventResult { // Event was not canceled by event handler or default event handler. @@ -282,6 +282,6 @@ class EventTargetWithInlineData : public EventTarget { // friend StyleDeclarationInstance; //}; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_EVENT_TARGET_H diff --git a/bridge/core/dom/events/event_target_impl.h b/bridge/core/dom/events/event_target_impl.h index c1f0378eb8..30e3e7d76e 100644 --- a/bridge/core/dom/events/event_target_impl.h +++ b/bridge/core/dom/events/event_target_impl.h @@ -7,7 +7,7 @@ #include "event_target.h" -namespace kraken { +namespace webf { // Constructible version of EventTarget. Calls to EventTarget // constructor in JavaScript will return an instance of this class. @@ -18,6 +18,6 @@ namespace kraken { // decrease. class EventTargetImpl : public EventTarget {}; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_EVENTS_EVENT_TARGET_IMPL_H_ diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index 48f3558585..66077d52ac 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -6,17 +6,17 @@ #include "gtest/gtest.h" #include "kraken_test_env.h" -using namespace kraken; +using namespace webf; TEST(EventTarget, addEventListener) { bool static errorCalled = false; bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "1234"); logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; + WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->GetExecutingContext(); @@ -31,11 +31,11 @@ TEST(EventTarget, addEventListener) { TEST(EventTarget, removeEventListener) { bool static errorCalled = false; bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; + WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->GetExecutingContext(); @@ -50,12 +50,12 @@ TEST(EventTarget, removeEventListener) { // TEST(EventTarget, setNoEventTargetProperties) { // bool static errorCalled = false; // bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { // logCalled = true; // EXPECT_STREQ(message.c_str(), "{name: 1}"); // }; // auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; +// WEBF_LOG(VERBOSE) << errmsg; // errorCalled = true; // }); // @@ -70,12 +70,12 @@ TEST(EventTarget, removeEventListener) { // TEST(EventTarget, propertyEventHandler) { // bool static errorCalled = false; // bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { // logCalled = true; // EXPECT_STREQ(message.c_str(), "ƒ () 1234"); // }; // auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; +// WEBF_LOG(VERBOSE) << errmsg; // errorCalled = true; // }); // auto context = bridge->GetExecutingContext(); @@ -93,11 +93,11 @@ TEST(EventTarget, removeEventListener) { // TEST(EventTarget, setUnExpectedAttributeEventHandler) { // bool static errorCalled = false; // bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { // logCalled = false; // }; // auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; +// WEBF_LOG(VERBOSE) << errmsg; // errorCalled = true; // }); // auto context = bridge->GetExecutingContext(); @@ -115,12 +115,12 @@ TEST(EventTarget, removeEventListener) { // TEST(EventTarget, propertyEventOnWindow) { // bool static errorCalled = false; // bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { // logCalled = true; // EXPECT_STREQ(message.c_str(), "1234"); // }; // auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; +// WEBF_LOG(VERBOSE) << errmsg; // errorCalled = true; // }); // auto context = bridge->GetExecutingContext(); @@ -135,12 +135,12 @@ TEST(EventTarget, removeEventListener) { // TEST(EventTarget, asyncFunctionCallback) { // bool static errorCalled = false; // bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { // logCalled = true; // EXPECT_STREQ(message.c_str(), "done"); // }; // auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; +// WEBF_LOG(VERBOSE) << errmsg; // errorCalled = true; // }); // auto context = bridge->GetExecutingContext(); @@ -148,7 +148,7 @@ TEST(EventTarget, removeEventListener) { // const img = document.createElement('img'); // img.style.width = '100px'; // img.style.height = '100px'; -// img.src = "assets/kraken.png"; +// img.src = "assets/webf.png"; // document.body.appendChild(img); // const img2 = img.cloneNode(false); // document.body.appendChild(img2); @@ -177,12 +177,12 @@ TEST(EventTarget, removeEventListener) { // TEST(EventTarget, ClassInheritEventTarget) { // bool static errorCalled = false; // bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { // logCalled = true; // EXPECT_STREQ(message.c_str(), "ƒ () ƒ ()"); // }; // auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { -// KRAKEN_LOG(VERBOSE) << errmsg; +// WEBF_LOG(VERBOSE) << errmsg; // errorCalled = true; // }); // auto context = bridge->GetExecutingContext(); @@ -211,11 +211,11 @@ TEST(EventTarget, removeEventListener) { //} // // TEST(EventTarget, dispatchEventOnGC) { -// using namespace kraken; +// using namespace webf; // // bool static errorCalled = false; // bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { // logCalled = true; // EXPECT_STREQ(message.c_str(), "1234"); // }; @@ -255,7 +255,7 @@ TEST(EventTarget, removeEventListener) { // // TEST(EventTarget, globalBindListener) { // bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { // logCalled = true; // EXPECT_STREQ(message.c_str(), "clicked"); // }; @@ -268,7 +268,7 @@ TEST(EventTarget, removeEventListener) { // TEST(EventTarget, shouldKeepAtom) { // auto bridge = TEST_init(); // bool static logCalled = false; -// kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { +// webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { // logCalled = true; // EXPECT_STREQ(message.c_str(), "2"); // }; diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc index 5a1afdaa4d..501eeb8d38 100644 --- a/bridge/core/dom/events/registered_eventListener.cc +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -5,7 +5,7 @@ #include "registered_eventListener.h" #include "qjs_add_event_listener_options.h" -namespace kraken { +namespace webf { RegisteredEventListener::RegisteredEventListener() = default; @@ -59,4 +59,4 @@ bool operator==(const RegisteredEventListener& lhs, const RegisteredEventListene return lhs.Callback()->Matches(*rhs.Callback()) && lhs.Capture() == rhs.Capture(); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/events/registered_eventListener.h b/bridge/core/dom/events/registered_eventListener.h index a9c5873c72..4157916680 100644 --- a/bridge/core/dom/events/registered_eventListener.h +++ b/bridge/core/dom/events/registered_eventListener.h @@ -8,7 +8,7 @@ #include "event_listener.h" #include "foundation/macros.h" -namespace kraken { +namespace webf { class AddEventListenerOptions; class EventListenerOptions; @@ -58,6 +58,6 @@ class RegisteredEventListener final { bool operator==(const RegisteredEventListener&, const RegisteredEventListener&); -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_EVENTS_REGISTERED_EVENTLISTENER_H_ diff --git a/bridge/core/dom/frame_request_callback_collection.cc b/bridge/core/dom/frame_request_callback_collection.cc index 82d025f0ae..ede130b6d6 100644 --- a/bridge/core/dom/frame_request_callback_collection.cc +++ b/bridge/core/dom/frame_request_callback_collection.cc @@ -7,7 +7,7 @@ #include <utility> #include "bindings/qjs/cppgc/gc_visitor.h" -namespace kraken { +namespace webf { std::shared_ptr<FrameCallback> FrameCallback::Create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback) { @@ -54,4 +54,4 @@ void FrameRequestCallbackCollection::Trace(GCVisitor* visitor) const { } } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/frame_request_callback_collection.h b/bridge/core/dom/frame_request_callback_collection.h index accfb434c4..a9b232a1b1 100644 --- a/bridge/core/dom/frame_request_callback_collection.h +++ b/bridge/core/dom/frame_request_callback_collection.h @@ -7,7 +7,7 @@ #include "core/executing_context.h" -namespace kraken { +namespace webf { // |FrameCallback| is an interface type which generalizes callbacks which are // invoked when a script-based animation needs to be resampled. @@ -39,7 +39,7 @@ class FrameRequestCallbackCollection final { std::unordered_map<uint32_t, std::shared_ptr<FrameCallback>> frameCallbacks_; }; -} // namespace kraken +} // namespace webf class frame_request_callback_collection {}; diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc index cc17634fd5..a6c1c8d54b 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.cc +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -6,7 +6,7 @@ #include "bounding_client_rect.h" #include "core/executing_context.h" -namespace kraken { +namespace webf { BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, NativeBoundingClientRect* native_bounding_client_rect) { @@ -30,4 +30,4 @@ BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBounding void BoundingClientRect::Trace(GCVisitor* visitor) const {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index 89ebec3e5d..2e1e5b59d1 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -9,7 +9,7 @@ #include "bindings/qjs/exception_state.h" #include "bindings/qjs/script_wrappable.h" -namespace kraken { +namespace webf { class ExecutingContext; @@ -55,6 +55,6 @@ class BoundingClientRect : public ScriptWrappable { double left_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_LEGACY_BOUNDING_CLIENT_RECT_H_ diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index c0c321cc55..5ab0a2ef55 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -8,7 +8,7 @@ #include "built_in_string.h" #include "core/dom/element.h" -namespace kraken { +namespace webf { static inline bool IsNumberIndex(const StringView& name) { if (name.Empty()) @@ -101,4 +101,4 @@ bool ElementAttributes::IsEquivalent(const ElementAttributes& other) const { void ElementAttributes::Trace(GCVisitor* visitor) const {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index b8abe6f38e..7d624a1b63 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -11,7 +11,7 @@ #include "bindings/qjs/script_wrappable.h" #include "space_split_string.h" -namespace kraken { +namespace webf { class ExceptionState; class Element; @@ -49,6 +49,6 @@ class ElementAttributes : public ScriptWrappable { std::shared_ptr<SpaceSplitString> class_name_{std::make_shared<SpaceSplitString>("")}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ diff --git a/bridge/core/dom/legacy/space_split_string.cc b/bridge/core/dom/legacy/space_split_string.cc index a0fb15e980..6f57494323 100644 --- a/bridge/core/dom/legacy/space_split_string.cc +++ b/bridge/core/dom/legacy/space_split_string.cc @@ -5,7 +5,7 @@ #include "space_split_string.h" -namespace kraken { +namespace webf { std::string SpaceSplitString::m_delimiter{" "}; @@ -57,4 +57,4 @@ bool SpaceSplitString::containsAll(std::string s) { return flag; } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/legacy/space_split_string.h b/bridge/core/dom/legacy/space_split_string.h index eae4450c9f..16aff3bb3e 100644 --- a/bridge/core/dom/legacy/space_split_string.h +++ b/bridge/core/dom/legacy/space_split_string.h @@ -9,7 +9,7 @@ #include <string> #include <vector> -namespace kraken { +namespace webf { class SpaceSplitString { public: @@ -25,6 +25,6 @@ class SpaceSplitString { std::vector<std::string> m_szData; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_LEGACY_SPACE_SPLIT_STRING_H_ diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 576cb49a12..45a1bbfd4c 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -14,7 +14,7 @@ #include "node_traversal.h" #include "text.h" -namespace kraken { +namespace webf { Node* Node::Create(ExecutingContext* context, ExceptionState& exception_state) { exception_state.ThrowException(context->ctx(), ErrorType::TypeError, "Illegal constructor"); @@ -420,4 +420,4 @@ void Node::Trace(GCVisitor* visitor) const { EventTarget::Trace(visitor); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 9547e4b9ec..ae1fa09756 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -14,7 +14,7 @@ #include "node_data.h" #include "tree_scope.h" -namespace kraken { +namespace webf { const int kDOMNodeTypeShift = 2; const int kElementNamespaceTypeShift = 4; @@ -316,6 +316,6 @@ inline void Node::SetParentOrShadowHostNode(ContainerNode* parent) { parent_or_shadow_host_node_ = reinterpret_cast<Node*>(parent); } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_NODE_H diff --git a/bridge/core/dom/node_data.cc b/bridge/core/dom/node_data.cc index bd1b7e0f84..cb5b3b5261 100644 --- a/bridge/core/dom/node_data.cc +++ b/bridge/core/dom/node_data.cc @@ -10,7 +10,7 @@ #include "empty_node_list.h" #include "node_list.h" -namespace kraken { +namespace webf { ChildNodeList* NodeData::GetChildNodeList(ContainerNode& node) { assert(!child_node_list_ || &node == child_node_list_->VirtualOwnerNode()); @@ -37,4 +37,4 @@ void NodeData::Trace(GCVisitor* visitor) const { visitor->Trace(child_node_list_->ToQuickJSUnsafe()); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/node_data.h b/bridge/core/dom/node_data.h index cafa800baf..3e2b84eee8 100644 --- a/bridge/core/dom/node_data.h +++ b/bridge/core/dom/node_data.h @@ -10,7 +10,7 @@ #include "bindings/qjs/cppgc/garbage_collected.h" #include "bindings/qjs/cppgc/gc_visitor.h" -namespace kraken { +namespace webf { class ChildNodeList; class EmptyNodeList; @@ -37,6 +37,6 @@ class NodeData { Member<NodeList> child_node_list_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_NODE_DATA_H_ diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h index 92878f5147..7823be4ef1 100644 --- a/bridge/core/dom/node_list.h +++ b/bridge/core/dom/node_list.h @@ -8,7 +8,7 @@ #include "bindings/qjs/script_wrappable.h" -namespace kraken { +namespace webf { class Node; class ExceptionState; @@ -39,6 +39,6 @@ class NodeList : public ScriptWrappable { protected: }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_NODE_LIST_H_ diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 30f52d81f0..a209f631df 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -57,7 +57,7 @@ TEST(Node, childNodes) { TEST(Node, textNodeHaveEmptyChildNodes) { bool static errorCalled = false; bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); diff --git a/bridge/core/dom/node_traversal.cc b/bridge/core/dom/node_traversal.cc index 2a0efc762a..e7490855f1 100644 --- a/bridge/core/dom/node_traversal.cc +++ b/bridge/core/dom/node_traversal.cc @@ -5,7 +5,7 @@ #include "node_traversal.h" -namespace kraken { +namespace webf { Node* NodeTraversal::NextAncestorSibling(const Node& current) { assert(!current.nextSibling()); @@ -95,4 +95,4 @@ Node* NodeTraversal::PreviousPostOrder(const Node& current, const Node* stay_wit return PreviousAncestorSiblingPostOrder(current, stay_within); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/node_traversal.h b/bridge/core/dom/node_traversal.h index 1f08789700..01245e59a3 100644 --- a/bridge/core/dom/node_traversal.h +++ b/bridge/core/dom/node_traversal.h @@ -11,7 +11,7 @@ #include "node.h" #include "traversal_range.h" -namespace kraken { +namespace webf { class NodeTraversal { KRAKEN_STATIC_ONLY(NodeTraversal); @@ -171,6 +171,6 @@ inline Node* NodeTraversal::ChildAtTemplate(NodeType& parent, unsigned index) { return child; } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_NODE_TRAVERSAL_H_ diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index 50926508ca..6a02710cba 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -6,7 +6,7 @@ #include "scripted_animation_controller.h" #include "frame_request_callback_collection.h" -namespace kraken { +namespace webf { static void handleRAFTransientCallback(void* ptr, int32_t contextId, double highResTimeStamp, const char* errmsg) { auto* frame_callback = static_cast<FrameCallback*>(ptr); @@ -63,4 +63,4 @@ void ScriptAnimationController::Trace(GCVisitor* visitor) const { frame_request_callback_collection_.Trace(visitor); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/scripted_animation_controller.h b/bridge/core/dom/scripted_animation_controller.h index c9f87b92d0..a5dfe6e35c 100644 --- a/bridge/core/dom/scripted_animation_controller.h +++ b/bridge/core/dom/scripted_animation_controller.h @@ -9,7 +9,7 @@ #include "bindings/qjs/cppgc/garbage_collected.h" #include "frame_request_callback_collection.h" -namespace kraken { +namespace webf { class ScriptAnimationController { public: @@ -23,6 +23,6 @@ class ScriptAnimationController { FrameRequestCallbackCollection frame_request_callback_collection_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BINDINGS_QJS_BOM_SCRIPT_ANIMATION_CONTROLLER_H_ diff --git a/bridge/core/dom/text.cc b/bridge/core/dom/text.cc index cbec1f787f..edd507e8d8 100644 --- a/bridge/core/dom/text.cc +++ b/bridge/core/dom/text.cc @@ -6,7 +6,7 @@ #include "text.h" #include "document.h" -namespace kraken { +namespace webf { Text* Text::Create(Document& document, const AtomicString& value) { return MakeGarbageCollected<Text>(document, value, ConstructionType::kCreateText); @@ -33,4 +33,4 @@ Node* Text::Clone(Document& document, CloneChildrenFlag flag) const { return Create(document, data()); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/text.h b/bridge/core/dom/text.h index 6ebac35670..98eece4c47 100644 --- a/bridge/core/dom/text.h +++ b/bridge/core/dom/text.h @@ -8,7 +8,7 @@ #include "character_data.h" -namespace kraken { +namespace webf { class Text : public CharacterData { DEFINE_WRAPPERTYPEINFO(); @@ -37,6 +37,6 @@ struct DowncastTraits<Text> { static bool AllowFrom(const Node& node) { return node.IsTextNode(); }; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_TEXT_H_ diff --git a/bridge/core/dom/traversal_range.h b/bridge/core/dom/traversal_range.h index 727e8b266c..1db9417162 100644 --- a/bridge/core/dom/traversal_range.h +++ b/bridge/core/dom/traversal_range.h @@ -7,7 +7,7 @@ #include "foundation/macros.h" -namespace kraken { +namespace webf { class Node; @@ -118,6 +118,6 @@ using TraversalDescendantRange = TraversalRange<TraversalDescendantIterator<T>>; template <class T> using TraversalInclusiveDescendantRange = TraversalRange<TraversalInclusiveDescendantIterator<T>>; -} // namespace kraken +} // 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 index a6e2989989..ba21eaf88b 100644 --- a/bridge/core/dom/tree_scope.cc +++ b/bridge/core/dom/tree_scope.cc @@ -6,10 +6,10 @@ #include "tree_scope.h" #include "document.h" -namespace kraken { +namespace webf { TreeScope::TreeScope(Document& document) : root_node_(&document), document_(&document) { root_node_->SetTreeScope(this); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/dom/tree_scope.h b/bridge/core/dom/tree_scope.h index 45e810ca76..7eee0515f8 100644 --- a/bridge/core/dom/tree_scope.h +++ b/bridge/core/dom/tree_scope.h @@ -8,7 +8,7 @@ #include <cassert> -namespace kraken { +namespace webf { class ContainerNode; class Document; @@ -38,6 +38,6 @@ class TreeScope { TreeScope* parent_tree_scope_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_TREE_SCOPE_H_ diff --git a/bridge/core/events/close_event.d.ts b/bridge/core/events/close_event.d.ts index 7b5a552c91..5404a269a4 100644 --- a/bridge/core/events/close_event.d.ts +++ b/bridge/core/events/close_event.d.ts @@ -1,5 +1,7 @@ +import {Event} from "../dom/events/event"; + interface CloseEvent extends Event { - readonly code: int64; - readonly reason: string; - readonly wasClean: boolean; + readonly code: int64; + readonly reason: string; + readonly wasClean: boolean; } diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc index 88bc00466f..96bc9a24dc 100644 --- a/bridge/core/events/error_event.cc +++ b/bridge/core/events/error_event.cc @@ -4,7 +4,7 @@ */ #include "error_event.h" -namespace kraken { +namespace webf { ErrorEvent* ErrorEvent::Create(ExecutingContext* context, const std::string& message) { return MakeGarbageCollected<ErrorEvent>(context, message); @@ -39,4 +39,4 @@ ErrorEvent::ErrorEvent(ExecutingContext* context, initializer->lineno(), initializer->colno())) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/events/error_event.h b/bridge/core/events/error_event.h index 7dc0631676..b7f4d2db13 100644 --- a/bridge/core/events/error_event.h +++ b/bridge/core/events/error_event.h @@ -10,7 +10,7 @@ #include "core/dom/events/event.h" #include "qjs_error_event_init.h" -namespace kraken { +namespace webf { class ErrorEvent : public Event { DEFINE_WRAPPERTYPEINFO(); @@ -53,6 +53,6 @@ struct DowncastTraits<ErrorEvent> { static bool AllowFrom(const Event& event) { return event.IsErrorEvent(); } }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_DOM_EVENTS_ERROR_EVENT_H_ diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index 8249c98cde..26ef891941 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -5,7 +5,7 @@ #include "message_event.h" -namespace kraken { +namespace webf { MessageEvent* MessageEvent::Create(ExecutingContext* context, const AtomicString& type, @@ -47,4 +47,4 @@ AtomicString MessageEvent::source() const { return source_; } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/events/message_event.h b/bridge/core/events/message_event.h index 113229de34..9821429a4f 100644 --- a/bridge/core/events/message_event.h +++ b/bridge/core/events/message_event.h @@ -9,7 +9,7 @@ #include "core/dom/events/event.h" #include "qjs_message_event_init.h" -namespace kraken { +namespace webf { class MessageEvent : public Event { DEFINE_WRAPPERTYPEINFO(); @@ -40,6 +40,6 @@ class MessageEvent : public Event { AtomicString source_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_EVENTS_MESSAGE_EVENT_H_ diff --git a/bridge/core/events/touch_event.cc b/bridge/core/events/touch_event.cc index 82d61acfde..c441fbe6f4 100644 --- a/bridge/core/events/touch_event.cc +++ b/bridge/core/events/touch_event.cc @@ -7,7 +7,7 @@ #include "bindings/qjs/qjs_engine_patch.h" #include "page.h" -namespace kraken { +namespace webf { void bindTouchEvent(ExecutionContext* context) { auto* constructor = TouchEvent::instance(context); @@ -260,4 +260,4 @@ IMPL_PROPERTY_GETTER(TouchEvent, shiftKey)(JSContext* ctx, JSValue this_val, int TouchEventInstance::TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent) : EventInstance(event, nativeEvent) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/events/touch_event.h b/bridge/core/events/touch_event.h index 12510b0bf0..99c99b3e4e 100644 --- a/bridge/core/events/touch_event.h +++ b/bridge/core/events/touch_event.h @@ -8,7 +8,7 @@ #include "bindings/qjs/dom/element.h" -namespace kraken { +namespace webf { void bindTouchEvent(ExecutionContext* context); @@ -113,6 +113,6 @@ class TouchEventInstance : public EventInstance { friend TouchEvent; }; -} // namespace kraken +} // namespace webf #endif // BRIDGE_TOUCH_EVENTT_H diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index d86380e4a7..d07f945aa7 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -11,7 +11,7 @@ #include "foundation/logging.h" -namespace kraken { +namespace webf { static std::atomic<int32_t> context_unique_id{0}; @@ -25,15 +25,15 @@ std::unique_ptr<ExecutingContext> createJSContext(int32_t contextId, const JSExc ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) : context_id_(contextId), handler_(handler), owner_(owner), ctx_invalid_(false), unique_id_(context_unique_id++) { -#if ENABLE_PROFILE - auto jsContextStartTime = - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()) - .count(); - auto nativePerformance = Performance::instance(context_)->m_nativePerformance; - nativePerformance.mark(PERF_JS_CONTEXT_INIT_START, jsContextStartTime); - nativePerformance.mark(PERF_JS_CONTEXT_INIT_END); - nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_START); -#endif +//#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; @@ -57,20 +57,20 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& // Binding global object and window. InstallGlobal(); -#if ENABLE_PROFILE - nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); - nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); -#endif +//#if ENABLE_PROFILE +// nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); +// nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); +//#endif - initKrakenPolyFill(this); + initWebFPolyFill(this); for (auto& p : pluginByteCode) { EvaluateByteCode(p.second.bytes, p.second.length); } -#if ENABLE_PROFILE - nativePerformance.mark(PERF_JS_POLYFILL_INIT_END); -#endif +//#if ENABLE_PROFILE +// nativePerformance.mark(PERF_JS_POLYFILL_INIT_END); +//#endif } ExecutingContext::~ExecutingContext() { @@ -396,4 +396,4 @@ bool isContextValid(int32_t contextId) { return valid_contexts[contextId]; } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index d0ec0ba5c5..9747da8efa 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -29,7 +29,7 @@ #include "frame/module_listener_container.h" #include "script_state.h" -namespace kraken { +namespace webf { struct NativeByteCode { uint8_t* bytes; @@ -45,7 +45,7 @@ using JSExceptionHandler = std::function<void(ExecutingContext* context, const c bool isContextValid(int32_t contextId); // An environment in which script can execute. This class exposes the common -// properties of script execution environments on the kraken. +// properties of script execution environments on the webf. // Window : Document : ExecutionContext = 1 : 1 : 1 at any point in time. class ExecutingContext { public: @@ -81,10 +81,10 @@ class ExecutingContext { // not be used after the ExecutionContext is destroyed. DOMTimerCoordinator* Timers(); - // Gets the ModuleListeners which registered by `kraken.addModuleListener API`. + // Gets the ModuleListeners which registered by `webf.addModuleListener API`. ModuleListenerContainer* ModuleListeners(); - // Gets the ModuleCallbacks which from the 4th parameter of `kraken.invokeModule` function. + // Gets the ModuleCallbacks which from the 4th parameter of `webf.invokeModule` function. ModuleCallbackCoordinator* ModuleCallbacks(); // Get all pending promises which are not resolved or rejected. @@ -111,7 +111,7 @@ class ExecutingContext { static void DispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error); static void DispatchGlobalErrorEvent(ExecutingContext* context, JSValueConst error); - // Bytecodes which registered by kraken plugins. + // Bytecodes which registered by webf plugins. static std::unordered_map<std::string, NativeByteCode> pluginByteCode; private: @@ -168,6 +168,6 @@ class ObjectProperty { std::unique_ptr<ExecutingContext> createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_JS_CONTEXT_H diff --git a/bridge/core/executing_context_data.cc b/bridge/core/executing_context_data.cc index ce8962fc99..132ef743d1 100644 --- a/bridge/core/executing_context_data.cc +++ b/bridge/core/executing_context_data.cc @@ -5,7 +5,7 @@ #include "executing_context_data.h" #include "executing_context.h" -namespace kraken { +namespace webf { JSValue ExecutionContextData::constructorForType(const WrapperTypeInfo* type) { auto it = constructor_map_.find(type); @@ -80,4 +80,4 @@ void ExecutionContextData::Dispose() { } } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/executing_context_data.h b/bridge/core/executing_context_data.h index 5e7ad73710..9259aabd05 100644 --- a/bridge/core/executing_context_data.h +++ b/bridge/core/executing_context_data.h @@ -9,7 +9,7 @@ #include <unordered_map> #include "bindings/qjs/wrapper_type_info.h" -namespace kraken { +namespace webf { class ExecutingContext; @@ -36,6 +36,6 @@ class ExecutionContextData final { ExecutingContext* m_context; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CONTEXT_DATA_H diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 65afa376d7..8ab2198d6e 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -7,7 +7,7 @@ #include "page.h" #include "webf_test_env.h" -using namespace kraken; +using namespace webf; TEST(Context, isValid) { { @@ -105,7 +105,7 @@ TEST(Context, unrejectPromiseWillTriggerUnhandledRejectionEvent) { static int logIndex = 0; static std::string logs[] = {"error event cannot read property 'forceNullError' of null", "unhandled event {promise: Promise {...}, reason: Error {...}} true"}; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(logs[logIndex++].c_str(), message.c_str()); }; @@ -173,7 +173,7 @@ TEST(Context, unhandledRejectionEventWillTriggerWhenNotHandled) { static bool logCalled = false; auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; auto bridge = TEST_init(errorHandler); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; @@ -204,7 +204,7 @@ TEST(Context, handledRejectionEventWillTriggerWhenUnHandledRejectHandled) { static bool logCalled = false; auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; auto bridge = TEST_init(errorHandler); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; }; diff --git a/bridge/core/fileapi/array_buffer_data.h b/bridge/core/fileapi/array_buffer_data.h index 4bf861f3b8..ff1ff0e248 100644 --- a/bridge/core/fileapi/array_buffer_data.h +++ b/bridge/core/fileapi/array_buffer_data.h @@ -5,13 +5,13 @@ #ifndef KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ #define KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ -namespace kraken { +namespace webf { struct ArrayBufferData { uint8_t* buffer; int32_t length; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_FILEAPI_ARRAY_BUFFER_DATA_H_ diff --git a/bridge/core/fileapi/blob.cc b/bridge/core/fileapi/blob.cc index 554deeeb11..2fb1e52857 100644 --- a/bridge/core/fileapi/blob.cc +++ b/bridge/core/fileapi/blob.cc @@ -8,7 +8,7 @@ #include "built_in_string.h" #include "core/executing_context.h" -namespace kraken { +namespace webf { class BlobReaderClient { public: @@ -161,4 +161,4 @@ void Blob::AppendBytes(uint8_t* buffer, uint32_t length) { } } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index 9c10a9ad93..7e2af0e46d 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -14,7 +14,7 @@ #include "blob_part.h" #include "blob_property_bag.h" -namespace kraken { +namespace webf { class Blob : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); @@ -72,6 +72,6 @@ class Blob : public ScriptWrappable { std::vector<uint8_t> _data; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_BLOB_H diff --git a/bridge/core/fileapi/blob_part.cc b/bridge/core/fileapi/blob_part.cc index d628974d7f..eedc6c22eb 100644 --- a/bridge/core/fileapi/blob_part.cc +++ b/bridge/core/fileapi/blob_part.cc @@ -5,7 +5,7 @@ #include "blob_part.h" #include "qjs_blob.h" -namespace kraken { +namespace webf { std::shared_ptr<BlobPart> BlobPart::Create(JSContext* ctx, JSValue value, ExceptionState& exception_state) { auto* context = ExecutingContext::From(ctx); @@ -82,4 +82,4 @@ Blob* BlobPart::GetBlob() const { return blob_; } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/fileapi/blob_part.h b/bridge/core/fileapi/blob_part.h index af135002dc..dc6438d0b1 100644 --- a/bridge/core/fileapi/blob_part.h +++ b/bridge/core/fileapi/blob_part.h @@ -11,7 +11,7 @@ #include <utility> #include "bindings/qjs/exception_state.h" -namespace kraken { +namespace webf { class Blob; @@ -50,6 +50,6 @@ class BlobPart { uint32_t byte_length_{0}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_FILEAPI_BLOB_PART_H_ diff --git a/bridge/core/fileapi/blob_property_bag.cc b/bridge/core/fileapi/blob_property_bag.cc index f13ba7f92c..76cfd92112 100644 --- a/bridge/core/fileapi/blob_property_bag.cc +++ b/bridge/core/fileapi/blob_property_bag.cc @@ -4,7 +4,7 @@ */ #include "blob_property_bag.h" -namespace kraken { +namespace webf { std::shared_ptr<BlobPropertyBag> BlobPropertyBag::Create(JSContext* ctx, JSValue value, @@ -27,4 +27,4 @@ void BlobPropertyBag::FillMemberFromQuickjsObject(JSContext* ctx, JSValue value, JS_FreeValue(ctx, typeValue); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/fileapi/blob_property_bag.h b/bridge/core/fileapi/blob_property_bag.h index 686561a6cc..fd4caf75ec 100644 --- a/bridge/core/fileapi/blob_property_bag.h +++ b/bridge/core/fileapi/blob_property_bag.h @@ -9,7 +9,7 @@ #include <memory> #include "core/executing_context.h" -namespace kraken { +namespace webf { class BlobPropertyBag final { public: @@ -24,6 +24,6 @@ class BlobPropertyBag final { std::string m_type; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_FILEAPI_BLOB_PROPERTY_BAG_H_ diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index 5b95381335..d9c4539a97 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -7,7 +7,7 @@ #include "built_in_string.h" #include "foundation/logging.h" -namespace kraken { +namespace webf { void Console::__kraken_print__(ExecutingContext* context, const AtomicString& log, @@ -26,4 +26,4 @@ void Console::__kraken_print__(ExecutingContext* context, const AtomicString& lo printLog(context, stream, "info", nullptr); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index fbe769394e..46d3ee3d4a 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -9,7 +9,7 @@ #include "bindings/qjs/script_value.h" #include "core/executing_context.h" -namespace kraken { +namespace webf { class Console final { public: @@ -20,6 +20,6 @@ class Console final { static void __kraken_print__(ExecutingContext* context, const AtomicString& log, ExceptionState& exception_state); }; -} // namespace kraken +} // namespace webf #endif // KRAKE_CONSOLE_H diff --git a/bridge/core/frame/console_test.cc b/bridge/core/frame/console_test.cc index d427282d4d..f09d7d2459 100644 --- a/bridge/core/frame/console_test.cc +++ b/bridge/core/frame/console_test.cc @@ -7,7 +7,7 @@ #include "gtest/gtest.h" #include "webf_test_env.h" -using namespace kraken; +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 index 8fc6e0acbc..fcb42f0e52 100644 --- a/bridge/core/frame/dom_timer.cc +++ b/bridge/core/frame/dom_timer.cc @@ -13,7 +13,7 @@ #include "kraken_test_env.h" #endif -namespace kraken { +namespace webf { std::shared_ptr<DOMTimer> DOMTimer::create(ExecutingContext* context, const std::shared_ptr<QJSFunction>& callback) { return std::make_shared<DOMTimer>(context, callback); @@ -37,4 +37,4 @@ void DOMTimer::setTimerId(int32_t timerId) { timerId_ = timerId; } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/dom_timer.h b/bridge/core/frame/dom_timer.h index d97d6144d9..5db4ee944d 100644 --- a/bridge/core/frame/dom_timer.h +++ b/bridge/core/frame/dom_timer.h @@ -9,7 +9,7 @@ #include "bindings/qjs/script_wrappable.h" #include "dom_timer_coordinator.h" -namespace kraken { +namespace webf { class DOMTimer { public: @@ -37,6 +37,6 @@ class DOMTimer { std::shared_ptr<QJSFunction> callback_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_DOM_TIMER_H diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 12534c1aae..a0dc2a3364 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -11,7 +11,7 @@ #include "kraken_test_env.h" #endif -namespace kraken { +namespace webf { static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { auto* context = timer->context(); @@ -63,4 +63,4 @@ std::shared_ptr<DOMTimer> DOMTimerCoordinator::getTimerById(int32_t timerId) { return m_activeTimers[timerId]; } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/dom_timer_test.cc b/bridge/core/frame/dom_timer_test.cc index 3af079fe55..a742e9b0f6 100644 --- a/bridge/core/frame/dom_timer_test.cc +++ b/bridge/core/frame/dom_timer_test.cc @@ -73,7 +73,7 @@ clearTimeout(timer); TEST(Timer, clearTimeoutWhenSetTimeout) { auto bridge = TEST_init(); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; std::string code = R"( let timer = setTimeout(() => { diff --git a/bridge/core/frame/legacy/location.cc b/bridge/core/frame/legacy/location.cc index 6ac3aaf1fa..6cbb58900a 100644 --- a/bridge/core/frame/legacy/location.cc +++ b/bridge/core/frame/legacy/location.cc @@ -5,9 +5,9 @@ #include "location.h" #include "core/executing_context.h" -namespace kraken { +namespace webf { -void Location::__kraken_location_reload__(ExecutingContext* context, ExceptionState& exception_state) { +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."); @@ -18,4 +18,4 @@ void Location::__kraken_location_reload__(ExecutingContext* context, ExceptionSt context->dartMethodPtr()->reloadApp(context->contextId()); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/legacy/location.d.ts b/bridge/core/frame/legacy/location.d.ts index 8567de2aea..c80ee732fd 100644 --- a/bridge/core/frame/legacy/location.d.ts +++ b/bridge/core/frame/legacy/location.d.ts @@ -1 +1 @@ -declare const __kraken_location_reload__: () => void; +declare const __webf_location_reload__: () => void; diff --git a/bridge/core/frame/legacy/location.h b/bridge/core/frame/legacy/location.h index 91a612c75e..5a1b838bf1 100644 --- a/bridge/core/frame/legacy/location.h +++ b/bridge/core/frame/legacy/location.h @@ -8,13 +8,13 @@ #include "bindings/qjs/exception_state.h" #include "bindings/qjs/script_wrappable.h" -namespace kraken { +namespace webf { class Location { public: - static void __kraken_location_reload__(ExecutingContext* context, ExceptionState& exception_state); + static void __webf_location_reload__(ExecutingContext* context, ExceptionState& exception_state); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_LOCATION_H diff --git a/bridge/core/frame/module_callback.cc b/bridge/core/frame/module_callback.cc index c2fec46e06..46380d7a77 100644 --- a/bridge/core/frame/module_callback.cc +++ b/bridge/core/frame/module_callback.cc @@ -4,7 +4,7 @@ */ #include "module_callback.h" -namespace kraken { +namespace webf { std::shared_ptr<ModuleCallback> ModuleCallback::Create(std::shared_ptr<QJSFunction> function) { return std::make_shared<ModuleCallback>(function); @@ -16,4 +16,4 @@ std::shared_ptr<QJSFunction> ModuleCallback::value() { return function_; } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/module_callback.h b/bridge/core/frame/module_callback.h index 9a730a660b..e0145a06da 100644 --- a/bridge/core/frame/module_callback.h +++ b/bridge/core/frame/module_callback.h @@ -8,9 +8,9 @@ #include <quickjs/list.h> #include "bindings/qjs/qjs_function.h" -namespace kraken { +namespace webf { -// ModuleCallback is an asynchronous callback function, usually from the 4th parameter of `kraken.invokeModule` +// 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 { @@ -24,6 +24,6 @@ class ModuleCallback { std::shared_ptr<QJSFunction> function_{nullptr}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_MODULE_CALLBACK_H diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc index df319f5424..ee38f913e3 100644 --- a/bridge/core/frame/module_callback_coordinator.cc +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -4,7 +4,7 @@ */ #include "module_callback_coordinator.h" -namespace kraken { +namespace webf { void ModuleCallbackCoordinator::AddModuleCallbacks(std::shared_ptr<ModuleCallback>&& callback) { listeners_.push_front(callback); @@ -20,4 +20,4 @@ const std::forward_list<std::shared_ptr<ModuleCallback>>* ModuleCallbackCoordina ModuleCallbackCoordinator::ModuleCallbackCoordinator() {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_callback_coordinator.h index 98f548af9d..4df17fe1aa 100644 --- a/bridge/core/frame/module_callback_coordinator.h +++ b/bridge/core/frame/module_callback_coordinator.h @@ -11,7 +11,7 @@ #include "module_callback.h" #include "module_manager.h" -namespace kraken { +namespace webf { class ModuleListener; @@ -29,6 +29,6 @@ class ModuleCallbackCoordinator final { friend ModuleListener; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_MODULE_CALLBACK_COORDINATOR_H diff --git a/bridge/core/frame/module_listener.cc b/bridge/core/frame/module_listener.cc index 00dff2a8dd..aee52e812a 100644 --- a/bridge/core/frame/module_listener.cc +++ b/bridge/core/frame/module_listener.cc @@ -6,7 +6,7 @@ #include <utility> -namespace kraken { +namespace webf { std::shared_ptr<ModuleListener> ModuleListener::Create(const std::shared_ptr<QJSFunction>& function) { return std::make_shared<ModuleListener>(function); @@ -14,4 +14,4 @@ std::shared_ptr<ModuleListener> ModuleListener::Create(const std::shared_ptr<QJS ModuleListener::ModuleListener(std::shared_ptr<QJSFunction> function) : function_(std::move(function)) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h index 33740c1dc7..237a154975 100644 --- a/bridge/core/frame/module_listener.h +++ b/bridge/core/frame/module_listener.h @@ -7,12 +7,12 @@ #include "bindings/qjs/qjs_function.h" -namespace kraken { +namespace webf { class ModuleCallbackCoordinator; class ModuleListenerContainer; -// ModuleListener is an persistent callback function. Registered from user with `kraken.addModuleListener` method. +// 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 { @@ -27,6 +27,6 @@ class ModuleListener { friend ModuleCallbackCoordinator; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_MODULE_LISTENER_H diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc index 340d132e9c..ee82da24f8 100644 --- a/bridge/core/frame/module_listener_container.cc +++ b/bridge/core/frame/module_listener_container.cc @@ -4,10 +4,10 @@ */ #include "module_listener_container.h" -namespace kraken { +namespace webf { void ModuleListenerContainer::AddModuleListener(const std::shared_ptr<ModuleListener>& listener) { listeners_.push_front(listener); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/module_listener_container.h b/bridge/core/frame/module_listener_container.h index a577ad5a91..aceccf83f5 100644 --- a/bridge/core/frame/module_listener_container.h +++ b/bridge/core/frame/module_listener_container.h @@ -8,7 +8,7 @@ #include <forward_list> #include "module_listener.h" -namespace kraken { +namespace webf { class ModuleListenerContainer final { public: @@ -19,6 +19,6 @@ class ModuleListenerContainer final { friend ModuleListener; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_MODULE_LISTENER_CONTAINER_H diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 3125b61299..77dc125d71 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -6,7 +6,7 @@ #include "core/executing_context.h" #include "module_callback.h" -namespace kraken { +namespace webf { struct ModuleContext { ExecutingContext* context; @@ -126,4 +126,4 @@ void ModuleManager::__kraken_add_module_listener__(ExecutingContext* context, context->ModuleListeners()->AddModuleListener(listener); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index afc8aefe57..9542498c3a 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -10,7 +10,7 @@ #include "bindings/qjs/qjs_function.h" #include "module_callback.h" -namespace kraken { +namespace webf { class ModuleManager { public: @@ -34,6 +34,6 @@ class ModuleManager { ExceptionState& exception); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_MODULE_MANAGER_H diff --git a/bridge/core/frame/module_manager_test.cc b/bridge/core/frame/module_manager_test.cc index 1e35edb7bb..da3217a484 100644 --- a/bridge/core/frame/module_manager_test.cc +++ b/bridge/core/frame/module_manager_test.cc @@ -11,7 +11,7 @@ namespace webf { TEST(ModuleManager, ShouldReturnCorrectValue) { bool static errorCalled = false; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; + webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) {}; auto context = bridge->GetExecutingContext(); @@ -25,7 +25,7 @@ let object = { } } }; -let result = kraken.methodChannel.invokeMethod('abc', 'fn', object); +let result = webf.methodChannel.invokeMethod('abc', 'fn', object); console.log(result); )"); context->EvaluateJavaScript(code.c_str(), code.size(), "vm://", 0); diff --git a/bridge/core/frame/screen.cc b/bridge/core/frame/screen.cc index 10f579546f..74b669a9e0 100644 --- a/bridge/core/frame/screen.cc +++ b/bridge/core/frame/screen.cc @@ -7,11 +7,11 @@ #include "core/frame/window.h" #include "foundation/native_value_converter.h" -namespace kraken { +namespace webf { Screen::Screen(Window* window, NativeBindingObject* native_binding_object) : EventTargetWithInlineData(window->GetExecutingContext()) { BindDartObject(native_binding_object); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/screen.h b/bridge/core/frame/screen.h index 8844dea9e6..629fd14250 100644 --- a/bridge/core/frame/screen.h +++ b/bridge/core/frame/screen.h @@ -7,7 +7,7 @@ #include "core/dom/events/event_target.h" -namespace kraken { +namespace webf { class Window; @@ -23,6 +23,6 @@ class Screen : public EventTargetWithInlineData { private: }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_SCREEN_H diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index d0f64c5d56..d9ef4f305c 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -11,7 +11,7 @@ #include "event_type_names.h" #include "foundation/native_value_converter.h" -namespace kraken { +namespace webf { Window::Window(ExecutingContext* context) : EventTargetWithInlineData(context) { context->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateWindow, (void*)bindingObject()); @@ -139,4 +139,4 @@ void Window::Trace(GCVisitor* visitor) const { EventTargetWithInlineData::Trace(visitor); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index c841c9b82d..7d1e67a4cc 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -11,7 +11,7 @@ #include "qjs_scroll_to_options.h" #include "screen.h" -namespace kraken { +namespace webf { class Window : public EventTargetWithInlineData { DEFINE_WRAPPERTYPEINFO(); @@ -51,6 +51,6 @@ class Window : public EventTargetWithInlineData { Member<Screen> screen_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_WINDOW_H diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 463a3cd64b..2192d3a4dd 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -5,7 +5,7 @@ #include "window_or_worker_global_scope.h" #include "core/frame/dom_timer.h" -namespace kraken { +namespace webf { static void handleTimerCallback(DOMTimer* timer, const char* errmsg) { auto* context = timer->context(); @@ -122,4 +122,4 @@ void WindowOrWorkerGlobalScope::clearTimeout(ExecutingContext* context, int32_t context->Timers()->removeTimeoutById(timerId); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/frame/window_or_worker_global_scope.h b/bridge/core/frame/window_or_worker_global_scope.h index dd27fe62d3..6e256f2743 100644 --- a/bridge/core/frame/window_or_worker_global_scope.h +++ b/bridge/core/frame/window_or_worker_global_scope.h @@ -9,7 +9,7 @@ #include "bindings/qjs/qjs_function.h" #include "core/executing_context.h" -namespace kraken { +namespace webf { class WindowOrWorkerGlobalScope { public: @@ -26,6 +26,6 @@ class WindowOrWorkerGlobalScope { static void clearTimeout(ExecutingContext* context, int32_t timerId, ExceptionState& exception); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_WINDOW_OR_WORKER_GLOBAL_SCROPE_H diff --git a/bridge/core/frame/window_test.cc b/bridge/core/frame/window_test.cc index 03cedd62e8..3da32656b9 100644 --- a/bridge/core/frame/window_test.cc +++ b/bridge/core/frame/window_test.cc @@ -7,17 +7,17 @@ #include "gtest/gtest.h" #include "kraken_test_env.h" -using namespace kraken; +using namespace webf; TEST(Window, windowIsGlobalThis) { bool static errorCalled = false; bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; EXPECT_STREQ(message.c_str(), "true"); }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - KRAKEN_LOG(VERBOSE) << errmsg; + WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); auto context = bridge->GetExecutingContext(); @@ -51,7 +51,7 @@ TEST(Window, requestAnimationFrame) { auto bridge = TEST_init(); bool static logCalled = false; - kraken::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + webf::KrakenPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { EXPECT_STREQ(message.c_str(), "456"); logCalled = true; }; diff --git a/bridge/core/html/canvas/html_canvas_element.cc b/bridge/core/html/canvas/html_canvas_element.cc index 1a866984d3..47ceb5cd10 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -5,7 +5,7 @@ #include "html_canvas_element.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLCanvasElement::HTMLCanvasElement(Document& document) : HTMLElement(html_names::kcanvas, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/canvas/html_canvas_element.h b/bridge/core/html/canvas/html_canvas_element.h index cd0cb27c58..0ace5226f9 100644 --- a/bridge/core/html/canvas/html_canvas_element.h +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -7,13 +7,13 @@ #include "core/html/html_element.h" -namespace kraken { +namespace webf { class HTMLCanvasElement : public HTMLElement { public: explicit HTMLCanvasElement(Document&); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_input_element.cc b/bridge/core/html/forms/html_input_element.cc index 3d9b870a48..86e7798e7e 100644 --- a/bridge/core/html/forms/html_input_element.cc +++ b/bridge/core/html/forms/html_input_element.cc @@ -5,8 +5,8 @@ #include "html_input_element.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLInputElement::HTMLInputElement(Document& document) : HTMLElement(html_names::kinput, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h index b387075b40..45ed0ff0a2 100644 --- a/bridge/core/html/forms/html_input_element.h +++ b/bridge/core/html/forms/html_input_element.h @@ -7,13 +7,13 @@ #include "core/html/html_element.h" -namespace kraken { +namespace webf { class HTMLInputElement : public HTMLElement { public: explicit HTMLInputElement(Document&); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_FORMS_HTML_INPUT_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_textarea_element.cc b/bridge/core/html/forms/html_textarea_element.cc index f933df2431..cbf6ea2cd5 100644 --- a/bridge/core/html/forms/html_textarea_element.cc +++ b/bridge/core/html/forms/html_textarea_element.cc @@ -5,8 +5,8 @@ #include "html_textarea_element.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLTextareaElement::HTMLTextareaElement(Document& document) : HTMLElement(html_names::ktextarea, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h index 10ea01a135..313ebd8e8c 100644 --- a/bridge/core/html/forms/html_textarea_element.h +++ b/bridge/core/html/forms/html_textarea_element.h @@ -7,13 +7,13 @@ #include "core/html/html_element.h" -namespace kraken { +namespace webf { class HTMLTextareaElement : public HTMLElement { public: explicit HTMLTextareaElement(Document&); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_FORMS_HTML_TEXTAREA_ELEMENT_H_ diff --git a/bridge/core/html/html_all_collection.cc b/bridge/core/html/html_all_collection.cc index 3fb06081ff..4e68b1db2c 100644 --- a/bridge/core/html/html_all_collection.cc +++ b/bridge/core/html/html_all_collection.cc @@ -5,7 +5,7 @@ // //#include "html_all_collection.h" // -// namespace kraken { +// namespace webf{ // // JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // if (argc < 1) { @@ -77,4 +77,4 @@ // return JS_NewUint32(ctx, collection->m_nodes.size()); //} // -//} // namespace kraken +//} // namespace webf diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h index 9483d30fd0..63215a2be8 100644 --- a/bridge/core/html/html_all_collection.h +++ b/bridge/core/html/html_all_collection.h @@ -8,7 +8,7 @@ // //#include "bindings/qjs/garbage_collected.h" // -// namespace kraken { +// namespace webf{ // // class HTMLAllCollection : public HostObject { // public: @@ -26,6 +26,6 @@ // std::vector<NodeInstance*> m_nodes; //}; // -//} // namespace kraken +//} // namespace webf // //#endif // KRAKENBRIDGE_HTML_ALL_COLLECTION_H diff --git a/bridge/core/html/html_anchor_element.cc b/bridge/core/html/html_anchor_element.cc index 6abe4f74e1..1615384da8 100644 --- a/bridge/core/html/html_anchor_element.cc +++ b/bridge/core/html/html_anchor_element.cc @@ -5,8 +5,8 @@ #include "html_anchor_element.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLAnchorElement::HTMLAnchorElement(Document& document) : HTMLElement(html_names::ka, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h index 18e1d838a0..0fdb532da2 100644 --- a/bridge/core/html/html_anchor_element.h +++ b/bridge/core/html/html_anchor_element.h @@ -7,13 +7,13 @@ #include "html_element.h" -namespace kraken { +namespace webf { class HTMLAnchorElement : public HTMLElement { public: explicit HTMLAnchorElement(Document&); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_HTML_ANCHOR_ELEMENT_H diff --git a/bridge/core/html/html_body_element.cc b/bridge/core/html/html_body_element.cc index ea30da52d7..f353b84393 100644 --- a/bridge/core/html/html_body_element.cc +++ b/bridge/core/html/html_body_element.cc @@ -5,8 +5,8 @@ #include "html_body_element.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLBodyElement::HTMLBodyElement(Document& document) : HTMLElement(html_names::kbody, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index ab3f8e522a..14688cf13e 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -7,7 +7,7 @@ #include "html_element.h" -namespace kraken { +namespace webf { class HTMLBodyElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); @@ -17,6 +17,6 @@ class HTMLBodyElement : public HTMLElement { explicit HTMLBodyElement(Document&); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ diff --git a/bridge/core/html/html_collection.cc b/bridge/core/html/html_collection.cc index ae3e9ce3c1..50ad620a29 100644 --- a/bridge/core/html/html_collection.cc +++ b/bridge/core/html/html_collection.cc @@ -5,4 +5,4 @@ #include "html_collection.h" -namespace kraken {} +namespace webf {} diff --git a/bridge/core/html/html_collection.h b/bridge/core/html/html_collection.h index 416d73fb54..39be18b9d8 100644 --- a/bridge/core/html/html_collection.h +++ b/bridge/core/html/html_collection.h @@ -8,13 +8,13 @@ #include "bindings/qjs/script_wrappable.h" -namespace kraken { +namespace webf { class HTMLCollection : public ScriptWrappable { public: private: }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_HTML_COLLECTION_H_ diff --git a/bridge/core/html/html_div_element.cc b/bridge/core/html/html_div_element.cc index 4008d73e86..f0bd24d58e 100644 --- a/bridge/core/html/html_div_element.cc +++ b/bridge/core/html/html_div_element.cc @@ -6,8 +6,8 @@ #include "html_div_element.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLDivElement::HTMLDivElement(Document& document) : HTMLElement(html_names::kdiv, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h index 85e00d9dae..e75b195e15 100644 --- a/bridge/core/html/html_div_element.h +++ b/bridge/core/html/html_div_element.h @@ -8,7 +8,7 @@ #include "html_element.h" -namespace kraken { +namespace webf { class HTMLDivElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); @@ -20,6 +20,6 @@ class HTMLDivElement : public HTMLElement { private: }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_HTML_DIV_ELEMENT_H_ diff --git a/bridge/core/html/html_element.cc b/bridge/core/html/html_element.cc index 74e7091354..9570063f4c 100644 --- a/bridge/core/html/html_element.cc +++ b/bridge/core/html/html_element.cc @@ -5,4 +5,4 @@ #include "html_element.h" -namespace kraken {} // namespace kraken +namespace webf {} // namespace webf diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index 4c8e892284..ea3b61c5e3 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -8,7 +8,7 @@ #include "core/dom/element.h" -namespace kraken { +namespace webf { class HTMLElement : public Element { DEFINE_WRAPPERTYPEINFO(); @@ -40,6 +40,6 @@ struct DowncastTraits<HTMLElement> { static bool AllowFrom(const Node& node) { return node.IsHTMLElement(); } }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_head_element.cc b/bridge/core/html/html_head_element.cc index 38906f4ae9..2c649bddf9 100644 --- a/bridge/core/html/html_head_element.cc +++ b/bridge/core/html/html_head_element.cc @@ -5,8 +5,8 @@ #include "html_head_element.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLHeadElement::HTMLHeadElement(Document& document) : HTMLElement(html_names::khead, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/html_head_element.h b/bridge/core/html/html_head_element.h index c4446b46dd..1762b8c9c3 100644 --- a/bridge/core/html/html_head_element.h +++ b/bridge/core/html/html_head_element.h @@ -7,7 +7,7 @@ #include "html_element.h" -namespace kraken { +namespace webf { class HTMLHeadElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); @@ -19,6 +19,6 @@ class HTMLHeadElement : public HTMLElement { private: }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_HTML_HEAD_ELEMENT_H_ diff --git a/bridge/core/html/html_html_element.cc b/bridge/core/html/html_html_element.cc index d4b965cd53..cb3fd5e995 100644 --- a/bridge/core/html/html_html_element.cc +++ b/bridge/core/html/html_html_element.cc @@ -5,8 +5,8 @@ #include "html_html_element.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLHtmlElement::HTMLHtmlElement(Document& document) : HTMLElement(html_names::khtml, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/html_html_element.h b/bridge/core/html/html_html_element.h index d44fb4dd03..8fa06686d3 100644 --- a/bridge/core/html/html_html_element.h +++ b/bridge/core/html/html_html_element.h @@ -7,7 +7,7 @@ #include "html_element.h" -namespace kraken { +namespace webf { class HTMLHtmlElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); @@ -19,6 +19,6 @@ class HTMLHtmlElement : public HTMLElement { private: }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_HTML_HTML_ELEMENT_H_ diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index 6b10466bf0..8dee2ac34d 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -5,8 +5,8 @@ #include "html_image_element.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLImageElement::HTMLImageElement(Document& document) : HTMLElement(html_names::kimg, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index 1442c898ec..b50021b795 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -7,7 +7,7 @@ #include "html_element.h" -namespace kraken { +namespace webf { class HTMLImageElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); @@ -18,6 +18,6 @@ class HTMLImageElement : public HTMLElement { private: }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_HTML_IMAGE_ELEMENT_H_ diff --git a/bridge/core/html/html_script_element.cc b/bridge/core/html/html_script_element.cc index e92aea1768..308e253c95 100644 --- a/bridge/core/html/html_script_element.cc +++ b/bridge/core/html/html_script_element.cc @@ -5,8 +5,8 @@ #include "html_script_element.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLScriptElement::HTMLScriptElement(Document& document) : HTMLElement(html_names::kscript, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h index bf24f988e0..5b1c0a9ac2 100644 --- a/bridge/core/html/html_script_element.h +++ b/bridge/core/html/html_script_element.h @@ -7,7 +7,7 @@ #include "html_element.h" -namespace kraken { +namespace webf { class HTMLScriptElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); @@ -18,6 +18,6 @@ class HTMLScriptElement : public HTMLElement { private: }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_HTML_SCRIPT_ELEMENT_H_ diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index 15d67ccd7e..bb030a75fe 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -6,7 +6,7 @@ #include "core/dom/document_fragment.h" #include "html_names.h" -namespace kraken { +namespace webf { HTMLTemplateElement::HTMLTemplateElement(Document& document) : HTMLElement(html_names::ktemplate, &document) {} @@ -21,4 +21,4 @@ DocumentFragment* HTMLTemplateElement::ContentInternal() const { return content_.Get(); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index 4561612323..00106846f7 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -7,7 +7,7 @@ #include "html_element.h" -namespace kraken { +namespace webf { class DocumentFragment; @@ -24,6 +24,6 @@ class HTMLTemplateElement : public HTMLElement { mutable Member<DocumentFragment> content_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_TEMPLATE_ELEMENTT_H diff --git a/bridge/core/html/html_unknown_element.cc b/bridge/core/html/html_unknown_element.cc index 8359cb77bd..dfe858b782 100644 --- a/bridge/core/html/html_unknown_element.cc +++ b/bridge/core/html/html_unknown_element.cc @@ -4,9 +4,9 @@ */ #include "html_unknown_element.h" -namespace kraken { +namespace webf { HTMLUnknownElement::HTMLUnknownElement(const AtomicString& tag_name, Document& document) : HTMLElement(tag_name, &document) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/html_unknown_element.h b/bridge/core/html/html_unknown_element.h index 6ee3edb426..8bb1292e91 100644 --- a/bridge/core/html/html_unknown_element.h +++ b/bridge/core/html/html_unknown_element.h @@ -7,7 +7,7 @@ #include "core/html/html_element.h" -namespace kraken { +namespace webf { class HTMLUnknownElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); @@ -18,6 +18,6 @@ class HTMLUnknownElement : public HTMLElement { private: }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_HTML_UNKNOWN_ELEMENT_H_ diff --git a/bridge/core/html/parser/html_parser.cc b/bridge/core/html/parser/html_parser.cc index df326e0809..57fc841cd6 100644 --- a/bridge/core/html/parser/html_parser.cc +++ b/bridge/core/html/parser/html_parser.cc @@ -10,7 +10,7 @@ #include "foundation/logging.h" #include "html_parser.h" -namespace kraken { +namespace webf { inline std::string trim(const std::string& str) { std::string tmp = str; @@ -104,7 +104,7 @@ bool HTMLParser::parseHTML(const std::string& html, Node* root_node, bool isHTML } } } else { - KRAKEN_LOG(ERROR) << "Root node is null."; + WEBF_LOG(ERROR) << "Root node is null."; } return true; @@ -164,4 +164,4 @@ void HTMLParser::parseProperty(Element* element, GumboElement* gumboElement) { } } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/html/parser/html_parser.h b/bridge/core/html/parser/html_parser.h index 0f672511f5..21fac7c02b 100644 --- a/bridge/core/html/parser/html_parser.h +++ b/bridge/core/html/parser/html_parser.h @@ -9,7 +9,7 @@ #include <string> #include "foundation/native_string.h" -namespace kraken { +namespace webf { class Node; class Element; @@ -28,6 +28,6 @@ class HTMLParser { static bool parseHTML(const std::string& html, Node* rootNode, bool isHTMLFragment); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_HTML_PARSER_H diff --git a/bridge/core/page.cc b/bridge/core/page.cc index f64c297b15..adf3f16f40 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -14,13 +14,13 @@ #include "page.h" #include "polyfill.h" -namespace kraken { +namespace webf { -ConsoleMessageHandler KrakenPage::consoleMessageHandler{nullptr}; +ConsoleMessageHandler WebFPage::consoleMessageHandler{nullptr}; -kraken::KrakenPage** KrakenPage::pageContextPool{nullptr}; +webf::WebFPage** WebFPage::pageContextPool{nullptr}; -KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) +WebFPage::WebFPage(int32_t contextId, const JSExceptionHandler& handler) : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { context_ = new ExecutingContext( contextId, @@ -28,12 +28,12 @@ KrakenPage::KrakenPage(int32_t contextId, const JSExceptionHandler& handler) if (context->dartMethodPtr()->onJsError != nullptr) { context->dartMethodPtr()->onJsError(context->contextId(), message); } - KRAKEN_LOG(ERROR) << message << std::endl; + WEBF_LOG(ERROR) << message << std::endl; }, this); } -bool KrakenPage::parseHTML(const char* code, size_t length) { +bool WebFPage::parseHTML(const char* code, size_t length) { if (!context_->IsValid()) return false; @@ -47,10 +47,10 @@ bool KrakenPage::parseHTML(const char* code, size_t length) { return true; } -void KrakenPage::invokeModuleEvent(const NativeString* moduleName, - const char* eventType, - void* ptr, - NativeString* extra) { +void WebFPage::invokeModuleEvent(const NativeString* moduleName, + const char* eventType, + void* ptr, + NativeString* extra) { if (!context_->IsValid()) return; @@ -80,46 +80,46 @@ void KrakenPage::invokeModuleEvent(const NativeString* moduleName, } } -void KrakenPage::evaluateScript(const NativeString* script, const char* url, int startLine) { +void WebFPage::evaluateScript(const NativeString* script, const char* url, int startLine) { if (!context_->IsValid()) return; -#if ENABLE_PROFILE - auto nativePerformance = Performance::instance(context_)->m_nativePerformance; - nativePerformance.mark(PERF_JS_PARSE_TIME_START); - std::u16string patchedCode = std::u16string(u"performance.mark('js_parse_time_end');") + - std::u16string(reinterpret_cast<const char16_t*>(script->string), script->length); - context_->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); -#else +//#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 +//#endif } -void KrakenPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { +void WebFPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { if (!context_->IsValid()) return; context_->EvaluateJavaScript(script, length, url, startLine); } -void KrakenPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { +void WebFPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { if (!context_->IsValid()) return; context_->EvaluateJavaScript(script, length, url, startLine); } -uint8_t* KrakenPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { +uint8_t* WebFPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { if (!context_->IsValid()) return nullptr; return context_->DumpByteCode(script, length, url, byteLength); } -void KrakenPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { +void WebFPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { if (!context_->IsValid()) return; context_->EvaluateByteCode(bytes, byteLength); } -void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { +void WebFPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { size_t i = 0; auto& dartMethodPointer = context_->dartMethodPtr(); @@ -136,7 +136,7 @@ void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { dartMethodPointer->flushUICommand = reinterpret_cast<FlushUICommand>(methodBytes[i++]); #if ENABLE_PROFILE - methodPointer->getPerformanceEntries = reinterpret_cast<GetPerformanceEntries>(methodBytes[i++]); + dartMethodPointer->getPerformanceEntries = reinterpret_cast<GetPerformanceEntries>(methodBytes[i++]); #else i++; #endif @@ -147,22 +147,22 @@ void KrakenPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); } -std::thread::id KrakenPage::currentThread() const { +std::thread::id WebFPage::currentThread() const { return ownerThreadId; } -KrakenPage::~KrakenPage() { +WebFPage::~WebFPage() { #if IS_TEST if (disposeCallback != nullptr) { disposeCallback(this); } #endif delete context_; - KrakenPage::pageContextPool[contextId] = nullptr; + WebFPage::pageContextPool[contextId] = nullptr; } -void KrakenPage::reportError(const char* errmsg) { +void WebFPage::reportError(const char* errmsg) { handler_(context_, errmsg); } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/page.h b/bridge/core/page.h index 383206a612..4e4e3eaca4 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -17,7 +17,9 @@ namespace webf { -using JSBridgeDisposeCallback = void (*)(KrakenPage* bridge); +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. @@ -44,7 +46,6 @@ 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; } void registerDartMethods(uint64_t* methodBytes, int32_t length); std::thread::id currentThread() const; diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 4c190c45e7..ef64f847f5 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -9,7 +9,7 @@ #include "html_element_factory.h" #include "html_names.h" -namespace kraken { +namespace webf { JSRuntime* runtime_ = nullptr; std::atomic<int32_t> runningContexts{0}; @@ -63,4 +63,4 @@ ScriptState::~ScriptState() { #endif ctx_ = nullptr; } -} // namespace kraken +} // namespace webf diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h index 60dc1fffd2..b3ecd20983 100644 --- a/bridge/core/script_state.h +++ b/bridge/core/script_state.h @@ -8,7 +8,7 @@ #include <quickjs/quickjs.h> #include "bindings/qjs/script_wrappable.h" -namespace kraken { +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 @@ -26,6 +26,6 @@ class ScriptState { JSContext* ctx_{nullptr}; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_SCRIPT_STATE_H_ diff --git a/bridge/core/timing/performance.cc b/bridge/core/timing/performance.cc index a77cb43039..d87a9a7166 100644 --- a/bridge/core/timing/performance.cc +++ b/bridge/core/timing/performance.cc @@ -5,633 +5,628 @@ #include "performance.h" #include <chrono> -#include "dart_methods.h" +#include "core/executing_context.h" #define PERFORMANCE_ENTRY_NONE_UNIQUE_ID -1024 -namespace kraken { - -void bindPerformance(ExecutionContext* context) { - auto* performance = Performance::instance(context); - context->defineGlobalProperty("performance", performance->jsObject); -} +namespace webf { using namespace std::chrono; -IMPL_PROPERTY_GETTER(PerformanceEntry, name)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* entry = static_cast<PerformanceEntry*>(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<PerformanceEntry*>(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<PerformanceEntry*>(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<PerformanceEntry*>(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<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - int64_t time = - std::chrono::duration_cast<std::chrono::milliseconds>(performance->m_context->timeOrigin.time_since_epoch()) - .count(); - return JS_NewUint32(ctx, time); -} - -JSValue Performance::now(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* performance = static_cast<Performance*>(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<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); - double now = performance->internalNow(); - int64_t timeOrigin = - std::chrono::duration_cast<std::chrono::milliseconds>(performance->m_context->timeOrigin.time_since_epoch()) - .count(); - - 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<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::internalNow() { - auto now = std::chrono::system_clock::now(); - auto duration = std::chrono::duration_cast<std::chrono::microseconds>(now - m_context->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 kraken +//IMPL_PROPERTY_GETTER(PerformanceEntry, name)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* entry = static_cast<PerformanceEntry*>(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<PerformanceEntry*>(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<PerformanceEntry*>(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<PerformanceEntry*>(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<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// int64_t time = +// std::chrono::duration_cast<std::chrono::milliseconds>(performance->m_context->timeOrigin.time_since_epoch()) +// .count(); +// return JS_NewUint32(ctx, time); +//} + +//JSValue Performance::now(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// auto* performance = static_cast<Performance*>(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<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); +// double now = performance->internalNow(); +// int64_t timeOrigin = +// std::chrono::duration_cast<std::chrono::milliseconds>(performance->m_context->timeOrigin.time_since_epoch()) +// .count(); +// +// 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<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..ce2aaa4ce8 --- /dev/null +++ b/bridge/core/timing/performance.d.ts @@ -0,0 +1,4 @@ +interface Performance { + now(): double; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/timing/performance.h b/bridge/core/timing/performance.h index c532043571..047fc36e43 100644 --- a/bridge/core/timing/performance.h +++ b/bridge/core/timing/performance.h @@ -6,6 +6,9 @@ #ifndef BRIDGE_PERFORMANCE_H #define BRIDGE_PERFORMANCE_H +#include "core/dom/binding_object.h" +#include "bindings/qjs/script_wrappable.h" + #if ENABLE_PROFILE #define PERF_WIDGET_CREATION_COST "widget_creation_cost" #define PERF_CONTROLLER_PROPERTIES_INIT_COST "controller_properties_init_cost" @@ -121,12 +124,8 @@ #define PERF_PAINT_END "paint_end" #endif -#include "bindings/qjs/host_object.h" - namespace webf { -void bindPerformance(ExecutionContext* context); - struct NativePerformanceEntry { NativePerformanceEntry(const std::string& name, const std::string& entryType, @@ -146,88 +145,76 @@ struct NativePerformanceEntry { 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<NativePerformanceEntry*>* entries{new std::vector<NativePerformanceEntry*>()}; -}; - -class Performance : public HostObject { +//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<NativePerformanceEntry*>* entries{new std::vector<NativePerformanceEntry*>()}; +//}; + +class Performance : public ScriptWrappable, BindingObject { + DEFINE_WRAPPERTYPEINFO(); public: Performance() = delete; - explicit Performance(ExecutionContext* context); - OBJECT_INSTANCE(Performance); + double now() const; - 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); +// 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); +// 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<NativePerformanceEntry*> 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 +// void internalMeasure(const std::string& name, +// const std::string& startMark, +// const std::string& endMark, +// JSValue* exception); +// double internalNow(); +// std::vector<NativePerformanceEntry*> getFullEntries(); +// +//#if ENABLE_PROFILE +// DEFINE_FUNCTION(__webf_navigation_summary__, 0); +//#endif }; } // namespace webf diff --git a/bridge/foundation/ascii_types.h b/bridge/foundation/ascii_types.h index 9a3b1dc5d9..01ab3cdcbc 100644 --- a/bridge/foundation/ascii_types.h +++ b/bridge/foundation/ascii_types.h @@ -5,7 +5,7 @@ #ifndef KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ #define KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ -namespace kraken { +namespace webf { template <typename CharType> inline bool IsASCII(CharType c) { @@ -59,6 +59,6 @@ inline bool IsLowerASCII(const CharacterType* characters, size_t length) { return !contains_upper_case; } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_FOUNDATION_ASCII_TYPES_H_ diff --git a/bridge/foundation/casting.h b/bridge/foundation/casting.h index 93b9999938..0a746e21ad 100644 --- a/bridge/foundation/casting.h +++ b/bridge/foundation/casting.h @@ -9,7 +9,7 @@ #include <cassert> #include <type_traits> -namespace kraken { +namespace webf { // Helpers for downcasting in a class hierarchy. // @@ -142,6 +142,6 @@ Derived* DynamicTo(Base& from) { return IsA<Derived>(from) ? &To<Derived>(from) : nullptr; } -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_FOUNDATION_CASTING_H_ diff --git a/bridge/foundation/inspector_task_queue.cc b/bridge/foundation/inspector_task_queue.cc index 0e9ea51cb6..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 kraken { +namespace webf { std::mutex InspectorTaskQueue::inspector_task_creation_mutex_{}; fml::RefPtr<InspectorTaskQueue> InspectorTaskQueue::instance_{}; -} // namespace kraken +} // namespace webf diff --git a/bridge/foundation/inspector_task_queue.h b/bridge/foundation/inspector_task_queue.h index e0df2dbc1b..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 kraken { +namespace webf { class InspectorTaskQueue; using Task = void (*)(void*); @@ -35,6 +34,6 @@ class InspectorTaskQueue : public TaskQueue { static fml::RefPtr<InspectorTaskQueue> instance_; }; -} // namespace kraken +} // namespace webf #endif // BRIDGE_INSPECTOR_TASK_QUEUE_H diff --git a/bridge/foundation/logging.cc b/bridge/foundation/logging.cc index 29d86f070c..0d7a8ac4d1 100644 --- a/bridge/foundation/logging.cc +++ b/bridge/foundation/logging.cc @@ -24,7 +24,7 @@ #include "inspector/impl/jsc_console_client_impl.h" #endif -namespace kraken { +namespace webf { namespace { const char* StripDots(const char* path) { @@ -131,4 +131,4 @@ void printLog(ExecutingContext* context, std::stringstream& stream, std::string } } -} // namespace kraken +} // namespace webf diff --git a/bridge/foundation/logging.h b/bridge/foundation/logging.h index 17247bb760..ac0760a75e 100644 --- a/bridge/foundation/logging.h +++ b/bridge/foundation/logging.h @@ -9,19 +9,19 @@ #include <sstream> #include <string> -#define KRAKEN_LOG_STREAM(severity) ::kraken::LogMessage(::kraken::severity, __FILE__, __LINE__, nullptr).stream() +#define WEBF_LOG_STREAM(severity) ::webf::LogMessage(::webf::severity, __FILE__, __LINE__, nullptr).stream() -#define KRAKEN_LAZY_STREAM(stream, condition) !(condition) ? (void)0 : ::kraken::LogMessageVoidify() & (stream) +#define WEBF_LAZY_STREAM(stream, condition) !(condition) ? (void)0 : ::webf::LogMessageVoidify() & (stream) -#define KRAKEN_EAT_STREAM_PARAMETERS(ignored) \ +#define WEBF_EAT_STREAM_PARAMETERS(ignored) \ true || (ignored) ? (void)0 : ::LogMessageVoidify() & ::LogMessage(::LOG_FATAL, 0, 0, nullptr).stream() -#define KRAKEN_LOG(severity) KRAKEN_LAZY_STREAM(KRAKEN_LOG_STREAM(severity), true) +#define WEBF_LOG(severity) WEBF_LAZY_STREAM(WEBF_LOG_STREAM(severity), true) -#define KRAKEN_CHECK(condition) \ - KRAKEN_LAZY_STREAM(::kraken::LogMessage(::kraken::FATAL, __FILE__, __LINE__, #condition).stream(), !(condition)) +#define WEBF_CHECK(condition) \ + WEBF_LAZY_STREAM(::webf::LogMessage(::webf::FATAL, __FILE__, __LINE__, #condition).stream(), !(condition)) -namespace kraken { +namespace webf { class ExecutingContext; @@ -65,6 +65,6 @@ class LogMessage { void printLog(ExecutingContext* context, std::stringstream& stream, std::string level, void* ctx); -} // namespace kraken +} // namespace webf #endif // FOUNDATION_LOGGING_H_ diff --git a/bridge/foundation/native_string.cc b/bridge/foundation/native_string.cc index d2e744e7ed..8408b73404 100644 --- a/bridge/foundation/native_string.cc +++ b/bridge/foundation/native_string.cc @@ -5,7 +5,7 @@ #include "native_string.h" #include <string> -namespace kraken { +namespace webf { NativeString::NativeString(const uint16_t* string, uint32_t length) : length_(length) { string_ = static_cast<const uint16_t*>(malloc(length * sizeof(uint16_t))); @@ -21,4 +21,4 @@ NativeString::~NativeString() { delete[] string_; } -} // namespace kraken +} // namespace webf diff --git a/bridge/foundation/native_string.h b/bridge/foundation/native_string.h index 6e792943b3..6e0b6a32ad 100644 --- a/bridge/foundation/native_string.h +++ b/bridge/foundation/native_string.h @@ -11,7 +11,7 @@ #include "foundation/macros.h" -namespace kraken { +namespace webf { struct NativeString { NativeString(const uint16_t* string, uint32_t length); @@ -26,6 +26,6 @@ struct NativeString { uint32_t length_; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_NATIVE_STRING_H diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h index ff2272da2a..9e51f7545c 100644 --- a/bridge/foundation/native_type.h +++ b/bridge/foundation/native_type.h @@ -10,7 +10,7 @@ #include "bindings/qjs/script_value.h" #include "foundation/native_string.h" -namespace kraken { +namespace webf { struct NativeTypeBase { using ImplType = void; @@ -49,6 +49,6 @@ struct NativeTypeFunction final : public NativeTypeBaseHelper<std::shared_ptr<QJ // Async function struct NativeTypeAsyncFunction final : public NativeTypeBaseHelper<std::shared_ptr<QJSFunction>> {}; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_FOUNDATION_NATIVE_TYPE_H_ diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index adc386203a..b460c364b7 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -7,7 +7,7 @@ #include "bindings/qjs/script_value.h" #include "core/executing_context.h" -namespace kraken { +namespace webf { NativeValue Native_NewNull() { return (NativeValue){.u = {.int64 = 0}, NativeTag::TAG_NULL}; @@ -70,4 +70,4 @@ NativeValue Native_NewJSON(const ScriptValue& value) { return result; } -} // namespace kraken +} // namespace webf diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 343af68c7b..72b28a88c6 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -12,7 +12,7 @@ #include <string> #include "bindings/qjs/native_string_utils.h" -namespace kraken { +namespace webf { enum NativeTag { TAG_STRING = 0, @@ -77,6 +77,6 @@ NativeValue Native_NewInt64(int64_t value); NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr); NativeValue Native_NewJSON(const ScriptValue& value); -} // namespace kraken +} // namespace webf #endif // BRIDGE_NATIVE_VALUE_H diff --git a/bridge/foundation/native_value_converter.cc b/bridge/foundation/native_value_converter.cc index 1cd4416d08..3a5243fc5f 100644 --- a/bridge/foundation/native_value_converter.cc +++ b/bridge/foundation/native_value_converter.cc @@ -5,7 +5,7 @@ #include "native_value_converter.h" -namespace kraken { +namespace webf { #define AnonymousFunctionCallPreFix "_anonymous_fn_" #define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" @@ -134,4 +134,4 @@ std::shared_ptr<QJSFunction> CreateAsyncCallback(JSContext* ctx, int function_id return result; } -} // namespace kraken +} // namespace webf diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 56de3e7717..a74a793d70 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -9,7 +9,7 @@ #include "native_type.h" #include "native_value.h" -namespace kraken { +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. @@ -125,6 +125,6 @@ struct NativeValueConverter<NativeTypeAsyncFunction> : public NativeValueConvert static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { return CreateAsyncCallback(ctx, value.u.int64); } }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ diff --git a/bridge/foundation/ref_counter.h b/bridge/foundation/ref_counter.h index 8455b07a1a..f4626d2dfc 100644 --- a/bridge/foundation/ref_counter.h +++ b/bridge/foundation/ref_counter.h @@ -119,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/string_view.cc b/bridge/foundation/string_view.cc index 9dd2e63bf0..1de4a0b735 100644 --- a/bridge/foundation/string_view.cc +++ b/bridge/foundation/string_view.cc @@ -4,7 +4,7 @@ */ #include "string_view.h" -namespace kraken { +namespace webf { StringView::StringView(const std::string& string) : bytes_(string.data()), length_(string.length()), is_8bit_(true) {} @@ -13,4 +13,4 @@ StringView::StringView(const NativeString* string) StringView::StringView(void* bytes, unsigned length, bool is_wide_char) : bytes_(bytes), length_(length), is_8bit_(!is_wide_char) {} -} // namespace kraken +} // namespace webf diff --git a/bridge/foundation/string_view.h b/bridge/foundation/string_view.h index ba0d505030..9b5f11ca9e 100644 --- a/bridge/foundation/string_view.h +++ b/bridge/foundation/string_view.h @@ -9,7 +9,7 @@ #include "ascii_types.h" #include "native_string.h" -namespace kraken { +namespace webf { class StringView final { public: @@ -23,9 +23,9 @@ class StringView final { bool IsLowerASCII() const { if (is_8bit_) { - return kraken::IsLowerASCII(Characters8(), length()); + return webf::IsLowerASCII(Characters8(), length()); } - return kraken::IsLowerASCII(Characters16(), length()); + return webf::IsLowerASCII(Characters16(), length()); } const char* Characters8() const { return static_cast<const char*>(bytes_); } @@ -41,6 +41,6 @@ class StringView final { unsigned is_8bit_ : 1; }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_FOUNDATION_STRING_VIEW_H_ diff --git a/bridge/foundation/task_queue.cc b/bridge/foundation/task_queue.cc index 3e07231f1b..c95d0c4095 100644 --- a/bridge/foundation/task_queue.cc +++ b/bridge/foundation/task_queue.cc @@ -5,7 +5,7 @@ #include "task_queue.h" -namespace kraken { +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 kraken +} // namespace webf diff --git a/bridge/foundation/task_queue.h b/bridge/foundation/task_queue.h index 8e7b27eefc..56c758a0be 100644 --- a/bridge/foundation/task_queue.h +++ b/bridge/foundation/task_queue.h @@ -11,7 +11,7 @@ #include "ref_counter.h" #include "ref_ptr.h" -namespace kraken { +namespace webf { using Task = void (*)(void*); @@ -36,6 +36,6 @@ class TaskQueue : public fml::RefCountedThreadSafe<TaskQueue> { FML_FRIEND_REF_COUNTED_THREAD_SAFE(TaskQueue); }; -} // namespace kraken +} // namespace webf #endif // BRIDGE_TASK_QUEUE_H diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 109cf942b6..c516c63e2e 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -9,7 +9,7 @@ #include "foundation/logging.h" #include "include/webf_bridge.h" -namespace kraken { +namespace webf { UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) { queue.reserve(0); @@ -53,4 +53,4 @@ void UICommandBuffer::clear() { queue.clear(); } -} // namespace kraken +} // namespace webf diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 30c4bb0323..130a08e88e 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -11,7 +11,7 @@ #include "bindings/qjs/native_string_utils.h" #include "native_value.h" -namespace kraken { +namespace webf { class ExecutingContext; @@ -79,6 +79,6 @@ class UICommandBuffer { std::vector<UICommandItem> queue; }; -} // namespace kraken +} // namespace webf #endif // BRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ diff --git a/bridge/foundation/ui_task_queue.cc b/bridge/foundation/ui_task_queue.cc index c287045b63..1de9e5af55 100644 --- a/bridge/foundation/ui_task_queue.cc +++ b/bridge/foundation/ui_task_queue.cc @@ -5,7 +5,7 @@ #include "ui_task_queue.h" -namespace kraken { +namespace webf { std::mutex UITaskQueue::ui_task_creation_mutex_{}; fml::RefPtr<UITaskQueue> UITaskQueue::instance_{}; @@ -14,4 +14,4 @@ int32_t UITaskQueue::registerTask(const Task& task, void* data) { return taskId; } -} // namespace kraken +} // namespace webf diff --git a/bridge/foundation/ui_task_queue.h b/bridge/foundation/ui_task_queue.h index 15f4dc8d2e..793e0d28cd 100644 --- a/bridge/foundation/ui_task_queue.h +++ b/bridge/foundation/ui_task_queue.h @@ -8,7 +8,7 @@ #include "task_queue.h" -namespace kraken { +namespace webf { class UITaskQueue : public TaskQueue { public: @@ -28,6 +28,6 @@ class UITaskQueue : public TaskQueue { int m_contextId; }; -} // namespace kraken +} // namespace webf #endif // BRIDGE_UI_TASK_QUEUE_H diff --git a/bridge/include/webf_bridge.h b/bridge/include/webf_bridge.h index d8aff01f83..b719fe2cfe 100644 --- a/bridge/include/webf_bridge.h +++ b/bridge/include/webf_bridge.h @@ -11,9 +11,6 @@ #define WEBF_EXPORT_C extern "C" __attribute__((visibility("default"))) __attribute__((used)) #define WEBF_EXPORT __attribute__((__visibility__("default"))) -WEBF_EXPORT_C -std::thread::id getUIThreadId(); - typedef struct NativeString NativeString; typedef struct NativeScreen NativeScreen; typedef struct NativeByteCode NativeByteCode; diff --git a/bridge/polyfill/src/bridge.ts b/bridge/polyfill/src/bridge.ts index 680ddcafba..8a8c83872d 100644 --- a/bridge/polyfill/src/bridge.ts +++ b/bridge/polyfill/src/bridge.ts @@ -9,5 +9,8 @@ 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_location_reload__: () => void; +export const webfLocationReload = __webf_location_reload__; + declare const __webf_print__: (log: string, level?: string) => void; export const webfPrint = __webf_print__; \ No newline at end of file diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index 3bd12a4926..c70ac181cb 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -5,42 +5,38 @@ import './dom'; // import './query-selector'; -import { console } from './console'; -// import { fetch, Request, Response, Headers } from './fetch'; -// import { matchMedia } from './match-media'; -import { location } from './location'; -// import { history } from './history'; -// import { navigator } from './navigator'; -// import { XMLHttpRequest } from './xhr'; -// import { asyncStorage } from './async-storage'; -// import { URLSearchParams } from './url-search-params'; -// import { URL } from './url'; -import { webf } from './webf'; -// import { ErrorEvent, PromiseRejectionEvent } from './events'; +import {console} from './console'; +import {fetch, Headers, Request, Response} from './fetch'; +import {matchMedia} from './match-media'; +import {location} from './location'; +import {history} from './history'; +import {navigator} from './navigator'; +import {XMLHttpRequest} from './xhr'; +import {asyncStorage} from './async-storage'; +import {URLSearchParams} from './url-search-params'; +import {URL} from './url'; +import {webf} from './webf'; -// defineGlobalProperty('ErrorEvent', ErrorEvent); -// defineGlobalProperty('PromiseRejectionEvent', PromiseRejectionEvent); defineGlobalProperty('console', console); -// defineGlobalProperty('Request', Request); -// defineGlobalProperty('Response', Response); -// defineGlobalProperty('Headers', Headers); -// defineGlobalProperty('fetch', fetch); -// defineGlobalProperty('matchMedia', matchMedia); +defineGlobalProperty('Request', Request); +defineGlobalProperty('Response', Response); +defineGlobalProperty('Headers', Headers); +defineGlobalProperty('fetch', fetch); +defineGlobalProperty('matchMedia', matchMedia); defineGlobalProperty('location', location); -// defineGlobalProperty('history', history); -// defineGlobalProperty('navigator', navigator); -// defineGlobalProperty('XMLHttpRequest', XMLHttpRequest); -// defineGlobalProperty('asyncStorage', asyncStorage); -// defineGlobalProperty('URLSearchParams', URLSearchParams); -// defineGlobalProperty('URL', URL); +defineGlobalProperty('history', history); +defineGlobalProperty('navigator', navigator); +defineGlobalProperty('XMLHttpRequest', XMLHttpRequest); +defineGlobalProperty('asyncStorage', asyncStorage); +defineGlobalProperty('URLSearchParams', URLSearchParams); +defineGlobalProperty('URL', URL); defineGlobalProperty('webf', webf); -// defineGlobalProperty('ErrorEvent', ErrorEvent); function defineGlobalProperty(key: string, value: any, isEnumerable: boolean = true) { - Object.defineProperty(globalThis, key, { - value: value, - enumerable: isEnumerable, - writable: true, - configurable: true - }); + Object.defineProperty(globalThis, key, { + value: value, + enumerable: isEnumerable, + writable: true, + configurable: true + }); } diff --git a/bridge/polyfill/src/location.ts b/bridge/polyfill/src/location.ts index f00a056ec7..51c69708e3 100644 --- a/bridge/polyfill/src/location.ts +++ b/bridge/polyfill/src/location.ts @@ -3,9 +3,9 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import { URL } from './url'; -import { webf } from './webf'; -import { krakenLocationReload } from './bridge'; +import {URL} from './url'; +import {webf} from './webf'; +import {webfLocationReload} from './bridge'; // Lazy parse url. let _url: URL; @@ -51,7 +51,7 @@ export const location = { }; }, get reload() { - return krakenLocationReload.bind(this); + return webfLocationReload.bind(this); }, get replace() { return (replaceURL: string) => { diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl index 49d45329ed..a4e44555e1 100644 --- a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl @@ -14,7 +14,7 @@ #include "bindings/qjs/cppgc/mutation_scope.h" #include "core/executing_context.h" -namespace kraken { +namespace webf { <% if (wrapperTypeInfoInit) { %> <%= wrapperTypeInfoInit %> diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl index cecbe09048..3089a9251f 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl @@ -3,7 +3,7 @@ #include "qjs_<%= _.snakeCase(object.parent) %>.h" <% } %> -namespace kraken { +namespace webf { class ExecutingContext; class ExceptionState; diff --git a/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl b/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl index 059bb60cda..4b38cfe531 100644 --- a/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl @@ -1,6 +1,6 @@ #include "core/<%= blob.implement %>.h" -namespace kraken { +namespace webf { class ExecutingContext; diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl index 63e4b3246d..9299d49a34 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl @@ -1,6 +1,6 @@ #include "core/<%= blob.implement %>.h" -namespace kraken { +namespace webf { class ExecutingContext; diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl index d5a3809af6..5f1d4fcf6b 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl @@ -25,8 +25,7 @@ <% } %> <% }); %> - -namespace kraken { +namespace webf { using HTMLConstructorFunction = HTMLElement* (*)(Document&); @@ -100,4 +99,4 @@ void HTMLElementFactory::Dispose() { g_html_constructors = nullptr; } -} // namespace kraken +} // namespace webf diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl b/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl index e96d34ad8f..6ce96981f9 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl @@ -8,7 +8,7 @@ #include "bindings/qjs/atomic_string.h" -namespace kraken { +namespace webf { class Document; class HTMLElement; @@ -20,6 +20,6 @@ class HTMLElementFactory { static void Dispose(); }; -} // namespace kraken +} // namespace webf #endif // KRAKENBRIDGE_CORE_HTML_ELEMENT_FACTORY_H_ diff --git a/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl b/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl index 47c98314b7..570b93d8f9 100644 --- a/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl @@ -22,8 +22,7 @@ <% } %> <% }); %> - -namespace kraken { +namespace webf { <% function generateTypeHelperTemplate(name) { diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index 5a67cae366..e0a2c1af2f 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -5,7 +5,7 @@ #include "<%= name %>.h" -namespace kraken { +namespace webf { namespace <%= name %> { void* names_storage[kNamesCount * ((sizeof(AtomicString) + sizeof(void *) - 1) / sizeof(void *))]; @@ -51,4 +51,4 @@ void Dispose(){ } -} // kraken +} // webf diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl index 44fb2dbb49..eca9463669 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl @@ -9,7 +9,7 @@ #include "bindings/qjs/atomic_string.h" -namespace kraken { +namespace webf { namespace <%= name %> { <% _.forEach(data, function(name, index) { %> @@ -29,6 +29,6 @@ void Dispose(); } -} // kraken +} // webf #endif // #define <%= _.snakeCase(name).toUpperCase() %> diff --git a/bridge/test/run_integration_test.cc b/bridge/test/run_integration_test.cc index 9ea7626a80..a902ba5332 100644 --- a/bridge/test/run_integration_test.cc +++ b/bridge/test/run_integration_test.cc @@ -9,7 +9,7 @@ #include "kraken_bridge_test.h" #include "kraken_test_env.h" -using namespace kraken; +using namespace webf; #include "page.h" #include "webf_bridge_test.h" #include "webf_test_env.h" @@ -42,7 +42,7 @@ TEST(IntegrationTest, runSpecs) { bridge->evaluateScript(code.c_str(), code.size(), "vm://", 0); executeTest(context->contextId(), [](int32_t contextId, void* status) -> void* { - KRAKEN_LOG(VERBOSE) << "done"; + WEBF_LOG(VERBOSE) << "done"; return nullptr; }); diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index f7a5ae6cec..d503085330 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -10,9 +10,9 @@ 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 diff --git a/bridge/test/kraken_test_context.cc b/bridge/test/webf_test_context.cc similarity index 96% rename from bridge/test/kraken_test_context.cc rename to bridge/test/webf_test_context.cc index e24c195eb7..b39beb03b9 100644 --- a/bridge/test/kraken_test_context.cc +++ b/bridge/test/webf_test_context.cc @@ -3,7 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#include "kraken_test_context.h" +#include "webf_test_context.h" #include "bindings/qjs/member_installer.h" #include "core/dom/document.h" #include "core/fileapi/blob.h" @@ -11,7 +11,7 @@ #include "qjs_blob.h" #include "testframework.h" -namespace kraken { +namespace webf { static JSValue executeTest(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv) { JSValue& callback = argv[0]; @@ -67,7 +67,7 @@ static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int arg "Failed to execute '__kraken_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); } - std::unique_ptr<NativeString> screenShotNativeString = kraken::jsValueToNativeString(ctx, screenShotValue); + 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) { @@ -186,7 +186,7 @@ static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc "Failed to execute '__kraken_simulate_keypress__': first arguments should be a string"); } - std::unique_ptr<NativeString> nativeString = kraken::jsValueToNativeString(ctx, charStringValue); + 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; @@ -242,9 +242,9 @@ void KrakenTestContext::invokeExecuteTest(ExecuteCallback executeCallback) { return JS_ThrowTypeError(ctx, "failed to execute 'done': parameter 1 (status) is not a string"); } - KRAKEN_LOG(VERBOSE) << "Done.."; + WEBF_LOG(VERBOSE) << "Done.."; - std::unique_ptr<NativeString> status = kraken::jsValueToNativeString(ctx, statusValue); + std::unique_ptr<NativeString> status = webf::jsValueToNativeString(ctx, statusValue); callbackContext->executeCallback(callbackContext->context->contextId(), status.get()); JS_FreeValue(ctx, proxyObject); return JS_NULL; @@ -313,4 +313,4 @@ void KrakenTestContext::registerTestEnvDartMethods(uint64_t* methodBytes, int32_ assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); } -} // namespace kraken +} // namespace webf diff --git a/bridge/test/kraken_test_context.h b/bridge/test/webf_test_context.h similarity index 84% rename from bridge/test/kraken_test_context.h rename to bridge/test/webf_test_context.h index 687d4dd8b4..e6345caa60 100644 --- a/bridge/test/kraken_test_context.h +++ b/bridge/test/webf_test_context.h @@ -3,15 +3,15 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#ifndef KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H -#define KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H +#ifndef KRAKENBRIDGE_WEBF_TEST_CONTEXT_H +#define KRAKENBRIDGE_WEBF_TEST_CONTEXT_H #include "bindings/qjs/qjs_function.h" #include "core/executing_context.h" #include "core/page.h" -#include "kraken_bridge_test.h" +#include "webf_bridge_test.h" -namespace kraken { +namespace webf { struct ImageSnapShotContext { JSValue callback; @@ -39,6 +39,6 @@ class KrakenTestContext final { KrakenPage* page_; }; -} // namespace kraken +} // namespace webf -#endif // KRAKENBRIDGE_KRAKEN_TEST_CONTEXT_H +#endif // KRAKENBRIDGE_WEBF_TEST_CONTEXT_H diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index f696cae1ae..cae017ebea 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -29,12 +29,12 @@ static int64_t get_time_ms(void) { } #endif -namespace kraken { +namespace webf { typedef struct { struct list_head link; int64_t timeout; - kraken::DOMTimer* timer; + webf::DOMTimer* timer; int32_t contextId; bool isInterval; AsyncCallback func; @@ -42,7 +42,7 @@ typedef struct { typedef struct { struct list_head link; - kraken::FrameCallback* callback; + webf::FrameCallback* callback; int32_t contextId; AsyncRAFCallback handler; int32_t callbackId; @@ -86,7 +86,7 @@ void TEST_reloadApp(int32_t contextId) {} int32_t timerId = 0; -int32_t TEST_setTimeout(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { +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)); @@ -103,7 +103,7 @@ int32_t TEST_setTimeout(kraken::DOMTimer* timer, int32_t contextId, AsyncCallbac return id; } -int32_t TEST_setInterval(kraken::DOMTimer* timer, int32_t contextId, AsyncCallback callback, int32_t timeout) { +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)); @@ -122,7 +122,7 @@ int32_t TEST_setInterval(kraken::DOMTimer* timer, int32_t contextId, AsyncCallba int32_t callbackId = 0; -uint32_t TEST_requestAnimationFrame(kraken::FrameCallback* frameCallback, int32_t contextId, AsyncRAFCallback handler) { +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)); @@ -140,14 +140,14 @@ uint32_t TEST_requestAnimationFrame(kraken::FrameCallback* frameCallback, int32_ } void TEST_cancelAnimationFrame(int32_t contextId, int32_t id) { - auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); + auto* page = static_cast<webf::KrakenPage*>(getPage(contextId)); auto* context = page->GetExecutingContext(); JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(ScriptState::runtime())); ts->os_frameCallbacks.erase(id); } void TEST_clearTimeout(int32_t contextId, int32_t timerId) { - auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); + auto* page = static_cast<webf::KrakenPage*>(getPage(contextId)); auto* context = page->GetExecutingContext(); JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(ScriptState::runtime())); ts->os_timers.erase(timerId); @@ -202,7 +202,7 @@ std::unique_ptr<webf::WebFPage> TEST_init(OnJSError onJsError) { TEST_mockDartMethods(contextId, onJsError); initTestFramework(contextId); - auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); + auto* page = static_cast<webf::KrakenPage*>(getPage(contextId)); auto* context = page->GetExecutingContext(); JSThreadState* th = new JSThreadState(); JS_SetRuntimeOpaque(ScriptState::runtime(), th); @@ -220,7 +220,7 @@ std::unique_ptr<webf::WebFPage> TEST_allocateNewPage() { return std::unique_ptr<webf::WebFPage>(static_cast<webf::WebFPage*>(getPage(newContextId))); } -static bool jsPool(kraken::ExecutingContext* context) { +static bool jsPool(webf::ExecutingContext* context) { JSRuntime* rt = ScriptState::runtime(); JSThreadState* ts = static_cast<JSThreadState*>(JS_GetRuntimeOpaque(rt)); int64_t cur_time, delay; @@ -267,7 +267,7 @@ static bool jsPool(kraken::ExecutingContext* context) { return false; } -void TEST_runLoop(kraken::ExecutingContext* context) { +void TEST_runLoop(webf::ExecutingContext* context) { for (;;) { context->DrainPendingPromiseJobs(); if (jsPool(context)) @@ -300,7 +300,7 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { registerDartMethods(contextId, mockMethods.data(), mockMethods.size()); } -} // namespace kraken +} // namespace webf // void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { // NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); diff --git a/bridge/test/webf_test_env.h b/bridge/test/webf_test_env.h index 29b0b5d471..8caaee0c6b 100644 --- a/bridge/test/webf_test_env.h +++ b/bridge/test/webf_test_env.h @@ -18,10 +18,10 @@ // TEST_OnEventTargetDisposed onEventTargetDisposed{nullptr}; //}; // -//// Mock dart methods and add async timer to emulate kraken environment in C++ unit test. +//// Mock dart methods and add async timer to emulate webf environment in C++ unit test. // -namespace kraken { +namespace webf { std::unique_ptr<KrakenPage> TEST_init(OnJSError onJsError); std::unique_ptr<KrakenPage> TEST_init(); @@ -29,7 +29,7 @@ std::unique_ptr<KrakenPage> TEST_allocateNewPage(); void TEST_runLoop(ExecutingContext* context); void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); -} // namespace kraken +} // 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); // void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback); diff --git a/bridge/webf_bridge.cc b/bridge/webf_bridge.cc index 1b693fa71f..971ddd14f1 100644 --- a/bridge/webf_bridge.cc +++ b/bridge/webf_bridge.cc @@ -13,7 +13,7 @@ #include "foundation/logging.h" #include "foundation/ui_command_buffer.h" #include "foundation/ui_task_queue.h" -#include "include/kraken_bridge.h" +#include "include/webf_bridge.h" #if defined(_WIN32) #define SYSTEM_NAME "windows" // Windows @@ -74,7 +74,7 @@ void initJSPagePool(int poolSize) { webf::WebFPage::pageContextPool[i] = nullptr; } - kraken::KrakenPage::pageContextPool[0] = new kraken::KrakenPage(0, nullptr); + webf::WebFPage::pageContextPool[0] = new webf::WebFPage(0, nullptr); inited = true; maxPoolSize = poolSize; } @@ -98,12 +98,12 @@ int32_t allocateNewPage(int32_t targetContextId) { targetContextId = searchForAvailableContextId(); } - assert(kraken::KrakenPage::pageContextPool[targetContextId] == nullptr && + 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 kraken::KrakenPage(targetContextId, nullptr); - kraken::KrakenPage::pageContextPool[targetContextId] = page; + auto* page = new webf::WebFPage(targetContextId, nullptr); + webf::WebFPage::pageContextPool[targetContextId] = page; return targetContextId; } @@ -120,14 +120,14 @@ bool checkPage(int32_t contextId) { bool checkPage(int32_t contextId, void* context) { if (webf::WebFPage::pageContextPool[contextId] == nullptr) return false; - auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); + auto* page = static_cast<webf::WebFPage*>(getPage(contextId)); return page->GetExecutingContext() == context; } void evaluateScripts(int32_t contextId, NativeString* code, const char* bundleFilename, int startLine) { assert(checkPage(contextId) && "evaluateScripts: contextId is not valid"); - auto context = static_cast<kraken::KrakenPage*>(getPage(contextId)); - context->evaluateScript(reinterpret_cast<kraken::NativeString*>(code), bundleFilename, startLine); + auto context = static_cast<webf::WebFPage*>(getPage(contextId)); + context->evaluateScript(reinterpret_cast<webf::NativeString*>(code), bundleFilename, startLine); } void evaluateQuickjsByteCode(int32_t contextId, uint8_t* bytes, int32_t byteLen) { @@ -145,8 +145,8 @@ void parseHTML(int32_t contextId, const char* code, int32_t length) { void reloadJsContext(int32_t contextId) { assert(checkPage(contextId) && "reloadJSContext: contextId is not valid"); auto bridgePtr = getPage(contextId); - auto context = static_cast<kraken::KrakenPage*>(bridgePtr); - auto newContext = new kraken::KrakenPage(contextId, nullptr); + auto context = static_cast<webf::WebFPage*>(bridgePtr); + auto newContext = new webf::WebFPage(contextId, nullptr); delete context; webf::WebFPage::pageContextPool[contextId] = newContext; } @@ -157,14 +157,14 @@ void invokeModuleEvent(int32_t contextId, void* event, NativeString* extra) { assert(checkPage(contextId) && "invokeEventListener: contextId is not valid"); - auto context = static_cast<kraken::KrakenPage*>(getPage(contextId)); - context->invokeModuleEvent(reinterpret_cast<kraken::NativeString*>(moduleName), eventType, event, - reinterpret_cast<kraken::NativeString*>(extra)); + auto context = static_cast<webf::WebFPage*>(getPage(contextId)); + context->invokeModuleEvent(reinterpret_cast<webf::NativeString*>(moduleName), eventType, event, + reinterpret_cast<webf::NativeString*>(extra)); } void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length) { assert(checkPage(contextId) && "registerDartMethods: contextId is not valid"); - auto context = static_cast<kraken::KrakenPage*>(getPage(contextId)); + auto context = static_cast<webf::WebFPage*>(getPage(contextId)); context->registerDartMethods(methodBytes, length); } @@ -187,21 +187,21 @@ void setConsoleMessageHandler(ConsoleMessageHandler handler) { } void dispatchUITask(int32_t contextId, void* context, void* callback) { - auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); + 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) { - kraken::UITaskQueue::instance(contextId)->flushTask(); + webf::UITaskQueue::instance(contextId)->flushTask(); } void registerUITask(int32_t contextId, Task task, void* data) { - kraken::UITaskQueue::instance(contextId)->registerTask(task, data); + webf::UITaskQueue::instance(contextId)->registerTask(task, data); }; void* getUICommandItems(int32_t contextId) { - auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); + auto* page = static_cast<webf::WebFPage*>(getPage(contextId)); if (page == nullptr) return nullptr; return page->GetExecutingContext()->uiCommandBuffer()->data(); @@ -227,7 +227,7 @@ void registerContextDisposedCallbacks(int32_t contextId, Task task, void* data) } void registerPluginByteCode(uint8_t* bytes, int32_t length, const char* pluginName) { - kraken::ExecutingContext::pluginByteCode[pluginName] = kraken::NativeByteCode{bytes, length}; + webf::ExecutingContext::pluginByteCode[pluginName] = webf::NativeByteCode{bytes, length}; } int32_t profileModeEnabled() { diff --git a/bridge/webf_bridge_test.cc b/bridge/webf_bridge_test.cc index 9750bb965f..84d14f42d9 100644 --- a/bridge/webf_bridge_test.cc +++ b/bridge/webf_bridge_test.cc @@ -3,25 +3,23 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +#include "webf_bridge_test.h" #include <atomic> #include "bindings/qjs/native_string_utils.h" -#include "kraken_bridge_test.h" -#include "kraken_test_context.h" +#include "webf_test_context.h" -std::unordered_map<int, kraken::KrakenTestContext*> testContextPool = - std::unordered_map<int, kraken::KrakenTestContext*>(); +std::unordered_map<int, webf::KrakenTestContext*> testContextPool = std::unordered_map<int, webf::KrakenTestContext*>(); void initTestFramework(int32_t contextId) { - auto* page = static_cast<kraken::KrakenPage*>(getPage(contextId)); - auto testContext = new kraken::KrakenTestContext(page->GetExecutingContext()); + auto* page = static_cast<webf::KrakenPage*>(getPage(contextId)); + auto testContext = new webf::KrakenTestContext(page->GetExecutingContext()); testContextPool[contextId] = testContext; } int8_t evaluateTestScripts(int32_t contextId, void* code, const char* bundleFilename, int startLine) { auto testContext = testContextPool[contextId]; - return testContext->evaluateTestScripts(static_cast<kraken::NativeString*>(code)->string(), - static_cast<kraken::NativeString*>(code)->length(), bundleFilename, - startLine); + return testContext->evaluateTestScripts(static_cast<webf::NativeString*>(code)->string(), + static_cast<webf::NativeString*>(code)->length(), bundleFilename, startLine); } void executeTest(int32_t contextId, ExecuteCallback executeCallback) { From 0b35f507ba5abe20ebae4406b55baaab553ecbb0 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Tue, 9 Aug 2022 21:47:08 +0800 Subject: [PATCH 155/498] fix: fix global error report. --- bridge/bindings/qjs/converter_impl.h | 24 +++---- bridge/bindings/qjs/dictionary_base.h | 3 + bridge/bindings/qjs/script_wrappable.h | 2 +- bridge/core/dom/events/event.cc | 2 - bridge/core/dom/events/event.h | 2 - bridge/core/events/error_event.cc | 9 +-- bridge/core/executing_context.cc | 68 +++++++++---------- bridge/core/executing_context.h | 6 ++ bridge/core/executing_context_test.cc | 8 ++- bridge/core/frame/window.cc | 5 ++ bridge/core/frame/window.h | 3 + .../static/idl_templates/dictionary.cc.tpl | 8 +-- .../static/idl_templates/dictionary.h.tpl | 2 +- 13 files changed, 77 insertions(+), 65 deletions(-) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 69f16d6b60..efe06bc2b2 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -415,18 +415,18 @@ struct Converter<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T static JSValue ToValue(JSContext* ctx, const T* value) { return value->ToQuickJS(); } }; -template <> -struct Converter<Window> : public ConverterBase<Window> { - static Window* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - return toScriptWrappable<Window>(value); - } - static JSValue ToValue(JSContext* ctx, Window* window) { - return JS_DupValue(ctx, window->GetExecutingContext()->Global()); - } - static JSValue ToValue(JSContext* ctx, const Window* window) { - return JS_DupValue(ctx, window->GetExecutingContext()->Global()); - } -}; +//template <> +//struct Converter<Window> : public ConverterBase<Window> { +// static Window* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { +// return toScriptWrappable<Window>(value); +// } +// static JSValue ToValue(JSContext* ctx, Window* window) { +// return JS_DupValue(ctx, window->GetExecutingContext()->Global()); +// } +// static JSValue ToValue(JSContext* ctx, const Window* window) { +// return JS_DupValue(ctx, window->GetExecutingContext()->Global()); +// } +//}; }; // namespace webf diff --git a/bridge/bindings/qjs/dictionary_base.h b/bridge/bindings/qjs/dictionary_base.h index 058f012ad4..e51e50a0b6 100644 --- a/bridge/bindings/qjs/dictionary_base.h +++ b/bridge/bindings/qjs/dictionary_base.h @@ -10,6 +10,8 @@ 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 @@ -32,6 +34,7 @@ class DictionaryBase { // 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 diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 21dc4cf673..249bfb2027 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -47,7 +47,7 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { void Trace(GCVisitor* visitor) const override{}; - JSValue ToQuickJS() const; + virtual JSValue ToQuickJS() const; JSValue ToQuickJSUnsafe() const; ScriptValue ToValue(); diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index ebaf7d8a82..3af0e00848 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -21,8 +21,6 @@ Event* Event::From(ExecutingContext* context, NativeEvent* native_event) { return event; } -Event::Event(ExecutingContext* context) : Event(context, AtomicString::Empty(context->ctx())) {} - Event::Event(ExecutingContext* context, const AtomicString& event_type) : Event(context, event_type, diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 65b8f344fd..7054ab97ce 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -89,7 +89,6 @@ class Event : public ScriptWrappable { enum PhaseType { kNone = 0, kCapturingPhase = 1, kAtTarget = 2, kBubblingPhase = 3 }; - static Event* Create(ExecutingContext* context) { return MakeGarbageCollected<Event>(context); }; static Event* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { return MakeGarbageCollected<Event>(context, type); }; @@ -103,7 +102,6 @@ class Event : public ScriptWrappable { static Event* From(ExecutingContext* context, NativeEvent* native_event); Event() = delete; - explicit Event(ExecutingContext* context); explicit Event(ExecutingContext* context, const AtomicString& event_type); explicit Event(ExecutingContext* context, const AtomicString& type, const std::shared_ptr<EventInit>& init); explicit Event(ExecutingContext* context, diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc index 96bc9a24dc..22257094f0 100644 --- a/bridge/core/events/error_event.cc +++ b/bridge/core/events/error_event.cc @@ -3,6 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "error_event.h" +#include "event_type_names.h" namespace webf { @@ -20,19 +21,19 @@ ErrorEvent* ErrorEvent::Create(ExecutingContext* context, } ErrorEvent::ErrorEvent(ExecutingContext* context, const std::string& message) - : Event(context), message_(message), source_location_(std::make_unique<SourceLocation>("", 0, 0)) {} + : 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), message_(message), source_location_(std::move(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), message_(type.ToStdString()), source_location_(std::make_unique<SourceLocation>("", 0, 0)) {} + : 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(context, event_type_names::kerror), message_(type.ToStdString()), error_(initializer->error()), source_location_(std::make_unique<SourceLocation>(initializer->filename().ToStdString(), diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index d07f945aa7..4153b4ee96 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -3,12 +3,13 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "executing_context.h" +#include "bindings/qjs/converter_impl.h" #include "built_in_string.h" +#include "event_type_names.h" #include "core/dom/document.h" -#include "core/html/html_html_element.h" +#include "core/events/error_event.h" #include "polyfill.h" #include "qjs_window.h" - #include "foundation/logging.h" namespace webf { @@ -145,7 +146,7 @@ void* ExecutingContext::owner() { bool ExecutingContext::HandleException(JSValue* exc) { if (JS_IsException(*exc)) { JSValue error = JS_GetException(script_state_.ctx()); - ReportError(error); + MemberMutationScope scope{this}; DispatchGlobalErrorEvent(this, error); JS_FreeValue(script_state_.ctx(), error); return false; @@ -244,35 +245,13 @@ uint8_t* ExecutingContext::DumpByteCode(const char* code, } void ExecutingContext::DispatchGlobalErrorEvent(ExecutingContext* context, JSValueConst error) { - // JSContext* ctx = context->ctx(); - // auto* window = static_cast<Window*>(JS_GetOpaque(context->global(), Window::classId())); - // - // { - // JSValue ErrorEventValue = JS_GetPropertyStr(ctx, context->global(), "ErrorEvent"); - // JSValue errorType = JS_NewString(ctx, "error"); - // JSValue errorInit = JS_NewObject(ctx); - // JS_SetPropertyStr(ctx, errorInit, "error", JS_DupValue(ctx, error)); - // JS_SetPropertyStr(ctx, errorInit, "message", JS_GetPropertyStr(ctx, error, "message")); - // JS_SetPropertyStr(ctx, errorInit, "lineno", JS_GetPropertyStr(ctx, error, "lineNumber")); - // JS_SetPropertyStr(ctx, errorInit, "filename", JS_GetPropertyStr(ctx, error, "fileName")); - // JS_SetPropertyStr(ctx, errorInit, "colno", JS_NewUint32(ctx, 0)); - // JSValue arguments[] = {errorType, errorInit}; - // JSValue errorEventValue = JS_CallConstructor(context->ctx(), ErrorEventValue, 2, arguments); - // if (JS_IsException(errorEventValue)) { - // context->handleException(&errorEventValue); - // return; - // } - // - // auto* errorEvent = static_cast<EventInstance*>(JS_GetOpaque(errorEventValue, Event::kEventClassID)); - // window->dispatchEvent(errorEvent); - // - // JS_FreeValue(ctx, ErrorEventValue); - // JS_FreeValue(ctx, errorEventValue); - // JS_FreeValue(ctx, errorType); - // JS_FreeValue(ctx, errorInit); - // - // context->drainPendingPromiseJobs(); - // } + 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 char* eventType, @@ -312,6 +291,27 @@ void ExecutingContext::FlushUICommand() { 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; + auto* window = toScriptWrappable<Window>(Global()); + window->dispatchEvent(error_event, ASSERT_NO_EXCEPTION()); + in_dispatch_error_event_ = false; +} + +void ExecutingContext::ReportErrorEvent(ErrorEvent* error_event) { + ReportError(error_event->error().QJSValue()); +} + void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, JSValueConst error) { @@ -371,10 +371,6 @@ void ExecutingContext::ClearMutationScope() { active_mutation_scope = active_mutation_scope->Parent(); } -// PendingPromises* ExecutingContext::PendingPromises() { -// return &pending_promises_; -//} - void ExecutingContext::InstallDocument() { MemberMutationScope scope{this}; document_ = MakeGarbageCollected<Document>(this); diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index a7daf0d295..65881101c9 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -39,6 +39,7 @@ struct NativeByteCode { class ExecutingContext; class Document; class MemberMutationScope; +class ErrorEvent; using JSExceptionHandler = std::function<void(ExecutingContext* context, const char* message)>; @@ -105,6 +106,10 @@ class ExecutingContext { // 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); @@ -141,6 +146,7 @@ class ExecutingContext { ModuleListenerContainer module_listener_container_; ModuleCallbackCoordinator module_callbacks_; ExecutionContextData context_data_{this}; + bool in_dispatch_error_event_{false}; UICommandBuffer ui_command_buffer_{this}; std::unique_ptr<DartMethodPointer> dart_method_ptr_ = std::make_unique<DartMethodPointer>(); RejectedPromises rejected_promises_; diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 558da651f5..232954a74b 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -78,12 +78,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); diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index d9ef4f305c..ea2d71af88 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -26,6 +26,7 @@ Window* Window::open(const AtomicString& url, ExceptionState& exception_state) { NativeValueConverter<NativeTypeString>::ToNativeValue(url.ToNativeString().release()), }; InvokeBindingMethod(binding_call_methods::kopen, 1, args, exception_state); + return this; } Screen* Window::screen() { @@ -139,4 +140,8 @@ void Window::Trace(GCVisitor* visitor) const { EventTargetWithInlineData::Trace(visitor); } +JSValue Window::ToQuickJS() const { + return JS_GetGlobalObject(ctx()); +} + } // namespace webf diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index 4b32108e25..22112ca865 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -47,6 +47,9 @@ class Window : public EventTargetWithInlineData { 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_; }; diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl index 72968a5df2..aa36b27608 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl @@ -20,19 +20,19 @@ bool <%= className %>::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dict } <% _.forEach(props, function(prop, index) { %> - JS_SetPropertyStr(ctx, qjs_dictionary, "<%= prop.name %>_", Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, <%= prop.name %>_)); + JS_SetPropertyStr(ctx, qjs_dictionary, "<%= prop.name %>", Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, <%= prop.name %>_)); <% }); %> return true; } -void <%= className %>::FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state) { +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; + return false; } <% _.forEach(props, function(prop, index) { %> @@ -44,5 +44,5 @@ void <%= className %>::FillMembersWithQJSObject(JSContext* ctx, JSValue value, E <% }); %> - + return true; } diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl index 3089a9251f..1759a3c621 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl @@ -21,7 +21,7 @@ class <%= className %> : public <%= object.parent ? object.parent : 'DictionaryB void set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(<%= generateTypeValue(prop.type) %> value) { <%= prop.name %>_ = value; } <% })); %> bool FillQJSObjectWithMembers(JSContext *ctx, JSValue qjs_dictionary) const override; - void FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state); + bool FillMembersWithQJSObject(JSContext* ctx, JSValue value, ExceptionState& exception_state) override; private: <% _.forEach(props, (function(prop, index) { %> <%= generateTypeValue(prop.type) %> <%= prop.name %>_; From 925d8fdb4222cd55c14d6546856e5fcfc846ce69 Mon Sep 17 00:00:00 2001 From: openwebf-bot <openwebf@openwebf.com> Date: Tue, 9 Aug 2022 13:48:19 +0000 Subject: [PATCH 156/498] Committing clang-format changes --- bridge/bindings/qjs/converter_impl.h | 4 +- bridge/core/dom/document_test.cc | 4 +- bridge/core/dom/events/event_target_test.cc | 4 +- bridge/core/dom/node_test.cc | 4 +- bridge/core/events/error_event.cc | 8 +- bridge/core/executing_context.cc | 36 +++---- bridge/core/executing_context_test.cc | 8 +- bridge/core/frame/console.cc | 6 +- bridge/core/frame/console.h | 6 +- bridge/core/frame/module_manager.cc | 28 +++--- bridge/core/frame/module_manager.h | 28 +++--- bridge/core/page.cc | 16 +-- bridge/core/timing/performance.cc | 104 ++++++++++---------- bridge/core/timing/performance.h | 51 +++++----- bridge/foundation/macros.h | 18 ++-- bridge/test/webf_test_context.cc | 19 ++-- 16 files changed, 170 insertions(+), 174 deletions(-) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index efe06bc2b2..bc44eaa5ce 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -415,8 +415,8 @@ struct Converter<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T static JSValue ToValue(JSContext* ctx, const T* value) { return value->ToQuickJS(); } }; -//template <> -//struct Converter<Window> : public ConverterBase<Window> { +// template <> +// struct Converter<Window> : public ConverterBase<Window> { // static Window* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { // return toScriptWrappable<Window>(value); // } diff --git a/bridge/core/dom/document_test.cc b/bridge/core/dom/document_test.cc index 1f3cf16bc8..be4a2a6dd9 100644 --- a/bridge/core/dom/document_test.cc +++ b/bridge/core/dom/document_test.cc @@ -48,9 +48,7 @@ TEST(Document, body) { TEST(Document, appendParentWillFail) { 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; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); auto context = bridge->GetExecutingContext(); const char* code = "document.body.appendChild(document.documentElement)"; diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index 3378d6e193..df970ade17 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -31,9 +31,7 @@ TEST(EventTarget, addEventListener) { TEST(EventTarget, removeEventListener) { 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; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; diff --git a/bridge/core/dom/node_test.cc b/bridge/core/dom/node_test.cc index 1a84f1b397..4610d13477 100644 --- a/bridge/core/dom/node_test.cc +++ b/bridge/core/dom/node_test.cc @@ -57,9 +57,7 @@ TEST(Node, childNodes) { TEST(Node, textNodeHaveEmptyChildNodes) { 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; }; auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { errorCalled = true; }); auto context = bridge->GetExecutingContext(); const char* code = diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc index 22257094f0..caa02aaf17 100644 --- a/bridge/core/events/error_event.cc +++ b/bridge/core/events/error_event.cc @@ -21,13 +21,17 @@ ErrorEvent* ErrorEvent::Create(ExecutingContext* context, } ErrorEvent::ErrorEvent(ExecutingContext* context, const std::string& message) - : Event(context, event_type_names::kerror), message_(message), source_location_(std::make_unique<SourceLocation>("", 0, 0)) {} + : 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)) {} + : Event(context, event_type_names::kerror), + message_(type.ToStdString()), + source_location_(std::make_unique<SourceLocation>("", 0, 0)) {} ErrorEvent::ErrorEvent(ExecutingContext* context, const AtomicString& type, diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 4153b4ee96..b98764d0fa 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -5,12 +5,12 @@ #include "executing_context.h" #include "bindings/qjs/converter_impl.h" #include "built_in_string.h" -#include "event_type_names.h" #include "core/dom/document.h" #include "core/events/error_event.h" +#include "event_type_names.h" +#include "foundation/logging.h" #include "polyfill.h" #include "qjs_window.h" -#include "foundation/logging.h" namespace webf { @@ -26,15 +26,15 @@ std::unique_ptr<ExecutingContext> createJSContext(int32_t contextId, const JSExc ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) : context_id_(contextId), handler_(handler), owner_(owner), ctx_invalid_(false), unique_id_(context_unique_id++) { -//#if ENABLE_PROFILE -// auto jsContextStartTime = -// std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()) -// .count(); -// auto nativePerformance = Performance::instance(context_)->m_nativePerformance; -// nativePerformance.mark(PERF_JS_CONTEXT_INIT_START, jsContextStartTime); -// nativePerformance.mark(PERF_JS_CONTEXT_INIT_END); -// nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_START); -//#endif + //#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; @@ -58,10 +58,10 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& // Binding global object and window. InstallGlobal(); -//#if ENABLE_PROFILE -// nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); -// nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); -//#endif + //#if ENABLE_PROFILE + // nativePerformance.mark(PERF_JS_NATIVE_METHOD_INIT_END); + // nativePerformance.mark(PERF_JS_POLYFILL_INIT_START); + //#endif initWebFPolyFill(this); @@ -69,9 +69,9 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& EvaluateByteCode(p.second.bytes, p.second.length); } -//#if ENABLE_PROFILE -// nativePerformance.mark(PERF_JS_POLYFILL_INIT_END); -//#endif + //#if ENABLE_PROFILE + // nativePerformance.mark(PERF_JS_POLYFILL_INIT_END); + //#endif } ExecutingContext::~ExecutingContext() { diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 232954a74b..b358940bfb 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -175,9 +175,7 @@ TEST(Context, unhandledRejectionEventWillTriggerWhenNotHandled) { static bool logCalled = false; auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; 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; }; std::string code = R"( window.addEventListener('unhandledrejection', event => { @@ -206,9 +204,7 @@ TEST(Context, handledRejectionEventWillTriggerWhenUnHandledRejectHandled) { static bool logCalled = false; auto errorHandler = [](int32_t contextId, const char* errmsg) { errorHandlerExecuted = true; }; 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; }; std::string code = R"( window.addEventListener('unhandledrejection', event => { diff --git a/bridge/core/frame/console.cc b/bridge/core/frame/console.cc index f9f0b7dcf8..d6132e1f90 100644 --- a/bridge/core/frame/console.cc +++ b/bridge/core/frame/console.cc @@ -10,9 +10,9 @@ namespace webf { void Console::__webf_print__(ExecutingContext* context, - const AtomicString& log, - const AtomicString& level, - ExceptionState& exception) { + const AtomicString& log, + const AtomicString& level, + ExceptionState& exception) { std::stringstream stream; std::string buffer = log.ToStdString(); stream << buffer; diff --git a/bridge/core/frame/console.h b/bridge/core/frame/console.h index 3efe040047..da45d5c0ca 100644 --- a/bridge/core/frame/console.h +++ b/bridge/core/frame/console.h @@ -14,9 +14,9 @@ namespace webf { class Console final { public: static void __webf_print__(ExecutingContext* context, - const AtomicString& log, - const AtomicString& level, - ExceptionState& exception); + const AtomicString& log, + const AtomicString& level, + ExceptionState& exception); static void __webf_print__(ExecutingContext* context, const AtomicString& log, ExceptionState& exception_state); }; diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 3051723ecc..4e8d99158b 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -61,27 +61,27 @@ void handleInvokeModuleUnexpectedCallback(void* callbackContext, } AtomicString ModuleManager::__webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ExceptionState& exception) { + const AtomicString& moduleName, + const AtomicString& method, + ExceptionState& exception) { ScriptValue empty = ScriptValue::Empty(context->ctx()); return __webf_invoke_module__(context, moduleName, method, empty, nullptr, exception); } AtomicString ModuleManager::__webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& paramsValue, - ExceptionState& exception) { + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& paramsValue, + ExceptionState& exception) { return __webf_invoke_module__(context, moduleName, method, paramsValue, nullptr, exception); } AtomicString ModuleManager::__webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& paramsValue, - std::shared_ptr<QJSFunction> callback, - ExceptionState& exception) { + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& paramsValue, + std::shared_ptr<QJSFunction> callback, + ExceptionState& exception) { std::unique_ptr<NativeString> params; if (!paramsValue.IsEmpty()) { params = paramsValue.ToJSONStringify(&exception).ToString().ToNativeString(); @@ -120,8 +120,8 @@ AtomicString ModuleManager::__webf_invoke_module__(ExecutingContext* context, } void ModuleManager::__webf_add_module_listener__(ExecutingContext* context, - const std::shared_ptr<QJSFunction>& handler, - ExceptionState& exception) { + const std::shared_ptr<QJSFunction>& handler, + ExceptionState& exception) { auto listener = ModuleListener::Create(handler); context->ModuleListeners()->AddModuleListener(listener); } diff --git a/bridge/core/frame/module_manager.h b/bridge/core/frame/module_manager.h index b61adcd197..1eb30d8179 100644 --- a/bridge/core/frame/module_manager.h +++ b/bridge/core/frame/module_manager.h @@ -15,23 +15,23 @@ namespace webf { class ModuleManager { public: static AtomicString __webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ExceptionState& exception); + const AtomicString& moduleName, + const AtomicString& method, + ExceptionState& exception); static AtomicString __webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& paramsValue, - ExceptionState& exception); + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& paramsValue, + ExceptionState& exception); static AtomicString __webf_invoke_module__(ExecutingContext* context, - const AtomicString& moduleName, - const AtomicString& method, - ScriptValue& paramsValue, - std::shared_ptr<QJSFunction> callback, - ExceptionState& exception); - static void __webf_add_module_listener__(ExecutingContext* context, - const std::shared_ptr<QJSFunction>& handler, + const AtomicString& moduleName, + const AtomicString& method, + ScriptValue& paramsValue, + std::shared_ptr<QJSFunction> callback, ExceptionState& exception); + static void __webf_add_module_listener__(ExecutingContext* context, + const std::shared_ptr<QJSFunction>& handler, + ExceptionState& exception); }; } // namespace webf diff --git a/bridge/core/page.cc b/bridge/core/page.cc index adf3f16f40..b7188a446f 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -84,15 +84,15 @@ void WebFPage::evaluateScript(const NativeString* script, const char* url, int s if (!context_->IsValid()) return; -//#if ENABLE_PROFILE -// auto nativePerformance = Performance::instance(context_)->m_nativePerformance; -// nativePerformance.mark(PERF_JS_PARSE_TIME_START); -// std::u16string patchedCode = std::u16string(u"performance.mark('js_parse_time_end');") + -// std::u16string(reinterpret_cast<const char16_t*>(script->string), script->length); -// context_->evaluateJavaScript(patchedCode.c_str(), patchedCode.size(), url, startLine); -//#else + //#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 + //#endif } void WebFPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { diff --git a/bridge/core/timing/performance.cc b/bridge/core/timing/performance.cc index d87a9a7166..7bc9070f8a 100644 --- a/bridge/core/timing/performance.cc +++ b/bridge/core/timing/performance.cc @@ -13,27 +13,27 @@ namespace webf { using namespace std::chrono; -//IMPL_PROPERTY_GETTER(PerformanceEntry, name)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// IMPL_PROPERTY_GETTER(PerformanceEntry, name)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // auto* entry = static_cast<PerformanceEntry*>(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) { +// IMPL_PROPERTY_GETTER(PerformanceEntry, entryType)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // auto* entry = static_cast<PerformanceEntry*>(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) { +// IMPL_PROPERTY_GETTER(PerformanceEntry, startTime)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // auto* entry = static_cast<PerformanceEntry*>(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) { +// IMPL_PROPERTY_GETTER(PerformanceEntry, duration)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // auto* entry = static_cast<PerformanceEntry*>(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) { +// IMPL_PROPERTY_GETTER(Performance, timeOrigin)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); // int64_t time = // std::chrono::duration_cast<std::chrono::milliseconds>(performance->m_context->timeOrigin.time_since_epoch()) @@ -41,11 +41,11 @@ using namespace std::chrono; // return JS_NewUint32(ctx, time); //} -//JSValue Performance::now(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// JSValue Performance::now(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); // return JS_NewFloat64(ctx, performance->internalNow()); //} -//JSValue Performance::toJSON(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// JSValue Performance::toJSON(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // auto* performance = static_cast<Performance*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); // double now = performance->internalNow(); // int64_t timeOrigin = @@ -58,7 +58,7 @@ using namespace std::chrono; // return object; //} // -//static JSValue buildPerformanceEntry(const std::string& entryType, +// static JSValue buildPerformanceEntry(const std::string& entryType, // ExecutionContext* context, // NativePerformanceEntry* nativePerformanceEntry) { // if (entryType == "mark") { @@ -71,7 +71,7 @@ using namespace std::chrono; // return JS_NULL; //} // -//JSValue Performance::clearMarks(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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) { @@ -102,7 +102,7 @@ using namespace std::chrono; // // return JS_NULL; //} -//JSValue Performance::clearMeasures(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// JSValue Performance::clearMeasures(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { // JSValue targetMark = JS_NULL; // if (argc == 1) { // targetMark = argv[0]; @@ -133,7 +133,7 @@ using namespace std::chrono; // // return JS_NULL; //} -//JSValue Performance::getEntries(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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(); // @@ -152,7 +152,7 @@ using namespace std::chrono; // JS_FreeValue(ctx, pushMethod); // return returnArray; //} -//JSValue Performance::getEntriesByName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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."); @@ -175,7 +175,7 @@ using namespace std::chrono; // JS_FreeValue(ctx, pushMethod); // return targetEntriesArray; //} -//JSValue Performance::getEntriesByType(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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."); @@ -197,7 +197,7 @@ using namespace std::chrono; // JS_FreeValue(ctx, pushMethod); // return targetEntriesArray; //} -//JSValue Performance::mark(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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."); @@ -209,10 +209,11 @@ using namespace std::chrono; // // return JS_NULL; //} -//JSValue Performance::measure(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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."); +// "Failed to execute 'measure' on 'Performance': 1 argument required, but only 0 +// present."); // } // // std::string name = jsValueToStdString(ctx, argv[0]); @@ -239,37 +240,37 @@ using namespace std::chrono; // return JS_NULL; //} // -//PerformanceEntry::PerformanceEntry(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) +// PerformanceEntry::PerformanceEntry(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) // : HostObject(context, "PerformanceEntry"), m_nativePerformanceEntry(nativePerformanceEntry) {} // -//PerformanceMark::PerformanceMark(ExecutionContext* context, std::string& name, int64_t startTime) +// 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) +// PerformanceMark::PerformanceMark(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) // : PerformanceEntry(context, nativePerformanceEntry) {} -//PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, +// 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) +// PerformanceMeasure::PerformanceMeasure(ExecutionContext* context, NativePerformanceEntry* nativePerformanceEntry) // : PerformanceEntry(context, nativePerformanceEntry) {} -//void NativePerformance::mark(const std::string& markName) { +// 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) { +// 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, +// Performance::Performance(ExecutionContext* context) : HostObject(context, "Performance") {} +// void Performance::internalMeasure(const std::string& name, // const std::string& startMark, // const std::string& endMark, // JSValue* exception) { @@ -281,7 +282,8 @@ using namespace std::chrono; // [&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.", +// *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not +// exist.", // startMark.c_str()); // return; // } @@ -291,7 +293,8 @@ using namespace std::chrono; // [&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.", +// *exception = JS_ThrowTypeError(m_ctx, "Failed to execute 'measure' on 'Performance': The mark %s does not +// exist.", // endMark.c_str()); // return; // } @@ -340,13 +343,13 @@ using namespace std::chrono; // } // } //} -//double Performance::now() const { +// 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() { +// std::vector<NativePerformanceEntry*> Performance::getFullEntries() { // auto* bridgeEntries = m_nativePerformance.entries; //#if ENABLE_PROFILE // if (getDartMethod()->getPerformanceEntries == nullptr) { @@ -382,7 +385,7 @@ using namespace std::chrono; // //#if ENABLE_PROFILE // -//void Performance::measureSummary(JSValue* exception) { +// 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); @@ -397,15 +400,16 @@ using namespace std::chrono; // 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, +// 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, +// 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); @@ -436,7 +440,7 @@ using namespace std::chrono; // 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, +// std::vector<NativePerformanceEntry*> findAllMeasures(const std::vector<NativePerformanceEntry*>& entries, // const std::string& targetName) { // std::vector<NativePerformanceEntry*> resultEntries; // @@ -449,7 +453,7 @@ using namespace std::chrono; // return resultEntries; //}; // -//double getMeasureTotalDuration(const std::vector<NativePerformanceEntry*>& measures) { +// double getMeasureTotalDuration(const std::vector<NativePerformanceEntry*>& measures) { // double duration = 0.0; // for (auto entry : measures) { // duration += entry->duration; @@ -457,7 +461,7 @@ using namespace std::chrono; // return duration / 1000; //} -//JSValue Performance::__webf_navigation_summary__(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { +// 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); @@ -538,7 +542,7 @@ using namespace std::chrono; // char buffer[5000]; // // clang-format off // sprintf(buffer, R"( -//Total time cost(without paint and layout): %.*fms +// Total time cost(without paint and layout): %.*fms // //%s: %.*fms // + %s %.*fms @@ -553,7 +557,7 @@ using namespace std::chrono; // + %s %.*fms // + %s %.*fms // + %s %.*fms -//First Bundle Load: %.*fms +// First Bundle Load: %.*fms // + %s %.*fms // + %s %.*fms // + %s %.*fms @@ -574,7 +578,7 @@ using namespace std::chrono; // + %s %.*fms avg: %.*fms count: %zu // + %s %.*fms avg: %.*fms count: %zu // + %s %.*fms avg: %.*fms count: %zu -//Rendering: %.*fms +// Rendering: %.*fms // + %s %.*fms avg: %.*fms count: %zu // + %s %.*fms avg: %.*fms count: %zu // + %s %.*fms avg: %.*fms count: %zu @@ -601,17 +605,17 @@ using namespace std::chrono; // 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_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, diff --git a/bridge/core/timing/performance.h b/bridge/core/timing/performance.h index 047fc36e43..a6180a543e 100644 --- a/bridge/core/timing/performance.h +++ b/bridge/core/timing/performance.h @@ -6,8 +6,8 @@ #ifndef BRIDGE_PERFORMANCE_H #define BRIDGE_PERFORMANCE_H -#include "core/dom/binding_object.h" #include "bindings/qjs/script_wrappable.h" +#include "core/dom/binding_object.h" #if ENABLE_PROFILE #define PERF_WIDGET_CREATION_COST "widget_creation_cost" @@ -145,7 +145,7 @@ struct NativePerformanceEntry { int64_t uniqueId; }; -//class PerformanceEntry : public HostObject { +// class PerformanceEntry : public HostObject { // public: // PerformanceEntry() = delete; // explicit PerformanceEntry(ExecutionContext* context, NativePerformanceEntry* m_nativePerformanceEntry); @@ -159,21 +159,21 @@ struct NativePerformanceEntry { // NativePerformanceEntry* m_nativePerformanceEntry{nullptr}; //}; // -//class PerformanceMark : public PerformanceEntry { +// 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 { +// 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 { +// class NativePerformance { // public: // void mark(const std::string& markName); // void mark(const std::string& markName, int64_t startTime); @@ -182,39 +182,40 @@ struct NativePerformanceEntry { class Performance : public ScriptWrappable, BindingObject { DEFINE_WRAPPERTYPEINFO(); + public: Performance() = delete; double now() const; -// 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); + // 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 -// DEFINE_READONLY_PROPERTY(timeOrigin); + // 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<NativePerformanceEntry*> getFullEntries(); -// -//#if ENABLE_PROFILE -// DEFINE_FUNCTION(__webf_navigation_summary__, 0); -//#endif + // void internalMeasure(const std::string& name, + // const std::string& startMark, + // const std::string& endMark, + // JSValue* exception); + // double internalNow(); + // std::vector<NativePerformanceEntry*> getFullEntries(); + // + //#if ENABLE_PROFILE + // DEFINE_FUNCTION(__webf_navigation_summary__, 0); + //#endif }; } // namespace webf diff --git a/bridge/foundation/macros.h b/bridge/foundation/macros.h index f7c87b1c85..df10440442 100644 --- a/bridge/foundation/macros.h +++ b/bridge/foundation/macros.h @@ -25,17 +25,17 @@ #define WEBF_DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete #define WEBF_DISALLOW_MOVE(TypeName) \ - TypeName(TypeName&&) = delete; \ + TypeName(TypeName&&) = delete; \ TypeName& operator=(TypeName&&) = delete -#define WEBF_STATIC_ONLY(Type) \ +#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() \ +#define WEBF_STACK_ALLOCATED() \ private: \ void* operator new(size_t) = delete; \ void* operator new(size_t, void*) = delete @@ -45,7 +45,7 @@ // Members you need a trace method and the containing object needs to call that // trace method. // -#define WEBF_DISALLOW_NEW() \ +#define WEBF_DISALLOW_NEW() \ public: \ using IsDisallowNewMarker = int; \ void* operator new(size_t, void* location) { return location; } \ @@ -54,17 +54,17 @@ void* operator new(size_t) = delete; #define WEBF_DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&) = delete; \ + 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(const TypeName&) = delete; \ + TypeName(TypeName&&) = delete; \ + TypeName& operator=(const TypeName&) = delete; \ TypeName& operator=(TypeName&&) = delete #define WEBF_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ - TypeName() = delete; \ + TypeName() = delete; \ WEBF_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) #endif // BRIDGE_MACROS_H diff --git a/bridge/test/webf_test_context.cc b/bridge/test/webf_test_context.cc index 6587d5df76..301623d85b 100644 --- a/bridge/test/webf_test_context.cc +++ b/bridge/test/webf_test_context.cc @@ -63,8 +63,7 @@ static JSValue matchImageSnapshot(JSContext* ctx, JSValueConst this_val, int arg if (context->dartMethodPtr()->matchImageSnapshot == nullptr) { return JS_ThrowTypeError( - ctx, - "Failed to execute '__webf_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); + ctx, "Failed to execute '__webf_match_image_snapshot__': dart method (matchImageSnapshot) is not registered."); } std::unique_ptr<NativeString> screenShotNativeString = webf::jsValueToNativeString(ctx, screenShotValue); @@ -99,8 +98,8 @@ static JSValue environment(JSContext* ctx, JSValueConst this_val, int argc, JSVa 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."); + 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), ""); @@ -118,8 +117,7 @@ static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JSValue inputArrayValue = argv[0]; if (!JS_IsObject(inputArrayValue)) { - return JS_ThrowTypeError(ctx, - "Failed to execute '__webf_simulate_pointer__': first arguments should be an array."); + return JS_ThrowTypeError(ctx, "Failed to execute '__webf_simulate_pointer__': first arguments should be an array."); } JSValue pointerValue = argv[1]; @@ -182,8 +180,7 @@ static JSValue simulateInputText(JSContext* ctx, JSValueConst this_val, int argc JSValue& charStringValue = argv[0]; if (!JS_IsString(charStringValue)) { - return JS_ThrowTypeError(ctx, - "Failed to execute '__webf_simulate_keypress__': first arguments should be a string"); + 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); @@ -284,9 +281,9 @@ WebFTestContext::WebFTestContext(ExecutingContext* context) } bool WebFTestContext::evaluateTestScripts(const uint16_t* code, - size_t codeLength, - const char* sourceURL, - int startLine) { + size_t codeLength, + const char* sourceURL, + int startLine) { if (!context_->IsValid()) return false; return context_->EvaluateJavaScript(code, codeLength, sourceURL, startLine); From 9c76d189b9f99cadd7d32321afc51c1682d4aeb3 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Wed, 10 Aug 2022 16:38:22 +0800 Subject: [PATCH 157/498] feat: add promise rejection event. --- bridge/CMakeLists.txt | 6 ++ bridge/bindings/qjs/binding_initializer.cc | 2 + bridge/bindings/qjs/rejected_promises.cc | 2 + bridge/bindings/qjs/wrapper_type_info.h | 1 + bridge/core/dom/events/event.cc | 4 ++ bridge/core/dom/events/event.h | 1 + bridge/core/events/error_event.cc | 2 + bridge/core/events/error_event.h | 2 + bridge/core/events/promise_rejection_event.cc | 42 ++++++++++++++ .../core/events/promise_rejection_event.d.ts | 8 +++ bridge/core/events/promise_rejection_event.h | 52 ++++++++++++++++++ .../events/promise_rejection_event_init.d.ts | 8 +++ bridge/core/executing_context.cc | 55 ++++++++----------- 13 files changed, 154 insertions(+), 31 deletions(-) create mode 100644 bridge/core/events/promise_rejection_event.cc create mode 100644 bridge/core/events/promise_rejection_event.d.ts create mode 100644 bridge/core/events/promise_rejection_event.h create mode 100644 bridge/core/events/promise_rejection_event_init.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index dc7470eefe..78b9bfe7c2 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -330,6 +330,8 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/events/error_event.h core/events/message_event.h core/events/message_event.cc + core/events/promise_rejection_event.cc + core/events/promise_rejection_event.h core/html/parser/html_parser.cc core/html/parser/html_parser.h core/html/html_collection.cc @@ -462,6 +464,10 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/qjs_html_body_element.h out/qjs_html_html_element.cc out/qjs_html_html_element.h + out/qjs_promise_rejection_event.h + out/qjs_promise_rejection_event.cc + out/qjs_promise_rejection_event_init.h + out/qjs_promise_rejection_event_init.cc out/qjs_html_template_element.cc out/qjs_html_template_element.h out/html_element_type_helper.h diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 9fb95fdc68..f75cb64151 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -16,6 +16,7 @@ #include "qjs_element.h" #include "qjs_element_attributes.h" #include "qjs_error_event.h" +#include "qjs_promise_rejection_event.h" #include "qjs_event.h" #include "qjs_event_target.h" #include "qjs_html_body_element.h" @@ -48,6 +49,7 @@ void InstallBindings(ExecutingContext* context) { QJSWindow::Install(context); QJSEvent::Install(context); QJSErrorEvent::Install(context); + QJSPromiseRejectionEvent::Install(context); QJSMessageEvent::Install(context); QJSNode::Install(context); QJSNodeList::Install(context); diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index b19fd3e92d..37b438bfa8 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -54,6 +54,8 @@ void RejectedPromises::Process(ExecutingContext* context) { } report_handled_rejection_.clear(); + MemberMutationScope mutation_scope{context}; + // Dispatch unhandled rejectionEvents. for (auto& entry : unhandledRejections) { context->ReportError(entry.second->m_reason); diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 46a24b0ba3..aac785a82f 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -18,6 +18,7 @@ enum { JS_CLASS_EVENT, JS_CLASS_ERROR_EVENT, JS_CLASS_MESSAGE_EVENT, + JS_CLASS_PROMISE_REJECTION_EVENT, JS_CLASS_EVENT_TARGET, JS_CLASS_WINDOW, JS_CLASS_NODE, diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 3af0e00848..cf33ab0883 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -129,6 +129,10 @@ 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; diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 7054ab97ce..62db134af5 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -141,6 +141,7 @@ class Event : public ScriptWrappable { 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 diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc index caa02aaf17..109e662e9f 100644 --- a/bridge/core/events/error_event.cc +++ b/bridge/core/events/error_event.cc @@ -44,4 +44,6 @@ ErrorEvent::ErrorEvent(ExecutingContext* context, initializer->lineno(), initializer->colno())) {} +bool ErrorEvent::IsErrorEvent() const { return true; } + } // namespace webf diff --git a/bridge/core/events/error_event.h b/bridge/core/events/error_event.h index f3c84ef867..357c917393 100644 --- a/bridge/core/events/error_event.h +++ b/bridge/core/events/error_event.h @@ -42,6 +42,8 @@ class ErrorEvent : public Event { SourceLocation* Location() const { return source_location_.get(); } + bool IsErrorEvent() const override; + private: std::string message_; std::unique_ptr<SourceLocation> source_location_{nullptr}; diff --git a/bridge/core/events/promise_rejection_event.cc b/bridge/core/events/promise_rejection_event.cc new file mode 100644 index 0000000000..b7f147d8db --- /dev/null +++ b/bridge/core/events/promise_rejection_event.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 "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->reason()), + promise_(initializer->promise()) {} + +bool PromiseRejectionEvent::IsPromiseRejectionEvent() const { + return true; +} + +} \ 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..cd043e610d --- /dev/null +++ b/bridge/core/events/promise_rejection_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_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_; +}; + +template <> +struct DowncastTraits<PromiseRejectionEvent> { + static bool AllowFrom(const Event& event) { return event.IsErrorEvent(); } +}; + +} + +#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/executing_context.cc b/bridge/core/executing_context.cc index b98764d0fa..c6c107896c 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -9,6 +9,7 @@ #include "core/events/error_event.h" #include "event_type_names.h" #include "foundation/logging.h" +#include "core/events/promise_rejection_event.h" #include "polyfill.h" #include "qjs_window.h" @@ -254,37 +255,22 @@ void ExecutingContext::DispatchGlobalErrorEvent(ExecutingContext* context, JSVal context->DispatchErrorEvent(error_event); } -static void dispatchPromiseRejectionEvent(const char* eventType, +static void DispatchPromiseRejectionEvent(const AtomicString& event_type, ExecutingContext* context, JSValueConst promise, JSValueConst error) { - // JSContext* ctx = context->ctx(); - // auto* window = static_cast<WindowInstance*>(JS_GetOpaque(context->global(), Window::classId())); - // - // // Trigger PromiseRejectionEvent(unhandledrejection) event. - // { - // JSValue PromiseRejectionEventValue = JS_GetPropertyStr(ctx, context->global(), "PromiseRejectionEvent"); - // JSValue errorType = JS_NewString(ctx, eventType); - // JSValue errorInit = JS_NewObject(ctx); - // JS_SetPropertyStr(ctx, errorInit, "promise", JS_DupValue(ctx, promise)); - // JS_SetPropertyStr(ctx, errorInit, "reason", JS_DupValue(ctx, error)); - // JSValue arguments[] = {errorType, errorInit}; - // JSValue rejectEventValue = JS_CallConstructor(context->ctx(), PromiseRejectionEventValue, 2, arguments); - // if (JS_IsException(rejectEventValue)) { - // context->handleException(&rejectEventValue); - // return; - // } - // - // auto* rejectEvent = static_cast<EventInstance*>(JS_GetOpaque(rejectEventValue, Event::kEventClassID)); - // window->dispatchEvent(rejectEvent); - // - // JS_FreeValue(ctx, errorType); - // JS_FreeValue(ctx, errorInit); - // JS_FreeValue(ctx, rejectEventValue); - // JS_FreeValue(ctx, PromiseRejectionEventValue); - // - // context->drainPendingPromiseJobs(); - // } + 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(), error, exception_state)); + auto event = PromiseRejectionEvent::Create(context, event_type, event_init, exception_state); + + auto* window = toScriptWrappable<Window>(context->Global()); + window->dispatchEvent(event, exception_state); + if (exception_state.HasException()) { + context->ReportError(error); + } } void ExecutingContext::FlushUICommand() { @@ -304,8 +290,15 @@ void ExecutingContext::DispatchErrorEventInterval(ErrorEvent* error_event) { assert(!in_dispatch_error_event_); in_dispatch_error_event_ = true; auto* window = toScriptWrappable<Window>(Global()); - window->dispatchEvent(error_event, ASSERT_NO_EXCEPTION()); + 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) { @@ -319,12 +312,12 @@ void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* c DispatchGlobalErrorEvent(context, error); // Trigger unhandledRejection event. - dispatchPromiseRejectionEvent("unhandledrejection", context, promise, error); + DispatchPromiseRejectionEvent(event_type_names::kunhandledrejection, context, promise, error); } void ExecutingContext::DispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValue promise, JSValue error) { // Trigger rejectionhandled event. - dispatchPromiseRejectionEvent("rejectionhandled", context, promise, error); + DispatchPromiseRejectionEvent(event_type_names::krejectionhandled, context, promise, error); } std::unordered_map<std::string, NativeByteCode> ExecutingContext::pluginByteCode{}; From d8a5f149f98abcbb4cb3dfd6cc279fc27403b012 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sun, 14 Aug 2022 23:14:54 +0800 Subject: [PATCH 158/498] feat: auto generate property event handlers from IDL. --- bridge/CMakeLists.txt | 2 + bridge/bindings/qjs/converter_impl.h | 36 +++ bridge/bindings/qjs/idl_type.h | 4 + bridge/bindings/qjs/js_based_event_listener.h | 6 +- bridge/bindings/qjs/js_event_handler.cc | 6 +- bridge/bindings/qjs/js_event_handler.h | 10 +- bridge/bindings/qjs/js_event_listener.cc | 9 +- bridge/bindings/qjs/js_event_listener.h | 4 +- bridge/bindings/qjs/rejected_promises.cc | 2 +- bridge/core/dom/events/event_target.cc | 43 +++ bridge/core/dom/events/event_target.h | 14 +- bridge/core/dom/global_event_handlers.d.ts | 244 ++++++++++++++++++ bridge/core/dom/global_event_handlers.h | 91 +++++++ bridge/core/events/animation_event.d.ts | 8 + bridge/core/events/event_type_names.json5 | 1 + bridge/core/events/focus_event.d.ts | 7 + bridge/core/events/keyboard_event.d.ts | 25 ++ bridge/core/events/mouse_event.d.ts | 28 ++ bridge/core/events/pointer_event.d.ts | 15 ++ bridge/core/events/touch_event.d.ts | 39 +++ bridge/core/events/transition_event.d.ts | 8 + bridge/core/events/ui_event.d.ts | 10 + bridge/core/events/wheel_event.d.ts | 11 + bridge/core/html/html_element.d.ts | 3 +- bridge/core/html/html_element.h | 1 + bridge/core/html/html_element_test.cc | 29 +++ .../code_generator/src/idl/analyzer.ts | 25 +- .../code_generator/src/idl/declaration.ts | 5 +- .../code_generator/src/idl/generateHeader.ts | 6 +- .../code_generator/src/idl/generateSource.ts | 30 ++- .../static/idl_templates/interface.cc.tpl | 35 +++ bridge/test/test.cmake | 1 + 32 files changed, 718 insertions(+), 40 deletions(-) create mode 100644 bridge/core/dom/global_event_handlers.d.ts create mode 100644 bridge/core/dom/global_event_handlers.h create mode 100644 bridge/core/events/animation_event.d.ts create mode 100644 bridge/core/events/focus_event.d.ts create mode 100644 bridge/core/events/keyboard_event.d.ts create mode 100644 bridge/core/events/mouse_event.d.ts create mode 100644 bridge/core/events/pointer_event.d.ts create mode 100644 bridge/core/events/touch_event.d.ts create mode 100644 bridge/core/events/transition_event.d.ts create mode 100644 bridge/core/events/ui_event.d.ts create mode 100644 bridge/core/events/wheel_event.d.ts create mode 100644 bridge/core/html/html_element_test.cc diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 78b9bfe7c2..d2de89679e 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -312,6 +312,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/dom/element_traversal.h core/dom/document.cc core/dom/document.h + core/dom/global_event_handlers.h core/dom/scripted_animation_controller.cc core/dom/scripted_animation_controller.h core/dom/node_data.cc @@ -348,6 +349,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/html/html_html_element.h core/html/html_template_element.cc core/html/html_template_element.h + # core/html/html_anchor_element.h # core/html/html_anchor_element.cc # core/html/html_template_element.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index bc44eaa5ce..828caa3b44 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -24,6 +24,7 @@ #include "exception_message.h" #include "idl_type.h" #include "js_event_listener.h" +#include "js_event_handler.h" #include "native_string_utils.h" namespace webf { @@ -370,6 +371,40 @@ struct Converter<JSEventListener> : public ConverterBase<JSEventListener> { } }; +template<> +struct Converter<IDLEventHandler> : public ConverterBase<IDLEventHandler> { + 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<JSBasedEventListener>(*value)) { + return To<JSBasedEventListener>(*value).GetListenerObject(); + } + return JS_NULL; + } +}; + +template<> +struct Converter<IDLNullable<IDLEventHandler>> : public ConverterBase<IDLEventHandler> { + static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsNull(value)) { + return nullptr; + } + assert(!JS_IsException(value)); + return Converter<IDLEventHandler>::FromValue(ctx, value, exception_state); + } + + static JSValue ToValue(JSContext* ctx, ImplType value) { + if (value == nullptr) { + return JS_NULL; + } + + return Converter<IDLEventHandler>::ToValue(ctx, value); + } +}; + template <> struct Converter<IDLNullable<JSEventListener>> : public ConverterBase<JSEventListener> { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { @@ -382,6 +417,7 @@ struct Converter<IDLNullable<JSEventListener>> : public ConverterBase<JSEventLis } }; + // DictionaryBase and Derived class. template <typename T> struct Converter<T, typename std::enable_if_t<std::is_base_of<DictionaryBase, T>::value>> : public ConverterBase<T> { diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 05b5158ff6..bc4ebd9e6c 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -55,6 +55,10 @@ struct IDLUSVString final : public IDLTypeBaseHelper<AtomicString> {}; // Object struct IDLObject : public IDLTypeBaseHelper<ScriptValue> {}; +class JSEventHandler; +// EventHandler +struct IDLEventHandler : public IDLTypeBaseHelper<std::shared_ptr<EventListener>> {}; + class QJSFunction; // Function callback struct IDLCallback : public IDLTypeBaseHelper<std::shared_ptr<QJSFunction>> { diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index 6cfa6b1622..a2d7128172 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -27,11 +27,7 @@ class JSBasedEventListener : public EventListener { // Returns v8::Null with firing error event instead of throwing an exception // on failing to compile the uncompiled script body in eventHandler's value. // Also, this can return empty because of crbug.com/881688 . - virtual JSValue GetListenerObject(EventTarget&) = 0; - - // Returns Functions that handles invoked event or undefined without - // throwing any exception. - virtual JSValue GetEffectiveFunction(EventTarget&) = 0; + virtual JSValue GetListenerObject() = 0; bool IsJSBasedEventListener() const override { return true; } virtual bool IsJSEventListener() const { return false; } diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index e247fc97a2..24086ce1d8 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -11,14 +11,14 @@ namespace webf { -std::unique_ptr<JSEventHandler> JSEventHandler::CreateOrNull(JSContext* ctx, +std::shared_ptr<JSEventHandler> JSEventHandler::CreateOrNull(JSContext* ctx, JSValue value, JSEventHandler::HandlerType handler_type) { if (!JS_IsFunction(ctx, value)) { return nullptr; } - return std::make_unique<JSEventHandler>(QJSFunction::Create(ctx, value), handler_type); + return std::make_shared<JSEventHandler>(QJSFunction::Create(ctx, value), handler_type); } bool JSEventHandler::Matches(const EventListener& other) const { @@ -30,7 +30,7 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // Step 1. Let callback be the result of getting the current value of the // event handler given eventTarget and name. // Step 2. If callback is null, then return. - JSValue listener_value = GetListenerObject(*event.currentTarget()); + JSValue listener_value = GetListenerObject(); if (JS_IsNull(listener_value)) return; diff --git a/bridge/bindings/qjs/js_event_handler.h b/bridge/bindings/qjs/js_event_handler.h index 79cb9ea31a..981ca7ac2d 100644 --- a/bridge/bindings/qjs/js_event_handler.h +++ b/bridge/bindings/qjs/js_event_handler.h @@ -27,10 +27,10 @@ class JSEventHandler : public JSBasedEventListener { kOnBeforeUnloadEventHandler, }; - static std::unique_ptr<JSEventHandler> CreateOrNull(JSContext* ctx, JSValue value, HandlerType handler_type); + static std::shared_ptr<JSEventHandler> CreateOrNull(JSContext* ctx, JSValue value, HandlerType handler_type); static JSValue ToQuickJS(JSContext* ctx, EventTarget* event_target, EventListener* listener) { if (auto* event_handler = DynamicTo<JSEventHandler>(listener)) { - return event_handler->GetEffectiveFunction(*event_target); + return event_handler->GetListenerObject(); } return JS_NULL; } @@ -38,12 +38,10 @@ class JSEventHandler : public JSBasedEventListener { explicit JSEventHandler(const std::shared_ptr<QJSFunction>& event_handler, HandlerType type) : type_(type), event_handler_(event_handler){}; - JSValue GetListenerObject(EventTarget&) override { return event_handler_->ToQuickJS(); } - - JSValue GetEffectiveFunction(EventTarget&) override { return event_handler_->ToQuickJS(); } + JSValue GetListenerObject() override { return event_handler_->ToQuickJS(); } // Helper functions for DowncastTraits. - bool IsJSEventHandler() const override { return true; } + bool IsEventHandler() const override { return true; } // For checking special types of EventHandler. bool IsOnErrorEventHandler() const { return type_ == HandlerType::kOnErrorEventHandler; } diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index 761e925edd..0e662aeb5e 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -4,15 +4,14 @@ */ #include "js_event_listener.h" + +#include <utility> #include "core/dom/events/event_target.h" namespace webf { -JSEventListener::JSEventListener(std::shared_ptr<QJSFunction> listener) : event_listener_(listener) {} -JSValue JSEventListener::GetListenerObject(EventTarget&) { - return event_listener_->ToQuickJS(); -} -JSValue JSEventListener::GetEffectiveFunction(EventTarget&) { +JSEventListener::JSEventListener(std::shared_ptr<QJSFunction> listener) : event_listener_(std::move(listener)) {} +JSValue JSEventListener::GetListenerObject() { return event_listener_->ToQuickJS(); } void JSEventListener::InvokeInternal(EventTarget& event_target, Event& event, ExceptionState& exception_state) { diff --git a/bridge/bindings/qjs/js_event_listener.h b/bridge/bindings/qjs/js_event_listener.h index 3bac19b496..5346f90a54 100644 --- a/bridge/bindings/qjs/js_event_listener.h +++ b/bridge/bindings/qjs/js_event_listener.h @@ -24,9 +24,7 @@ class JSEventListener final : public JSBasedEventListener { explicit JSEventListener(std::shared_ptr<QJSFunction> listener); - JSValue GetListenerObject(EventTarget&) override; - - JSValue GetEffectiveFunction(EventTarget&) override; + JSValue GetListenerObject() override; bool IsJSEventListener() const override { return true; } diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 37b438bfa8..7ff504936c 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -66,6 +66,6 @@ void RejectedPromises::Process(ExecutingContext* context) { for (auto& entry : reportHandledRejection) { context->DispatchGlobalRejectionHandledEvent(context, entry->m_promise, entry->m_reason); } -} + } } // namespace webf diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 9d23445aa8..ed77a29535 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -133,6 +133,36 @@ DispatchEventResult EventTarget::GetDispatchEventResult(const Event& event) { 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::AddEventListenerInternal(const AtomicString& event_type, const std::shared_ptr<EventListener>& listener, const std::shared_ptr<AddEventListenerOptions>& options) { @@ -197,6 +227,19 @@ NativeValue EventTarget::HandleCallFromDartSide(NativeString* method, int32_t ar return Native_NewNull(); } +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, diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index ae1e775f96..d3f22f99f0 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -113,6 +113,12 @@ class EventTarget : public ScriptWrappable, public BindingObject { 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; } @@ -135,6 +141,8 @@ class EventTarget : public ScriptWrappable, public BindingObject { virtual EventTargetData& EnsureEventTargetData() = 0; private: + RegisteredEventListener* GetAttributeRegisteredEventListener(const AtomicString& event_type); + int32_t event_target_id_; bool FireEventListeners(Event&, EventTargetData*, EventListenerVector&, ExceptionState&); }; @@ -165,11 +173,11 @@ class EventTargetWithInlineData : public EventTarget { } #define DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ - static EventListener* on##lower_name(EventTarget& eventTarget) { \ + 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, EventListener* listener) { \ - eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener); \ + 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) \ 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..035803fb03 --- /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 "foundation/macros.h" +#include "event_type_names.h" +#include "core/dom/events/event_target.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); +}; + +} + +#endif // BRIDGE_CORE_DOM_GLOBAL_EVENT_HANDLERS_H_ diff --git a/bridge/core/events/animation_event.d.ts b/bridge/core/events/animation_event.d.ts new file mode 100644 index 0000000000..f2779f2ca5 --- /dev/null +++ b/bridge/core/events/animation_event.d.ts @@ -0,0 +1,8 @@ +import {Event} from "../dom/events/event"; + +/** Events providing information related to animations. */ +interface AnimationEvent extends Event { + readonly animationName: string; + readonly elapsedTime: number; + readonly pseudoElement: string; +} \ No newline at end of file diff --git a/bridge/core/events/event_type_names.json5 b/bridge/core/events/event_type_names.json5 index 6b1d1756da..be26438462 100644 --- a/bridge/core/events/event_type_names.json5 +++ b/bridge/core/events/event_type_names.json5 @@ -49,6 +49,7 @@ "close", "closing", "complete", + "gotpointercapture", "compositionend", "compositionstart", "compositionupdate", diff --git a/bridge/core/events/focus_event.d.ts b/bridge/core/events/focus_event.d.ts new file mode 100644 index 0000000000..7ee29c5df5 --- /dev/null +++ b/bridge/core/events/focus_event.d.ts @@ -0,0 +1,7 @@ +import {UIEvent} from "./ui_event"; +import {EventTarget} from "../dom/events/event_target"; + +/** Focus-related events like focus, blur, focusin, or focusout. */ +interface FocusEvent extends UIEvent { + readonly relatedTarget: EventTarget | null; +} \ 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..ddc4449d3d --- /dev/null +++ b/bridge/core/events/keyboard_event.d.ts @@ -0,0 +1,25 @@ +import {UIEvent} from "./ui_event"; + +/** 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 */ + char: string; + /** @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: number; + readonly DOM_KEY_LOCATION_NUMPAD: number; + readonly DOM_KEY_LOCATION_RIGHT: number; + readonly DOM_KEY_LOCATION_STANDARD: number; +} \ 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..913c141d06 --- /dev/null +++ b/bridge/core/events/mouse_event.d.ts @@ -0,0 +1,28 @@ +import {UIEvent} from "./ui_event"; +import {EventTarget} from "../dom/events/event_target"; +import {Window} from "../frame/window"; + +/** 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; + getModifierState(keyArg: string): boolean; + initMouseEvent(typeArg: string, canBubbleArg: boolean, cancelableArg: boolean, viewArg: Window, detailArg: number, screenXArg: number, screenYArg: number, clientXArg: number, clientYArg: number, ctrlKeyArg: boolean, altKeyArg: boolean, shiftKeyArg: boolean, metaKeyArg: boolean, buttonArg: number, relatedTargetArg: EventTarget | null): void; +} \ 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..69d6126127 --- /dev/null +++ b/bridge/core/events/pointer_event.d.ts @@ -0,0 +1,15 @@ +import {MouseEvent} from "./mouse_event"; + +/** 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; +} \ No newline at end of file diff --git a/bridge/core/events/touch_event.d.ts b/bridge/core/events/touch_event.d.ts new file mode 100644 index 0000000000..6fd103eeef --- /dev/null +++ b/bridge/core/events/touch_event.d.ts @@ -0,0 +1,39 @@ +import {UIEvent} from "./ui_event"; +import {EventTarget} from "../dom/events/event_target"; + +/** 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; + readonly touchType: "direct" | "stylus"; +} + +/** 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. */ +interface TouchList { + readonly length: number; + item(index: number): Touch | null; + [index: number]: Touch; +} + +/** 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 altKey: boolean; + readonly changedTouches: TouchList; + readonly ctrlKey: boolean; + readonly metaKey: boolean; + readonly shiftKey: boolean; + readonly targetTouches: TouchList; + readonly touches: TouchList; +} \ 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..6bae2a8a2f --- /dev/null +++ b/bridge/core/events/transition_event.d.ts @@ -0,0 +1,8 @@ +import {Event} from "../dom/events/event"; +/** Events providing information related to transitions. */ + +interface TransitionEvent extends Event { + readonly elapsedTime: number; + readonly propertyName: string; + readonly pseudoElement: string; +} \ No newline at end of file diff --git a/bridge/core/events/ui_event.d.ts b/bridge/core/events/ui_event.d.ts new file mode 100644 index 0000000000..017fe397b6 --- /dev/null +++ b/bridge/core/events/ui_event.d.ts @@ -0,0 +1,10 @@ +import {Event} from "../dom/events/event"; +import {Window} from "../frame/window"; + +/** Simple user interface events. */ +interface UIEvent extends Event { + readonly detail: number; + readonly view: Window | null; + /** @deprecated */ + readonly which: number; +} \ No newline at end of file diff --git a/bridge/core/events/wheel_event.d.ts b/bridge/core/events/wheel_event.d.ts new file mode 100644 index 0000000000..fe994e89bb --- /dev/null +++ b/bridge/core/events/wheel_event.d.ts @@ -0,0 +1,11 @@ +import {MouseEvent} from "./mouse_event"; +/** Events that occur due to the user moving a mouse wheel or similar input device. */ +interface WheelEvent extends MouseEvent { + readonly deltaMode: number; + readonly deltaX: number; + readonly deltaY: number; + readonly deltaZ: number; + readonly DOM_DELTA_LINE: number; + readonly DOM_DELTA_PAGE: number; + readonly DOM_DELTA_PIXEL: number; +} diff --git a/bridge/core/html/html_element.d.ts b/bridge/core/html/html_element.d.ts index 7865df7552..4ea8f07a89 100644 --- a/bridge/core/html/html_element.d.ts +++ b/bridge/core/html/html_element.d.ts @@ -1,5 +1,6 @@ import {Element} from "../dom/element"; +import {GlobalEventHandlers} from "../dom/global_event_handlers"; -export interface HTMLElement extends Element { +export interface HTMLElement extends Element, GlobalEventHandlers { new(): void; } diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index c10952000e..410243cb9c 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -7,6 +7,7 @@ #define BRIDGE_CORE_HTML_HTML_ELEMENT_H_ #include "core/dom/element.h" +#include "core/dom/global_event_handlers.h" namespace webf { diff --git a/bridge/core/html/html_element_test.cc b/bridge/core/html/html_element_test.cc new file mode 100644 index 0000000000..73a1774230 --- /dev/null +++ b/bridge/core/html/html_element_test.cc @@ -0,0 +1,29 @@ +/* +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ + +#include "html_element.h" +#include "gtest/gtest.h" +#include "webf_test_env.h" + +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; + }; + 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'); function f(){ console.log(1234); }; div.onclick = f; " + "div.dispatchEvent(new Event('click'));"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + + EXPECT_EQ(errorCalled, false); +} diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index e7023e525b..a0cdd11cc6 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -6,7 +6,9 @@ import { FunctionArguments, FunctionArgumentType, FunctionDeclaration, - FunctionObject, IndexedPropertyDeclaration, ParameterMode, + FunctionObject, + IndexedPropertyDeclaration, + ParameterMode, PropsDeclaration, } from './declaration'; import {generatorSource} from './generator'; @@ -32,6 +34,18 @@ function getHeritageType(heritage: HeritageClause) { 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!); + } + }); + return mixins; +} + function getPropName(propName: ts.PropertyName) { if (propName.kind == ts.SyntaxKind.Identifier) { return propName.escapedText.toString(); @@ -132,13 +146,15 @@ function isParamsReadOnly(m: ts.PropertySignature): boolean { function walkProgram(statement: ts.Statement) { switch(statement.kind) { case ts.SyntaxKind.InterfaceDeclaration: { - let interfaceName = getInterfaceName(statement); + let interfaceName = getInterfaceName(statement) as string; let s = (statement as ts.InterfaceDeclaration); let obj = new ClassObject(); 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(); @@ -148,6 +164,9 @@ function walkProgram(statement: ts.Statement) { // @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; } } @@ -227,6 +246,8 @@ function walkProgram(statement: ts.Statement) { } }); + ClassObject.globalClassMap[interfaceName] = obj; + return obj; } case ts.SyntaxKind.VariableStatement: { diff --git a/bridge/scripts/code_generator/src/idl/declaration.ts b/bridge/scripts/code_generator/src/idl/declaration.ts index c0e97ecb82..81197fcaf1 100644 --- a/bridge/scripts/code_generator/src/idl/declaration.ts +++ b/bridge/scripts/code_generator/src/idl/declaration.ts @@ -46,12 +46,15 @@ export class FunctionDeclaration extends PropsDeclaration { export enum ClassObjectKind { interface, - dictionary + dictionary, + mixin } export class ClassObject { + static globalClassMap = new Map<string, ClassObject>(); name: string; parent: string; + mixinParent: string[]; props: PropsDeclaration[] = []; indexedProp?: IndexedPropertyDeclaration; methods: FunctionDeclaration[] = []; diff --git a/bridge/scripts/code_generator/src/idl/generateHeader.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts index b120cd42b7..f116917d90 100644 --- a/bridge/scripts/code_generator/src/idl/generateHeader.ts +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -1,10 +1,10 @@ -import {ClassObject, ClassObjectKind, FunctionObject} from "./declaration"; -import _ from "lodash"; +import {ClassObject, ClassObjectKind, FunctionDeclaration, FunctionObject, PropsDeclaration} from "./declaration"; +import _, {mixin} from "lodash"; import {IDLBlob} from "./IDLBlob"; import {getClassName} from "./utils"; import fs from 'fs'; import path from 'path'; -import {generateIDLTypeConverter, generateTypeValue} from "./generateSource"; +import {generateTypeValue} from "./generateSource"; import {GenerateOptions} from "./generator"; export enum TemplateKind { diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 22edc9e2b2..e47327388b 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -360,13 +360,11 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { switch (templateKind) { case TemplateKind.Interface: { object = object as ClassObject; - object.props.forEach(prop => { - options.classMethodsInstallList.push(`{"${prop.name}", ${prop.name}AttributeGetCallback, ${prop.readonly ? 'nullptr' : `${prop.name}AttributeSetCallback`}}`) - }); - let overloadMethods = {}; - let filtedMethods: FunctionDeclaration[] = []; - object.methods.forEach((method, i) => { + 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 { @@ -374,7 +372,13 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { 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}`) @@ -405,6 +409,17 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { } } + 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_;`; @@ -412,6 +427,7 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass className: getClassName(blob), blob: blob, object: object, + mixinObjects, generateFunctionBody, generateTypeValue, generateOverLoadSwitchBody, diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index 6d7e412f6c..540f160b1f 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -128,3 +128,38 @@ static JSValue <%= prop.name %>AttributeSetCallback(JSContext* ctx, JSValueConst } <% } %> <% }); %> + + +<% 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) %>>::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) %>>::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/test/test.cmake b/bridge/test/test.cmake index d503085330..83ad85da13 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -28,6 +28,7 @@ list(APPEND WEBF_UNIT_TEST_SOURCEURCE ./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 ) ### webf_unit_test executable From 172a71b499b90918c9e5e9d7baaf62082447ba8c Mon Sep 17 00:00:00 2001 From: openwebf-bot <openwebf@openwebf.com> Date: Sun, 14 Aug 2022 15:16:21 +0000 Subject: [PATCH 159/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 2 +- bridge/bindings/qjs/converter_impl.h | 7 ++-- bridge/bindings/qjs/rejected_promises.cc | 2 +- bridge/core/dom/events/event_target.h | 17 +++++---- bridge/core/dom/global_event_handlers.h | 10 +++--- bridge/core/events/error_event.cc | 4 ++- bridge/core/events/promise_rejection_event.cc | 25 ++++++------- bridge/core/events/promise_rejection_event.h | 23 ++++++------ bridge/core/executing_context.cc | 2 +- bridge/core/html/html_element_test.cc | 36 +++++++++---------- 10 files changed, 64 insertions(+), 64 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index f75cb64151..139cf91d9a 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -16,7 +16,6 @@ #include "qjs_element.h" #include "qjs_element_attributes.h" #include "qjs_error_event.h" -#include "qjs_promise_rejection_event.h" #include "qjs_event.h" #include "qjs_event_target.h" #include "qjs_html_body_element.h" @@ -31,6 +30,7 @@ #include "qjs_module_manager.h" #include "qjs_node.h" #include "qjs_node_list.h" +#include "qjs_promise_rejection_event.h" #include "qjs_screen.h" #include "qjs_text.h" #include "qjs_window.h" diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 828caa3b44..603f043370 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -23,8 +23,8 @@ #include "core/html/html_html_element.h" #include "exception_message.h" #include "idl_type.h" -#include "js_event_listener.h" #include "js_event_handler.h" +#include "js_event_listener.h" #include "native_string_utils.h" namespace webf { @@ -371,7 +371,7 @@ struct Converter<JSEventListener> : public ConverterBase<JSEventListener> { } }; -template<> +template <> struct Converter<IDLEventHandler> : public ConverterBase<IDLEventHandler> { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { assert(!JS_IsException(value)); @@ -386,7 +386,7 @@ struct Converter<IDLEventHandler> : public ConverterBase<IDLEventHandler> { } }; -template<> +template <> struct Converter<IDLNullable<IDLEventHandler>> : public ConverterBase<IDLEventHandler> { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { if (JS_IsNull(value)) { @@ -417,7 +417,6 @@ struct Converter<IDLNullable<JSEventListener>> : public ConverterBase<JSEventLis } }; - // DictionaryBase and Derived class. template <typename T> struct Converter<T, typename std::enable_if_t<std::is_base_of<DictionaryBase, T>::value>> : public ConverterBase<T> { diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 7ff504936c..37b438bfa8 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -66,6 +66,6 @@ void RejectedPromises::Process(ExecutingContext* context) { for (auto& entry : reportHandledRejection) { context->DispatchGlobalRejectionHandledEvent(context, entry->m_promise, entry->m_reason); } - } +} } // namespace webf diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index d3f22f99f0..cca148be32 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -114,7 +114,9 @@ class EventTarget : public ScriptWrappable, public BindingObject { 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); + 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); @@ -172,12 +174,13 @@ class EventTargetWithInlineData : public EventTarget { 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_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) \ diff --git a/bridge/core/dom/global_event_handlers.h b/bridge/core/dom/global_event_handlers.h index 035803fb03..4f20a50441 100644 --- a/bridge/core/dom/global_event_handlers.h +++ b/bridge/core/dom/global_event_handlers.h @@ -1,13 +1,13 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * 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 "foundation/macros.h" -#include "event_type_names.h" #include "core/dom/events/event_target.h" +#include "event_type_names.h" +#include "foundation/macros.h" namespace webf { @@ -86,6 +86,6 @@ class GlobalEventHandlers { DEFINE_STATIC_ATTRIBUTE_EVENT_LISTENER(wheel, kwheel); }; -} +} // namespace webf #endif // BRIDGE_CORE_DOM_GLOBAL_EVENT_HANDLERS_H_ diff --git a/bridge/core/events/error_event.cc b/bridge/core/events/error_event.cc index 109e662e9f..74a6c9a3fa 100644 --- a/bridge/core/events/error_event.cc +++ b/bridge/core/events/error_event.cc @@ -44,6 +44,8 @@ ErrorEvent::ErrorEvent(ExecutingContext* context, initializer->lineno(), initializer->colno())) {} -bool ErrorEvent::IsErrorEvent() const { return true; } +bool ErrorEvent::IsErrorEvent() const { + return true; +} } // namespace webf diff --git a/bridge/core/events/promise_rejection_event.cc b/bridge/core/events/promise_rejection_event.cc index b7f147d8db..9fdeb87256 100644 --- a/bridge/core/events/promise_rejection_event.cc +++ b/bridge/core/events/promise_rejection_event.cc @@ -1,14 +1,13 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "promise_rejection_event.h" #include "event_type_names.h" namespace webf { - PromiseRejectionEvent* PromiseRejectionEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { @@ -16,9 +15,9 @@ PromiseRejectionEvent* PromiseRejectionEvent::Create(ExecutingContext* context, } PromiseRejectionEvent* PromiseRejectionEvent::Create(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr<PromiseRejectionEventInit>& initializer, - ExceptionState& exception_state) { + const AtomicString& type, + const std::shared_ptr<PromiseRejectionEventInit>& initializer, + ExceptionState& exception_state) { return MakeGarbageCollected<PromiseRejectionEvent>(context, type, initializer, exception_state); } @@ -28,15 +27,13 @@ PromiseRejectionEvent::PromiseRejectionEvent(ExecutingContext* context, : Event(context, type) {} PromiseRejectionEvent::PromiseRejectionEvent(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr<PromiseRejectionEventInit>& initializer, - ExceptionState& exception_state) - : Event(context, type), - reason_(initializer->reason()), - promise_(initializer->promise()) {} + const AtomicString& type, + const std::shared_ptr<PromiseRejectionEventInit>& initializer, + ExceptionState& exception_state) + : Event(context, type), reason_(initializer->reason()), promise_(initializer->promise()) {} bool PromiseRejectionEvent::IsPromiseRejectionEvent() const { return true; } -} \ No newline at end of file +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/promise_rejection_event.h b/bridge/core/events/promise_rejection_event.h index cd043e610d..3aeb6b3b73 100644 --- a/bridge/core/events/promise_rejection_event.h +++ b/bridge/core/events/promise_rejection_event.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_CORE_EVENTS_PROMISE_REJECTION_EVENT_H_ #define BRIDGE_CORE_EVENTS_PROMISE_REJECTION_EVENT_H_ @@ -13,25 +13,24 @@ 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); + 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); - explicit PromiseRejectionEvent(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr<PromiseRejectionEventInit>& initializer, - ExceptionState& exception_state); - ScriptValue promise() { return promise_; } ScriptValue reason() { return reason_; } @@ -47,6 +46,6 @@ struct DowncastTraits<PromiseRejectionEvent> { static bool AllowFrom(const Event& event) { return event.IsErrorEvent(); } }; -} +} // namespace webf #endif // BRIDGE_CORE_EVENTS_PROMISE_REJECTION_EVENT_H_ diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index c6c107896c..15979e8775 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -7,9 +7,9 @@ #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 "core/events/promise_rejection_event.h" #include "polyfill.h" #include "qjs_window.h" diff --git a/bridge/core/html/html_element_test.cc b/bridge/core/html/html_element_test.cc index 73a1774230..70147ea996 100644 --- a/bridge/core/html/html_element_test.cc +++ b/bridge/core/html/html_element_test.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #include "html_element.h" #include "gtest/gtest.h" @@ -9,21 +9,21 @@ 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; - }; - 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'); function f(){ console.log(1234); }; div.onclick = f; " - "div.dispatchEvent(new Event('click'));"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); + 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; + }; + 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'); 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(errorCalled, false); } From afd26447aa21c1103c3a5e8528d26cb67d66fe2f Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Mon, 15 Aug 2022 17:31:02 +0800 Subject: [PATCH 160/498] fix: fix dispatch event memory leak. --- bridge/bindings/qjs/js_event_handler.cc | 11 ++++++----- bridge/core/dom/global_event_handlers.d.ts | 2 -- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index 24086ce1d8..fad084019d 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -30,8 +30,7 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // Step 1. Let callback be the result of getting the current value of the // event handler given eventTarget and name. // Step 2. If callback is null, then return. - JSValue listener_value = GetListenerObject(); - if (JS_IsNull(listener_value)) + if (event_handler_ == nullptr) return; // Step 3. Let special error event handling be true if event is an ErrorEvent @@ -74,10 +73,10 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc ScriptValue(ctx, Converter<IDLInt64>::ToValue(ctx, error_event->lineno())), ScriptValue(ctx, Converter<IDLInt64>::ToValue(ctx, error_event->colno())), error_attribute}; } else { - arguments.emplace_back(ctx, event.ToQuickJS()); + arguments.emplace_back(event.ToValue()); } - ScriptValue result = event_handler_->Invoke(event.ctx(), ScriptValue(event_target.ctx(), event_target.ToQuickJS()), + ScriptValue result = event_handler_->Invoke(event.ctx(), event_target.ToValue(), arguments.size(), arguments.data()); if (result.IsException()) { exception_state.ThrowException(event.ctx(), result.QJSValue()); @@ -100,6 +99,8 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc // TODO: special handling for beforeunload event and onerror event. } -void JSEventHandler::Trace(GCVisitor* visitor) const {} +void JSEventHandler::Trace(GCVisitor* visitor) const { + event_handler_->Trace(visitor); +} } // namespace webf diff --git a/bridge/core/dom/global_event_handlers.d.ts b/bridge/core/dom/global_event_handlers.d.ts index c1456d0c47..d8405d626d 100644 --- a/bridge/core/dom/global_event_handlers.d.ts +++ b/bridge/core/dom/global_event_handlers.d.ts @@ -1,7 +1,5 @@ type IDLEventHandler = Function; -// @ts-ignore -@Mixin() export interface GlobalEventHandlers { /** * Fires when the user aborts the download. From 5c9554c1682ed61e15248b2de56e75d8c991fc80 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Mon, 15 Aug 2022 23:48:51 +0800 Subject: [PATCH 161/498] feat: add property event listener for window and body element. --- bridge/CMakeLists.txt | 1 + bridge/core/dom/document.cc | 20 +++++++++ bridge/core/dom/document.h | 13 +++--- bridge/core/dom/element.cc | 2 + bridge/core/dom/events/event_target.cc | 4 ++ bridge/core/dom/events/event_target.h | 45 ++++++++++--------- bridge/core/dom/global_event_handlers.d.ts | 2 + bridge/core/dom/node.cc | 5 +++ bridge/core/dom/node.h | 2 + bridge/core/executing_context.cc | 12 +++-- bridge/core/executing_context.h | 3 ++ bridge/core/executing_context_test.cc | 5 +-- bridge/core/frame/window.d.ts | 3 +- bridge/core/frame/window_event_handlers.d.ts | 14 ++++++ bridge/core/frame/window_event_handlers.h | 33 ++++++++++++++ bridge/core/html/html_body_element.d.ts | 10 ++++- bridge/core/html/html_body_element.h | 10 +++++ bridge/core/html/parser/html_parser.cc | 1 + bridge/core/page.cc | 1 + .../code_generator/src/idl/analyzer.ts | 2 +- .../code_generator/src/idl/generateHeader.ts | 6 ++- .../static/idl_templates/base.cc.tpl | 5 +++ bridge/test/webf_test_context.cc | 1 + 23 files changed, 159 insertions(+), 41 deletions(-) create mode 100644 bridge/core/frame/window_event_handlers.d.ts create mode 100644 bridge/core/frame/window_event_handlers.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index d2de89679e..ab723baea5 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -277,6 +277,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/frame/legacy/location.cc core/frame/legacy/location.h core/frame/module_callback_coordinator.h + core/frame/window_event_handlers.h core/css/legacy/css_style_declaration.cc core/css/legacy/css_style_declaration.h core/dom/frame_request_callback_collection.cc diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 3b653baeda..58ab4b3492 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -4,7 +4,11 @@ */ #include "document.h" #include "bindings/qjs/exception_message.h" +#include "core/frame/window.h" #include "core/dom/element.h" +#include "core/dom/comment.h" +#include "core/dom/document_fragment.h" +#include "core/dom/text.h" #include "core/html/html_body_element.h" #include "core/html/html_element.h" #include "core/html/html_head_element.h" @@ -130,6 +134,10 @@ Node* Document::Clone(Document&, CloneChildrenFlag) const { return nullptr; } +HTMLHtmlElement* Document::documentElement() const { + return DynamicTo<HTMLHtmlElement>(document_element_.Get()); +} + void Document::InitDocumentElement() { ExceptionState exception_state; AppendChild(document_element_, exception_state); @@ -202,6 +210,18 @@ void Document::CancelAnimationFrame(uint32_t request_id, ExceptionState& excepti script_animation_controller_.CancelFrameCallback(GetExecutingContext(), request_id, exception_state); } +void Document::SetWindowAttributeEventListener(const AtomicString& event_type, const std::shared_ptr<EventListener>& listener, ExceptionState& exception_state) { + Window* window = GetExecutingContext()->window(); + if (!window) return; + window->SetAttributeEventListener(event_type, listener, exception_state); +} + +std::shared_ptr<EventListener> Document::GetWindowAttributeEventListener(const AtomicString& event_type) { + Window* window = GetExecutingContext()->window(); + if (!window) return nullptr; + return window->GetAttributeEventListener(event_type); +} + void Document::Trace(GCVisitor* visitor) const { visitor->Trace(document_element_); script_animation_controller_.Trace(visitor); diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index ba41b467d9..d293934088 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -7,10 +7,6 @@ #include "bindings/qjs/cppgc/local_handle.h" #include "container_node.h" -#include "core/dom/comment.h" -#include "core/dom/document_fragment.h" -#include "core/dom/text.h" -#include "html_element_type_helper.h" #include "scripted_animation_controller.h" #include "tree_scope.h" @@ -19,6 +15,8 @@ namespace webf { class HTMLBodyElement; class HTMLHeadElement; class HTMLHtmlElement; +class Text; +class Comment; // 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 @@ -48,7 +46,7 @@ class Document : public ContainerNode, public TreeScope { Node* Clone(Document&, CloneChildrenFlag) const override; - [[nodiscard]] HTMLHtmlElement* documentElement() const { return DynamicTo<HTMLHtmlElement>(document_element_.Get()); } + [[nodiscard]] HTMLHtmlElement* documentElement() const; void InitDocumentElement(); // "body element" as defined by HTML5 @@ -71,6 +69,11 @@ class Document : public ContainerNode, public TreeScope { uint32_t RequestAnimationFrame(const std::shared_ptr<FrameCallback>& 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<EventListener>& listener, ExceptionState& exception_state); + std::shared_ptr<EventListener> GetWindowAttributeEventListener(const AtomicString& event_type); + void Trace(GCVisitor* visitor) const override; private: diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 8ef831ae0f..1594e1345e 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -3,12 +3,14 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "element.h" +#include "text.h" #include <utility> #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 "html_element_type_helper.h" #include "core/dom/document_fragment.h" #include "core/fileapi/blob.h" #include "core/html/html_template_element.h" diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index ed77a29535..c7eaedb112 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -45,6 +45,10 @@ EventTarget::~EventTarget() { EventTarget::EventTarget(ExecutingContext* context) : BindingObject(context), 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, diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index cca148be32..5ddd680781 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -72,6 +72,8 @@ class EventTargetData final { 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 @@ -89,6 +91,8 @@ class EventTarget : public ScriptWrappable, public BindingObject { ~EventTarget(); explicit EventTarget(ExecutingContext* context); + virtual Node* ToNode(); + bool addEventListener(const AtomicString& event_type, const std::shared_ptr<EventListener>& event_listener, const std::shared_ptr<AddEventListenerOptions>& options, @@ -183,29 +187,28 @@ class EventTargetWithInlineData : public EventTarget { eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener, exception_state); \ } -#define DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(lower_name, symbol_name) \ - EventListener* on##lower_name() { \ - return GetDocument().GetWindowAttributeEventListener(event_type_names::symbol_name); \ - } \ - void setOn##lower_name(EventListener* listener) { \ - GetDocument().SetWindowAttributeEventListener(event_type_names::symbol_name, listener); \ +#define DEFINE_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 EventListener* on##lower_name(EventTarget& eventTarget) { \ - if (Node* node = eventTarget.ToNode()) { \ - return node->GetDocument().GetWindowAttributeEventListener(event_type_names::symbol_name); \ - } \ - DCHECK(eventTarget.ToLocalDOMWindow()); \ - return eventTarget.GetAttributeEventListener(event_type_names::symbol_name); \ - } \ - static void setOn##lower_name(EventTarget& eventTarget, EventListener* listener) { \ - if (Node* node = eventTarget.ToNode()) { \ - node->GetDocument().SetWindowAttributeEventListener(event_type_names::symbol_name, listener); \ - } else { \ - DCHECK(eventTarget.ToLocalDOMWindow()); \ - eventTarget.SetAttributeEventListener(event_type_names::symbol_name, listener); \ - } \ +#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); \ + } \ } // diff --git a/bridge/core/dom/global_event_handlers.d.ts b/bridge/core/dom/global_event_handlers.d.ts index d8405d626d..c1456d0c47 100644 --- a/bridge/core/dom/global_event_handlers.d.ts +++ b/bridge/core/dom/global_event_handlers.d.ts @@ -1,5 +1,7 @@ type IDLEventHandler = Function; +// @ts-ignore +@Mixin() export interface GlobalEventHandlers { /** * Fires when the user aborts the download. diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 45a1bbfd4c..699c88c01c 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -7,6 +7,7 @@ #include <unordered_map> #include "character_data.h" #include "child_node_list.h" +#include "element.h" #include "document.h" #include "document_fragment.h" #include "empty_node_list.h" @@ -21,6 +22,10 @@ Node* Node::Create(ExecutingContext* context, ExceptionState& exception_state) { return nullptr; } +Node* Node::ToNode() { + return this; +} + void Node::setNodeValue(const AtomicString& value, ExceptionState& exception_state) { // By default, setting nodeValue has no effect. } diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 3cea3332d7..294ec177af 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -60,6 +60,8 @@ class Node : public EventTarget { 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; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 15979e8775..80752b2a75 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -266,8 +266,7 @@ static void DispatchPromiseRejectionEvent(const AtomicString& event_type, event_init->setReason(Converter<IDLAny>::FromValue(context->ctx(), error, exception_state)); auto event = PromiseRejectionEvent::Create(context, event_type, event_init, exception_state); - auto* window = toScriptWrappable<Window>(context->Global()); - window->dispatchEvent(event, exception_state); + context->window()->dispatchEvent(event, exception_state); if (exception_state.HasException()) { context->ReportError(error); } @@ -289,9 +288,8 @@ void ExecutingContext::DispatchErrorEvent(ErrorEvent* error_event) { void ExecutingContext::DispatchErrorEventInterval(ErrorEvent* error_event) { assert(!in_dispatch_error_event_); in_dispatch_error_event_ = true; - auto* window = toScriptWrappable<Window>(Global()); ExceptionState exception_state; - window->dispatchEvent(error_event, exception_state); + window_->dispatchEvent(error_event, exception_state); in_dispatch_error_event_ = false; if (exception_state.HasException()) { @@ -373,9 +371,9 @@ void ExecutingContext::InstallDocument() { void ExecutingContext::InstallGlobal() { MemberMutationScope mutation_scope{this}; - auto* window = MakeGarbageCollected<Window>(this); - JS_SetPrototype(ctx(), Global(), window->ToQuickJSUnsafe()); - JS_SetOpaque(Global(), window); + window_ = MakeGarbageCollected<Window>(this); + JS_SetPrototype(ctx(), Global(), window_->ToQuickJSUnsafe()); + JS_SetOpaque(Global(), window_); } // An lock free context validator. diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 65881101c9..a7e28f2f7c 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -38,6 +38,7 @@ struct NativeByteCode { class ExecutingContext; class Document; +class Window; class MemberMutationScope; class ErrorEvent; @@ -100,6 +101,7 @@ class ExecutingContext { void ClearMutationScope(); FORCE_INLINE Document* document() { return document_; }; + FORCE_INLINE Window* window() { return window_; } FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; FORCE_INLINE std::unique_ptr<DartMethodPointer>& dartMethodPtr() { return dart_method_ptr_; } @@ -142,6 +144,7 @@ class ExecutingContext { JSValue global_object_{JS_NULL}; bool ctx_invalid_{false}; Document* document_{nullptr}; + Window* window_{nullptr}; DOMTimerCoordinator timers_; ModuleListenerContainer module_listener_container_; ModuleCallbackCoordinator module_callbacks_; diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index b358940bfb..f25e1f2abf 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -105,8 +105,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()); @@ -130,7 +129,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; } diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts index a6a0294c7f..4ba04e7c40 100644 --- a/bridge/core/frame/window.d.ts +++ b/bridge/core/frame/window.d.ts @@ -2,8 +2,9 @@ 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"; -interface Window extends EventTarget { +interface Window extends EventTarget, WindowEventHandlers { open(url?: string): Window | null; scrollTo(options?: ScrollToOptions): void; scrollTo(x: number, y: number): void; 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..c5194d1abf --- /dev/null +++ b/bridge/core/frame/window_event_handlers.d.ts @@ -0,0 +1,14 @@ +type IDLEventHandler = Function; + +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..09f06f3c37 --- /dev/null +++ b/bridge/core/frame/window_event_handlers.h @@ -0,0 +1,33 @@ +/* +* 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 "foundation/macros.h" +#include "event_type_names.h" +#include "core/dom/document.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); +}; + +} + + +#endif // BRIDGE_CORE_FRAME_WINDOW_EVENT_HANDLERS_H_ diff --git a/bridge/core/html/html_body_element.d.ts b/bridge/core/html/html_body_element.d.ts index b2b9c4ca94..fd04e56800 100644 --- a/bridge/core/html/html_body_element.d.ts +++ b/bridge/core/html/html_body_element.d.ts @@ -1,5 +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; -export interface HTMLBodyElement extends HTMLElement { new(): void; } diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index 48639b15ba..f98a02bc14 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -6,6 +6,8 @@ #define BRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ #include "html_element.h" +#include "core/dom/document.h" +#include "core/frame/window_event_handlers.h" namespace webf { @@ -15,6 +17,14 @@ class HTMLBodyElement : public HTMLElement { public: using ImplType = HTMLBodyElement*; explicit HTMLBodyElement(Document&); + + 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 diff --git a/bridge/core/html/parser/html_parser.cc b/bridge/core/html/parser/html_parser.cc index 57fc841cd6..ea6911aff3 100644 --- a/bridge/core/html/parser/html_parser.cc +++ b/bridge/core/html/parser/html_parser.cc @@ -7,6 +7,7 @@ #include "core/dom/document.h" #include "core/dom/element.h" +#include "core/dom/text.h" #include "foundation/logging.h" #include "html_parser.h" diff --git a/bridge/core/page.cc b/bridge/core/page.cc index b7188a446f..385720a484 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -9,6 +9,7 @@ #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 "foundation/logging.h" #include "page.h" diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index a0cdd11cc6..ff2cc85419 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -40,7 +40,7 @@ function getMixins(hertage: HeritageClause): string[] | null { hertage.types.slice(1).forEach(types => { let expression = types.expression; if (expression.kind === ts.SyntaxKind.Identifier) { - mixins.push((expression as ts.Identifier).escapedText!); + mixins.push((expression as ts.Identifier).escapedText! as string); } }); return mixins; diff --git a/bridge/scripts/code_generator/src/idl/generateHeader.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts index f116917d90..a724b37e43 100644 --- a/bridge/scripts/code_generator/src/idl/generateHeader.ts +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -1,5 +1,5 @@ -import {ClassObject, ClassObjectKind, FunctionDeclaration, FunctionObject, PropsDeclaration} from "./declaration"; -import _, {mixin} from "lodash"; +import {ClassObject, ClassObjectKind, FunctionObject} from "./declaration"; +import _ from "lodash"; import {IDLBlob} from "./IDLBlob"; import {getClassName} from "./utils"; import fs from 'fs'; @@ -20,6 +20,8 @@ export function getTemplateKind(object: ClassObject | FunctionObject | null): Te } 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; } diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl index a4e44555e1..4238566054 100644 --- a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl @@ -13,6 +13,11 @@ #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" namespace webf { diff --git a/bridge/test/webf_test_context.cc b/bridge/test/webf_test_context.cc index 301623d85b..1abcfff172 100644 --- a/bridge/test/webf_test_context.cc +++ b/bridge/test/webf_test_context.cc @@ -6,6 +6,7 @@ #include "webf_test_context.h" #include "bindings/qjs/member_installer.h" #include "core/dom/document.h" +#include "core/html/html_body_element.h" #include "core/fileapi/blob.h" #include "core/html/parser/html_parser.h" #include "qjs_blob.h" From 8cf26ff3139c80667cbfa2f701f586a45c0e098f Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Wed, 17 Aug 2022 17:27:19 +0800 Subject: [PATCH 162/498] fix: fix context specs. --- bridge/core/executing_context.cc | 13 +++++-------- bridge/core/executing_context_test.cc | 13 +++++++------ bridge/test/webf_test_env.cc | 5 ++++- bridge/test/webf_test_env.h | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 80752b2a75..de74ee14a6 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -258,17 +258,17 @@ void ExecutingContext::DispatchGlobalErrorEvent(ExecutingContext* context, JSVal static void DispatchPromiseRejectionEvent(const AtomicString& event_type, ExecutingContext* context, JSValueConst promise, - JSValueConst error) { + 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(), error, 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(error); + context->ReportError(reason); } } @@ -305,12 +305,9 @@ void ExecutingContext::ReportErrorEvent(ErrorEvent* error_event) { void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* context, JSValueConst promise, - JSValueConst error) { - // Trigger onerror event. - DispatchGlobalErrorEvent(context, error); - + JSValueConst reason) { // Trigger unhandledRejection event. - DispatchPromiseRejectionEvent(event_type_names::kunhandledrejection, context, promise, error); + DispatchPromiseRejectionEvent(event_type_names::kunhandledrejection, context, promise, reason); } void ExecutingContext::DispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValue promise, JSValue error) { diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index f25e1f2abf..14a3e40f8e 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -170,11 +170,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 => { @@ -193,7 +195,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; } @@ -253,7 +254,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" @@ -265,7 +266,7 @@ 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) { diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index 5cb1d5b395..683652f9d1 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -214,8 +214,11 @@ std::unique_ptr<webf::WebFPage> TEST_init() { return TEST_init(nullptr); } -std::unique_ptr<webf::WebFPage> TEST_allocateNewPage() { +std::unique_ptr<webf::WebFPage> TEST_allocateNewPage(OnJSError onJsError) { uint32_t newContextId = allocateNewPage(-1); + + TEST_mockDartMethods(newContextId, onJsError); + initTestFramework(newContextId); return std::unique_ptr<webf::WebFPage>(static_cast<webf::WebFPage*>(getPage(newContextId))); } diff --git a/bridge/test/webf_test_env.h b/bridge/test/webf_test_env.h index b5f7ccae4f..07b6dc568f 100644 --- a/bridge/test/webf_test_env.h +++ b/bridge/test/webf_test_env.h @@ -25,7 +25,7 @@ namespace webf { std::unique_ptr<WebFPage> TEST_init(OnJSError onJsError); std::unique_ptr<WebFPage> TEST_init(); -std::unique_ptr<WebFPage> TEST_allocateNewPage(); +std::unique_ptr<WebFPage> TEST_allocateNewPage(OnJSError onJsError); void TEST_runLoop(ExecutingContext* context); void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); From 2e0e0be18a28eb096c32eb618fb09bfd5692a0e1 Mon Sep 17 00:00:00 2001 From: openwebf-bot <openwebf@openwebf.com> Date: Wed, 17 Aug 2022 09:28:27 +0000 Subject: [PATCH 163/498] Committing clang-format changes --- bridge/bindings/qjs/js_event_handler.cc | 3 +-- bridge/core/dom/document.cc | 14 +++++++++----- bridge/core/dom/document.h | 4 +++- bridge/core/dom/element.cc | 2 +- bridge/core/dom/node.cc | 2 +- bridge/core/executing_context.cc | 2 +- bridge/core/executing_context_test.cc | 2 +- bridge/core/frame/window_event_handlers.h | 11 +++++------ bridge/core/html/html_body_element.h | 2 +- bridge/test/webf_test_context.cc | 2 +- 10 files changed, 24 insertions(+), 20 deletions(-) diff --git a/bridge/bindings/qjs/js_event_handler.cc b/bridge/bindings/qjs/js_event_handler.cc index fad084019d..937e528619 100644 --- a/bridge/bindings/qjs/js_event_handler.cc +++ b/bridge/bindings/qjs/js_event_handler.cc @@ -76,8 +76,7 @@ void JSEventHandler::InvokeInternal(EventTarget& event_target, Event& event, Exc arguments.emplace_back(event.ToValue()); } - ScriptValue result = event_handler_->Invoke(event.ctx(), event_target.ToValue(), - arguments.size(), arguments.data()); + 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; diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 58ab4b3492..33b9e9c8a0 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -4,11 +4,11 @@ */ #include "document.h" #include "bindings/qjs/exception_message.h" -#include "core/frame/window.h" -#include "core/dom/element.h" #include "core/dom/comment.h" #include "core/dom/document_fragment.h" +#include "core/dom/element.h" #include "core/dom/text.h" +#include "core/frame/window.h" #include "core/html/html_body_element.h" #include "core/html/html_element.h" #include "core/html/html_head_element.h" @@ -210,15 +210,19 @@ void Document::CancelAnimationFrame(uint32_t request_id, ExceptionState& excepti script_animation_controller_.CancelFrameCallback(GetExecutingContext(), request_id, exception_state); } -void Document::SetWindowAttributeEventListener(const AtomicString& event_type, const std::shared_ptr<EventListener>& listener, ExceptionState& exception_state) { +void Document::SetWindowAttributeEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + ExceptionState& exception_state) { Window* window = GetExecutingContext()->window(); - if (!window) return; + if (!window) + return; window->SetAttributeEventListener(event_type, listener, exception_state); } std::shared_ptr<EventListener> Document::GetWindowAttributeEventListener(const AtomicString& event_type) { Window* window = GetExecutingContext()->window(); - if (!window) return nullptr; + if (!window) + return nullptr; return window->GetAttributeEventListener(event_type); } diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index d293934088..5c5a7185e7 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -71,7 +71,9 @@ class Document : public ContainerNode, public TreeScope { // Helper functions for forwarding LocalDOMWindow event related tasks to the // LocalDOMWindow if it exists. - void SetWindowAttributeEventListener(const AtomicString& event_type, const std::shared_ptr<EventListener>& listener, ExceptionState& exception_state); + void SetWindowAttributeEventListener(const AtomicString& event_type, + const std::shared_ptr<EventListener>& listener, + ExceptionState& exception_state); std::shared_ptr<EventListener> GetWindowAttributeEventListener(const AtomicString& event_type); void Trace(GCVisitor* visitor) const override; diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 1594e1345e..ca170892dd 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -10,12 +10,12 @@ #include "bindings/qjs/exception_state.h" #include "bindings/qjs/script_promise.h" #include "bindings/qjs/script_promise_resolver.h" -#include "html_element_type_helper.h" #include "core/dom/document_fragment.h" #include "core/fileapi/blob.h" #include "core/html/html_template_element.h" #include "core/html/parser/html_parser.h" #include "foundation/native_value_converter.h" +#include "html_element_type_helper.h" namespace webf { diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 699c88c01c..96d3c70464 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -7,9 +7,9 @@ #include <unordered_map> #include "character_data.h" #include "child_node_list.h" -#include "element.h" #include "document.h" #include "document_fragment.h" +#include "element.h" #include "empty_node_list.h" #include "node_data.h" #include "node_traversal.h" diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index de74ee14a6..221ef91576 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -368,7 +368,7 @@ void ExecutingContext::InstallDocument() { void ExecutingContext::InstallGlobal() { MemberMutationScope mutation_scope{this}; - window_ = MakeGarbageCollected<Window>(this); + window_ = MakeGarbageCollected<Window>(this); JS_SetPrototype(ctx(), Global(), window_->ToQuickJSUnsafe()); JS_SetOpaque(Global(), window_); } diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 14a3e40f8e..8e1f981473 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -171,7 +171,7 @@ generateRejectedPromise(true); TEST(Context, unhandledRejectionEventWillTriggerWhenNotHandled) { static bool logCalled = false; - auto errorHandler = [](int32_t contextId, const char* errmsg) { }; + 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; diff --git a/bridge/core/frame/window_event_handlers.h b/bridge/core/frame/window_event_handlers.h index 09f06f3c37..bf3db3dd65 100644 --- a/bridge/core/frame/window_event_handlers.h +++ b/bridge/core/frame/window_event_handlers.h @@ -1,13 +1,13 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * 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 "foundation/macros.h" -#include "event_type_names.h" #include "core/dom/document.h" +#include "event_type_names.h" +#include "foundation/macros.h" namespace webf { @@ -27,7 +27,6 @@ class WindowEventHandlers { DEFINE_STATIC_WINDOW_ATTRIBUTE_EVENT_LISTENER(unload, kunload); }; -} - +} // namespace webf #endif // BRIDGE_CORE_FRAME_WINDOW_EVENT_HANDLERS_H_ diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index f98a02bc14..b9927d0184 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -5,9 +5,9 @@ #ifndef BRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ #define BRIDGE_CORE_HTML_HTML_BODY_ELEMENT_H_ -#include "html_element.h" #include "core/dom/document.h" #include "core/frame/window_event_handlers.h" +#include "html_element.h" namespace webf { diff --git a/bridge/test/webf_test_context.cc b/bridge/test/webf_test_context.cc index 1abcfff172..4733e209a1 100644 --- a/bridge/test/webf_test_context.cc +++ b/bridge/test/webf_test_context.cc @@ -6,8 +6,8 @@ #include "webf_test_context.h" #include "bindings/qjs/member_installer.h" #include "core/dom/document.h" -#include "core/html/html_body_element.h" #include "core/fileapi/blob.h" +#include "core/html/html_body_element.h" #include "core/html/parser/html_parser.h" #include "qjs_blob.h" #include "testframework.h" From cc5b41d4b59d2765af5103518f005a25bb0f863a Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Fri, 19 Aug 2022 16:33:45 +0800 Subject: [PATCH 164/498] fix: fix key query in element.style --- bridge/core/css/legacy/css_property_list.h | 435 ++++++++++++++++++ .../core/css/legacy/css_style_declaration.cc | 5 + .../core/css/legacy/css_style_declaration.h | 2 + bridge/core/dom/element_test.cc | 3 +- .../code_generator/src/idl/generateSource.ts | 1 + .../static/idl_templates/interface.cc.tpl | 10 + .../static/idl_templates/interface.h.tpl | 1 + 7 files changed, 455 insertions(+), 2 deletions(-) create mode 100644 bridge/core/css/legacy/css_property_list.h diff --git a/bridge/core/css/legacy/css_property_list.h b/bridge/core/css/legacy/css_property_list.h new file mode 100644 index 0000000000..964c42ca88 --- /dev/null +++ b/bridge/core/css/legacy/css_property_list.h @@ -0,0 +1,435 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ + +#ifndef BRIDGE_CORE_CSS_LEGACY_CSS_PROPERTY_LIST_H_ +#define BRIDGE_CORE_CSS_LEGACY_CSS_PROPERTY_LIST_H_ + +#include <unordered_map> + +namespace webf { + +std::unordered_map<std::string, bool> cssPropertyList{{"accentColor", true}, + {"additiveSymbols", true}, + {"alignContent", true}, + {"alignItems", true}, + {"alignSelf", true}, + {"alignmentBaseline", true}, + {"all", true}, + {"animation", true}, + {"animationDelay", true}, + {"animationDirection", true}, + {"animationDuration", true}, + {"animationFillMode", true}, + {"animationIterationCount", true}, + {"animationName", true}, + {"animationPlayState", true}, + {"animationTimingFunction", true}, + {"appRegion", true}, + {"appearance", true}, + {"ascentOverride", true}, + {"aspectRatio", true}, + {"backdropFilter", true}, + {"backfaceVisibility", true}, + {"background", true}, + {"backgroundAttachment", true}, + {"backgroundBlendMode", true}, + {"backgroundClip", true}, + {"backgroundColor", true}, + {"backgroundImage", true}, + {"backgroundOrigin", true}, + {"backgroundPosition", true}, + {"backgroundPositionX", true}, + {"backgroundPositionY", true}, + {"backgroundRepeat", true}, + {"backgroundRepeatX", true}, + {"backgroundRepeatY", true}, + {"backgroundSize", true}, + {"baselineShift", true}, + {"blockSize", true}, + {"border", true}, + {"borderBlock", true}, + {"borderBlockColor", true}, + {"borderBlockEnd", true}, + {"borderBlockEndColor", true}, + {"borderBlockEndStyle", true}, + {"borderBlockEndWidth", true}, + {"borderBlockStart", true}, + {"borderBlockStartColor", true}, + {"borderBlockStartStyle", true}, + {"borderBlockStartWidth", true}, + {"borderBlockStyle", true}, + {"borderBlockWidth", true}, + {"borderBottom", true}, + {"borderBottomColor", true}, + {"borderBottomLeftRadius", true}, + {"borderBottomRightRadius", true}, + {"borderBottomStyle", true}, + {"borderBottomWidth", true}, + {"borderCollapse", true}, + {"borderColor", true}, + {"borderEndEndRadius", true}, + {"borderEndStartRadius", true}, + {"borderImage", true}, + {"borderImageOutset", true}, + {"borderImageRepeat", true}, + {"borderImageSlice", true}, + {"borderImageSource", true}, + {"borderImageWidth", true}, + {"borderInline", true}, + {"borderInlineColor", true}, + {"borderInlineEnd", true}, + {"borderInlineEndColor", true}, + {"borderInlineEndStyle", true}, + {"borderInlineEndWidth", true}, + {"borderInlineStart", true}, + {"borderInlineStartColor", true}, + {"borderInlineStartStyle", true}, + {"borderInlineStartWidth", true}, + {"borderInlineStyle", true}, + {"borderInlineWidth", true}, + {"borderLeft", true}, + {"borderLeftColor", true}, + {"borderLeftStyle", true}, + {"borderLeftWidth", true}, + {"borderRadius", true}, + {"borderRight", true}, + {"borderRightColor", true}, + {"borderRightStyle", true}, + {"borderRightWidth", true}, + {"borderSpacing", true}, + {"borderStartEndRadius", true}, + {"borderStartStartRadius", true}, + {"borderStyle", true}, + {"borderTop", true}, + {"borderTopColor", true}, + {"borderTopLeftRadius", true}, + {"borderTopRightRadius", true}, + {"borderTopStyle", true}, + {"borderTopWidth", true}, + {"borderWidth", true}, + {"bottom", true}, + {"boxShadow", true}, + {"boxSizing", true}, + {"breakAfter", true}, + {"breakBefore", true}, + {"breakInside", true}, + {"bufferedRendering", true}, + {"captionSide", true}, + {"caretColor", true}, + {"clear", true}, + {"clip", true}, + {"clipPath", true}, + {"clipRule", true}, + {"color", true}, + {"colorInterpolation", true}, + {"colorInterpolationFilters", true}, + {"colorRendering", true}, + {"colorScheme", true}, + {"columnCount", true}, + {"columnFill", true}, + {"columnGap", true}, + {"columnRule", true}, + {"columnRuleColor", true}, + {"columnRuleStyle", true}, + {"columnRuleWidth", true}, + {"columnSpan", true}, + {"columnWidth", true}, + {"columns", true}, + {"content", true}, + {"contentVisibility", true}, + {"counterIncrement", true}, + {"counterReset", true}, + {"counterSet", true}, + {"cursor", true}, + {"cx", true}, + {"cy", true}, + {"d", true}, + {"descentOverride", true}, + {"direction", true}, + {"display", true}, + {"dominantBaseline", true}, + {"emptyCells", true}, + {"fallback", true}, + {"fill", true}, + {"fillOpacity", true}, + {"fillRule", true}, + {"filter", true}, + {"flex", true}, + {"flexBasis", true}, + {"flexDirection", true}, + {"flexFlow", true}, + {"flexGrow", true}, + {"flexShrink", true}, + {"flexWrap", true}, + {"float", true}, + {"floodColor", true}, + {"floodOpacity", true}, + {"font", true}, + {"fontDisplay", true}, + {"fontFamily", true}, + {"fontFeatureSettings", true}, + {"fontKerning", true}, + {"fontOpticalSizing", true}, + {"fontSize", true}, + {"fontStretch", true}, + {"fontStyle", true}, + {"fontSynthesis", true}, + {"fontSynthesisSmallCaps", true}, + {"fontSynthesisStyle", true}, + {"fontSynthesisWeight", true}, + {"fontVariant", true}, + {"fontVariantCaps", true}, + {"fontVariantEastAsian", true}, + {"fontVariantLigatures", true}, + {"fontVariantNumeric", true}, + {"fontVariationSettings", true}, + {"fontWeight", true}, + {"forcedColorAdjust", true}, + {"gap", true}, + {"grid", true}, + {"gridArea", true}, + {"gridAutoColumns", true}, + {"gridAutoFlow", true}, + {"gridAutoRows", true}, + {"gridColumn", true}, + {"gridColumnEnd", true}, + {"gridColumnGap", true}, + {"gridColumnStart", true}, + {"gridGap", true}, + {"gridRow", true}, + {"gridRowEnd", true}, + {"gridRowGap", true}, + {"gridRowStart", true}, + {"gridTemplate", true}, + {"gridTemplateAreas", true}, + {"gridTemplateColumns", true}, + {"gridTemplateRows", true}, + {"height", true}, + {"hyphens", true}, + {"imageOrientation", true}, + {"imageRendering", true}, + {"inherits", true}, + {"initialValue", true}, + {"inlineSize", true}, + {"inset", true}, + {"insetBlock", true}, + {"insetBlockEnd", true}, + {"insetBlockStart", true}, + {"insetInline", true}, + {"insetInlineEnd", true}, + {"insetInlineStart", true}, + {"isolation", true}, + {"justifyContent", true}, + {"justifyItems", true}, + {"justifySelf", true}, + {"left", true}, + {"letterSpacing", true}, + {"lightingColor", true}, + {"lineBreak", true}, + {"lineGapOverride", true}, + {"lineHeight", true}, + {"listStyle", true}, + {"listStyleImage", true}, + {"listStylePosition", true}, + {"listStyleType", true}, + {"margin", true}, + {"marginBlock", true}, + {"marginBlockEnd", true}, + {"marginBlockStart", true}, + {"marginBottom", true}, + {"marginInline", true}, + {"marginInlineEnd", true}, + {"marginInlineStart", true}, + {"marginLeft", true}, + {"marginRight", true}, + {"marginTop", true}, + {"marker", true}, + {"markerEnd", true}, + {"markerMid", true}, + {"markerStart", true}, + {"mask", true}, + {"maskType", true}, + {"maxBlockSize", true}, + {"maxHeight", true}, + {"maxInlineSize", true}, + {"maxWidth", true}, + {"maxZoom", true}, + {"minBlockSize", true}, + {"minHeight", true}, + {"minInlineSize", true}, + {"minWidth", true}, + {"minZoom", true}, + {"mixBlendMode", true}, + {"negative", true}, + {"objectFit", true}, + {"objectPosition", true}, + {"offset", true}, + {"offsetDistance", true}, + {"offsetPath", true}, + {"offsetRotate", true}, + {"opacity", true}, + {"order", true}, + {"orientation", true}, + {"orphans", true}, + {"outline", true}, + {"outlineColor", true}, + {"outlineOffset", true}, + {"outlineStyle", true}, + {"outlineWidth", true}, + {"overflow", true}, + {"overflowAnchor", true}, + {"overflowClipMargin", true}, + {"overflowWrap", true}, + {"overflowX", true}, + {"overflowY", true}, + {"overscrollBehavior", true}, + {"overscrollBehaviorBlock", true}, + {"overscrollBehaviorInline", true}, + {"overscrollBehaviorX", true}, + {"overscrollBehaviorY", true}, + {"pad", true}, + {"padding", true}, + {"paddingBlock", true}, + {"paddingBlockEnd", true}, + {"paddingBlockStart", true}, + {"paddingBottom", true}, + {"paddingInline", true}, + {"paddingInlineEnd", true}, + {"paddingInlineStart", true}, + {"paddingLeft", true}, + {"paddingRight", true}, + {"paddingTop", true}, + {"page", true}, + {"pageBreakAfter", true}, + {"pageBreakBefore", true}, + {"pageBreakInside", true}, + {"pageOrientation", true}, + {"paintOrder", true}, + {"perspective", true}, + {"perspectiveOrigin", true}, + {"placeContent", true}, + {"placeItems", true}, + {"placeSelf", true}, + {"pointerEvents", true}, + {"position", true}, + {"prefix", true}, + {"quotes", true}, + {"r", true}, + {"range", true}, + {"resize", true}, + {"right", true}, + {"rowGap", true}, + {"rubyPosition", true}, + {"rx", true}, + {"ry", true}, + {"scrollBehavior", true}, + {"scrollMargin", true}, + {"scrollMarginBlock", true}, + {"scrollMarginBlockEnd", true}, + {"scrollMarginBlockStart", true}, + {"scrollMarginBottom", true}, + {"scrollMarginInline", true}, + {"scrollMarginInlineEnd", true}, + {"scrollMarginInlineStart", true}, + {"scrollMarginLeft", true}, + {"scrollMarginRight", true}, + {"scrollMarginTop", true}, + {"scrollPadding", true}, + {"scrollPaddingBlock", true}, + {"scrollPaddingBlockEnd", true}, + {"scrollPaddingBlockStart", true}, + {"scrollPaddingBottom", true}, + {"scrollPaddingInline", true}, + {"scrollPaddingInlineEnd", true}, + {"scrollPaddingInlineStart", true}, + {"scrollPaddingLeft", true}, + {"scrollPaddingRight", true}, + {"scrollPaddingTop", true}, + {"scrollSnapAlign", true}, + {"scrollSnapStop", true}, + {"scrollSnapType", true}, + {"scrollbarGutter", true}, + {"shapeImageThreshold", true}, + {"shapeMargin", true}, + {"shapeOutside", true}, + {"shapeRendering", true}, + {"size", true}, + {"sizeAdjust", true}, + {"speak", true}, + {"speakAs", true}, + {"src", true}, + {"stopColor", true}, + {"stopOpacity", true}, + {"stroke", true}, + {"strokeDasharray", true}, + {"strokeDashoffset", true}, + {"strokeLinecap", true}, + {"strokeLinejoin", true}, + {"strokeMiterlimit", true}, + {"strokeOpacity", true}, + {"strokeWidth", true}, + {"suffix", true}, + {"symbols", true}, + {"syntax", true}, + {"system", true}, + {"tabSize", true}, + {"tableLayout", true}, + {"textAlign", true}, + {"textAlignLast", true}, + {"textAnchor", true}, + {"textCombineUpright", true}, + {"textDecoration", true}, + {"textDecorationColor", true}, + {"textDecorationLine", true}, + {"textDecorationSkipInk", true}, + {"textDecorationStyle", true}, + {"textDecorationThickness", true}, + {"textEmphasis", true}, + {"textEmphasisColor", true}, + {"textEmphasisPosition", true}, + {"textEmphasisStyle", true}, + {"textIndent", true}, + {"textOrientation", true}, + {"textOverflow", true}, + {"textRendering", true}, + {"textShadow", true}, + {"textSizeAdjust", true}, + {"textTransform", true}, + {"textUnderlineOffset", true}, + {"textUnderlinePosition", true}, + {"top", true}, + {"touchAction", true}, + {"transform", true}, + {"transformBox", true}, + {"transformOrigin", true}, + {"transformStyle", true}, + {"transition", true}, + {"transitionDelay", true}, + {"transitionDuration", true}, + {"transitionProperty", true}, + {"transitionTimingFunction", true}, + {"unicodeBidi", true}, + {"unicodeRange", true}, + {"userSelect", true}, + {"userZoom", true}, + {"vectorEffect", true}, + {"verticalAlign", true}, + {"visibility", true}, + {"whiteSpace", true}, + {"widows", true}, + {"width", true}, + {"willChange", true}, + {"wordBreak", true}, + {"wordSpacing", true}, + {"wordWrap", true}, + {"writingMode", true}, + {"x", true}, + {"y", true}, + {"zIndex", true}, + {"zoom", true}}; + +} // namespace webf + +#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 index 19e86e5647..590aec732b 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -6,6 +6,7 @@ #include <vector> #include "core/dom/element.h" #include "core/executing_context.h" +#include "css_property_list.h" namespace webf { @@ -108,6 +109,10 @@ std::string CSSStyleDeclaration::ToString() const { return s; } +bool CSSStyleDeclaration::NamedPropertyQuery(const AtomicString& key, ExceptionState&) { + return cssPropertyList.count(key.ToStdString()) > 0; +} + AtomicString CSSStyleDeclaration::InternalGetPropertyValue(std::string& name) { name = parseJavaScriptCSSPropertyName(name); diff --git a/bridge/core/css/legacy/css_style_declaration.h b/bridge/core/css/legacy/css_style_declaration.h index 2b4b69cc6f..c78f1cb64c 100644 --- a/bridge/core/css/legacy/css_style_declaration.h +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -36,6 +36,8 @@ class CSSStyleDeclaration : public ScriptWrappable { std::string ToString() const; + bool NamedPropertyQuery(const AtomicString&, ExceptionState&); + private: AtomicString InternalGetPropertyValue(std::string& name); bool InternalSetProperty(std::string& name, const AtomicString& value); diff --git a/bridge/core/dom/element_test.cc b/bridge/core/dom/element_test.cc index 0db75f9985..76957cfa61 100644 --- a/bridge/core/dom/element_test.cc +++ b/bridge/core/dom/element_test.cc @@ -90,8 +90,7 @@ TEST(Element, style) { WEBF_LOG(VERBOSE) << errmsg; errorCalled = true; }); - auto context = bridge->GetExecutingContext(); - const char* code = "console.log(Object.keys(document.body))"; + 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); } diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index e47327388b..e81eacc328 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -406,6 +406,7 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { if (!object.indexedProp.readonly) { wrapperTypeRegisterList.push(`StringPropertySetterCallback`); } + wrapperTypeRegisterList.push('StringPropertyCheckerCallback'); } } diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index 540f160b1f..a2759e4a00 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -31,6 +31,16 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob } return Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); }; + bool QJS<%= className %>::StringPropertyCheckerCallback(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; + } <% } %> <% if (!object.indexedProp.readonly) { %> <% if (object.indexedProp.indexKeyType == 'number') { %> diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl index 9299d49a34..3e028226df 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl @@ -23,6 +23,7 @@ class QJS<%= className %> : public QJSInterfaceBridge<QJS<%= className %>, <%= c static JSValue IndexedPropertyGetterCallback(JSContext* ctx, JSValue obj, uint32_t index); <% } else { %> static JSValue StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key); + static bool StringPropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSAtom atom); <% } %> <% if (!object.indexedProp.readonly) { %> From a01c309cee8963fc19570492de5b406d6d80c550 Mon Sep 17 00:00:00 2001 From: openwebf-bot <openwebf@openwebf.com> Date: Fri, 19 Aug 2022 08:34:44 +0000 Subject: [PATCH 165/498] Committing clang-format changes --- bridge/core/css/legacy/css_property_list.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bridge/core/css/legacy/css_property_list.h b/bridge/core/css/legacy/css_property_list.h index 964c42ca88..fae1eafa82 100644 --- a/bridge/core/css/legacy/css_property_list.h +++ b/bridge/core/css/legacy/css_property_list.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_CORE_CSS_LEGACY_CSS_PROPERTY_LIST_H_ #define BRIDGE_CORE_CSS_LEGACY_CSS_PROPERTY_LIST_H_ From 00d0b5cc7bcb6d22957eb952439c6b0270842a67 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sat, 20 Aug 2022 00:22:17 +0800 Subject: [PATCH 166/498] feat: add more events implements. --- bridge/CMakeLists.txt | 78 ++++++------ bridge/bindings/qjs/binding_initializer.cc | 16 +++ bridge/bindings/qjs/converter_impl.h | 73 +++++------- bridge/bindings/qjs/idl_type.h | 2 +- bridge/bindings/qjs/wrapper_type_info.h | 10 ++ bridge/core/events/animation_event.cc | 67 +++++++++++ bridge/core/events/animation_event.d.ts | 2 + bridge/core/events/animation_event.h | 59 ++++++++++ bridge/core/events/animation_event_init.d.ts | 9 ++ bridge/core/events/close_event.cc | 61 +++++++++- bridge/core/events/close_event.d.ts | 2 + bridge/core/events/close_event.h | 59 +++++++++- bridge/core/events/close_event_init.d.ts | 9 ++ bridge/core/events/focus_event.cc | 52 ++++++++ bridge/core/events/focus_event.d.ts | 2 + bridge/core/events/focus_event.h | 63 ++++++++++ bridge/core/events/focus_event_init.d.ts | 8 ++ bridge/core/events/gesture_event.cc | 70 +++++++++++ bridge/core/events/gesture_event.d.ts | 4 + bridge/core/events/gesture_event.h | 62 ++++++++++ bridge/core/events/gesture_event_init.d.ts | 14 +++ bridge/core/events/input_event.cc | 41 ++++++- bridge/core/events/input_event.d.ts | 6 +- bridge/core/events/input_event.h | 48 +++++++- bridge/core/events/input_event_init.d.ts | 8 ++ .../core/events/intersection_change_event.cc | 40 ++++++- .../events/intersection_change_event.d.ts | 4 + .../core/events/intersection_change_event.h | 45 ++++++- .../intersection_change_event_init.d.ts | 7 ++ bridge/core/events/keyboard_event.cc | 111 ++++++++++++++++++ bridge/core/events/keyboard_event.d.ts | 7 +- bridge/core/events/keyboard_event.h | 88 ++++++++++++++ bridge/core/events/keyboard_event_init.d.ts | 24 ++++ bridge/core/events/ui_event.cc | 63 ++++++++++ bridge/core/events/ui_event.d.ts | 2 + bridge/core/events/ui_event.h | 72 ++++++++++++ bridge/core/events/ui_event_init.d.ts | 13 ++ .../scripts/code_generator/src/idl/utils.ts | 3 + webf/lib/src/bridge/binding.dart | 2 +- webf/lib/src/bridge/to_native.dart | 10 +- webf/lib/src/devtools/service.dart | 2 +- webf/lib/src/dom/window.dart | 4 +- webf/lib/src/foundation/binding.dart | 2 +- 43 files changed, 1198 insertions(+), 126 deletions(-) create mode 100644 bridge/core/events/animation_event.cc create mode 100644 bridge/core/events/animation_event.h create mode 100644 bridge/core/events/animation_event_init.d.ts create mode 100644 bridge/core/events/close_event_init.d.ts create mode 100644 bridge/core/events/focus_event.cc create mode 100644 bridge/core/events/focus_event.h create mode 100644 bridge/core/events/focus_event_init.d.ts create mode 100644 bridge/core/events/gesture_event.cc create mode 100644 bridge/core/events/gesture_event.h create mode 100644 bridge/core/events/gesture_event_init.d.ts create mode 100644 bridge/core/events/input_event_init.d.ts create mode 100644 bridge/core/events/intersection_change_event_init.d.ts create mode 100644 bridge/core/events/keyboard_event.cc create mode 100644 bridge/core/events/keyboard_event.h create mode 100644 bridge/core/events/keyboard_event_init.d.ts create mode 100644 bridge/core/events/ui_event.cc create mode 100644 bridge/core/events/ui_event.h create mode 100644 bridge/core/events/ui_event_init.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index ab723baea5..955e181d67 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -332,6 +332,22 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/events/error_event.h core/events/message_event.h core/events/message_event.cc + core/events/animation_event.cc + core/events/animation_event.h + core/events/close_event.cc + core/events/close_event.h + core/events/ui_event.cc + core/events/ui_event.h + core/events/focus_event.cc + core/events/focus_event.h + core/events/gesture_event.cc + core/events/gesture_event.h + core/events/input_event.cc + core/events/input_event.h + core/events/intersection_change_event.cc + core/events/intersection_change_event.h + core/events/keyboard_event.cc + core/events/keyboard_event.h core/events/promise_rejection_event.cc core/events/promise_rejection_event.h core/html/parser/html_parser.cc @@ -394,92 +410,64 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") # Gen sources. list(APPEND BRIDGE_SOURCE out/qjs_console.cc - out/qjs_console.h out/qjs_module_manager.cc - out/qjs_module_manager.h out/qjs_window_or_worker_global_scope.cc - out/qjs_window_or_worker_global_scope.h out/qjs_window.cc - out/qjs_window.h out/qjs_location.cc - out/qjs_location.h out/qjs_blob.cc - out/qjs_blob.h out/qjs_event.cc - out/qjs_event.h out/qjs_add_event_listener_options.cc - out/qjs_add_event_listener_options.h out/qjs_event_listener_options.cc - out/qjs_event_listener_options.h - out/qjs_error_event.h out/qjs_error_event.cc out/qjs_message_event.cc - out/qjs_message_event.h - out/qjs_message_event_init.h out/qjs_message_event_init.cc - out/qjs_error_event_init.h + out/qjs_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_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_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.h out/qjs_event_init.cc out/qjs_event_target.cc - out/qjs_event_target.h - out/qjs_node.h out/qjs_node.cc out/qjs_document.cc - out/qjs_document.h out/qjs_element.cc - out/qjs_element.h out/qjs_element_attributes.cc - out/qjs_element_attributes.h out/qjs_character_data.cc - out/qjs_character_data.h out/qjs_comment.cc - out/qjs_comment.h out/qjs_document_fragment.cc - out/qjs_document_fragment.h out/qjs_bounding_client_rect.cc - out/qjs_bounding_client_rect.h out/qjs_css_style_declaration.cc - out/qjs_css_style_declaration.h out/qjs_text.cc - out/qjs_text.h out/qjs_screen.cc - out/qjs_screen.h out/qjs_node_list.cc - out/qjs_node_list.h - out/event_type_names.h out/event_type_names.cc out/built_in_string.cc - out/built_in_string.h out/binding_call_methods.cc - out/binding_call_methods.h out/qjs_scroll_options.cc - out/qjs_scroll_options.h out/qjs_scroll_to_options.cc - out/qjs_scroll_to_options.h out/qjs_html_element.cc - out/qjs_html_element.h out/qjs_html_div_element.cc - out/qjs_html_div_element.h out/qjs_html_head_element.cc - out/qjs_html_head_element.h out/qjs_html_body_element.cc - out/qjs_html_body_element.h out/qjs_html_html_element.cc - out/qjs_html_html_element.h - out/qjs_promise_rejection_event.h out/qjs_promise_rejection_event.cc - out/qjs_promise_rejection_event_init.h out/qjs_promise_rejection_event_init.cc out/qjs_html_template_element.cc - out/qjs_html_template_element.h - out/html_element_type_helper.h out/qjs_html_unknown_element.cc - out/qjs_html_unknown_element.h out/html_element_factory.cc - out/html_element_factory.h out/html_names.cc - out/html_names.h ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 @@ -490,7 +478,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") endif () list(APPEND PUBLIC_HEADER - include/webf_bridge.h + include/webf_bridge.h ) add_library(webf SHARED ${BRIDGE_SOURCE}) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 139cf91d9a..92f5f71c70 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -17,6 +17,14 @@ #include "qjs_element_attributes.h" #include "qjs_error_event.h" #include "qjs_event.h" +#include "qjs_ui_event.h" +#include "qjs_animation_event.h" +#include "qjs_close_event.h" +#include "qjs_focus_event.h" +#include "qjs_gesture_event.h" +#include "qjs_input_event.h" +#include "qjs_intersection_change_event.h" +#include "qjs_keyboard_event.h" #include "qjs_event_target.h" #include "qjs_html_body_element.h" #include "qjs_html_div_element.h" @@ -51,6 +59,14 @@ void InstallBindings(ExecutingContext* context) { QJSErrorEvent::Install(context); QJSPromiseRejectionEvent::Install(context); QJSMessageEvent::Install(context); + QJSUIEvent::Install(context); + QJSAnimationEvent::Install(context); + QJSCloseEvent::Install(context); + QJSFocusEvent::Install(context); + QJSGestureEvent::Install(context); + QJSInputEvent::Install(context); + QJSIntersectionChangeEvent::Install(context); + QJSKeyboardEvent::Install(context); QJSNode::Install(context); QJSNodeList::Install(context); QJSDocument::Install(context); diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 603f043370..de39e3b838 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -66,38 +66,6 @@ struct Converter<IDLOptional<T>, std::enable_if_t<std::is_pointer<typename Conve } }; -// Nullable value for pointer value -template <typename T> -struct Converter<IDLNullable<T>, std::enable_if_t<std::is_pointer<typename Converter<T>::ImplType>::value>> - : public ConverterBase<IDLNullable<T>> { - using ImplType = typename Converter<T>::ImplType; - - static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - if (JS_IsNull(value)) { - return nullptr; - } - return Converter<T>::FromValue(ctx, value, exception_state); - } - - static ImplType ArgumentsValue(ExecutingContext* context, - JSValue value, - uint32_t argv_index, - ExceptionState& exception_state) { - if (JS_IsNull(value)) { - return nullptr; - } - return Converter<T>::ArgumentsValue(context, value, argv_index, exception_state); - } - - static JSValue ToValue(JSContext* ctx, typename Converter<T>::ImplType value) { - if (value == nullptr) { - return JS_NULL; - } - - return Converter<T>::ToValue(ctx, value); - } -}; - template <typename T> struct Converter<IDLOptional<T>, std::enable_if_t<is_shared_ptr<typename Converter<T>::ImplType>::value>> : public ConverterBase<IDLOptional<T>> { @@ -450,18 +418,35 @@ struct Converter<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T static JSValue ToValue(JSContext* ctx, const T* value) { return value->ToQuickJS(); } }; -// template <> -// struct Converter<Window> : public ConverterBase<Window> { -// static Window* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { -// return toScriptWrappable<Window>(value); -// } -// static JSValue ToValue(JSContext* ctx, Window* window) { -// return JS_DupValue(ctx, window->GetExecutingContext()->Global()); -// } -// static JSValue ToValue(JSContext* ctx, const Window* window) { -// return JS_DupValue(ctx, window->GetExecutingContext()->Global()); -// } -//}; +template <typename T> +struct Converter<IDLNullable<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T>::value>>> : ConverterBase<T> { + static T* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { + if (JS_IsNull(value)) { + return nullptr; + } + return Converter<T>::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<T>::ArgumentsValue(context, value, argv_index, exception_state); + } + + static JSValue ToValue(JSContext* ctx, T* value) { + if (value == nullptr) return JS_NULL; + return Converter<T>::ToValue(ctx, value); + } + + static JSValue ToValue(JSContext* ctx, const T* value) { + if (value == nullptr) return JS_NULL; + return Converter<T>::ToValue(ctx, value); + } +}; }; // namespace webf diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index bc4ebd9e6c..8f0a33fe62 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -30,7 +30,7 @@ struct IDLOptional final : public IDLTypeBase { }; // Nullable -template <typename T> +template <typename T, typename SFINAEHelper = void> struct IDLNullable final : public IDLTypeBase { using ImplType = typename Converter<T>::ImplType; }; diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index aac785a82f..f683ff6ba4 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -11,6 +11,8 @@ namespace webf { +class EventTarget; + // Define all built-in wrapper class id. enum { JS_CLASS_GC_TRACKER = JS_CLASS_INIT_COUNT + 1, @@ -18,6 +20,14 @@ enum { JS_CLASS_EVENT, JS_CLASS_ERROR_EVENT, JS_CLASS_MESSAGE_EVENT, + JS_CLASS_UI_EVENT, + JS_CLASS_CLOSE_EVENT, + JS_CLASS_INPUT_EVENT, + JS_CLASS_ANIMATION_EVENT, + JS_CLASS_FOCUS_EVENT, + JS_CLASS_GESTURE_EVENT, + JS_CLASS_INTERSECTION_CHANGE_EVENT, + JS_CLASS_KEYBOARD_EVENT, JS_CLASS_PROMISE_REJECTION_EVENT, JS_CLASS_EVENT_TARGET, JS_CLASS_WINDOW, diff --git a/bridge/core/events/animation_event.cc b/bridge/core/events/animation_event.cc new file mode 100644 index 0000000000..d519fa79ce --- /dev/null +++ b/bridge/core/events/animation_event.cc @@ -0,0 +1,67 @@ +/* + * 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, event_type_names::kerror), + animation_name_(initializer->animationName()), + pseudo_element_(initializer->pseudoElement()), + elapsed_time_(initializer->elapsedTime()) {} + +const AtomicString& AnimationEvent::animationName() const { + return animation_name_; +} + +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 index f2779f2ca5..77dd03556b 100644 --- a/bridge/core/events/animation_event.d.ts +++ b/bridge/core/events/animation_event.d.ts @@ -1,8 +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..df9be0e452 --- /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; + + 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 index ddf72d20d0..eaa8cf4530 100644 --- a/bridge/core/events/close_event.cc +++ b/bridge/core/events/close_event.cc @@ -1,5 +1,60 @@ -// -// Created by andycall on 2022/1/29. -// +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #include "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->code()), + reason_(initializer->reason()), + was_clean_(initializer->wasClean()) {} + +int32_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 index 5404a269a4..97935bd584 100644 --- a/bridge/core/events/close_event.d.ts +++ b/bridge/core/events/close_event.d.ts @@ -1,7 +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 index 44ab11f619..0ab54564f3 100644 --- a/bridge/core/events/close_event.h +++ b/bridge/core/events/close_event.h @@ -1,10 +1,61 @@ -// -// Created by andycall on 2022/1/29. -// +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #ifndef BRIDGE_CLOSE_EVENT_H #define BRIDGE_CLOSE_EVENT_H -class close_event {}; +#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 { + +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); + + int32_t code() const; + const AtomicString& reason() const; + bool wasClean() const; + + private: + int32_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..2f75f42e91 --- /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/focus_event.cc b/bridge/core/events/focus_event.cc new file mode 100644 index 0000000000..783a1de1c9 --- /dev/null +++ b/bridge/core/events/focus_event.cc @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "focus_event.h" +#include "core/dom/events/event_target.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->relatedTarget()) {} + +EventTarget* FocusEvent::relatedTarget() const { + return related_target_; +} + +} // 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 index 7ee29c5df5..019352394d 100644 --- a/bridge/core/events/focus_event.d.ts +++ b/bridge/core/events/focus_event.d.ts @@ -1,7 +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..1aca54a2c1 --- /dev/null +++ b/bridge/core/events/focus_event.h @@ -0,0 +1,63 @@ +/* + * 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/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 { + +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); + + EventTarget* relatedTarget() const; + + private: + 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..2c46aa9804 --- /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..0a7d0f8e04 --- /dev/null +++ b/bridge/core/events/gesture_event.cc @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "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->state()), + direction_(initializer->direction()), + deltaX_(initializer->deltaX()), + deltaY_(initializer->deltaY()), + scale_(initializer->scale()), + rotation_(initializer->rotation()) {} + +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/core/events/gesture_event.d.ts b/bridge/core/events/gesture_event.d.ts index 063db06d4e..de8876ef3e 100644 --- a/bridge/core/events/gesture_event.d.ts +++ b/bridge/core/events/gesture_event.d.ts @@ -1,3 +1,6 @@ +import {Event} from "../dom/events/event"; +import {GestureEventInit} from "./gesture_event_init"; + interface GestureEvent extends Event { readonly state: string; readonly direction: string; @@ -7,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..a4f885fb06 --- /dev/null +++ b/bridge/core/events/gesture_event.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_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 { + +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); + + 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; + + private: + AtomicString state_; + AtomicString direction_; + double deltaX_; + double deltaY_; + double velocityX_; + double velocityY_; + double scale_; + double rotation_; +}; + +} + +#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..384026f4d5 --- /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 index eb80d75711..213bf921d0 100644 --- a/bridge/core/events/input_event.cc +++ b/bridge/core/events/input_event.cc @@ -1,5 +1,40 @@ -// -// Created by andycall on 2022/1/29. -// +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #include "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->inputType()), + data_(initializer->data()) {} + +const AtomicString& InputEvent::inputType() const { + return input_type_; +} + +const AtomicString& InputEvent::data() const { + return data_; +} + +} // 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 index 481679126b..e7f45ff09c 100644 --- a/bridge/core/events/input_event.d.ts +++ b/bridge/core/events/input_event.d.ts @@ -1,6 +1,8 @@ -import {Event} from "../dom/events/event"; +import {UIEvent} from "./ui_event"; +import {InputEventInit} from "./input_event_init"; -interface InputEvent extends Event { +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 index 6643b828cd..4e6212021f 100644 --- a/bridge/core/events/input_event.h +++ b/bridge/core/events/input_event.h @@ -1,10 +1,50 @@ -// -// Created by andycall on 2022/1/29. -// +/* +* 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 -class input_event {}; +#include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "ui_event.h" +#include "qjs_input_event_init.h" + +namespace webf { + +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); + + const AtomicString& inputType() const; + const AtomicString& data() const; + + private: + AtomicString input_type_; + AtomicString data_; +}; + +} #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..c83eac68ab --- /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 index b18d9998ac..89663718e8 100644 --- a/bridge/core/events/intersection_change_event.cc +++ b/bridge/core/events/intersection_change_event.cc @@ -1,5 +1,39 @@ -// -// Created by andycall on 2022/1/29. -// +/* + * 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" + +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->intersectionRatio()) {} + +double IntersectionChangeEvent::intersectionRatio() const { + return intersection_ratio_; +} + +} // 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 index f612ab02d7..398936230e 100644 --- a/bridge/core/events/intersection_change_event.d.ts +++ b/bridge/core/events/intersection_change_event.d.ts @@ -1,3 +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 index a010eadfd8..277ab98f10 100644 --- a/bridge/core/events/intersection_change_event.h +++ b/bridge/core/events/intersection_change_event.h @@ -1,10 +1,47 @@ -// -// Created by andycall on 2022/1/29. -// +/* +* 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 -class intersection_change_event {}; +#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 { + +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); + + double intersectionRatio() const; + + private: + double intersection_ratio_; +}; + +} #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..1072d9ac8d --- /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..875513b333 --- /dev/null +++ b/bridge/core/events/keyboard_event.cc @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "keyboard_event.h" + +namespace webf { + +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->altKey()), + char_code_(initializer->charCode()), + code_(initializer->code()), + ctrl_key_(initializer->ctrlKey()), + is_composing_(initializer->isComposing()), + key_(initializer->key()), + key_code_(initializer->keyCode()), + location_(initializer->location()), + meta_key_(initializer->metaKey()), + repeat_(initializer->repeat()), + shift_key_(initializer->shiftKey()) {} + +bool KeyboardEvent::getModifierState(const AtomicString& key_args, ExceptionState& exception_state) { + return false; +} + +double KeyboardEvent::DOM_KEY_LOCATION_LEFT() const { + return KeyLocationCode::kDomKeyLocationLeft; +} + +double KeyboardEvent::DOM_KEY_LOCATION_NUMPAD() const { + return KeyLocationCode::kDomKeyLocationNumpad; +} + +double KeyboardEvent::DOM_KEY_LOCATION_RIGHT() const { + return KeyLocationCode::kDomKeyLocationRight; +} + +double KeyboardEvent::DOM_KEY_LOCATION_STANDARD() const { + return KeyLocationCode::kDomKeyLocationStandard; +} + +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 index ddc4449d3d..3008a3826c 100644 --- a/bridge/core/events/keyboard_event.d.ts +++ b/bridge/core/events/keyboard_event.d.ts @@ -1,11 +1,10 @@ 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 */ - char: string; - /** @deprecated */ readonly charCode: number; readonly code: string; readonly ctrlKey: boolean; @@ -17,9 +16,11 @@ interface KeyboardEvent extends UIEvent { readonly metaKey: boolean; readonly repeat: boolean; readonly shiftKey: boolean; - getModifierState(keyArg: string): boolean; + // getModifierState(keyArg: string): boolean; readonly DOM_KEY_LOCATION_LEFT: number; readonly DOM_KEY_LOCATION_NUMPAD: number; readonly DOM_KEY_LOCATION_RIGHT: number; readonly DOM_KEY_LOCATION_STANDARD: 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..4787278fdf --- /dev/null +++ b/bridge/core/events/keyboard_event.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_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 { + +class KeyboardEvent : public UIEvent { + DEFINE_WRAPPERTYPEINFO(); + + public: + enum KeyLocationCode { + kDomKeyLocationStandard = 0x00, + kDomKeyLocationLeft = 0x01, + kDomKeyLocationRight = 0x02, + kDomKeyLocationNumpad = 0x03 + }; + using ImplType = KeyboardEvent*; + + 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); + + 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; + + double DOM_KEY_LOCATION_LEFT() const; + double DOM_KEY_LOCATION_NUMPAD() const; + double DOM_KEY_LOCATION_RIGHT() const; + double DOM_KEY_LOCATION_STANDARD() 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_; +}; + +template <> +struct DowncastTraits<KeyboardEvent> { + static bool AllowFrom(const Event& event) { return event.IsKeyboardEvent(); } +}; + +} // 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..d24d8ec608 --- /dev/null +++ b/bridge/core/events/keyboard_event_init.d.ts @@ -0,0 +1,24 @@ +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; + getModifierState(keyArg: string): boolean; + DOM_KEY_LOCATION_LEFT: number; + DOM_KEY_LOCATION_NUMPAD: number; + DOM_KEY_LOCATION_RIGHT: number; + DOM_KEY_LOCATION_STANDARD: number; +} diff --git a/bridge/core/events/ui_event.cc b/bridge/core/events/ui_event.cc new file mode 100644 index 0000000000..5129a0aa94 --- /dev/null +++ b/bridge/core/events/ui_event.cc @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "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->detail()), view_(initializer->view()), which_(initializer->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; +} +} // namespace webf diff --git a/bridge/core/events/ui_event.d.ts b/bridge/core/events/ui_event.d.ts index 017fe397b6..bc076e52df 100644 --- a/bridge/core/events/ui_event.d.ts +++ b/bridge/core/events/ui_event.d.ts @@ -1,5 +1,6 @@ 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 { @@ -7,4 +8,5 @@ interface UIEvent extends Event { 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..298c3b3ebd --- /dev/null +++ b/bridge/core/events/ui_event.h @@ -0,0 +1,72 @@ +/* + * 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/dictionary_base.h" +#include "bindings/qjs/source_location.h" +#include "core/dom/events/event.h" +#include "qjs_ui_event_init.h" + + +namespace webf { + +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); + + double detail() const; + Window* view() const; + double which() const; + + bool IsUIEvent() const override; + + private: + double detail_; + 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..0e5374d85d --- /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/scripts/code_generator/src/idl/utils.ts b/bridge/scripts/code_generator/src/idl/utils.ts index 4d7fd6f040..f361783585 100644 --- a/bridge/scripts/code_generator/src/idl/utils.ts +++ b/bridge/scripts/code_generator/src/idl/utils.ts @@ -22,6 +22,9 @@ export function getClassName(blob: IDLBlob) { 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)}`; } diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 5cd8bdc111..99bce3b93a 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -29,7 +29,7 @@ typedef DartAsyncAnonymousFunctionCallback = void Function( void _invokeBindingMethodFromNativeImpl(Pointer<NativeBindingObject> nativeBindingObject, Pointer<NativeValue> returnValue, Pointer<NativeString> nativeMethod, int argc, Pointer<NativeValue> argv) { String method = nativeStringToString(nativeMethod); List<dynamic> values = List.generate(argc, (i) { - Pointer<NativeValue> nativeVal ue = argv.elementAt(i); + Pointer<NativeValue> nativeValue = argv.elementAt(i); return fromNativeValue(nativeValue); }); diff --git a/webf/lib/src/bridge/to_native.dart b/webf/lib/src/bridge/to_native.dart index ece9b80d5c..a7de3baced 100644 --- a/webf/lib/src/bridge/to_native.dart +++ b/webf/lib/src/bridge/to_native.dart @@ -70,7 +70,7 @@ final DartInvokeEventListener _invokeModuleEvent = WebFDynamicLibrary.ref.lookup<NativeFunction<NativeInvokeEventListener>>('invokeModuleEvent').asFunction(); void invokeModuleEvent(int contextId, String moduleName, Event? event, String extra) { - if (KrakenController.getControllerOfJSContextId(contextId) == null) { + if (WebFController.getControllerOfJSContextId(contextId) == null) { return; } Pointer<NativeString> nativeModuleName = stringToNativeString(moduleName); @@ -205,7 +205,7 @@ int allocateNewPage([int targetContextId = -1]) { typedef NativeRegisterPluginByteCode = Void Function(Pointer<Uint8> bytes, Int32 length, Pointer<Utf8> pluginName); typedef DartRegisterPluginByteCode = void Function(Pointer<Uint8> bytes, int length, Pointer<Utf8> pluginName); -final DartRegisterPluginByteCode _registerPluginByteCode = KrakenDynamicLibrary +final DartRegisterPluginByteCode _registerPluginByteCode = WebFDynamicLibrary .ref .lookup<NativeFunction<NativeRegisterPluginByteCode>>( 'registerPluginByteCode') @@ -410,13 +410,13 @@ void clearUICommand(int contextId) { } void flushUICommandWithContextId(int contextId) { - KrakenController? controller = KrakenController.getControllerOfJSContextId(contextId); + WebFController? controller = WebFController.getControllerOfJSContextId(contextId); if (controller != null) { flushUICommand(controller.view); } } -void flushUICommand(KrakenViewController view) { +void flushUICommand(WebFViewController view) { Pointer<Uint64> nativeCommandItems = _getUICommandItems(view.contextId); int commandLength = _getUICommandItemSize(view.contextId); @@ -432,7 +432,7 @@ void flushUICommand(KrakenViewController view) { List<UICommand> commands = readNativeUICommandToDart( nativeCommandItems, commandLength, view.contextId); - SchedulerBinding.instance!.scheduleFrame(); + SchedulerBinding.instance.scheduleFrame(); if (kProfileMode) { PerformanceTiming.instance().mark(PERF_FLUSH_UI_COMMAND_END); diff --git a/webf/lib/src/devtools/service.dart b/webf/lib/src/devtools/service.dart index aa781ca7e7..0f0d71edb0 100644 --- a/webf/lib/src/devtools/service.dart +++ b/webf/lib/src/devtools/service.dart @@ -117,7 +117,7 @@ class ChromeDevToolsService extends DevToolsService { // @TODO: Implement and remove. // ignore: unused_element static bool _registerUIDartMethodsToCpp(int contextId) { - final DartRegisterDartMethods _registerDartMethods = KrakenDynamicLibrary.ref.lookup<NativeFunction<NativeRegisterDartMethods>>('registerUIDartMethods').asFunction(); + final DartRegisterDartMethods _registerDartMethods = WebFDynamicLibrary.ref.lookup<NativeFunction<NativeRegisterDartMethods>>('registerUIDartMethods').asFunction(); Pointer<Uint64> bytes = malloc.allocate<Uint64>(_dartNativeMethods.length * sizeOf<Uint64>()); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); diff --git a/webf/lib/src/dom/window.dart b/webf/lib/src/dom/window.dart index eaaa496414..624ea2c69a 100644 --- a/webf/lib/src/dom/window.dart +++ b/webf/lib/src/dom/window.dart @@ -3,7 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ import 'dart:ui'; -import 'dart:ffi'; +import 'dart:ffi' as ffi; import 'package:ffi/ffi.dart'; import 'package:webf/bridge.dart'; @@ -19,7 +19,7 @@ class Window extends EventTarget { final Screen screen; Window(BindingContext? context, this.document) - : screen = Screen(BindingContext(context!.contextId, malloc.allocate(sizeOf<NativeBindingObject>()))), super(context); + : screen = Screen(BindingContext(context!.contextId, malloc.allocate(ffi.sizeOf<NativeBindingObject>()))), super(context); @override EventTarget? get parentEventTarget => null; diff --git a/webf/lib/src/foundation/binding.dart b/webf/lib/src/foundation/binding.dart index 46b5af6a4b..8c2a634d92 100644 --- a/webf/lib/src/foundation/binding.dart +++ b/webf/lib/src/foundation/binding.dart @@ -3,7 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ import 'package:flutter/foundation.dart'; -import 'package:kraken/bridge.dart'; +import 'package:webf/bridge.dart'; import 'dart:ffi'; typedef BindingObjectOperation = void Function(BindingObject bindingObject); From ce1860775437984af78ab639a5fd2bdcf20226ec Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Sun, 21 Aug 2022 00:20:48 +0800 Subject: [PATCH 167/498] feat: add touch and touchList class. --- bridge/CMakeLists.txt | 191 +----------------- bridge/bindings/qjs/binding_initializer.cc | 6 +- bridge/bindings/qjs/wrapper_type_info.h | 2 + bridge/core/events/focus_event.cc | 1 + bridge/core/events/focus_event.h | 3 +- bridge/core/events/touch_event.d.ts | 27 +-- bridge/core/events/ui_event.cc | 1 + bridge/core/events/ui_event.h | 4 +- bridge/core/events/ui_event_init.d.ts | 6 +- bridge/core/input/touch.cc | 92 +++++++++ bridge/core/input/touch.d.ts | 22 ++ bridge/core/input/touch.h | 64 ++++++ bridge/core/input/touch_init.d.ts | 20 ++ bridge/core/input/touch_list.cc | 31 +++ bridge/core/input/touch_list.d.ts | 9 + bridge/core/input/touch_list.h | 28 +++ .../code_generator/src/idl/analyzer.ts | 3 + .../code_generator/src/idl/declaration.ts | 1 + .../code_generator/src/idl/generateSource.ts | 11 +- .../static/idl_templates/dictionary.cc.tpl | 4 +- .../static/idl_templates/interface.cc.tpl | 18 +- 21 files changed, 319 insertions(+), 225 deletions(-) create mode 100644 bridge/core/input/touch.cc create mode 100644 bridge/core/input/touch.d.ts create mode 100644 bridge/core/input/touch.h create mode 100644 bridge/core/input/touch_init.d.ts create mode 100644 bridge/core/input/touch_list.cc create mode 100644 bridge/core/input/touch_list.d.ts create mode 100644 bridge/core/input/touch_list.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 955e181d67..3412f5b662 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -71,67 +71,31 @@ endif() list(APPEND BRIDGE_SOURCE webf_bridge.cc - ${CMAKE_CURRENT_SOURCE_DIR}/include/webf_bridge.h foundation/logging.cc - foundation/logging.h - foundation/colors.h foundation/native_string.cc - foundation/native_string.h - foundation/ref_counted_internal.h - foundation/ref_counter.h - foundation/ref_ptr.h - foundation/ref_ptr_internal.h - foundation/ui_task_queue.h 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/string_view.h foundation/native_value.cc - foundation/native_value.h - foundation/native_type.h - foundation/native_value_converter.h foundation/native_value_converter.cc - foundation/casting.h foundation/ui_command_buffer.cc - foundation/ui_command_buffer.h polyfill/dist/polyfill.cc ) list(APPEND GUMBO_PARSER ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/attribute.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/attribute.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/char_ref.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/char_ref.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/error.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/error.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/gumbo.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/insertion_mode.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/parser.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/parser.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/string_buffer.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/string_buffer.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/string_piece.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/string_piece.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/tag_enum.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/tag_gperf.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/tag_sizes.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/tag_strings.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/tag.c ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/string_piece.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/string_piece.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/token_type.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/tokenizer_states.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/tokenizer.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/tokenizer.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/utf8.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/utf8.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/util.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/util.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/vector.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/gumbo-parser/src/vector.h ) list(APPEND BRIDGE_INCLUDE @@ -156,20 +120,10 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") list(APPEND QUICK_JS_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/libbf.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/libbf.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/cutils.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/cutils.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/libregexp.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/libregexp.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/libregexp-opcode.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/libunicode.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/libunicode.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/libunicode-table.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/list.h ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/quickjs.c - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/quickjs.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/quickjs-atom.h - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/quickjs/quickjs-opcode.h ) if(${STATIC_QUICKJS}) add_library(quickjs STATIC ${QUICK_JS_SOURCE}) @@ -182,229 +136,103 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") list(APPEND BRIDGE_SOURCE # Binding files - bindings/qjs/idl_type.h - bindings/qjs/converter.h - bindings/qjs/converter_impl.h bindings/qjs/dictionary_base.cc - bindings/qjs/dictionary_base.h bindings/qjs/js_based_event_listener.cc - bindings/qjs/js_based_event_listener.h bindings/qjs/js_event_handler.cc - bindings/qjs/js_event_handler.h bindings/qjs/js_event_listener.cc - bindings/qjs/js_event_listener.h bindings/qjs/binding_initializer.cc - bindings/qjs/binding_initializer.h bindings/qjs/member_installer.cc - bindings/qjs/member_installer.h bindings/qjs/source_location.cc - bindings/qjs/source_location.h - bindings/qjs/cppgc/garbage_collected.h bindings/qjs/cppgc/gc_visitor.cc - bindings/qjs/cppgc/gc_visitor.h bindings/qjs/cppgc/mutation_scope.cc - bindings/qjs/cppgc/mutation_scope.h - bindings/qjs/cppgc/member.h - bindings/qjs/cppgc/local_handle.h bindings/qjs/script_wrappable.cc - bindings/qjs/script_wrappable.h - bindings/qjs/wrapper_type_info.h - bindings/qjs/heap_hashmap.h bindings/qjs/native_string_utils.cc - bindings/qjs/native_string_utils.h bindings/qjs/qjs_engine_patch.cc - bindings/qjs/qjs_engine_patch.h bindings/qjs/qjs_function.cc - bindings/qjs/qjs_function.h bindings/qjs/script_value.cc - bindings/qjs/script_value.h bindings/qjs/script_promise.cc - bindings/qjs/script_promise.h - bindings/qjs/to_quickjs.h - bindings/qjs/qjs_interface_bridge.h bindings/qjs/script_promise_resolver.cc - bindings/qjs/script_promise_resolver.h bindings/qjs/atomic_string.cc - bindings/qjs/atomic_string.h - bindings/qjs/generated_code_helper.h bindings/qjs/exception_state.cc - bindings/qjs/exception_state.h bindings/qjs/exception_message.cc - bindings/qjs/exception_message.h - bindings/qjs/rejected_promises.h bindings/qjs/rejected_promises.cc bindings/qjs/pending_promises.cc - bindings/qjs/pending_promises.h - # Core sources core/executing_context.cc - core/executing_context.h core/script_state.cc - core/script_state.h - core/page.h core/page.cc core/executing_context_data.cc - core/executing_context_data.h - core/dart_methods.h - core/dart_methods.h - core/fileapi/blob.h core/fileapi/blob.cc core/fileapi/blob_part.cc - core/fileapi/blob_part.h core/fileapi/blob_property_bag.cc - core/fileapi/blob_property_bag.h core/frame/console.cc - core/frame/console.h core/frame/dom_timer.cc - core/frame/dom_timer.h core/frame/dom_timer_coordinator.cc - core/frame/dom_timer_coordinator.h core/frame/window_or_worker_global_scope.cc - core/frame/window_or_worker_global_scope.h core/frame/module_listener.cc - core/frame/module_listener.h core/frame/module_listener_container.cc - core/frame/module_listener_container.h core/frame/module_manager.cc - core/frame/module_manager.h core/frame/module_callback.cc - core/frame/module_callback.h core/frame/module_callback_coordinator.cc - core/frame/window.h core/frame/window.cc - core/frame/screen.h core/frame/screen.cc core/frame/legacy/location.cc - core/frame/legacy/location.h - core/frame/module_callback_coordinator.h - core/frame/window_event_handlers.h + core/timing/performance.cc core/css/legacy/css_style_declaration.cc - core/css/legacy/css_style_declaration.h core/dom/frame_request_callback_collection.cc - core/dom/frame_request_callback_collection.h - core/dom/events/event_listener.h core/dom/events/registered_eventListener.cc - core/dom/events/registered_eventListener.h core/dom/events/event_listener_map.cc - core/dom/events/event_listener_map.h - core/dom/events/event.h core/dom/events/event.cc - core/dom/events/event_target.h core/dom/events/event_target.cc core/dom/events/event_listener_map.cc core/dom/events/event_target_impl.cc - core/dom/events/event_target_impl.h - core/dom/binding_object.h core/dom/binding_object.cc core/dom/node.cc - core/dom/node.h core/dom/node_traversal.cc - core/dom/node_traversal.h core/dom/character_data.cc - core/dom/character_data.h core/dom/comment.cc - core/dom/comment.h core/dom/text.cc - core/dom/text.h core/dom/tree_scope.cc - core/dom/tree_scope.h core/dom/element.cc - core/dom/element.h - core/dom/element_traversal.h core/dom/document.cc - core/dom/document.h - core/dom/global_event_handlers.h core/dom/scripted_animation_controller.cc - core/dom/scripted_animation_controller.h core/dom/node_data.cc - core/dom/node_data.h - core/dom/document_fragment.h core/dom/document_fragment.cc - core/dom/collection_index_cache.h core/dom/child_node_list.cc - core/dom/child_node_list.h core/dom/empty_node_list.cc - core/dom/empty_node_list.h - core/dom/node_list.h core/dom/container_node.cc - core/dom/container_node.h core/events/error_event.cc - core/events/error_event.h - core/events/message_event.h core/events/message_event.cc core/events/animation_event.cc - core/events/animation_event.h core/events/close_event.cc - core/events/close_event.h core/events/ui_event.cc - core/events/ui_event.h core/events/focus_event.cc - core/events/focus_event.h core/events/gesture_event.cc - core/events/gesture_event.h core/events/input_event.cc - core/events/input_event.h core/events/intersection_change_event.cc - core/events/intersection_change_event.h core/events/keyboard_event.cc - core/events/keyboard_event.h core/events/promise_rejection_event.cc - core/events/promise_rejection_event.h core/html/parser/html_parser.cc - core/html/parser/html_parser.h core/html/html_collection.cc - core/html/html_collection.h core/html/html_element.cc - core/html/html_element.h core/html/html_div_element.cc - core/html/html_div_element.h core/html/html_head_element.cc - core/html/html_head_element.h - core/html/html_body_element.h core/html/html_body_element.cc core/html/html_html_element.cc - core/html/html_html_element.h core/html/html_template_element.cc - core/html/html_template_element.h - -# core/html/html_anchor_element.h # core/html/html_anchor_element.cc # core/html/html_template_element.cc -# core/html/html_template_element.h # core/html/forms/html_input_element.cc -# core/html/forms/html_input_element.h # core/html/forms/html_textarea_element.cc -# core/html/forms/html_textarea_element.h # core/html/html_image_element.cc -# core/html/html_image_element.h # core/html/html_script_element.cc -# core/html/html_script_element.h - core/html/html_unknown_element.cc - core/html/html_unknown_element.h - # Legacy implements, should remove them in the future. - core/dom/legacy/space_split_string.cc - core/dom/legacy/space_split_string.h - core/dom/legacy/element_attributes.cc - core/dom/legacy/element_attributes.h - core/dom/legacy/bounding_client_rect.cc - core/dom/legacy/bounding_client_rect.h - core/timing/performance.cc - core/timing/performance.h - - # core/dom/character_data.cc - # core/dom/character_data.h - # core/dom/comment.cc - # core/dom/comment.h - # core/dom/node.cc - # core/dom/node.h - # core/dom/events/custom_event.cc - # core/dom/events/custom_event.h - # core/dom/events/event.h -# core/dom/events/event.cc -# core/dom/events/event_listener_map.cc -# core/dom/events/event_listener_map.h -# core/dom/events/event_target.cc -# core/dom/events/event_target.h + core/html/html_unknown_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. @@ -433,6 +261,9 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") 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_keyboard_event.cc out/qjs_keyboard_event_init.cc out/qjs_animation_event.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 92f5f71c70..e43428d2a7 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -23,6 +23,8 @@ #include "qjs_focus_event.h" #include "qjs_gesture_event.h" #include "qjs_input_event.h" +#include "qjs_touch.h" +#include "qjs_touch_list.h" #include "qjs_intersection_change_event.h" #include "qjs_keyboard_event.h" #include "qjs_event_target.h" @@ -56,10 +58,10 @@ void InstallBindings(ExecutingContext* context) { QJSEventTarget::Install(context); QJSWindow::Install(context); QJSEvent::Install(context); + QJSUIEvent::Install(context); QJSErrorEvent::Install(context); QJSPromiseRejectionEvent::Install(context); QJSMessageEvent::Install(context); - QJSUIEvent::Install(context); QJSAnimationEvent::Install(context); QJSCloseEvent::Install(context); QJSFocusEvent::Install(context); @@ -85,6 +87,8 @@ void InstallBindings(ExecutingContext* context) { QJSBoundingClientRect::Install(context); QJSScreen::Install(context); QJSBlob::Install(context); + QJSTouch::Install(context); + QJSTouchList::Install(context); // Legacy bindings, not standard. QJSElementAttributes::Install(context); diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index f683ff6ba4..096f41b65f 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -30,6 +30,8 @@ enum { 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, diff --git a/bridge/core/events/focus_event.cc b/bridge/core/events/focus_event.cc index 783a1de1c9..7b1b204b4c 100644 --- a/bridge/core/events/focus_event.cc +++ b/bridge/core/events/focus_event.cc @@ -4,6 +4,7 @@ #include "focus_event.h" #include "core/dom/events/event_target.h" +#include "core/frame/window.h" namespace webf { diff --git a/bridge/core/events/focus_event.h b/bridge/core/events/focus_event.h index 1aca54a2c1..20949c9f23 100644 --- a/bridge/core/events/focus_event.h +++ b/bridge/core/events/focus_event.h @@ -6,6 +6,7 @@ #define BRIDGE_CORE_EVENTS_FOCUS_EVENT_H_ #include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/cppgc/member.h" #include "bindings/qjs/source_location.h" #include "core/dom/events/event.h" #include "qjs_focus_event_init.h" @@ -55,7 +56,7 @@ class FocusEvent : public UIEvent { EventTarget* relatedTarget() const; private: - EventTarget* related_target_; + Member<EventTarget> related_target_; }; } // namespace webf diff --git a/bridge/core/events/touch_event.d.ts b/bridge/core/events/touch_event.d.ts index 6fd103eeef..eebcf82317 100644 --- a/bridge/core/events/touch_event.d.ts +++ b/bridge/core/events/touch_event.d.ts @@ -1,31 +1,6 @@ import {UIEvent} from "./ui_event"; import {EventTarget} from "../dom/events/event_target"; - -/** 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; - readonly touchType: "direct" | "stylus"; -} - -/** 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. */ -interface TouchList { - readonly length: number; - item(index: number): Touch | null; - [index: number]: Touch; -} +import {TouchList} from "../input/touch_list"; /** 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 { diff --git a/bridge/core/events/ui_event.cc b/bridge/core/events/ui_event.cc index 5129a0aa94..58af52bc34 100644 --- a/bridge/core/events/ui_event.cc +++ b/bridge/core/events/ui_event.cc @@ -3,6 +3,7 @@ */ #include "ui_event.h" +#include "core/frame/window.h" namespace webf { diff --git a/bridge/core/events/ui_event.h b/bridge/core/events/ui_event.h index 298c3b3ebd..ec09580f95 100644 --- a/bridge/core/events/ui_event.h +++ b/bridge/core/events/ui_event.h @@ -6,6 +6,8 @@ #define BRIDGE_CORE_EVENTS_UI_EVENT_H_ #include "bindings/qjs/dictionary_base.h" +#include "bindings/qjs/cppgc/member.h" +#include "core/frame/window.h" #include "bindings/qjs/source_location.h" #include "core/dom/events/event.h" #include "qjs_ui_event_init.h" @@ -58,7 +60,7 @@ class UIEvent : public Event { private: double detail_; - Window* view_; + Member<Window> view_; double which_; }; diff --git a/bridge/core/events/ui_event_init.d.ts b/bridge/core/events/ui_event_init.d.ts index 0e5374d85d..376a2497fa 100644 --- a/bridge/core/events/ui_event_init.d.ts +++ b/bridge/core/events/ui_event_init.d.ts @@ -5,9 +5,9 @@ import {UIEvent} from "./ui_event"; // @ts-ignore @Dictionary() export interface UIEventInit extends EventInit { - detail: number; - view: Window | null; + detail?: number; + view?: Window | null; /** @deprecated */ - which: number; + which?: number; new(type: string, init?: UIEventInit): UIEvent; } diff --git a/bridge/core/input/touch.cc b/bridge/core/input/touch.cc new file mode 100644 index 0000000000..9dce8361eb --- /dev/null +++ b/bridge/core/input/touch.cc @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "touch.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(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()) {} + +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_; +} + +} // 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..6b110a2f7e --- /dev/null +++ b/bridge/core/input/touch.h @@ -0,0 +1,64 @@ +/* + * 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 { + +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); + + explicit Touch(ExecutingContext* context, ExceptionState& exception_state); + explicit Touch(ExecutingContext* context, + const std::shared_ptr<TouchInit>& initializer, + ExceptionState& exception_state); + + 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; + + 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..db236a3f9b --- /dev/null +++ b/bridge/core/input/touch_list.cc @@ -0,0 +1,31 @@ +/* +* Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "touch_list.h" + +namespace webf { + +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; + } +} + +void TouchList::Trace(GCVisitor* visitor) const { + for(auto& item : values_) { + item->Trace(visitor); + } +} + +} \ 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..6843981de8 --- /dev/null +++ b/bridge/core/input/touch_list.h @@ -0,0 +1,28 @@ +/* +* 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 { + +class TouchList : public ScriptWrappable { + DEFINE_WRAPPERTYPEINFO(); + public: + using ImplType = TouchList*; + uint32_t length() const; + Touch* item(uint32_t index, ExceptionState& exception_state) const; + bool SetItem(uint32_t index, Touch* touch, ExceptionState& exception_state); + + void Trace(GCVisitor *visitor) const override; + + private: + std::vector<Touch*> values_; +}; + +} + +#endif // BRIDGE_CORE_INPUT_TOUCH_LIST_H_ diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index ff2cc85419..44cc62a534 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -183,6 +183,9 @@ function walkProgram(statement: ts.Statement) { 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; diff --git a/bridge/scripts/code_generator/src/idl/declaration.ts b/bridge/scripts/code_generator/src/idl/declaration.ts index 81197fcaf1..00463dcfe3 100644 --- a/bridge/scripts/code_generator/src/idl/declaration.ts +++ b/bridge/scripts/code_generator/src/idl/declaration.ts @@ -32,6 +32,7 @@ export class PropsDeclaration { typeMode: ParameterMode; name: string; readonly: boolean; + optional: boolean; } export class IndexedPropertyDeclaration extends PropsDeclaration { diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index e81eacc328..a17dbc909e 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -68,12 +68,12 @@ export function generateTypeValue(type: ParameterType[]): string { return ''; } -export function generateIDLTypeConverter(type: ParameterType[]): string { +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))}>`; + returnValue = `IDLSequence<${generateIDLTypeConverter(type.slice(1), isOptional)}>`; } else if (typeof type[0] === 'string') { returnValue = type[0]; } else { @@ -108,6 +108,8 @@ export function generateIDLTypeConverter(type: ParameterType[]): string { if (haveNull) { returnValue = `IDLNullable<${returnValue}>`; + } else if (isOptional) { + returnValue = `IDLOptional<${returnValue}>`; } return returnValue; @@ -189,6 +191,10 @@ function generateFunctionCallBody(blob: IDLBlob, declaration: FunctionDeclaratio 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++; @@ -225,6 +231,7 @@ ${returnValueAssignment} self->${generateCallMethodName(declaration.name)}(${min call = `${returnValueAssignment} ${getClassName(blob)}::${generateCallMethodName(declaration.name)}(context, ${requiredArguments.join(',')});`; } + return `${requiredArgumentsInit.join('\n')} if (argc <= ${minimalRequiredArgc}) { ${call} diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl index aa36b27608..b2935c6bb9 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl @@ -20,7 +20,7 @@ bool <%= className %>::FillQJSObjectWithMembers(JSContext* ctx, JSValue qjs_dict } <% _.forEach(props, function(prop, index) { %> - JS_SetPropertyStr(ctx, qjs_dictionary, "<%= prop.name %>", Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, <%= prop.name %>_)); + JS_SetPropertyStr(ctx, qjs_dictionary, "<%= prop.name %>", Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::ToValue(ctx, <%= prop.name %>_)); <% }); %> return true; @@ -38,7 +38,7 @@ bool <%= className %>::FillMembersWithQJSObject(JSContext* ctx, JSValue value, E <% _.forEach(props, function(prop, index) { %> { JSValue v = JS_GetPropertyStr(ctx, value, "<%= prop.name %>"); - <%= prop.name %>_ = Converter<<%= generateIDLTypeConverter(prop.type) %>>::FromValue(ctx, v, exception_state); + <%= prop.name %>_ = Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::FromValue(ctx, v, exception_state); JS_FreeValue(ctx, v); } diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index a2759e4a00..993f910683 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -18,7 +18,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob return exception_state.ToQuickJS(); } - return Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); + return Converter<<%= generateIDLTypeConverter(object.indexedProp.type, object.indexedProp.optional) %>>::ToValue(ctx, result); }; <% } else { %> JSValue QJS<%= className %>::StringPropertyGetterCallback(JSContext* ctx, JSValue obj, JSAtom key) { @@ -29,7 +29,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } - return Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::ToValue(ctx, result); + return Converter<<%= generateIDLTypeConverter(object.indexedProp.type, object.indexedProp.optional) %>>::ToValue(ctx, result); }; bool QJS<%= className %>::StringPropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSAtom key) { auto* self = toScriptWrappable<<%= className %>>(obj); @@ -48,7 +48,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; MemberMutationScope scope{ExecutingContext::From(ctx)}; - auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::FromValue(ctx, value, exception_state); + auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type, object.indexedProp.optional) %>>::FromValue(ctx, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; } @@ -63,7 +63,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; MemberMutationScope scope{ExecutingContext::From(ctx)}; - auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type) %>>::FromValue(ctx, value, exception_state); + auto&& v = Converter<<%= generateIDLTypeConverter(object.indexedProp.type, object.indexedProp.optional) %>>::FromValue(ctx, value, exception_state); if (UNLIKELY(exception_state.HasException())) { return false; } @@ -110,16 +110,16 @@ static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } - return Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, v); + return Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::ToValue(ctx, v); <% } else { %> - return Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, <%= blob.filename %>-><%= prop.name %>()); + 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) %>>::FromValue(ctx, argv[0], exception_state); + auto&& v = Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::FromValue(ctx, argv[0], exception_state); if (exception_state.HasException()) { return exception_state.ToQuickJS(); } @@ -148,13 +148,13 @@ static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst auto* <%= blob.filename %> = toScriptWrappable<<%= className %>>(this_val); assert(<%= blob.filename %> != nullptr); MemberMutationScope scope{ExecutingContext::From(ctx)}; - return Converter<<%= generateIDLTypeConverter(prop.type) %>>::ToValue(ctx, <%= object.name %>::<%= prop.name %>(*<%= blob.filename %>)); + 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) %>>::FromValue(ctx, argv[0], exception_state); + auto&& v = Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::FromValue(ctx, argv[0], exception_state); if (exception_state.HasException()) { return exception_state.ToQuickJS(); } From 4cfba9a11f77116a99b13b76cc2bfb116290edef Mon Sep 17 00:00:00 2001 From: openwebf-bot <openwebf@openwebf.com> Date: Sat, 20 Aug 2022 16:21:56 +0000 Subject: [PATCH 168/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 18 +++++++-------- bridge/bindings/qjs/converter_impl.h | 15 ++++++++----- bridge/core/events/animation_event.h | 4 +--- bridge/core/events/close_event.cc | 3 ++- bridge/core/events/close_event.h | 7 ++---- bridge/core/events/focus_event.cc | 3 ++- bridge/core/events/focus_event.h | 10 +++------ bridge/core/events/gesture_event.cc | 4 ++-- bridge/core/events/gesture_event.h | 22 ++++++++----------- bridge/core/events/input_event.h | 22 ++++++++----------- .../core/events/intersection_change_event.h | 19 ++++++++-------- bridge/core/events/keyboard_event.h | 8 ++----- bridge/core/events/ui_event.cc | 5 ++--- bridge/core/events/ui_event.h | 13 ++++------- bridge/core/input/touch_list.cc | 6 ++--- bridge/core/input/touch_list.h | 9 ++++---- 16 files changed, 74 insertions(+), 94 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index e43428d2a7..e7b1974f1c 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -6,9 +6,11 @@ #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_character_data.h" +#include "qjs_close_event.h" #include "qjs_comment.h" #include "qjs_console.h" #include "qjs_css_style_declaration.h" @@ -17,17 +19,9 @@ #include "qjs_element_attributes.h" #include "qjs_error_event.h" #include "qjs_event.h" -#include "qjs_ui_event.h" -#include "qjs_animation_event.h" -#include "qjs_close_event.h" +#include "qjs_event_target.h" #include "qjs_focus_event.h" #include "qjs_gesture_event.h" -#include "qjs_input_event.h" -#include "qjs_touch.h" -#include "qjs_touch_list.h" -#include "qjs_intersection_change_event.h" -#include "qjs_keyboard_event.h" -#include "qjs_event_target.h" #include "qjs_html_body_element.h" #include "qjs_html_div_element.h" #include "qjs_html_element.h" @@ -35,6 +29,9 @@ #include "qjs_html_html_element.h" #include "qjs_html_template_element.h" #include "qjs_html_unknown_element.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" @@ -43,6 +40,9 @@ #include "qjs_promise_rejection_event.h" #include "qjs_screen.h" #include "qjs_text.h" +#include "qjs_touch.h" +#include "qjs_touch_list.h" +#include "qjs_ui_event.h" #include "qjs_window.h" #include "qjs_window_or_worker_global_scope.h" diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index de39e3b838..36d4ee3b5c 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -419,7 +419,8 @@ struct Converter<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T }; template <typename T> -struct Converter<IDLNullable<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T>::value>>> : ConverterBase<T> { +struct Converter<IDLNullable<T, typename std::enable_if_t<std::is_base_of<ScriptWrappable, T>::value>>> + : ConverterBase<T> { static T* FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { if (JS_IsNull(value)) { return nullptr; @@ -428,9 +429,9 @@ struct Converter<IDLNullable<T, typename std::enable_if_t<std::is_base_of<Script } static T* ArgumentsValue(ExecutingContext* context, - JSValue value, - uint32_t argv_index, - ExceptionState& exception_state) { + JSValue value, + uint32_t argv_index, + ExceptionState& exception_state) { if (JS_IsNull(value)) { return nullptr; } @@ -438,12 +439,14 @@ struct Converter<IDLNullable<T, typename std::enable_if_t<std::is_base_of<Script } static JSValue ToValue(JSContext* ctx, T* value) { - if (value == nullptr) return JS_NULL; + if (value == nullptr) + return JS_NULL; return Converter<T>::ToValue(ctx, value); } static JSValue ToValue(JSContext* ctx, const T* value) { - if (value == nullptr) return JS_NULL; + if (value == nullptr) + return JS_NULL; return Converter<T>::ToValue(ctx, value); } }; diff --git a/bridge/core/events/animation_event.h b/bridge/core/events/animation_event.h index df9be0e452..3570fff592 100644 --- a/bridge/core/events/animation_event.h +++ b/bridge/core/events/animation_event.h @@ -17,9 +17,7 @@ class AnimationEvent : public Event { public: using ImplType = AnimationEvent*; - static AnimationEvent* Create(ExecutingContext* context, - const AtomicString& type, - ExceptionState& exception_state); + static AnimationEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); static AnimationEvent* Create(ExecutingContext* context, const AtomicString& type, const AtomicString& animation_name, diff --git a/bridge/core/events/close_event.cc b/bridge/core/events/close_event.cc index eaa8cf4530..3362e84bb8 100644 --- a/bridge/core/events/close_event.cc +++ b/bridge/core/events/close_event.cc @@ -26,7 +26,8 @@ CloseEvent* CloseEvent::Create(ExecutingContext* context, 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, ExceptionState& exception_state) + : Event(context, type) {} CloseEvent::CloseEvent(ExecutingContext* context, const AtomicString& type, diff --git a/bridge/core/events/close_event.h b/bridge/core/events/close_event.h index 0ab54564f3..dcfc9870f2 100644 --- a/bridge/core/events/close_event.h +++ b/bridge/core/events/close_event.h @@ -24,17 +24,14 @@ class CloseEvent : public Event { 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, 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, ExceptionState& exception_state); explicit CloseEvent(ExecutingContext* context, const AtomicString& type, int32_t code, diff --git a/bridge/core/events/focus_event.cc b/bridge/core/events/focus_event.cc index 7b1b204b4c..ac07644503 100644 --- a/bridge/core/events/focus_event.cc +++ b/bridge/core/events/focus_event.cc @@ -29,7 +29,8 @@ FocusEvent* FocusEvent::Create(ExecutingContext* context, 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, ExceptionState& exception_state) + : UIEvent(context, type, exception_state) {} FocusEvent::FocusEvent(ExecutingContext* context, const AtomicString& type, diff --git a/bridge/core/events/focus_event.h b/bridge/core/events/focus_event.h index 20949c9f23..e5d210b2f0 100644 --- a/bridge/core/events/focus_event.h +++ b/bridge/core/events/focus_event.h @@ -5,8 +5,8 @@ #ifndef BRIDGE_CORE_EVENTS_FOCUS_EVENT_H_ #define BRIDGE_CORE_EVENTS_FOCUS_EVENT_H_ -#include "bindings/qjs/dictionary_base.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" @@ -20,9 +20,7 @@ class FocusEvent : public UIEvent { public: using ImplType = FocusEvent*; - static FocusEvent* Create(ExecutingContext* context, - const AtomicString& type, - ExceptionState& exception_state); + static FocusEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); static FocusEvent* Create(ExecutingContext* context, const AtomicString& type, @@ -36,9 +34,7 @@ class FocusEvent : public UIEvent { 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, ExceptionState& exception_state); explicit FocusEvent(ExecutingContext* context, const AtomicString& type, diff --git a/bridge/core/events/gesture_event.cc b/bridge/core/events/gesture_event.cc index 0a7d0f8e04..7f6ee00ec0 100644 --- a/bridge/core/events/gesture_event.cc +++ b/bridge/core/events/gesture_event.cc @@ -7,7 +7,6 @@ namespace webf { - GestureEvent* GestureEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { @@ -21,7 +20,8 @@ GestureEvent* GestureEvent::Create(ExecutingContext* context, 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, ExceptionState& exception_state) + : Event(context, type) {} GestureEvent::GestureEvent(ExecutingContext* context, const AtomicString& type, diff --git a/bridge/core/events/gesture_event.h b/bridge/core/events/gesture_event.h index a4f885fb06..1f0e393cf6 100644 --- a/bridge/core/events/gesture_event.h +++ b/bridge/core/events/gesture_event.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_CORE_EVENTS_GESTURE_EVENT_H_ #define BRIDGE_CORE_EVENTS_GESTURE_EVENT_H_ @@ -19,24 +19,20 @@ class GestureEvent : public Event { 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); - 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, - const std::shared_ptr<GestureEventInit>& initializer, - ExceptionState& exception_state); - const AtomicString& state() const; const AtomicString& direction() const; double deltaX() const; @@ -57,6 +53,6 @@ class GestureEvent : public Event { double rotation_; }; -} +} // namespace webf #endif // BRIDGE_CORE_EVENTS_GESTURE_EVENT_H_ diff --git a/bridge/core/events/input_event.h b/bridge/core/events/input_event.h index 4e6212021f..c5813ff706 100644 --- a/bridge/core/events/input_event.h +++ b/bridge/core/events/input_event.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_INPUT_EVENT_H @@ -8,8 +8,8 @@ #include "bindings/qjs/dictionary_base.h" #include "bindings/qjs/source_location.h" -#include "ui_event.h" #include "qjs_input_event_init.h" +#include "ui_event.h" namespace webf { @@ -19,24 +19,20 @@ class InputEvent : public UIEvent { 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); - 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, - const std::shared_ptr<InputEventInit>& initializer, - ExceptionState& exception_state); - const AtomicString& inputType() const; const AtomicString& data() const; @@ -45,6 +41,6 @@ class InputEvent : public UIEvent { AtomicString data_; }; -} +} // namespace webf #endif // BRIDGE_INPUT_EVENT_H diff --git a/bridge/core/events/intersection_change_event.h b/bridge/core/events/intersection_change_event.h index 277ab98f10..98adb0cae3 100644 --- a/bridge/core/events/intersection_change_event.h +++ b/bridge/core/events/intersection_change_event.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_INTERSECTION_CHANGE_EVENT_H @@ -15,6 +15,7 @@ namespace webf { class IntersectionChangeEvent : public Event { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = IntersectionChangeEvent*; @@ -23,14 +24,14 @@ class IntersectionChangeEvent : public Event { ExceptionState& exception_state); static IntersectionChangeEvent* Create(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr<IntersectionChangeEventInit>& initializer, - ExceptionState& exception_state); + 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); + const AtomicString& type, + const std::shared_ptr<IntersectionChangeEventInit>& initializer, + ExceptionState& exception_state); explicit IntersectionChangeEvent(ExecutingContext* context, const AtomicString& type, @@ -42,6 +43,6 @@ class IntersectionChangeEvent : public Event { double intersection_ratio_; }; -} +} // namespace webf #endif // BRIDGE_INTERSECTION_CHANGE_EVENT_H diff --git a/bridge/core/events/keyboard_event.h b/bridge/core/events/keyboard_event.h index 4787278fdf..d7b0dc95a9 100644 --- a/bridge/core/events/keyboard_event.h +++ b/bridge/core/events/keyboard_event.h @@ -25,18 +25,14 @@ class KeyboardEvent : public UIEvent { }; using ImplType = KeyboardEvent*; - static KeyboardEvent* Create(ExecutingContext* context, - const AtomicString& type, - ExceptionState& exception_state); + 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, ExceptionState& exception_state); explicit KeyboardEvent(ExecutingContext* context, const AtomicString& type, diff --git a/bridge/core/events/ui_event.cc b/bridge/core/events/ui_event.cc index 58af52bc34..3144479364 100644 --- a/bridge/core/events/ui_event.cc +++ b/bridge/core/events/ui_event.cc @@ -7,12 +7,10 @@ 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, @@ -30,7 +28,8 @@ UIEvent* UIEvent::Create(ExecutingContext* context, exception_state); } -UIEvent::UIEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state): Event(context, type) {} +UIEvent::UIEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context, type) {} UIEvent::UIEvent(ExecutingContext* context, const AtomicString& type, diff --git a/bridge/core/events/ui_event.h b/bridge/core/events/ui_event.h index ec09580f95..4dd38180ac 100644 --- a/bridge/core/events/ui_event.h +++ b/bridge/core/events/ui_event.h @@ -5,14 +5,13 @@ #ifndef BRIDGE_CORE_EVENTS_UI_EVENT_H_ #define BRIDGE_CORE_EVENTS_UI_EVENT_H_ -#include "bindings/qjs/dictionary_base.h" #include "bindings/qjs/cppgc/member.h" -#include "core/frame/window.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 { class UIEvent : public Event { @@ -21,9 +20,7 @@ class UIEvent : public Event { public: using ImplType = UIEvent*; - static UIEvent* Create(ExecutingContext* context, - const AtomicString& type, - ExceptionState& exception_state); + static UIEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); static UIEvent* Create(ExecutingContext* context, const AtomicString& type, @@ -36,9 +33,7 @@ class UIEvent : public Event { 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, ExceptionState& exception_state); explicit UIEvent(ExecutingContext* context, const AtomicString& type, diff --git a/bridge/core/input/touch_list.cc b/bridge/core/input/touch_list.cc index db236a3f9b..b350a7dee6 100644 --- a/bridge/core/input/touch_list.cc +++ b/bridge/core/input/touch_list.cc @@ -1,5 +1,5 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "touch_list.h" @@ -23,9 +23,9 @@ bool TouchList::SetItem(uint32_t index, Touch* touch, ExceptionState& exception_ } void TouchList::Trace(GCVisitor* visitor) const { - for(auto& item : values_) { + for (auto& item : values_) { item->Trace(visitor); } } -} \ No newline at end of file +} // namespace webf \ No newline at end of file diff --git a/bridge/core/input/touch_list.h b/bridge/core/input/touch_list.h index 6843981de8..b04598cb9c 100644 --- a/bridge/core/input/touch_list.h +++ b/bridge/core/input/touch_list.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #ifndef BRIDGE_CORE_INPUT_TOUCH_LIST_H_ #define BRIDGE_CORE_INPUT_TOUCH_LIST_H_ @@ -11,18 +11,19 @@ namespace webf { class TouchList : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = TouchList*; uint32_t length() const; Touch* item(uint32_t index, ExceptionState& exception_state) const; bool SetItem(uint32_t index, Touch* touch, ExceptionState& exception_state); - void Trace(GCVisitor *visitor) const override; + void Trace(GCVisitor* visitor) const override; private: std::vector<Touch*> values_; }; -} +} // namespace webf #endif // BRIDGE_CORE_INPUT_TOUCH_LIST_H_ From 38931d4ae2d5953034d250628e07622bc5408697 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Tue, 23 Aug 2022 17:10:23 +0800 Subject: [PATCH 169/498] feat: support generate code from static member. feat: add html_collection class. --- bridge/CMakeLists.txt | 2 + bridge/core/dom/binding_call_methods.json5 | 6 + bridge/core/dom/document.h | 12 + bridge/core/dom/element.d.ts | 8 +- bridge/core/dom/live_node_list_base.cc | 25 ++ bridge/core/dom/live_node_list_base.h | 161 ++++++++ bridge/core/dom/node.cc | 8 + bridge/core/dom/node.d.ts | 29 +- bridge/core/dom/node.h | 9 + bridge/core/html/collection_type.h | 87 +++++ bridge/core/html/html_all_collection.cc | 91 +---- bridge/core/html/html_all_collection.h | 47 +-- bridge/core/html/html_attribute_names.json5 | 348 ++++++++++++++++++ bridge/core/html/html_collection.d.ts | 7 + bridge/core/html/html_element.d.ts | 7 + bridge/core/html/html_tag_names.json5 | 5 +- .../code_generator/bin/code_generator.js | 10 +- bridge/scripts/code_generator/global.d.ts | 1 + .../code_generator/src/idl/analyzer.ts | 5 + .../code_generator/src/idl/declaration.ts | 1 + .../code_generator/src/json/generator.ts | 24 +- .../static/idl_templates/interface.cc.tpl | 2 + .../static/json_templates/make_names.cc.tpl | 33 ++ .../static/json_templates/make_names.h.tpl | 7 + 24 files changed, 805 insertions(+), 130 deletions(-) create mode 100644 bridge/core/dom/live_node_list_base.cc create mode 100644 bridge/core/dom/live_node_list_base.h create mode 100644 bridge/core/html/collection_type.h create mode 100644 bridge/core/html/html_attribute_names.json5 create mode 100644 bridge/core/html/html_collection.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 3412f5b662..1d386e40ad 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -189,6 +189,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/dom/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 @@ -220,6 +221,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") 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_template_element.cc # core/html/forms/html_input_element.cc diff --git a/bridge/core/dom/binding_call_methods.json5 b/bridge/core/dom/binding_call_methods.json5 index 3065d28e7b..9999771cdd 100644 --- a/bridge/core/dom/binding_call_methods.json5 +++ b/bridge/core/dom/binding_call_methods.json5 @@ -17,6 +17,12 @@ "clientHeight", "scrollLeft", "scrollTop", + "offsetTop", + "offsetLeft", + "offsetWidth", + "offsetHeight", + "scrollWidth", + "scrollHeight", "getBoundingClientRect", ["getPropertyMagic", "%g"], ["setPropertyMagic", "%s"], diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 5c5a7185e7..22718afc13 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -18,6 +18,18 @@ 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. diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index 898d455c76..a75319eff4 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -17,6 +17,8 @@ interface Element extends Node { readonly ownerDocument: Document; scrollLeft: number; scrollTop: number; + readonly scrollWidth: DartImpl<number>; + readonly scrollHeight: DartImpl<number>; /** * Returns the HTML-uppercased qualified name. */ @@ -33,6 +35,9 @@ interface Element extends Node { * Removes element's first attribute whose qualified name is qualifiedName. */ removeAttribute(qualifiedName: string): void; + + // CSSOM View Module + // https://drafts.csswg.org/cssom-view/#extension-to-the-element-interface getBoundingClientRect(): BoundingClientRect; scroll(options?: ScrollToOptions): void; @@ -42,7 +47,8 @@ interface Element extends Node { scrollTo(options?: ScrollToOptions): void; scrollTo(x: number, y: number): void; - // Kraken special API. + // Export the target element's rendering content to PNG. + // WebF special API. toBlob(devicePixelRatioValue?: double): Promise<ArrayBuffer>; new(): void; 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..047ee8ac15 --- /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..8cd5d89aee --- /dev/null +++ b/bridge/core/dom/live_node_list_base.h @@ -0,0 +1,161 @@ +/* + * 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/html/collection_type.h" +#include "document.h" +#include "html_names.h" +#include "core/dom/element_traversal.h" + +namespace webf { + +enum class NodeListSearchRoot { + kOwnerNode, + kTreeScope, +}; + +class LiveNodeListBase : public ScriptWrappable { + 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), + ScriptWrappable(owner_node->ctx()) { + 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 index 96d3c70464..4d2637d116 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -17,6 +17,14 @@ 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; diff --git a/bridge/core/dom/node.d.ts b/bridge/core/dom/node.d.ts index aff9dfd9fe..9ebd9a8cab 100644 --- a/bridge/core/dom/node.d.ts +++ b/bridge/core/dom/node.d.ts @@ -5,6 +5,25 @@ 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. */ @@ -25,15 +44,7 @@ interface Node extends EventTarget { * Returns the next sibling. */ readonly nextSibling: Node | null; - /** - * Returns a string appropriate for the type of node. - */ - readonly nodeName: string; - /** - * Returns the type of node. - */ - readonly nodeType: number; - nodeValue: string | null; + /** * Returns the node document. Returns null for documents. */ diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 294ec177af..44f900f7f4 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -57,6 +57,15 @@ class Node : public EventTarget { 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); diff --git a/bridge/core/html/collection_type.h b/bridge/core/html/collection_type.h new file mode 100644 index 0000000000..d85bd5060b --- /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; +} + +} + +#endif // BRIDGE_CORE_HTML_COLLECTION_TYPE_H_ diff --git a/bridge/core/html/html_all_collection.cc b/bridge/core/html/html_all_collection.cc index 4e68b1db2c..af36ba9859 100644 --- a/bridge/core/html/html_all_collection.cc +++ b/bridge/core/html/html_all_collection.cc @@ -1,80 +1,11 @@ -///* -// * Copyright (C) 2021 Alibaba Inc. All rights reserved. -// * Author: Kraken Team. -// */ -// -//#include "html_all_collection.h" -// -// namespace webf{ -// -// JSValue AllCollection::item(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// if (argc < 1) { -// return JS_NULL; -// } -// -// uint32_t index; -// JS_ToUint32(ctx, &index, argv[0]); -// auto* collection = static_cast<AllCollection*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// -// if (index >= collection->m_nodes.size()) { -// return JS_NULL; -// } -// -// auto node = collection->m_nodes[index]; -// return node->jsObject; -//} -// JSValue AllCollection::add(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// if (argc < 1) { -// return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: 1 arguments required."); -// } -// -// if (!JS_IsObject(argv[0])) { -// return JS_ThrowTypeError(ctx, "Failed to execute add() on HTMLAllCollection: first arguments should be a -// object."); -// } -// -// JSValue before = JS_NULL; -// -// if (argc == 2 && JS_IsObject(argv[1])) { -// before = argv[1]; -// } -// -// auto* node = static_cast<NodeInstance*>(JS_GetOpaque(argv[0], ExecutionContext::kHostObjectClassId)); -// auto* collection = static_cast<AllCollection*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// NodeInstance* beforeNode = nullptr; -// -// if (!JS_IsNull(before)) { -// beforeNode = static_cast<NodeInstance*>(JS_GetOpaque(before, ExecutionContext::kHostObjectClassId)); -// } -// -// collection->internalAdd(node, beforeNode); -// -// return JS_NULL; -//} -// JSValue AllCollection::remove(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// if (argc < 1) { -// return JS_ThrowTypeError(ctx, "Failed to execute remove() on HTMLAllCollection: 1 arguments required."); -// } -// -// uint32_t index; -// JS_ToUint32(ctx, &index, argv[0]); -// auto* collection = static_cast<AllCollection*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// collection->m_nodes.erase(collection->m_nodes.begin() + index); -// return JS_NULL; -//} -// void AllCollection::internalAdd(NodeInstance* node, NodeInstance* before) { -// if (before != nullptr) { -// auto it = std::find(m_nodes.begin(), m_nodes.end(), before); -// m_nodes.erase(it); -// m_nodes.insert(it, node); -// } else { -// m_nodes.emplace_back(node); -// } -//} -// -// IMPL_PROPERTY_GETTER(AllCollection, length)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { -// auto* collection = static_cast<AllCollection*>(JS_GetOpaque(this_val, ExecutionContext::kHostObjectClassId)); -// return JS_NewUint32(ctx, collection->m_nodes.size()); -//} -// -//} // namespace webf +/* +* 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 { + + +} \ No newline at end of file diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h index 4e316e38f9..d48b1f9e08 100644 --- a/bridge/core/html/html_all_collection.h +++ b/bridge/core/html/html_all_collection.h @@ -1,31 +1,16 @@ -///* -// * Copyright (C) 2021 Alibaba Inc. All rights reserved. -// * Author: Kraken Team. -// */ -// -//#ifndef BRIDGE_HTML_ALL_COLLECTION_H -//#define BRIDGE_HTML_ALL_COLLECTION_H -// -//#include "bindings/qjs/garbage_collected.h" -// -// namespace webf{ -// -// class HTMLAllCollection : public HostObject { -// public: -// AllCollection(ExecutionContext* context) : HostObject(context, "AllCollection"){}; -// -// static JSValue item(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); -// static JSValue add(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); -// static JSValue remove(JSContext* ctx, JSValueConst this_val, int argc, JSValueConst* argv); -// -// DEFINE_READONLY_PROPERTY(length); -// -// void internalAdd(NodeInstance* node, NodeInstance* before); -// -// private: -// std::vector<NodeInstance*> m_nodes; -//}; -// -//} // namespace webf -// -//#endif // BRIDGE_HTML_ALL_COLLECTION_H +/* +* 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_ + + + +namespace webf { + + +} + +#endif // BRIDGE_CORE_HTML_HTML_ALL_COLLECTION_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_collection.d.ts b/bridge/core/html/html_collection.d.ts new file mode 100644 index 0000000000..09b9f274bb --- /dev/null +++ b/bridge/core/html/html_collection.d.ts @@ -0,0 +1,7 @@ +import {Element} from "../dom/element"; + +interface HTMLCollection { + readonly length: double; + item(index: double): Element | null; + namedItem(name: string): Element | null; +} \ No newline at end of file diff --git a/bridge/core/html/html_element.d.ts b/bridge/core/html/html_element.d.ts index 4ea8f07a89..b74b448ade 100644 --- a/bridge/core/html/html_element.d.ts +++ b/bridge/core/html/html_element.d.ts @@ -2,5 +2,12 @@ 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>; + new(): void; } diff --git a/bridge/core/html/html_tag_names.json5 b/bridge/core/html/html_tag_names.json5 index bfdf8ff4aa..4869b1fd59 100644 --- a/bridge/core/html/html_tag_names.json5 +++ b/bridge/core/html/html_tag_names.json5 @@ -3,7 +3,10 @@ "templates": [ { "template": "make_names", - "filename": "html_names" + "filename": "html_names", + "deps": [ + "./html_attribute_names.json5" + ] }, { "template": "element_factory", diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 57107973be..b54981f547 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -77,10 +77,18 @@ function genCodeFromJSONData() { for (let i = 0; i < blobs.length; i ++) { let blob = blobs[i]; blob.json.metadata.templates.forEach((targetTemplate) => { + 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; + }); + } let targetTemplateHeaderData = templates.find(t => t.filename === targetTemplate.template + '.h'); let targetTemplateBodyData = templates.find(t => t.filename === targetTemplate.template + '.cc'); blob.filename = targetTemplate.filename; - let result = generateJSONTemplate(blobs[i], targetTemplateHeaderData, targetTemplateBodyData); + let result = generateJSONTemplate(blobs[i], targetTemplateHeaderData, targetTemplateBodyData, depsBlob); let dist = blob.dist; let genFilePath = path.join(dist, targetTemplate.filename); fs.writeFileSync(genFilePath + '.h', result.header); diff --git a/bridge/scripts/code_generator/global.d.ts b/bridge/scripts/code_generator/global.d.ts index 7f631cb160..afa675a8a3 100644 --- a/bridge/scripts/code_generator/global.d.ts +++ b/bridge/scripts/code_generator/global.d.ts @@ -10,3 +10,4 @@ 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/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 44cc62a534..fd33e32ed7 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -106,6 +106,11 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete 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; diff --git a/bridge/scripts/code_generator/src/idl/declaration.ts b/bridge/scripts/code_generator/src/idl/declaration.ts index 00463dcfe3..fdd682a0ce 100644 --- a/bridge/scripts/code_generator/src/idl/declaration.ts +++ b/bridge/scripts/code_generator/src/idl/declaration.ts @@ -25,6 +25,7 @@ export class FunctionArguments { export class ParameterMode { newObject?: boolean; dartImpl?: boolean; + static?: boolean; } export class PropsDeclaration { diff --git a/bridge/scripts/code_generator/src/json/generator.ts b/bridge/scripts/code_generator/src/json/generator.ts index 5588fe66c0..dc1c25a254 100644 --- a/bridge/scripts/code_generator/src/json/generator.ts +++ b/bridge/scripts/code_generator/src/json/generator.ts @@ -2,32 +2,42 @@ import {JSONBlob} from './JSONBlob'; import {JSONTemplate} from './JSONTemplate'; import _ from 'lodash'; -function generateHeader(blob: JSONBlob, template: JSONTemplate): string { +function generateHeader(blob: JSONBlob, template: JSONTemplate, deps?: JSONBlob[]): string { let compiled = _.template(template.raw); + console.log(deps); return compiled({ _: _, name: blob.filename, template_path: blob.source, - data: blob.json.data + data: blob.json.data, + deps, + upperCamelCase }).split('\n').filter(str => { return str.trim().length > 0; }).join('\n'); } -function generateBody(blob: JSONBlob, template: JSONTemplate): string { + +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 + 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) { - let header = generateHeader(blob, headerTemplate); - let body = bodyTemplate ? generateBody(blob, bodyTemplate) : ''; +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, diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index 993f910683..fd6b19a316 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -111,6 +111,8 @@ static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst 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 %>()); <% } %> diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl index e0a2c1af2f..1b43b8b6ce 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl @@ -10,6 +10,11 @@ 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 %>]; @@ -19,6 +24,12 @@ const AtomicString& k<%= name.name %> = reinterpret_cast<AtomicString*>(&names_s 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; @@ -36,10 +47,25 @@ void Init(JSContext* ctx) { <% }); %> }; + <% 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(){ @@ -47,6 +73,13 @@ void Dispose(){ 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(); + } + <% } %> }; diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl index eca9463669..a33e8b3ea9 100644 --- a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl +++ b/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl @@ -22,6 +22,13 @@ namespace <%= 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); From 8afcb92aeeb8708a6e7d4489fed8ec0a405ab252 Mon Sep 17 00:00:00 2001 From: openwebf-bot <openwebf@openwebf.com> Date: Tue, 23 Aug 2022 09:11:26 +0000 Subject: [PATCH 170/498] Committing clang-format changes --- bridge/core/dom/live_node_list_base.cc | 44 ++++++++++++------------- bridge/core/dom/live_node_list_base.h | 2 +- bridge/core/html/collection_type.h | 44 ++++++++++++------------- bridge/core/html/html_all_collection.cc | 11 +++---- bridge/core/html/html_all_collection.h | 13 +++----- 5 files changed, 53 insertions(+), 61 deletions(-) diff --git a/bridge/core/dom/live_node_list_base.cc b/bridge/core/dom/live_node_list_base.cc index 047ee8ac15..9178c9809b 100644 --- a/bridge/core/dom/live_node_list_base.cc +++ b/bridge/core/dom/live_node_list_base.cc @@ -1,25 +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. -* -*/ + * 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 index 8cd5d89aee..dbedd3bf93 100644 --- a/bridge/core/dom/live_node_list_base.h +++ b/bridge/core/dom/live_node_list_base.h @@ -27,10 +27,10 @@ #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" -#include "core/dom/element_traversal.h" namespace webf { diff --git a/bridge/core/html/collection_type.h b/bridge/core/html/collection_type.h index d85bd5060b..bbf1d8c8e7 100644 --- a/bridge/core/html/collection_type.h +++ b/bridge/core/html/collection_type.h @@ -1,25 +1,25 @@ /* -* 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. -* -*/ + * 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_ @@ -82,6 +82,6 @@ inline bool IsLiveNodeListType(CollectionType type) { return type >= kFirstLiveNodeListType; } -} +} // namespace webf #endif // BRIDGE_CORE_HTML_COLLECTION_TYPE_H_ diff --git a/bridge/core/html/html_all_collection.cc b/bridge/core/html/html_all_collection.cc index af36ba9859..92d223b51b 100644 --- a/bridge/core/html/html_all_collection.cc +++ b/bridge/core/html/html_all_collection.cc @@ -1,11 +1,8 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_all_collection.h" -namespace webf { - - -} \ No newline at end of file +namespace webf {} \ No newline at end of file diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h index d48b1f9e08..fb251bf71b 100644 --- a/bridge/core/html/html_all_collection.h +++ b/bridge/core/html/html_all_collection.h @@ -1,16 +1,11 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_CORE_HTML_HTML_ALL_COLLECTION_H_ #define BRIDGE_CORE_HTML_HTML_ALL_COLLECTION_H_ - - -namespace webf { - - -} +namespace webf {} #endif // BRIDGE_CORE_HTML_HTML_ALL_COLLECTION_H_ From fa03311d7b20071246be9941943c2983fab55958 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Wed, 24 Aug 2022 02:35:58 +0800 Subject: [PATCH 171/498] feat: add html collection and support enumerate property of object. --- bridge/CMakeLists.txt | 3 +- bridge/bindings/qjs/binding_initializer.cc | 1 + bridge/bindings/qjs/heap_hashmap.h | 78 +++++-------- bridge/bindings/qjs/heap_vector.h | 30 +++++ bridge/bindings/qjs/script_wrappable.cc | 20 +++- bridge/bindings/qjs/wrapper_type_info.h | 10 +- .../core/css/legacy/css_style_declaration.cc | 6 + .../core/css/legacy/css_style_declaration.h | 1 + .../css/legacy/css_style_declaration_test.cc | 19 ++++ bridge/core/dom/child_node_list.cc | 12 ++ bridge/core/dom/child_node_list.h | 3 + bridge/core/dom/collection_index_cache.h | 34 +++++- bridge/core/dom/collection_items_cache.h | 106 ++++++++++++++++++ bridge/core/dom/empty_node_list.cc | 6 + bridge/core/dom/empty_node_list.h | 3 + bridge/core/dom/live_node_list_base.h | 5 +- bridge/core/dom/node_list.h | 4 + bridge/core/html/html_collection.cc | 8 -- bridge/core/html/html_collection.d.ts | 7 -- bridge/core/html/html_collection.h | 20 ---- bridge/core/html/legacy/html_collection.cc | 39 +++++++ bridge/core/html/legacy/html_collection.d.ts | 8 ++ bridge/core/html/legacy/html_collection.h | 33 ++++++ bridge/core/input/touch_list.cc | 12 ++ bridge/core/input/touch_list.h | 3 + .../code_generator/src/idl/generateSource.ts | 10 +- .../code_generator/src/json/generator.ts | 1 - .../static/idl_templates/interface.cc.tpl | 37 ++++-- .../static/idl_templates/interface.h.tpl | 3 +- 29 files changed, 409 insertions(+), 113 deletions(-) create mode 100644 bridge/bindings/qjs/heap_vector.h create mode 100644 bridge/core/dom/collection_items_cache.h delete mode 100644 bridge/core/html/html_collection.cc delete mode 100644 bridge/core/html/html_collection.d.ts delete mode 100644 bridge/core/html/html_collection.h create mode 100644 bridge/core/html/legacy/html_collection.cc create mode 100644 bridge/core/html/legacy/html_collection.d.ts create mode 100644 bridge/core/html/legacy/html_collection.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 1d386e40ad..306bde75ef 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -214,7 +214,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/events/keyboard_event.cc core/events/promise_rejection_event.cc core/html/parser/html_parser.cc - core/html/html_collection.cc + core/html/legacy/html_collection.cc core/html/html_element.cc core/html/html_div_element.cc core/html/html_head_element.cc @@ -291,6 +291,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/qjs_scroll_options.cc out/qjs_scroll_to_options.cc out/qjs_html_element.cc + out/qjs_html_collection.cc out/qjs_html_div_element.cc out/qjs_html_head_element.cc out/qjs_html_body_element.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index e7b1974f1c..cbfabc0143 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -22,6 +22,7 @@ #include "qjs_event_target.h" #include "qjs_focus_event.h" #include "qjs_gesture_event.h" +#include "qjs_html_collection.h" #include "qjs_html_body_element.h" #include "qjs_html_div_element.h" #include "qjs_html_element.h" diff --git a/bridge/bindings/qjs/heap_hashmap.h b/bridge/bindings/qjs/heap_hashmap.h index dcff34087a..0baef2d3b7 100644 --- a/bridge/bindings/qjs/heap_hashmap.h +++ b/bridge/bindings/qjs/heap_hashmap.h @@ -6,95 +6,69 @@ #ifndef BRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ #define BRIDGE_BINDINGS_QJS_HEAP_HASHMAP_H_ -#include <quickjs/quickjs.h> #include <unordered_map> +#include "cppgc/gc_visitor.h" namespace webf { -template <typename K> +template <typename K, typename V> 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); + 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* runtime_{nullptr}; - JSContext* ctx_{nullptr}; - std::unordered_map<K, JSValue> entries_; + std::unordered_map<K, V> entries_; }; -template <typename K> -HeapHashMap<K>::HeapHashMap(JSContext* ctx) : runtime_(JS_GetRuntime(ctx)), ctx_(ctx) {} +template <typename K, typename V> +HeapHashMap<K, V>::HeapHashMap() {} -template <typename K> -HeapHashMap<K>::~HeapHashMap() { - for (auto& entry : entries_) { - JS_FreeAtomRT(runtime_, entry.first); - JS_FreeValueRT(runtime_, entry.second); - } -} -template <typename K> -bool HeapHashMap<K>::Contains(K key) { +template <typename K, typename V> +HeapHashMap<K, V>::~HeapHashMap() {} + +template <typename K, typename V> +bool HeapHashMap<K, V>::Contains(K key) { return entries_.count(key) > 0; } -template <typename K> -JSValue HeapHashMap<K>::GetProperty(K key) { +template <typename K, typename V> +V HeapHashMap<K, V>::GetProperty(K key) { if (entries_.count(key) == 0) return JS_NULL; return entries_[key]; } -template <typename K> -void HeapHashMap<K>::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 (entries_.count(key) > 0) { - JS_FreeAtom(ctx_, key); - JS_FreeValue(ctx_, entries_[key]); - } - +template <typename K, typename V> +void HeapHashMap<K, V>::SetProperty(K key, V value) { entries_[key] = value; } -template <typename K> -void HeapHashMap<K>::CopyWith(HeapHashMap* newValue) { - for (auto& entry : entries_) { - // We should also dup atom if K is JSAtom. - if (std::is_same<K, JSAtom>::value) { - JS_DupAtom(ctx_, entry.first); - } - - newValue->entries_[entry.first] = JS_DupValue(ctx_, entry.second); - } +template <typename K, typename V> +void HeapHashMap<K, V>::CopyWith(HeapHashMap* newValue) { + newValue->entries_ = entries_; } -template <typename K> -void HeapHashMap<K>::Erase(K key) { +template <typename K, typename V> +void HeapHashMap<K, V>::Erase(K key) { if (entries_.count(key) == 0) return; - // We should also free atom if K is JSAtom. - if (std::is_same<K, JSAtom>::value) { - JS_FreeAtomRT(runtime_, key); - } - JS_FreeValueRT(runtime_, entries_[key]); entries_.erase(key); } -template <typename K> -void HeapHashMap<K>::Trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { +template <typename K, typename V> +void HeapHashMap<K, V>::Trace(GCVisitor* visitor) const { for (auto& entry : entries_) { - JS_MarkValue(rt, entry.second, mark_func); + visitor->Trace(entry.second); } } diff --git a/bridge/bindings/qjs/heap_vector.h b/bridge/bindings/qjs/heap_vector.h new file mode 100644 index 0000000000..8ad3c07b0d --- /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 <typename V> +class HeapVector final { + public: + HeapVector() = default; + + void Trace(GCVisitor* visitor) const; + + private: + std::vector<V> entries_; +}; + +template <typename V> +void HeapVector<V>::Trace(GCVisitor* visitor) const { + for(auto& item : entries_) { + visitor->Trace(item); + } +} + +} + +#endif // BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_ diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 7148f6bf01..e466a136b4 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -90,7 +90,16 @@ static int HandleJSPropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSA auto* object = static_cast<ScriptWrappable*>(JS_GetOpaque(obj, JSValueGetClassId(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); - return wrapper_type_info->string_property_checker_handler_(ctx, obj, atom); + 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<ScriptWrappable*>(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) { @@ -140,12 +149,17 @@ void ScriptWrappable::InitializeQuickJSObject() { } // Define the callback when check object property exist. - if (UNLIKELY(wrapper_type_info->string_property_checker_handler_ != nullptr)) { + 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; + } else { + exotic_methods->get_own_property_names = HandleJSGetOwnPropertyNames; + } + // Support iterate script wrappable defined properties. - exotic_methods->get_own_property_names = HandleJSGetOwnPropertyNames; exotic_methods->get_own_property = HandleJSGetOwnProperty; def.exotic = exotic_methods; diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 096f41b65f..1469141b93 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -45,6 +45,7 @@ enum { JS_CLASS_DOCUMENT_FRAGMENT, JS_CLASS_BOUNDING_CLIENT_RECT, JS_CLASS_ELEMENT_ATTRIBUTES, + JS_CLASS_HTML_COLLECTION, JS_CLASS_HTML_ELEMENT, JS_CLASS_HTML_DIV_ELEMENT, JS_CLASS_HTML_BODY_ELEMENT, @@ -75,7 +76,11 @@ using StringPropertySetterHandler = bool (*)(JSContext* ctx, JSValueConst obj, J // Callback when check property exist on object. // exp: 'hello' in obj; -using StringPropertyCheckerHandler = bool (*)(JSContext* ctx, JSValueConst obj, JSAtom atom); +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 @@ -101,7 +106,8 @@ class WrapperTypeInfo final { IndexedPropertySetterHandler indexed_property_setter_handler_{nullptr}; StringPropertyGetterHandler string_property_getter_handler_{nullptr}; StringPropertySetterHandler string_property_setter_handler_{nullptr}; - StringPropertyCheckerHandler string_property_checker_handler_{nullptr}; + PropertyCheckerHandler property_checker_handler_{nullptr}; + PropertyEnumerateHandler property_enumerate_handler_{nullptr}; }; } // namespace webf diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index 590aec732b..6782a5d2fe 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -113,6 +113,12 @@ bool CSSStyleDeclaration::NamedPropertyQuery(const AtomicString& key, ExceptionS return cssPropertyList.count(key.ToStdString()) > 0; } +void CSSStyleDeclaration::NamedPropertyEnumerator(std::vector<AtomicString>& names, ExceptionState&) { + for(auto& entry : cssPropertyList) { + names.emplace_back(AtomicString(ctx(), entry.first)); + } +} + AtomicString CSSStyleDeclaration::InternalGetPropertyValue(std::string& name) { name = parseJavaScriptCSSPropertyName(name); diff --git a/bridge/core/css/legacy/css_style_declaration.h b/bridge/core/css/legacy/css_style_declaration.h index c78f1cb64c..94c03e2136 100644 --- a/bridge/core/css/legacy/css_style_declaration.h +++ b/bridge/core/css/legacy/css_style_declaration.h @@ -37,6 +37,7 @@ class CSSStyleDeclaration : public ScriptWrappable { std::string ToString() const; bool NamedPropertyQuery(const AtomicString&, ExceptionState&); + void NamedPropertyEnumerator(std::vector<AtomicString>& names, ExceptionState&); private: AtomicString InternalGetPropertyValue(std::string& name); diff --git a/bridge/core/css/legacy/css_style_declaration_test.cc b/bridge/core/css/legacy/css_style_declaration_test.cc index 049f23813a..097cd15bc1 100644 --- a/bridge/core/css/legacy/css_style_declaration_test.cc +++ b/bridge/core/css/legacy/css_style_declaration_test.cc @@ -23,3 +23,22 @@ TEST(CSSStyleDeclaration, setStyleData) { 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; + EXPECT_STREQ(message.c_str(), "['zoom', 'writingMode', 'wordSpacing', 'wordBreak', 'willChange', 'width', 'widows', 'visibility', 'vectorEffect', 'userZoom', 'userSelect', 'unicodeRange', 'unicodeBidi', 'transitionProperty', 'transition', 'touchAction', 'textRendering', 'range', 'textOverflow', 'breakAfter', 'order', 'textIndent', 'textUnderlineOffset', 'textEmphasisColor', 'textDecorationThickness', 'textDecorationSkipInk', 'markerEnd', 'textDecorationLine', 'textAnchor', 'tableLayout', 'listStyleType', 'tabSize', 'system', 'scrollMarginBlock', 'syntax', 'textEmphasisStyle', 'offsetDistance', 'isolation', 'symbols', 'scrollPaddingLeft', 'strokeWidth', 'strokeOpacity', 'strokeLinejoin', 'stopOpacity', 'fillOpacity', 'strokeMiterlimit', 'fontSynthesisWeight', 'sizeAdjust', 'direction', 'pageOrientation', 'size', 'ascentOverride', 'shapeOutside', 'shapeImageThreshold', 'scrollSnapType', 'borderTopStyle', 'scrollSnapAlign', 'borderBottomRightRadius', 'textTransform', 'textAlign', 'columnFill', 'scrollSnapStop', 'wordWrap', 'scrollPaddingTop', 'scrollPaddingRight', 'scrollPaddingInlineStart', 'gridArea', 'textEmphasisPosition', 'animationDuration', 'scrollPaddingBottom', 'scrollPaddingBlockEnd', 'stroke', 'scrollMarginLeft', 'scrollPadding', 'float', 'scrollMargin', 'backgroundClip', 'shapeRendering', 'borderStartEndRadius', 'rx', 'transitionDelay', 'flexShrink', 'rowGap', 'colorScheme', 'prefix', 'position', 'pageBreakAfter', 'pointerEvents', 'placeSelf', 'placeItems', 'scrollBehavior', 'scrollMarginBottom', 'perspective', 'borderInlineStartStyle', 'strokeDasharray', 'borderBottomStyle', 'gridTemplateAreas', 'pageBreakBefore', 'transitionTimingFunction', 'scrollPaddingInlineEnd', 'paddingLeft', 'paddingInlineStart', 'paddingInlineEnd', 'paddingInline', 'aspectRatio', 'paddingBlockStart', 'top', 'scrollPaddingBlock', 'paddingBlock', 'padding', 'overscrollBehaviorX', 'overscrollBehaviorInline', 'overscrollBehaviorBlock', 'overscrollBehavior', 'overflowWrap', 'listStylePosition', 'right', 'overflow', 'quotes', 'objectPosition', 'outlineWidth', 'outlineOffset', 'outline', 'orphans', 'borderBlockEndWidth', 'orientation', 'opacity', 'overflowY', 'maxZoom', 'objectFit', 'minWidth', 'textUnderlinePosition', 'minInlineSize', 'minHeight', 'y', 'paintOrder', 'columnGap', 'transformOrigin', 'borderLeft', 'minBlockSize', 'maxWidth', 'maxInlineSize', 'alignmentBaseline', 'color', 'maxHeight', 'maskType', 'markerMid', 'marker', 'marginInline', 'marginBottom', 'marginBlockStart', 'left', 'marginBlockEnd', 'textDecorationColor', 'marginBlock', 'textSizeAdjust', 'marginRight', 'margin', 'perspectiveOrigin', 'offsetPath', 'listStyle', 'lineGapOverride', 'overflowClipMargin', 'letterSpacing', 'justifySelf', 'markerStart', 'insetInlineStart', 'placeContent', 'insetInline', 'backgroundRepeatY', 'fontWeight', 'r', 'x', 'insetBlockEnd', 'borderSpacing', 'insetBlock', 'height', 'inset', 'offset', 'inlineSize', 'suffix', 'borderBlockColor', 'clip', 'initialValue', 'inherits', 'paddingBlockEnd', 'backgroundImage', 'imageRendering', 'mask', 'textEmphasis', 'hyphens', 'outlineStyle', 'textCombineUpright', 'borderRight', 'marginLeft', 'gridTemplateRows', 'marginInlineEnd', 'transformBox', 'resize', 'gridRowEnd', 'borderBlockEndColor', 'shapeMargin', 'gridColumnEnd', 'gridColumn', 'borderImageOutset', 'flexDirection', 'fallback', 'lightingColor', 'gridAutoFlow', 'borderRightWidth', 'gap', 'scrollMarginInline', 'fontVariantCaps', 'fontVariantEastAsian', 'textDecoration', 'insetBlockStart', 'fontSynthesisSmallCaps', 'fontStyle', 'appearance', 'overscrollBehaviorY', 'borderInlineWidth', 'filter', 'verticalAlign', 'backgroundAttachment', 'fontSize', 'gridColumnGap', 'flex', 'fontOpticalSizing', 'gridRowGap', 'fontFamily', 'font', 'colorInterpolationFilters', 'flexFlow', 'backgroundRepeatX', 'columnRuleColor', 'fillRule', 'emptyCells', 'display', 'textShadow', 'animationFillMode', 'floodColor', 'descentOverride', 'gridAutoRows', 'fontVariationSettings', 'stopColor', 'fontFeatureSettings', 'cursor', 'paddingRight', 'accentColor', 'borderColor', 'backdropFilter', 'counterReset', 'content', 'columns', 'cx', 'mixBlendMode', 'fontKerning', 'columnWidth', 'overflowAnchor', 'alignContent', 'columnSpan', 'zIndex', 'columnRule', 'backgroundRepeat', 'fontVariantNumeric', 'borderBlockStartStyle', 'columnCount', 'textAlignLast', 'fontVariant', 'colorRendering', 'lineHeight', 'borderBlockEndStyle', 'borderInlineEndColor', 'colorInterpolation', 'src', 'lineBreak', 'clipRule', 'clipPath', 'clear', 'floodOpacity', 'alignSelf', 'gridAutoColumns', 'caretColor', 'justifyItems', 'captionSide', 'backgroundBlendMode', 'bufferedRendering', 'listStyleImage', 'forcedColorAdjust', 'animationName', 'counterSet', 'breakInside', 'boxSizing', 'columnRuleStyle', 'justifyContent', 'textOrientation', 'breakBefore', 'outlineColor', 'borderTopWidth', 'all', 'gridColumnStart', 'minZoom', 'borderTopLeftRadius', 'marginTop', 'borderBlockStyle', 'backgroundOrigin', 'borderTop', 'cy', 'speakAs', 'negative', 'borderStartStartRadius', 'backgroundPositionY', 'borderLeftStyle', 'boxShadow', 'blockSize', 'borderInlineStartWidth', 'borderInlineEnd', 'borderInline', 'gridRowStart', 'fill', 'borderImageWidth', 'additiveSymbols', 'scrollMarginBlockEnd', 'borderImageSlice', 'borderImage', 'borderBottomLeftRadius', 'borderBottomWidth', 'borderImageRepeat', 'textDecorationStyle', 'borderRightStyle', 'page', 'imageOrientation', 'borderEndEndRadius', 'gridGap', 'scrollMarginInlineEnd', 'gridTemplateColumns', 'flexWrap', 'borderInlineColor', 'borderBottomColor', 'scrollMarginInlineStart', 'fontDisplay', 'dominantBaseline', 'borderRadius', 'borderBottom', 'borderBlockWidth', 'baselineShift', 'gridTemplate', 'borderBlockStartWidth', 'whiteSpace', 'fontSynthesis', 'fontSynthesisStyle', 'borderBlockStart', 'borderTopRightRadius', 'transformStyle', 'animation', 'marginInlineStart', 'borderInlineStyle', 'fontVariantLigatures', 'borderInlineStartColor', 'borderInlineStart', 'backgroundSize', 'scrollMarginBlockStart', 'borderEndStartRadius', 'backgroundPosition', 'scrollPaddingBlockStart', 'insetInlineEnd', 'borderLeftColor', 'border', 'flexBasis', 'borderInlineEndStyle', 'borderWidth', 'counterIncrement', 'ry', 'contentVisibility', 'background', 'borderCollapse', 'borderBlock', 'offsetRotate', 'animationTimingFunction', 'pad', 'maxBlockSize', 'fontStretch', 'animationDelay', 'speak', 'paddingBottom', 'borderLeftWidth', 'borderImageSource', 'gridRow', 'columnRuleWidth', 'backfaceVisibility', 'flexGrow', 'strokeDashoffset', 'grid', 'scrollbarGutter', 'scrollPaddingInline', 'borderStyle', 'animationIterationCount', 'animationPlayState', 'rubyPosition', 'animationDirection', 'paddingTop', 'pageBreakInside', 'd', 'transform', 'scrollMarginRight', 'bottom', 'overflowX', 'borderTopColor', 'appRegion', 'backgroundColor', 'transitionDuration', 'alignItems', 'borderBlockStartColor', 'borderBlockEnd', 'strokeLinecap', 'borderRightColor', 'scrollMarginTop', 'borderInlineEndWidth', 'backgroundPositionX']"); + }; + 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(Object.getOwnPropertyNames(document.body.style))"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); + EXPECT_EQ(logCalled, true); +} diff --git a/bridge/core/dom/child_node_list.cc b/bridge/core/dom/child_node_list.cc index 112fc34553..63ad81f5b3 100644 --- a/bridge/core/dom/child_node_list.cc +++ b/bridge/core/dom/child_node_list.cc @@ -19,6 +19,18 @@ 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<AtomicString>& 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); diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h index 0918ca1911..ca709740cb 100644 --- a/bridge/core/dom/child_node_list.h +++ b/bridge/core/dom/child_node_list.h @@ -25,6 +25,9 @@ class ChildNodeList : public NodeList { Node* item(unsigned index, ExceptionState& exception_state) const override; + bool NamedPropertyQuery(const AtomicString &key, ExceptionState &exception_state) override; + void NamedPropertyEnumerator(std::vector<AtomicString> &names, ExceptionState &exception_state) override; + // Non-DOM API. void InvalidateCache() { collection_index_cache_.Invalidate(); } ContainerNode& OwnerNode() const { return *parent_.Get(); } diff --git a/bridge/core/dom/collection_index_cache.h b/bridge/core/dom/collection_index_cache.h index 783cdcd609..953f9db3da 100644 --- a/bridge/core/dom/collection_index_cache.h +++ b/bridge/core/dom/collection_index_cache.h @@ -1,7 +1,33 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ +* 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_ @@ -39,7 +65,7 @@ class CollectionIndexCache { unsigned NodeCount(const Collection&); NodeType* NodeAt(const Collection&, unsigned index); - void Invalidate(); + virtual void Invalidate(); void NodeInserted(); void NodeRemoved(); 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 <typename Collection, typename NodeType> +class CollectionItemsCache : public CollectionIndexCache<Collection, NodeType> { + WEBF_DISALLOW_NEW(); + + typedef CollectionIndexCache<Collection, NodeType> 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<Member<NodeType>> cached_list_; +}; + +template <typename Collection, typename NodeType> +CollectionItemsCache<Collection, NodeType>::CollectionItemsCache() : list_valid_(false) {} + +template <typename Collection, typename NodeType> +CollectionItemsCache<Collection, NodeType>::~CollectionItemsCache() = default; + +template <typename Collection, typename NodeType> +void CollectionItemsCache<Collection, NodeType>::Invalidate() { + Base::Invalidate(); + if (list_valid_) { + cached_list_.Shrink(0); + list_valid_ = false; + } +} + +template <class Collection, class NodeType> +unsigned CollectionItemsCache<Collection, NodeType>::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 <typename Collection, typename NodeType> +inline NodeType* CollectionItemsCache<Collection, NodeType>::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/empty_node_list.cc b/bridge/core/dom/empty_node_list.cc index ec56962c65..0d0513ae20 100644 --- a/bridge/core/dom/empty_node_list.cc +++ b/bridge/core/dom/empty_node_list.cc @@ -12,6 +12,12 @@ EmptyNodeList::EmptyNodeList(Node* root_node) : owner_(root_node), NodeList(root 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(); } diff --git a/bridge/core/dom/empty_node_list.h b/bridge/core/dom/empty_node_list.h index 37ea15b020..74a5edcacb 100644 --- a/bridge/core/dom/empty_node_list.h +++ b/bridge/core/dom/empty_node_list.h @@ -11,6 +11,7 @@ namespace webf { class ExceptionState; +class AtomicString; class EmptyNodeList : public NodeList { public: @@ -22,6 +23,8 @@ class EmptyNodeList : public NodeList { 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; diff --git a/bridge/core/dom/live_node_list_base.h b/bridge/core/dom/live_node_list_base.h index dbedd3bf93..86dc9a6d3a 100644 --- a/bridge/core/dom/live_node_list_base.h +++ b/bridge/core/dom/live_node_list_base.h @@ -39,7 +39,7 @@ enum class NodeListSearchRoot { kTreeScope, }; -class LiveNodeListBase : public ScriptWrappable { +class LiveNodeListBase : public GarbageCollected<LiveNodeListBase> { public: explicit LiveNodeListBase(ContainerNode* owner_node, NodeListSearchRoot search_root, @@ -48,8 +48,7 @@ class LiveNodeListBase : public ScriptWrappable { : owner_node_(owner_node), search_root_(static_cast<unsigned>(search_root)), invalidation_type_(invalidation_type), - collection_type_(collection_type), - ScriptWrappable(owner_node->ctx()) { + 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)); diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h index 6264a91eda..62da2168d5 100644 --- a/bridge/core/dom/node_list.h +++ b/bridge/core/dom/node_list.h @@ -12,6 +12,7 @@ namespace webf { class Node; class ExceptionState; +class AtomicString; class NodeList : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); @@ -28,6 +29,9 @@ class NodeList : public ScriptWrappable { 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; } diff --git a/bridge/core/html/html_collection.cc b/bridge/core/html/html_collection.cc deleted file mode 100644 index 50ad620a29..0000000000 --- a/bridge/core/html/html_collection.cc +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -#include "html_collection.h" - -namespace webf {} diff --git a/bridge/core/html/html_collection.d.ts b/bridge/core/html/html_collection.d.ts deleted file mode 100644 index 09b9f274bb..0000000000 --- a/bridge/core/html/html_collection.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {Element} from "../dom/element"; - -interface HTMLCollection { - readonly length: double; - item(index: double): Element | null; - namedItem(name: string): Element | null; -} \ No newline at end of file diff --git a/bridge/core/html/html_collection.h b/bridge/core/html/html_collection.h deleted file mode 100644 index e88e6bd04e..0000000000 --- a/bridge/core/html/html_collection.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_CORE_HTML_HTML_COLLECTION_H_ -#define BRIDGE_CORE_HTML_HTML_COLLECTION_H_ - -#include "bindings/qjs/script_wrappable.h" - -namespace webf { - -class HTMLCollection : public ScriptWrappable { - public: - private: -}; - -} // namespace webf - -#endif // BRIDGE_CORE_HTML_HTML_COLLECTION_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..fe9ce9fb03 --- /dev/null +++ b/bridge/core/html/legacy/html_collection.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 "html_collection.h" +#include "core/dom/container_node.h" + +namespace webf { + +HTMLCollection::HTMLCollection(ContainerNode* base, CollectionType) : base_(base), ScriptWrappable(base->ctx()) {} + +unsigned int HTMLCollection::length() const { + return nodes_.size(); +} + +Element* HTMLCollection::item(unsigned int offset, ExceptionState& exception_state) const { + return nodes_.at(offset); +} + +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&) { + for (int i = 0; i < nodes_.size(); i++) { + names.emplace_back(AtomicString(ctx(), std::to_string(i))); + } +} + +void HTMLCollection::Trace(GCVisitor* visitor) const { + visitor->Trace(base_); + for (auto& node : nodes_) { + node->Trace(visitor); + } +} + +} // namespace webf diff --git a/bridge/core/html/legacy/html_collection.d.ts b/bridge/core/html/legacy/html_collection.d.ts new file mode 100644 index 0000000000..975d0cfdde --- /dev/null +++ b/bridge/core/html/legacy/html_collection.d.ts @@ -0,0 +1,8 @@ +import {Element} from "../../dom/element"; + +interface HTMLCollection { + 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/legacy/html_collection.h b/bridge/core/html/legacy/html_collection.h new file mode 100644 index 0000000000..cb5c91471b --- /dev/null +++ b/bridge/core/html/legacy/html_collection.h @@ -0,0 +1,33 @@ +#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 { + DEFINE_WRAPPERTYPEINFO(); + + 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: + Member<ContainerNode> base_; + std::vector<Element*> nodes_; +}; + +} // namespace webf + +#endif // BRIDGE_CORE_HTML_HTML_COLLECTION_H_ diff --git a/bridge/core/input/touch_list.cc b/bridge/core/input/touch_list.cc index b350a7dee6..e45a5b5d1c 100644 --- a/bridge/core/input/touch_list.cc +++ b/bridge/core/input/touch_list.cc @@ -20,6 +20,18 @@ bool TouchList::SetItem(uint32_t index, Touch* touch, ExceptionState& exception_ } 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 { diff --git a/bridge/core/input/touch_list.h b/bridge/core/input/touch_list.h index b04598cb9c..03f474d1ba 100644 --- a/bridge/core/input/touch_list.h +++ b/bridge/core/input/touch_list.h @@ -18,6 +18,9 @@ class TouchList : public ScriptWrappable { 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: diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index a17dbc909e..d683fe1ac4 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -404,7 +404,11 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { 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'); @@ -412,9 +416,13 @@ export function generateCppSource(blob: IDLBlob, options: GenerateOptions) { wrapperTypeRegisterList.push(`StringPropertyGetterCallback`); if (!object.indexedProp.readonly) { wrapperTypeRegisterList.push(`StringPropertySetterCallback`); + } else { + wrapperTypeRegisterList.push('nullptr'); } - wrapperTypeRegisterList.push('StringPropertyCheckerCallback'); } + + wrapperTypeRegisterList.push('PropertyCheckerCallback'); + wrapperTypeRegisterList.push('PropertyEnumerateCallback'); } let mixinParent = object.mixinParent; diff --git a/bridge/scripts/code_generator/src/json/generator.ts b/bridge/scripts/code_generator/src/json/generator.ts index dc1c25a254..bc927f2118 100644 --- a/bridge/scripts/code_generator/src/json/generator.ts +++ b/bridge/scripts/code_generator/src/json/generator.ts @@ -4,7 +4,6 @@ import _ from 'lodash'; function generateHeader(blob: JSONBlob, template: JSONTemplate, deps?: JSONBlob[]): string { let compiled = _.template(template.raw); - console.log(deps); return compiled({ _: _, name: blob.filename, diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index fd6b19a316..31ee06904b 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -5,6 +5,33 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob <% } %> <% 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) { auto* self = toScriptWrappable<<%= className %>>(obj); @@ -31,16 +58,6 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob } return Converter<<%= generateIDLTypeConverter(object.indexedProp.type, object.indexedProp.optional) %>>::ToValue(ctx, result); }; - bool QJS<%= className %>::StringPropertyCheckerCallback(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; - } <% } %> <% if (!object.indexedProp.readonly) { %> <% if (object.indexedProp.indexKeyType == 'number') { %> diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl index 3e028226df..28f8675b6b 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl @@ -19,11 +19,12 @@ class QJS<%= className %> : public QJSInterfaceBridge<QJS<%= className %>, <%= c <% 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); - static bool StringPropertyCheckerCallback(JSContext* ctx, JSValueConst obj, JSAtom atom); <% } %> <% if (!object.indexedProp.readonly) { %> From 251ea4b83cd43db066d8908759282d18f779fd86 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Wed, 24 Aug 2022 14:38:46 +0800 Subject: [PATCH 172/498] fix: fix enumerate props with Object.keys() --- bridge/bindings/qjs/script_wrappable.cc | 31 +++++++++++++++++-- bridge/bindings/qjs/wrapper_type_info.h | 2 +- .../css/legacy/css_style_declaration_test.cc | 2 +- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index e466a136b4..e920608621 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -155,13 +155,38 @@ void ScriptWrappable::InitializeQuickJSObject() { 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<ScriptWrappable*>(JS_GetOpaque(obj, JSValueGetClassId(obj))); + auto* wrapper_type_info = object->GetWrapperTypeInfo(); + + if (wrapper_type_info->string_property_getter_handler_ != nullptr) { + JSValue returnValue = wrapper_type_info->string_property_getter_handler_(ctx, obj, prop); + if (!JS_IsNull(returnValue)) { + desc->flags = JS_PROP_ENUMERABLE; + desc->value = returnValue; + return true; + } + } + + if (wrapper_type_info->indexed_property_getter_handler_ != nullptr) { + uint32_t index = JS_AtomToUInt32(prop); + JSValue returnValue = wrapper_type_info->indexed_property_getter_handler_(ctx, obj, index); + if (!JS_IsNull(returnValue)) { + desc->flags = JS_PROP_ENUMERABLE; + desc->value = returnValue; + return true; + } + } + + return false; + }; } else { + // Support iterate script wrappable defined properties. exotic_methods->get_own_property_names = HandleJSGetOwnPropertyNames; + exotic_methods->get_own_property = HandleJSGetOwnProperty; } - // Support iterate script wrappable defined properties. - exotic_methods->get_own_property = HandleJSGetOwnProperty; - def.exotic = exotic_methods; def.finalizer = HandleJSObjectFinalized; diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 1469141b93..eec44ed9c7 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -45,7 +45,7 @@ enum { JS_CLASS_DOCUMENT_FRAGMENT, JS_CLASS_BOUNDING_CLIENT_RECT, JS_CLASS_ELEMENT_ATTRIBUTES, - JS_CLASS_HTML_COLLECTION, + JS_CLASS_HTML_ALL_COLLECTION, JS_CLASS_HTML_ELEMENT, JS_CLASS_HTML_DIV_ELEMENT, JS_CLASS_HTML_BODY_ELEMENT, diff --git a/bridge/core/css/legacy/css_style_declaration_test.cc b/bridge/core/css/legacy/css_style_declaration_test.cc index 097cd15bc1..8c29a118ff 100644 --- a/bridge/core/css/legacy/css_style_declaration_test.cc +++ b/bridge/core/css/legacy/css_style_declaration_test.cc @@ -37,7 +37,7 @@ TEST(CSSStyleDeclaration, enumerateStyles) { }); auto context = bridge->GetExecutingContext(); const char* code = - "console.log(Object.getOwnPropertyNames(document.body.style))"; + "console.log(Object.keys(document.body.style))"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); From 4c0af68a46981e82f98762299549cfbcb80e0bdb Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Wed, 3 Aug 2022 21:25:59 +0800 Subject: [PATCH 173/498] feat: support common selectors --- webf/lib/css.dart | 5 +- webf/lib/src/css/element_rule_collector.dart | 70 + webf/lib/src/css/parser/parser.dart | 1241 ++++++++++++++++- webf/lib/src/css/parser/selector.dart | 338 +++++ .../lib/src/css/parser/style_rule_parser.dart | 181 --- .../src/css/parser/style_sheet_parser.dart | 113 -- webf/lib/src/css/parser/token.dart | 89 ++ webf/lib/src/css/parser/token_kind.dart | 597 ++++++++ webf/lib/src/css/parser/tokenizer.dart | 475 +++++++ webf/lib/src/css/parser/tokenizer_base.dart | 444 ++++++ webf/lib/src/css/parser/tree.dart | 107 ++ webf/lib/src/css/parser/visitor.dart | 106 ++ webf/lib/src/css/rule.dart | 3 +- webf/lib/src/css/rule_set.dart | 97 ++ webf/lib/src/css/selector_checker.dart | 236 ++++ webf/lib/src/css/style_declaration.dart | 129 +- webf/lib/src/css/style_rule.dart | 21 +- webf/lib/src/css/style_sheet.dart | 25 +- webf/lib/src/dom/element.dart | 22 +- webf/lib/src/dom/elements/head.dart | 6 +- webf/lib/src/dom/node.dart | 14 + webf/test/src/css/style_rule_parser.dart | 128 +- webf/test/src/css/style_sheet_parser.dart | 63 +- 23 files changed, 4047 insertions(+), 463 deletions(-) create mode 100644 webf/lib/src/css/element_rule_collector.dart create mode 100644 webf/lib/src/css/parser/selector.dart delete mode 100644 webf/lib/src/css/parser/style_rule_parser.dart delete mode 100644 webf/lib/src/css/parser/style_sheet_parser.dart create mode 100644 webf/lib/src/css/parser/token.dart create mode 100644 webf/lib/src/css/parser/token_kind.dart create mode 100644 webf/lib/src/css/parser/tokenizer.dart create mode 100644 webf/lib/src/css/parser/tokenizer_base.dart create mode 100644 webf/lib/src/css/parser/tree.dart create mode 100644 webf/lib/src/css/parser/visitor.dart create mode 100644 webf/lib/src/css/rule_set.dart create mode 100644 webf/lib/src/css/selector_checker.dart diff --git a/webf/lib/css.dart b/webf/lib/css.dart index 9b8a61baf4..851483723a 100644 --- a/webf/lib/css.dart +++ b/webf/lib/css.dart @@ -26,8 +26,7 @@ export 'src/css/style_rule.dart'; export 'src/css/style_declaration.dart'; export 'src/css/style_property.dart'; export 'src/css/style_sheet.dart'; -export 'src/css/parser/style_rule_parser.dart'; -export 'src/css/parser/style_sheet_parser.dart'; +export 'src/css/rule_set.dart'; export 'src/css/parser/parser.dart'; export 'src/css/text.dart'; export 'src/css/origin.dart'; @@ -44,6 +43,8 @@ export 'src/css/value.dart'; export 'src/css/variable.dart'; export 'src/css/keywords.dart'; +export 'src/css/element_rule_collector.dart'; + // CSS Values // ignore: directives_ordering export 'src/css/values/angle.dart'; diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart new file mode 100644 index 0000000000..cc3b95717f --- /dev/null +++ b/webf/lib/src/css/element_rule_collector.dart @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'package:webf/css.dart'; +import 'package:webf/dom.dart'; +import 'package:webf/src/css/selector_checker.dart'; + +class ElementRuleCollector { + + CSSStyleDeclaration collectionFromRuleSet(RuleSet ruleSet, Element element) { + + List<CSSRule> matchedRules = []; + + // #id + String? id = element.id; + if (id != null) { + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.idRules[id], element)); + } + + // .class + for (String className in element.classList) { + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.classRules[className], element)); + } + + // attribute selector + for (String attribute in element.attributes.keys) { + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.attributeRules[attribute], element)); + } + + // tag + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.tagRules[element.tagName], element)); + + // universal + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.universalRules, element)); + + matchedRules.sort((leftRule, rightRule) { + if (leftRule is! CSSStyleRule || rightRule is! CSSStyleRule) { + return 0; + } + return leftRule.selectorGroup.specificity.compareTo(rightRule.selectorGroup.specificity); + }); + + CSSStyleDeclaration declaration = CSSStyleDeclaration(); + for (CSSRule rule in matchedRules.reversed) { + if (rule is CSSStyleRule) { + declaration.merge(rule.declaration); + } + } + return declaration; + } + + List<CSSRule> _collectMatchingRulesForList(List<CSSRule>? rules, Element element) { + if (rules == null) { + return []; + } + List<CSSRule> matchedRules = []; + SelectorEvaluator evaluator = SelectorEvaluator(); + for (CSSRule rule in rules) { + if (rule is! CSSStyleRule) { + continue; + } + if (evaluator.matchSelector(rule.selectorGroup, element)) { + matchedRules.add(rule); + } + } + return matchedRules; + } +} diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 3ce4fd5e93..10a357d3dc 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -1,40 +1,1221 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +import 'dart:math' as math; + import 'package:webf/css.dart'; -const int SLASH_CODE = 47; // / -const int NEWLINE_CODE = 10; // \n -const int SPACE_CODE = 32; // ' ' -const int FEED_CODE = 12; // \f -const int TAB_CODE = 9; // \t -const int CR_CODE = 13; // \r -const int OPEN_CURLY_CODE = 123; // { -const int CLOSE_CURLY_CODE = 125; // } -const int SEMICOLON_CODE = 59; // ; -const int ASTERISK_CODE = 42; // * -const int COLON_CODE = 58; // : -const int OPEN_PARENTHESES_CODE = 40; // ( -const int CLOSE_PARENTHESES_CODE = 41; // ) -const int SINGLE_QUOTE_CODE = 39; // '; -const int DOUBLE_QUOTE_CODE = 34; // " -const int HYPHEN_CODE = 45; // - -const int AT_CODE = 64; // @ -const int DOT_CODE = 46; // . +import 'package:source_span/source_span.dart'; + +part 'tree.dart'; +part 'token.dart'; +part 'token_kind.dart'; +part 'tokenizer.dart'; +part 'tokenizer_base.dart'; +part 'selector.dart'; +part 'visitor.dart'; + +enum ClauseType { + none, + conjunction, + disjunction, +} + +/// Used for parser lookup ahead (used for nested selectors Less support). +class ParserState extends TokenizerState { + final Token peekToken; + final Token? previousToken; + + ParserState(this.peekToken, this.previousToken, Tokenizer tokenizer) : super(tokenizer); +} + +bool get isChecked => true; + +// CSS2.1 pseudo-elements which were defined with a single ':'. +const _legacyPseudoElements = <String>{ + 'after', + 'before', + 'first-letter', + 'first-line', +}; class CSSParser { - static CSSRule? parseRule(String text, {CSSStyleSheet? parentStyleSheet}) { - // TODO: parse other css rule - CSSStyleRule? rule = CSSStyleRuleParser.parse(text); - if (rule != null) { - rule.parentStyleSheet = parentStyleSheet; + final Tokenizer tokenizer; + + /// File containing the source being parsed, used to report errors with + /// source-span locations. + // final SourceFile file; + + Token? _previousToken; + late Token _peekToken; + + CSSParser(String text, {int start = 0}) : tokenizer = Tokenizer(SourceFile.fromString(text), text, true, start) { + _peekToken = tokenizer.next(); + } + + /// Main entry point for parsing an entire CSS file. + CSSStyleSheet parse() { + var ruleSet = RuleSet(); + while (!_maybeEat(TokenKind.END_OF_FILE)) { + final rule = processRule(); + if (rule != null) { + ruleSet.addRule(rule); + } else { + _next(); + } + } + checkEndOfFile(); + return CSSStyleSheet(ruleSet); + } + + List<CSSRule> parseRules() { + var rules = <CSSRule>[]; + while (!_maybeEat(TokenKind.END_OF_FILE)) { + final rule = processRule(); + if (rule != null) { + rules.add(rule); + } else { + _next(); + } + } + checkEndOfFile(); + return rules; + } + + /// Main entry point for parsing a simple selector sequence. + List<Selector> parseSelector() { + var productions = <Selector>[]; + while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) { + var selector = processSelector(); + if (selector != null) { + productions.add(selector); + } else { + break; // Prevent infinite loop if we can't parse something. + } } - return rule; + + checkEndOfFile(); + return productions; } - static List<CSSRule> parseRules(String text, {CSSStyleSheet? parentStyleSheet}) { - // TODO: should ingore style rule in at-rules - return CSSStyleSheetParser.parse(text); + /// Generate an error if [file] has not been completely consumed. + void checkEndOfFile() { + if (!(_peekKind(TokenKind.END_OF_FILE) || _peekKind(TokenKind.INCOMPLETE_COMMENT))) { + _error('premature end of file unknown CSS', _peekToken.span); + } + } + + /// Guard to break out of parser when an unexpected end of file is found. + // TODO(jimhug): Failure to call this method can lead to inifinite parser + // loops. Consider embracing exceptions for more errors to reduce + // the danger here. + bool isPrematureEndOfFile() { + if (_maybeEat(TokenKind.END_OF_FILE)) { + _error('unexpected end of file', _peekToken.span); + return true; + } else { + return false; + } } + + /////////////////////////////////////////////////////////////////// + // Basic support methods + /////////////////////////////////////////////////////////////////// + int _peek() { + return _peekToken.kind; + } + + Token _next({bool unicodeRange = false}) { + final next = _previousToken = _peekToken; + _peekToken = tokenizer.next(unicodeRange: unicodeRange); + return next; + } + + bool _peekKind(int kind) { + return _peekToken.kind == kind; + } + + // Is the next token a legal identifier? This includes pseudo-keywords. + bool _peekIdentifier() { + return TokenKind.isIdentifier(_peekToken.kind); + } + + /// Marks the parser/tokenizer look ahead to support Less nested selectors. + ParserState get _mark => ParserState(_peekToken, _previousToken, tokenizer); + + /// Restores the parser/tokenizer state to state remembered by _mark. + void _restore(ParserState markedData) { + tokenizer.restore(markedData); + _peekToken = markedData.peekToken; + _previousToken = markedData.previousToken; + } + + bool _maybeEat(int kind, {bool unicodeRange = false}) { + if (_peekToken.kind == kind) { + _previousToken = _peekToken; + _peekToken = tokenizer.next(unicodeRange: unicodeRange); + return true; + } else { + return false; + } + } + + void _eat(int kind, {bool unicodeRange = false}) { + if (!_maybeEat(kind, unicodeRange: unicodeRange)) { + _errorExpected(TokenKind.kindToString(kind)); + } + } + + void _errorExpected(String expected) { + var tok = _next(); + String message; + try { + message = 'expected $expected, but found $tok'; + } catch (e) { + message = 'parsing error expected $expected'; + } + _error(message, tok.span); + } + + void _error(String message, SourceSpan? location) { + location ??= _peekToken.span; + // messages.error(message, location); + } + + void _warning(String message, SourceSpan? location) { + location ??= _peekToken.span; + // messages.warning(message, location); + } + + /// Directive grammar: + /// + /// import: '@import' [string | URI] media_list? + /// media: '@media' media_query_list '{' ruleset '}' + /// page: '@page' [':' IDENT]? '{' declarations '}' + /// stylet: '@stylet' IDENT '{' ruleset '}' + /// media_query_list: IDENT [',' IDENT] + /// keyframes: '@-webkit-keyframes ...' (see grammar below). + /// font_face: '@font-face' '{' declarations '}' + /// namespace: '@namespace name url("xmlns") + /// host: '@host '{' ruleset '}' + /// mixin: '@mixin name [(args,...)] '{' declarations/ruleset '}' + /// include: '@include name [(@arg,@arg1)] + /// '@include name [(@arg...)] + /// content: '@content' + /// -moz-document: '@-moz-document' [ <url> | url-prefix(<string>) | + /// domain(<string>) | regexp(<string) ]# '{' + /// declarations + /// '}' + /// supports: '@supports' supports_condition group_rule_body + TreeNode? processDirective() { + var start = _peekToken.span; + + var tokId = _peek(); + final tokenId = tokId as int; + switch (tokenId) { + case TokenKind.DIRECTIVE_IMPORT: + _next(); + return null; + + case TokenKind.DIRECTIVE_MEDIA: + while (!_maybeEat(TokenKind.END_OF_FILE) && !_maybeEat(TokenKind.RBRACE)) { + _next(); + } + return null; + case TokenKind.DIRECTIVE_HOST: + _next(); + + return null; + + case TokenKind.DIRECTIVE_PAGE: + // @page S* IDENT? pseudo_page? + // S* '{' S* + // [ declaration | margin ]? + // [ ';' S* [ declaration | margin ]? ]* '}' S* + // + // pseudo_page : + // ':' [ "left" | "right" | "first" ] + // + // margin : + // margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* + // + // margin_sym : @top-left-corner, @top-left, @bottom-left, etc. + // + // See http://www.w3.org/TR/css3-page/#CSS21 + _next(); + + return null; + case TokenKind.DIRECTIVE_CHARSET: + // @charset S* STRING S* ';' + _next(); + + processQuotedString(false); + + return null; + + // TODO(terry): Workaround Dart2js bug continue not implemented in switch + // see https://code.google.com/p/dart/issues/detail?id=8270 + /* + case TokenKind.DIRECTIVE_MS_KEYFRAMES: + // TODO(terry): For now only IE 10 (are base level) supports @keyframes, + // -moz- has only been optional since Oct 2012 release of Firefox, not + // all versions of webkit support @keyframes and opera doesn't yet + // support w/o -o- prefix. Add more warnings for other prefixes when + // they become optional. + if (isChecked) { + _warning('@-ms-keyframes should be @keyframes'); + } + continue keyframeDirective; + + keyframeDirective: + */ + case TokenKind.DIRECTIVE_KEYFRAMES: + case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES: + case TokenKind.DIRECTIVE_MOZ_KEYFRAMES: + case TokenKind.DIRECTIVE_O_KEYFRAMES: + // TODO(terry): Remove workaround when bug 8270 is fixed. + case TokenKind.DIRECTIVE_MS_KEYFRAMES: + if (tokenId == TokenKind.DIRECTIVE_MS_KEYFRAMES && isChecked) { + // _warning('@-ms-keyframes should be @keyframes'); + } + // TODO(terry): End of workaround. + + // Key frames grammar: + // + // @[browser]? keyframes [IDENT|STRING] '{' keyframes-blocks '}'; + // + // browser: [-webkit-, -moz-, -ms-, -o-] + // + // keyframes-blocks: + // [keyframe-selectors '{' declarations '}']* ; + // + // keyframe-selectors: + // ['from'|'to'|PERCENTAGE] [',' ['from'|'to'|PERCENTAGE] ]* ; + _next(); + + return null; + + case TokenKind.DIRECTIVE_FONTFACE: + _next(); + return null; + case TokenKind.DIRECTIVE_STYLET: + // Stylet grammar: + // + // @stylet IDENT '{' + // ruleset + // '}' + _next(); + + return null; + case TokenKind.DIRECTIVE_NAMESPACE: + // Namespace grammar: + // + // @namespace S* [namespace_prefix S*]? [STRING|URI] S* ';' S* + // namespace_prefix : IDENT + _next(); + + return null; + + case TokenKind.DIRECTIVE_MIXIN: + return null; + + case TokenKind.DIRECTIVE_INCLUDE: + return null; + case TokenKind.DIRECTIVE_CONTENT: + // TODO(terry): TBD + // _warning('@content not implemented.'); + return null; + case TokenKind.DIRECTIVE_MOZ_DOCUMENT: + return null; + case TokenKind.DIRECTIVE_SUPPORTS: + return null; + case TokenKind.DIRECTIVE_VIEWPORT: + case TokenKind.DIRECTIVE_MS_VIEWPORT: + return null; + } + return null; + } + + SourceSpan _makeSpan(FileSpan start) { + // TODO(terry): there are places where we are creating spans before we eat + // the tokens, so using _previousToken is not always valid. + // TODO(nweiz): use < rather than compareTo when SourceSpan supports it. + if (_previousToken == null || _previousToken!.span.compareTo(start) < 0) { + return start; + } + return start.expand(_previousToken!.span); + } + + CSSRule? processRule([SelectorGroup? selectorGroup]) { + if (selectorGroup == null) { + processDirective(); + selectorGroup = processSelectorGroup(); + } + if (selectorGroup != null) { + return CSSStyleRule(selectorGroup, processDeclarations()); + } + return null; + } + + List<CSSRule> processGroupRuleBody() { + var nodes = <CSSRule>[]; + while (!(_peekKind(TokenKind.RBRACE) || _peekKind(TokenKind.END_OF_FILE))) { + var rule = processRule(); + if (rule != null) { + nodes.add(rule); + continue; + } + break; + } + return nodes; + } + + /// Look ahead to see if what should be a declaration is really a selector. + /// If it's a selector than it's a nested selector. This support's Less' + /// nested selector syntax (requires a look ahead). E.g., + /// + /// div { + /// width : 20px; + /// span { + /// color: red; + /// } + /// } + /// + /// Two tag name selectors div and span equivalent to: + /// + /// div { + /// width: 20px; + /// } + /// div span { + /// color: red; + /// } + /// + /// Return [:null:] if no selector or [SelectorGroup] if a selector was + /// parsed. + SelectorGroup? _nestedSelector() { + // var oldMessages = messages; + // _createMessages(); + + var markedData = _mark; + + // Look a head do we have a nested selector instead of a declaration? + var selGroup = processSelectorGroup(); + + var nestedSelector = selGroup != null && _peekKind(TokenKind.LBRACE); + // && messages.messages.isEmpty; + + if (!nestedSelector) { + // Not a selector so restore the world. + _restore(markedData); + // messages = oldMessages; + return null; + } else { + // Remember any messages from look ahead. + // oldMessages.mergeMessages(messages); + // messages = oldMessages; + return selGroup; + } + } + + CSSStyleDeclaration processDeclarations({bool checkBrace = true}) { + var start = _peekToken.span; + + if (checkBrace) _eat(TokenKind.LBRACE); + + var decls = <CSSRule>[]; + + var declaration = CSSStyleDeclaration(); + + do { + var selectorGroup = _nestedSelector(); + while (selectorGroup != null) { + // Nested selector so process as a ruleset. + var ruleset = processRule(selectorGroup)!; + decls.add(ruleset); + selectorGroup = _nestedSelector(); + } + + processDeclaration(declaration); + } while (_maybeEat(TokenKind.SEMICOLON)); + + if (checkBrace) _eat(TokenKind.RBRACE); + + return declaration; + } + + SelectorGroup? processSelectorGroup() { + var selectors = <Selector>[]; + var start = _peekToken.span; + + tokenizer.inSelector = true; + do { + var selector = processSelector(); + if (selector != null) { + selectors.add(selector); + } + } while (_maybeEat(TokenKind.COMMA)); + tokenizer.inSelector = false; + + if (selectors.isNotEmpty) { + return SelectorGroup(selectors); + } + return null; + } + + /// Return list of selectors + Selector? processSelector() { + var simpleSequences = <SimpleSelectorSequence>[]; + var start = _peekToken.span; + while (true) { + // First item is never descendant make sure it's COMBINATOR_NONE. + var selectorItem = simpleSelectorSequence(simpleSequences.isEmpty); + if (selectorItem != null) { + simpleSequences.add(selectorItem); + } else { + break; + } + } + + if (simpleSequences.isEmpty) return null; + + return Selector(simpleSequences); + } + + /// Same as [processSelector] but reports an error for each combinator. + /// + /// This is a quick fix for parsing <compound-selectors> until the parser + /// supports Selector Level 4 grammar: + /// https://drafts.csswg.org/selectors-4/#typedef-compound-selector + Selector? processCompoundSelector() { + var selector = processSelector(); + if (selector != null) { + for (var sequence in selector.simpleSelectorSequences) { + if (!sequence.isCombinatorNone) { + // _error('compound selector can not contain combinator'); + } + } + } + return selector; + } + + SimpleSelectorSequence? simpleSelectorSequence(bool forceCombinatorNone) { + var start = _peekToken.span; + var combinatorType = TokenKind.COMBINATOR_NONE; + var thisOperator = false; + + switch (_peek()) { + case TokenKind.PLUS: + _eat(TokenKind.PLUS); + combinatorType = TokenKind.COMBINATOR_PLUS; + break; + case TokenKind.GREATER: + _eat(TokenKind.GREATER); + combinatorType = TokenKind.COMBINATOR_GREATER; + break; + case TokenKind.TILDE: + _eat(TokenKind.TILDE); + combinatorType = TokenKind.COMBINATOR_TILDE; + break; + case TokenKind.AMPERSAND: + _eat(TokenKind.AMPERSAND); + thisOperator = true; + break; + } + + // Check if WHITESPACE existed between tokens if so we're descendent. + if (combinatorType == TokenKind.COMBINATOR_NONE && !forceCombinatorNone) { + if (_previousToken != null && _previousToken!.end != _peekToken.start) { + combinatorType = TokenKind.COMBINATOR_DESCENDANT; + } + } + + var simpleSel = thisOperator + ? ElementSelector( + ThisOperator(), + ) + : simpleSelector(); + if (simpleSel == null && (combinatorType == TokenKind.COMBINATOR_PLUS || combinatorType == TokenKind.COMBINATOR_GREATER || combinatorType == TokenKind.COMBINATOR_TILDE)) { + // For "+ &", "~ &" or "> &" a selector sequence with no name is needed + // so that the & will have a combinator too. This is needed to + // disambiguate selector expressions: + // .foo&:hover combinator before & is NONE + // .foo & combinator before & is DESCDENDANT + // .foo > & combinator before & is GREATER + simpleSel = ElementSelector(Identifier('')); + } + if (simpleSel != null) { + return SimpleSelectorSequence(simpleSel, combinatorType); + } + return null; + } + + /// Simple selector grammar: + /// + /// simple_selector_sequence + /// : [ type_selector | universal ] + /// [ HASH | class | attrib | pseudo | negation ]* + /// | [ HASH | class | attrib | pseudo | negation ]+ + /// type_selector + /// : [ namespace_prefix ]? element_name + /// namespace_prefix + /// : [ IDENT | '*' ]? '|' + /// element_name + /// : IDENT + /// universal + /// : [ namespace_prefix ]? '*' + /// class + /// : '.' IDENT + SimpleSelector? simpleSelector() { + // TODO(terry): Natalie makes a good point parsing of namespace and element + // are essentially the same (asterisk or identifier) other + // than the error message for element. Should consolidate the + // code. + // TODO(terry): Need to handle attribute namespace too. + dynamic first; + var start = _peekToken.span; + switch (_peek()) { + case TokenKind.ASTERISK: + // Mark as universal namespace. + var tok = _next(); + first = Wildcard(); + break; + case TokenKind.IDENTIFIER: + first = identifier(); + break; + default: + // Expecting simple selector. + // TODO(terry): Could be a synthesized token like value, etc. + if (TokenKind.isKindIdentifier(_peek())) { + first = identifier(); + } else if (_peekKind(TokenKind.SEMICOLON)) { + // Can't be a selector if we found a semi-colon. + return null; + } + break; + } + + if (_maybeEat(TokenKind.NAMESPACE)) { + _next(); + return null; + } else if (first != null) { + return ElementSelector(first); + } else { + // Check for HASH | class | attrib | pseudo | negation + return simpleSelectorTail(); + } + } + + bool _anyWhiteSpaceBeforePeekToken(int kind) { + if (_previousToken != null && _previousToken!.kind == kind) { + // If end of previous token isn't same as the start of peek token then + // there's something between these tokens probably whitespace. + return _previousToken!.end != _peekToken.start; + } + + return false; + } + + /// type_selector | universal | HASH | class | attrib | pseudo + SimpleSelector? simpleSelectorTail() { + // Check for HASH | class | attrib | pseudo | negation + var start = _peekToken.span; + switch (_peek()) { + case TokenKind.HASH: + _eat(TokenKind.HASH); + + if (_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { + // _error('Not a valid ID selector expected #id'); + return null; + } + return IdSelector(identifier()); + case TokenKind.DOT: + _eat(TokenKind.DOT); + + if (_anyWhiteSpaceBeforePeekToken(TokenKind.DOT)) { + // _error('Not a valid class selector expected .className'); + return null; + } + return ClassSelector(identifier()); + case TokenKind.COLON: + // :pseudo-class ::pseudo-element + return processPseudoSelector(start); + case TokenKind.LBRACK: + return processAttribute(); + case TokenKind.DOUBLE: + _error('name must start with a alpha character, but found a number', _peekToken.span); + _next(); + break; + } + return null; + } + + SimpleSelector? processPseudoSelector(FileSpan start) { + // :pseudo-class ::pseudo-element + // TODO(terry): '::' should be token. + _eat(TokenKind.COLON); + var pseudoElement = _maybeEat(TokenKind.COLON); + + // TODO(terry): If no identifier specified consider optimizing out the + // : or :: and making this a normal selector. For now, + // create an empty pseudoName. + Identifier pseudoName; + if (_peekIdentifier()) { + pseudoName = identifier(); + } else { + return null; + } + var name = pseudoName.name.toLowerCase(); + + // Functional pseudo? + if (_peekToken.kind == TokenKind.LPAREN) { + if (!pseudoElement && name == 'not') { + _eat(TokenKind.LPAREN); + + // Negation : ':NOT(' S* negation_arg S* ')' + var negArg = simpleSelector(); + + _eat(TokenKind.RPAREN); + return NegationSelector(negArg); + } else if (!pseudoElement && (name == 'host' || name == 'host-context' || name == 'global-context' || name == '-acx-global-context')) { + _eat(TokenKind.LPAREN); + var selector = processCompoundSelector(); + if (selector == null) { + _errorExpected('a selector argument'); + return null; + } + _eat(TokenKind.RPAREN); + return PseudoClassFunctionSelector(pseudoName, selector); + } else { + // Special parsing for expressions in pseudo functions. Minus is used + // as operator not identifier. + // TODO(jmesserly): we need to flip this before we eat the "(" as the + // next token will be fetched when we do that. I think we should try to + // refactor so we don't need this boolean; it seems fragile. + tokenizer.inSelectorExpression = true; + _eat(TokenKind.LPAREN); + + // Handle function expression. + var expr = processSelectorExpression(); + + tokenizer.inSelectorExpression = false; + + // Used during selector look-a-head if not a SelectorExpression is + // bad. + _eat(TokenKind.RPAREN); + return (pseudoElement) ? PseudoElementFunctionSelector(pseudoName, expr) : PseudoClassFunctionSelector(pseudoName, expr); + } + } + + // Treat CSS2.1 pseudo-elements defined with pseudo class syntax as pseudo- + // elements for backwards compatibility. + return pseudoElement || _legacyPseudoElements.contains(name) ? PseudoElementSelector(pseudoName, isLegacy: !pseudoElement) : PseudoClassSelector(pseudoName); + } + + /// In CSS3, the expressions are identifiers, strings, or of the form "an+b". + /// + /// : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ + /// + /// num [0-9]+|[0-9]*\.[0-9]+ + /// PLUS '+' + /// DIMENSION {num}{ident} + /// NUMBER {num} + List<String> /* SelectorExpression | LiteralTerm */ processSelectorExpression() { + var start = _peekToken.span; + + var expressions = <String>[]; + + Token? termToken; + + var keepParsing = true; + while (keepParsing) { + switch (_peek()) { + case TokenKind.PLUS: + case TokenKind.MINUS: + case TokenKind.INTEGER: + case TokenKind.DOUBLE: + termToken = _next(); + expressions.add(termToken.text); + break; + case TokenKind.SINGLE_QUOTE: + final value = processQuotedString(false); + return ["'${_escapeString(value as String, single: true)}'"]; + case TokenKind.DOUBLE_QUOTE: + final value = processQuotedString(false); + return ['"${_escapeString(value as String)}"']; + case TokenKind.IDENTIFIER: + final value = identifier(); // Snarf up the ident we'll remap, maybe. + expressions.add(value.name); + break; + default: + keepParsing = false; + } + } + + return expressions; + } + + // Attribute grammar: + // + // attributes : + // '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']' + // + // ATTRIB_MATCHES : + // [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ] + // + // INCLUDES: '~=' + // + // DASHMATCH: '|=' + // + // PREFIXMATCH: '^=' + // + // SUFFIXMATCH: '$=' + // + // SUBSTRMATCH: '*=' + AttributeSelector? processAttribute() { + var start = _peekToken.span; + + if (_maybeEat(TokenKind.LBRACK)) { + var attrName = identifier(); + + int op; + switch (_peek()) { + case TokenKind.EQUALS: + case TokenKind.INCLUDES: // ~= + case TokenKind.DASH_MATCH: // |= + case TokenKind.PREFIX_MATCH: // ^= + case TokenKind.SUFFIX_MATCH: // $= + case TokenKind.SUBSTRING_MATCH: // *= + op = _peek(); + _next(); + break; + default: + op = TokenKind.NO_MATCH; + } + + dynamic value; + if (op != TokenKind.NO_MATCH) { + // Operator hit so we require a value too. + if (_peekIdentifier()) { + value = identifier(); + } else { + value = processQuotedString(false); + } + + if (value == null) { + _error('expected attribute value string or ident', _peekToken.span); + } + } + + _eat(TokenKind.RBRACK); + + return AttributeSelector(attrName, op, value); + } + return null; + } + + // Declaration grammar: + // + // declaration: property ':' expr prio? + // + // property: IDENT [or IE hacks] + // prio: !important + // expr: (see processExpr) + // + // Here are the ugly IE hacks we need to support: + // property: expr prio? \9; - IE8 and below property, /9 before semi-colon + // *IDENT - IE7 or below + // _IDENT - IE6 property (automatically a valid ident) + void processDeclaration(CSSStyleDeclaration style) { + // IDENT ':' expr '!important'? + if (TokenKind.isIdentifier(_peekToken.kind)) { + var propertyIdent = identifier().name; + + var resetProperty = false; + var keepGoing = true; + while (keepGoing) { + switch (_peek()) { + case TokenKind.COLON: + _eat(TokenKind.COLON); + keepGoing = false; + break; + case TokenKind.SEMICOLON: + case TokenKind.NEWLINE: + resetProperty = true; + _next(); + break; + case TokenKind.IDENTIFIER: + if (resetProperty) { + propertyIdent = identifier().name; + } + break; + default: + keepGoing = false; + } + } + + var expr = processExpr().join(' '); + + // Handle !important (prio) + var importantPriority = _maybeEat(TokenKind.IMPORTANT); + style.setProperty(propertyIdent, expr, importantPriority); + } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) { + _next(); + } else if (_peekToken.kind == TokenKind.DIRECTIVE_INCLUDE) { + // TODO @include mixinName in the declaration area. + } else if (_peekToken.kind == TokenKind.DIRECTIVE_EXTEND) { + _next(); + } + } + + // Expression grammar: + // + // expression: term [ operator? term]* + // + // operator: '/' | ',' + // term: (see processTerm) + List<String> processExpr([bool ieFilter = false]) { + var start = _peekToken.span; + + var keepGoing = true; + List<String> result = []; + String? expr; + while (keepGoing && (expr = processTerm()) != null) { + Expression? op; + + var opStart = _peekToken.span; + + switch (_peek()) { + case TokenKind.SLASH: + op = OperatorSlash(); + break; + case TokenKind.COMMA: + op = OperatorComma(); + break; + case TokenKind.BACKSLASH: + _next(); + if (_peekKind(TokenKind.INTEGER)) { + _next(); + if (isChecked) { + _warning('\$value is not valid in an expression', _makeSpan(start)); + } + } + break; + } + + if (expr != null) { + result.add(expr); + } else { + keepGoing = false; + } + + if (op != null) { + result.add(_makeSpan(opStart).text); + _next(); + } + } + return result; + } + + String? processTerm() { + var start = _peekToken.span; + Token? t; // token for term's value + dynamic value; // value of term (numeric values) + + var unary = ''; + switch (_peek()) { + case TokenKind.HASH: + _eat(TokenKind.HASH); + if (!_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { + String? hexText; + if (_peekKind(TokenKind.INTEGER)) { + var hexText1 = _peekToken.text; + _next(); + // Append identifier only if there's no delimiting whitespace. + if (_peekIdentifier() && _previousToken!.end == _peekToken.start) { + hexText = '$hexText1${identifier().name}'; + } else { + hexText = hexText1; + } + } else if (_peekIdentifier()) { + hexText = identifier().name; + } + return '#$hexText'; + } + + if (isChecked) { + _warning('Expected hex number', _makeSpan(start)); + } + // Construct the bad hex value with a #<space>number. + return start.text; + case TokenKind.INTEGER: + case TokenKind.DOUBLE: + t = _next(); + return '$unary${t.text}'; + case TokenKind.SINGLE_QUOTE: + value = processQuotedString(false); + value = "'${_escapeString(value as String, single: true)}'"; + return value; + case TokenKind.DOUBLE_QUOTE: + value = processQuotedString(false); + value = '"${_escapeString(value as String)}"'; + return value; + case TokenKind.LPAREN: + _next(); + return null; + case TokenKind.LBRACK: + _next(); + return null; + case TokenKind.IDENTIFIER: + var nameValue = identifier(); // Snarf up the ident we'll remap, maybe. + + if (_maybeEat(TokenKind.LPAREN)) { + return processFunction(nameValue); + } + return nameValue.name; + case TokenKind.UNICODE_RANGE: + String? first; + String? second; + int firstNumber; + int secondNumber; + _eat(TokenKind.UNICODE_RANGE, unicodeRange: true); + if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { + first = _previousToken!.text; + firstNumber = int.parse('0x$first'); + if (firstNumber > MAX_UNICODE) { + _error('unicode range must be less than 10FFFF', _makeSpan(start)); + } + if (_maybeEat(TokenKind.MINUS, unicodeRange: true)) { + if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { + second = _previousToken!.text; + secondNumber = int.parse('0x$second'); + if (secondNumber > MAX_UNICODE) { + _error('unicode range must be less than 10FFFF', _makeSpan(start)); + } + if (firstNumber > secondNumber) { + _error('unicode first range can not be greater than last', _makeSpan(start)); + } + } + } + } else if (_maybeEat(TokenKind.HEX_RANGE, unicodeRange: true)) { + first = _previousToken!.text; + } + return 'U+$first-$second'; + case TokenKind.AT: + break; + } + + return null; + } + + static const int MAX_UNICODE = 0x10FFFF; + + String processQuotedString([bool urlString = false]) { + var start = _peekToken.span; + + // URI term sucks up everything inside of quotes(' or ") or between parens + var stopToken = urlString ? TokenKind.RPAREN : -1; + + // Note: disable skipping whitespace tokens inside a string. + // TODO(jmesserly): the layering here feels wrong. + var inString = tokenizer._inString; + tokenizer._inString = false; + + switch (_peek()) { + case TokenKind.SINGLE_QUOTE: + stopToken = TokenKind.SINGLE_QUOTE; + _next(); // Skip the SINGLE_QUOTE. + start = _peekToken.span; + break; + case TokenKind.DOUBLE_QUOTE: + stopToken = TokenKind.DOUBLE_QUOTE; + _next(); // Skip the DOUBLE_QUOTE. + start = _peekToken.span; + break; + default: + if (urlString) { + if (_peek() == TokenKind.LPAREN) { + _next(); // Skip the LPAREN. + start = _peekToken.span; + } + stopToken = TokenKind.RPAREN; + } else { + // _error('unexpected string'); + } + break; + } + + // Gobble up everything until we hit our stop token. + var stringValue = StringBuffer(); + while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) { + stringValue.write(_next().text); + } + + tokenizer._inString = inString; + + // All characters between quotes is the string. + if (stopToken != TokenKind.RPAREN) { + _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE; + } + + return stringValue.toString(); + } + + // TODO(terry): Hack to gobble up the calc expression as a string looking + // for the matching RPAREN the expression is not parsed into the + // AST. + // + // grammar should be: + // + // <calc()> = calc( <calc-sum> ) + // <calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]* + // <calc-product> = <calc-value> [ '*' <calc-value> | '/' <number> ]* + // <calc-value> = <number> | <dimension> | <percentage> | ( <calc-sum> ) + // + String processCalcExpression() { + var inString = tokenizer._inString; + tokenizer._inString = false; + + // Gobble up everything until we hit our stop token. + var stringValue = StringBuffer(); + var left = 1; + var matchingParens = false; + while (_peek() != TokenKind.END_OF_FILE && !matchingParens) { + var token = _peek(); + if (token == TokenKind.LPAREN) { + left++; + } else if (token == TokenKind.RPAREN) { + left--; + } + + matchingParens = left == 0; + if (!matchingParens) stringValue.write(_next().text); + } + + if (!matchingParens) { + _error('problem parsing function expected ), ', _peekToken.span); + } + + tokenizer._inString = inString; + + return stringValue.toString(); + } + + // Function grammar: + // + // function: IDENT '(' expr ')' + // + dynamic processFunction(Identifier func) { + var start = _peekToken.span; + var name = func.name; + + switch (name) { + case 'rgb': + var expr = processExpr(); + if (!_maybeEat(TokenKind.RPAREN)) { + _error('problem parsing function expected ), ', _peekToken.span); + } + return 'rgb(${expr.join()})'; + case 'url': + // URI term sucks up everything inside of quotes(' or ") or between + // parens. + var urlParam = processQuotedString(true); + + // TODO(terry): Better error message and checking for mismatched quotes. + if (_peek() == TokenKind.END_OF_FILE) { + _error('problem parsing URI', _peekToken.span); + } + + if (_peek() == TokenKind.RPAREN) { + _next(); + } + + return 'url($urlParam)'; + case 'var': + // TODO(terry): Consider handling var in IE specific filter/progid. + // This will require parsing entire IE specific syntax + // e.g. `param = value` or `progid:com_id`, etc. + // for example: + // + // var-blur: Blur(Add = 0, Direction = 225, Strength = 10); + // var-gradient: progid:DXImageTransform.Microsoft.gradient" + // (GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); + var expr = processExpr(); + if (!_maybeEat(TokenKind.RPAREN)) { + _error('problem parsing var expected ), ', _peekToken.span); + } + return expr; + default: + var expr = processExpr(); + if (!_maybeEat(TokenKind.RPAREN)) { + _error('problem parsing function expected ), ', _peekToken.span); + } + return expr.join(); + } + } + + Identifier identifier() { + var tok = _next(); + + if (!TokenKind.isIdentifier(tok.kind) && !TokenKind.isKindIdentifier(tok.kind)) { + if (isChecked) { + _warning('expected identifier, but found $tok', tok.span); + } + return Identifier(''); + } + return Identifier(tok.text); + } +} + +/// Escapes [text] for use in a CSS string. +/// [single] specifies single quote `'` vs double quote `"`. +String _escapeString(String text, {bool single = false}) { + StringBuffer? result; + + for (var i = 0; i < text.length; i++) { + var code = text.codeUnitAt(i); + String? replace; + switch (code) { + case 34 /*'"'*/ : + if (!single) replace = r'\"'; + break; + case 39 /*"'"*/ : + if (single) replace = r"\'"; + break; + } + + if (replace != null && result == null) { + result = StringBuffer(text.substring(0, i)); + } + + if (result != null) result.write(replace ?? text[i]); + } + + return result == null ? text : result.toString(); } diff --git a/webf/lib/src/css/parser/selector.dart b/webf/lib/src/css/parser/selector.dart new file mode 100644 index 0000000000..a67bd73527 --- /dev/null +++ b/webf/lib/src/css/parser/selector.dart @@ -0,0 +1,338 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +const kIdSpecificity = 0x010000; +const kClassLikeSpecificity = 0x000100; +const kTagSpecificity = 0x000001; + +// https://drafts.csswg.org/cssom/#parse-a-group-of-selectors +class SelectorGroup extends TreeNode { + final List<Selector> selectors; + + int _specificity = -1; + + int get specificity { + if (_specificity == -1) { + _specificity = _calcSpecificity(); + } + return _specificity; + } + + SelectorGroup(this.selectors) : super(); + + @override + SelectorGroup clone() => SelectorGroup(selectors); + + @override + dynamic visit(Visitor visitor) => visitor.visitSelectorGroup(this); + + int _calcSpecificity() { + int specificity = 0; + for (final selector in selectors) { + for (final simpleSelectorSequence in selector.simpleSelectorSequences) { + final simpleSelector = simpleSelectorSequence.simpleSelector; + switch (simpleSelector.runtimeType) { + case IdSelector: + specificity += kIdSpecificity; + break; + case ClassSelector: + specificity += kClassLikeSpecificity; + break; + case ElementSelector: + specificity += kTagSpecificity; + break; + } + } + } + return specificity; + } +} + +class Selector extends TreeNode { + final List<SimpleSelectorSequence> simpleSelectorSequences; + + Selector(this.simpleSelectorSequences) : super(); + + void add(SimpleSelectorSequence seq) => simpleSelectorSequences.add(seq); + + int get length => simpleSelectorSequences.length; + + @override + Selector clone() { + var simpleSequences = simpleSelectorSequences.map((ss) => ss.clone()).toList(); + + return Selector(simpleSequences); + } + + @override + dynamic visit(Visitor visitor) => visitor.visitSelector(this); +} + +class SimpleSelectorSequence extends TreeNode { + /// +, >, ~, NONE + int combinator; + final SimpleSelector simpleSelector; + + SimpleSelectorSequence(this.simpleSelector, [int combinator = TokenKind.COMBINATOR_NONE]) + : combinator = combinator, + super(); + + bool get isCombinatorNone => combinator == TokenKind.COMBINATOR_NONE; + bool get isCombinatorPlus => combinator == TokenKind.COMBINATOR_PLUS; + bool get isCombinatorGreater => combinator == TokenKind.COMBINATOR_GREATER; + bool get isCombinatorTilde => combinator == TokenKind.COMBINATOR_TILDE; + bool get isCombinatorDescendant => combinator == TokenKind.COMBINATOR_DESCENDANT; + + String get _combinatorToString { + switch (combinator) { + case TokenKind.COMBINATOR_DESCENDANT: + return ' '; + case TokenKind.COMBINATOR_GREATER: + return ' > '; + case TokenKind.COMBINATOR_PLUS: + return ' + '; + case TokenKind.COMBINATOR_TILDE: + return ' ~ '; + default: + return ''; + } + } + + @override + SimpleSelectorSequence clone() => SimpleSelectorSequence(simpleSelector, combinator); + + @override + dynamic visit(Visitor visitor) => visitor.visitSimpleSelectorSequence(this); + + @override + String toString() => simpleSelector.name; +} + +// All other selectors (element, #id, .class, attribute, pseudo, negation, +// namespace, *) are derived from this selector. +abstract class SimpleSelector extends TreeNode { + final dynamic _name; // ThisOperator, Identifier, Negation, others? + + SimpleSelector(this._name) : super(); + + // TOOD(srawlins): Figure this one out. + // ignore: avoid_dynamic_calls + String get name => _name.name as String; + + bool get isThis => _name is ThisOperator; + + @override + dynamic visit(Visitor visitor) => visitor.visitSimpleSelector(this); +} + +// element name +class ElementSelector extends SimpleSelector { + ElementSelector(name) : super(name); + + @override + ElementSelector clone() => ElementSelector(_name); + + @override + String toString() => name; + + @override + dynamic visit(Visitor visitor) => visitor.visitElementSelector(this); +} + +// [attr op value] +class AttributeSelector extends SimpleSelector { + final int _op; + final dynamic value; + + AttributeSelector(Identifier name, this._op, this.value) : super(name); + + int get operatorKind => _op; + + String? matchOperator() { + switch (_op) { + case TokenKind.EQUALS: + return '='; + case TokenKind.INCLUDES: + return '~='; + case TokenKind.DASH_MATCH: + return '|='; + case TokenKind.PREFIX_MATCH: + return '^='; + case TokenKind.SUFFIX_MATCH: + return '\$='; + case TokenKind.SUBSTRING_MATCH: + return '*='; + case TokenKind.NO_MATCH: + return ''; + } + return null; + } + + // Return the TokenKind for operator used by visitAttributeSelector. + String? matchOperatorAsTokenString() { + switch (_op) { + case TokenKind.EQUALS: + return 'EQUALS'; + case TokenKind.INCLUDES: + return 'INCLUDES'; + case TokenKind.DASH_MATCH: + return 'DASH_MATCH'; + case TokenKind.PREFIX_MATCH: + return 'PREFIX_MATCH'; + case TokenKind.SUFFIX_MATCH: + return 'SUFFIX_MATCH'; + case TokenKind.SUBSTRING_MATCH: + return 'SUBSTRING_MATCH'; + } + return null; + } + + String valueToString() { + if (value != null) { + if (value is Identifier) { + return value.toString(); + } else { + return '"$value"'; + } + } else { + return ''; + } + } + + @override + AttributeSelector clone() => AttributeSelector(_name as Identifier, _op, value); + + @override + String toString() => '[$name${matchOperator()}${valueToString()}]'; + + @override + dynamic visit(Visitor visitor) => visitor.visitAttributeSelector(this); +} + +// #id +class IdSelector extends SimpleSelector { + IdSelector(Identifier name) : super(name); + @override + IdSelector clone() => IdSelector(_name as Identifier); + + @override + String toString() => '#$_name'; + + @override + dynamic visit(Visitor visitor) => visitor.visitIdSelector(this); +} + +// .class +class ClassSelector extends SimpleSelector { + ClassSelector(Identifier name) : super(name); + @override + ClassSelector clone() => ClassSelector(_name as Identifier); + @override + String toString() => '.$_name'; + + @override + dynamic visit(Visitor visitor) => visitor.visitClassSelector(this); +} + +// :pseudoClass +class PseudoClassSelector extends SimpleSelector { + PseudoClassSelector(Identifier name) : super(name); + + @override + PseudoClassSelector clone() => PseudoClassSelector(_name as Identifier); + + @override + String toString() => ':$name'; + + @override + dynamic visit(Visitor visitor) => visitor.visitPseudoClassSelector(this); +} + +// ::pseudoElement +class PseudoElementSelector extends SimpleSelector { + // If true, this is a CSS2.1 pseudo-element with only a single ':'. + final bool isLegacy; + + PseudoElementSelector(Identifier name, {this.isLegacy = false}) : super(name); + + @override + PseudoElementSelector clone() => PseudoElementSelector(_name as Identifier); + + @override + String toString() => "${isLegacy ? ':' : '::'}$name"; + + @override + dynamic visit(Visitor visitor) => visitor.visitPseudoElementSelector(this); +} + +// :pseudoClassFunction(argument) +class PseudoClassFunctionSelector extends PseudoClassSelector { + final dynamic argument; // Selector, List<String> + + PseudoClassFunctionSelector(Identifier name, this.argument) : super(name); + + @override + PseudoClassFunctionSelector clone() => PseudoClassFunctionSelector(_name as Identifier, argument); + + Selector get selector => argument as Selector; + + List<String> get expression => argument as List<String>; + + @override + dynamic visit(Visitor visitor) => visitor.visitPseudoClassFunctionSelector(this); +} + +// ::pseudoElementFunction(expression) +class PseudoElementFunctionSelector extends PseudoElementSelector { + final List<String> expression; + + PseudoElementFunctionSelector(Identifier name, this.expression) : super(name); + + @override + PseudoElementFunctionSelector clone() => PseudoElementFunctionSelector(_name as Identifier, expression); + + @override + dynamic visit(Visitor visitor) => visitor.visitPseudoElementFunctionSelector(this); +} + +// :NOT(negation_arg) +class NegationSelector extends SimpleSelector { + final SimpleSelector? negationArg; + + NegationSelector(this.negationArg) : super(Negation()); + + @override + NegationSelector clone() => NegationSelector(negationArg); + + @override + dynamic visit(Visitor visitor) => visitor.visitNegationSelector(this); +} diff --git a/webf/lib/src/css/parser/style_rule_parser.dart b/webf/lib/src/css/parser/style_rule_parser.dart deleted file mode 100644 index f396cc5044..0000000000 --- a/webf/lib/src/css/parser/style_rule_parser.dart +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ -import 'package:webf/css.dart'; - -const int _SELECTOR = 0; -const int _NAME = 1; -const int _VALUE = 2; -const int _FUNCTION = 3; -const int _END = 4; - -const String _END_OF_COMMENT = '*/'; -const String _EMPTY_STRING = ''; - -class CSSStyleRuleParser { - static CSSStyleRule? parse(String ruleText) { - String selectorText = _EMPTY_STRING; - Map<String, String> style = {}; - - StringBuffer buffer = StringBuffer(); - int state = _SELECTOR; - String propertyName = _EMPTY_STRING; - bool isString = false; - bool isCustomProperty = false; - - for (int pos = 0, length = ruleText.length; pos < length && state != _END; pos++) { - int c = ruleText.codeUnitAt(pos); - - if (c == SINGLE_QUOTE_CODE || c == DOUBLE_QUOTE_CODE) { - isString = !isString; - } - - if (isString) { - buffer.writeCharCode(c); - continue; - } - - switch (c) { - case SPACE_CODE: - case TAB_CODE: - case CR_CODE: - case FEED_CODE: - case NEWLINE_CODE: - if ((state == _SELECTOR || state == _VALUE) && pos > 0) { - // Squash 2 or more white-spaces in the row into 1 space. - switch (ruleText.codeUnitAt(pos - 1)) { - case SPACE_CODE: - case TAB_CODE: - case CR_CODE: - case FEED_CODE: - case NEWLINE_CODE: - break; - default: - buffer.writeCharCode(SPACE_CODE); - break; - } - } else if (state == _FUNCTION) { - buffer.writeCharCode(c); - } - break; - case HYPHEN_CODE: - if (state == _NAME && isCustomProperty == false) { - int letter = ruleText.codeUnitAt(pos + 1); - if (letter == HYPHEN_CODE) { - // Ignore css custom properties: `--main-bg-color`. - buffer.writeCharCode(c); - isCustomProperty = true; - break; - } - // Convert background-image to backgroundImage - // a-z: 97-122 - if (letter > 96 && letter < 123) { - // Convert to upper case: A-Z: 65-90 - letter = letter - 32; - buffer.writeCharCode(letter); - pos++; - } - } else { - buffer.writeCharCode(c); - } - break; - case SLASH_CODE: - if (ruleText.codeUnitAt(pos + 1) == ASTERISK_CODE) { - // This is a comment, find the end of the comment. - pos += 2; - int index = ruleText.indexOf(_END_OF_COMMENT, pos); - if (index == -1) { - // Unterminated comment - state = _END; - } else { - pos = index + 2; - } - } else { - buffer.writeCharCode(c); - } - break; - case OPEN_CURLY_CODE: - if (state == _SELECTOR) { - selectorText = buffer.toString().trim(); - if (selectorText.isEmpty) { - // Invalid syntax - state = _END; - } - buffer.clear(); - state = _NAME; - } else { - // Unexpected { : `.foo { .foo {}; color: red}` - buffer.writeCharCode(c); - } - break; - case COLON_CODE: - if (state == _NAME) { - propertyName = buffer.toString().trim(); - buffer.clear(); - // Reset isCustomProperty flag. - isCustomProperty = false; - state = _VALUE; - } else { - buffer.writeCharCode(c); - } - break; - case CLOSE_PARENTHESES_CODE: - if (state == _FUNCTION) { - buffer.writeCharCode(c); - state = _VALUE; - } else { - buffer.writeCharCode(c); - } - break; - case OPEN_PARENTHESES_CODE: - // This is a function, find the end of the function. - if (state == _VALUE) { - state = _FUNCTION; - } - - // Pseudo-class selector: `th:nth-child(4)` - // Function value: `url()`, `rgb()` - buffer.writeCharCode(c); // Write ( - break; - case SEMICOLON_CODE: - if (state == _FUNCTION) { - // In data uri function - buffer.writeCharCode(c); - } else { - // `{ col;or: red; }` will parsed as {col: '', or: 'red'} - if (propertyName.isNotEmpty) { - String value = buffer.toString().trim(); - if (value.isNotEmpty) style[propertyName] = value; - propertyName = _EMPTY_STRING; - } - buffer.clear(); - // Skip empty property declaration like `color: red; ;;`. - state = _NAME; - } - break; - case CLOSE_CURLY_CODE: - if (state == _VALUE && propertyName.isNotEmpty) { - // `body { color: red }` that not end with semicolon is - // also the end of the declaration. - style[propertyName] = buffer.toString().trim(); - state = _END; - } else if (state == _NAME) { - // `.foo { .foo {}; color: red }` - buffer.writeCharCode(c); - } else { - // Unexpected } : `.fo } { color: red }` - state = _END; - } - break; - default: - buffer.writeCharCode(c); - } - } - - if (selectorText.isNotEmpty) { - return CSSStyleRule(selectorText, style); - } - return null; - } -} diff --git a/webf/lib/src/css/parser/style_sheet_parser.dart b/webf/lib/src/css/parser/style_sheet_parser.dart deleted file mode 100644 index 04e37c8aeb..0000000000 --- a/webf/lib/src/css/parser/style_sheet_parser.dart +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ -import 'package:webf/css.dart'; - -const int _BEFORE_SELECTOR = 0; -const int _SELECTOR = 1; -const int _BEFORE_NAME = 2; -const int _NAME = 3; -const int _VALUE = 4; - -final RegExp _classSelectorRegExp = RegExp(r'^\s*.[-_a-zA-Z][-_a-zA-Z0-9]*\s*$'); - -class CSSStyleSheetParser { - static List<CSSRule> parse(String sheetText) { - final List<CSSRule> rules = <CSSRule>[]; - StringBuffer buffer = StringBuffer(); - int state = _BEFORE_SELECTOR; - for (int pos = 0, length = sheetText.length; pos < length; pos++) { - int c = sheetText.codeUnitAt(pos); - switch (c) { - case DOT_CODE: - // Current only support single class selector: `.red`. - int code = sheetText.codeUnitAt(pos + 1); - // `.` must followed by `-`, `_`, `a-z`, `A-Z`. - if (state == _BEFORE_SELECTOR && - (code == 45 || code == 95 || (code < 123 && code > 96) || (code < 91 && code > 64))) { - state = _SELECTOR; - buffer.writeCharCode(c); - } else if (state != _BEFORE_SELECTOR) { - buffer.writeCharCode(c); - } - break; - case SPACE_CODE: - case TAB_CODE: - case CR_CODE: - case FEED_CODE: - case NEWLINE_CODE: - if (state != _BEFORE_SELECTOR && (state == _SELECTOR || state == _VALUE) && pos > 0) { - // Squash 2 or more white-spaces in the row into 1 space. - switch (sheetText.codeUnitAt(pos - 1)) { - case SPACE_CODE: - case TAB_CODE: - case CR_CODE: - case FEED_CODE: - case NEWLINE_CODE: - break; - default: - buffer.writeCharCode(SPACE_CODE); - break; - } - } - break; - case SEMICOLON_CODE: - if (state != _BEFORE_SELECTOR && state == _VALUE || state == _NAME) { - if (state == _NAME) { - state = _BEFORE_NAME; - } - buffer.writeCharCode(c); - } - break; - case OPEN_CURLY_CODE: - if (state == _SELECTOR) { - String selector = buffer.toString(); - // Only support single class selector now. - state = _classSelectorRegExp.hasMatch(selector) ? _BEFORE_NAME : _BEFORE_SELECTOR; - if (state == _BEFORE_SELECTOR) { - buffer.clear(); - } - } - if (state != _BEFORE_SELECTOR) { - buffer.writeCharCode(c); - } - break; - case COLON_CODE: - if (state == _NAME) { - state = _VALUE; - } - if (state != _BEFORE_SELECTOR) { - buffer.writeCharCode(c); - } - break; - case CLOSE_CURLY_CODE: - if (state == _BEFORE_SELECTOR) break; - if (state == _VALUE || state == _BEFORE_NAME) { - buffer.writeCharCode(c); - String ruleText = buffer.toString(); - CSSStyleRule? styleRule = CSSStyleRuleParser.parse(ruleText); - if (styleRule != null) { - rules.add(styleRule); - } - } else if (state == _NAME) { - // `.foo { .x {}; color: red }` - buffer.writeCharCode(c); - } - - if (state != _NAME) { - buffer.clear(); - state = _BEFORE_SELECTOR; - } - break; - default: - if (state == _BEFORE_SELECTOR) break; - buffer.writeCharCode(c); - if (state == _BEFORE_NAME) { - state = _NAME; - } - } - } - return rules; - } -} diff --git a/webf/lib/src/css/parser/token.dart b/webf/lib/src/css/parser/token.dart new file mode 100644 index 0000000000..b3d7823d89 --- /dev/null +++ b/webf/lib/src/css/parser/token.dart @@ -0,0 +1,89 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +/// A single token in the Dart language. +class Token { + /// A member of [TokenKind] specifying what kind of token this is. + final int kind; + + /// The location where this token was parsed from. + final FileSpan span; + + /// The start offset of this token. + int get start => span.start.offset; + + /// The end offset of this token. + int get end => span.end.offset; + + /// Returns the source text corresponding to this [Token]. + String get text => span.text; + + Token(this.kind, this.span); + + /// Returns a pretty representation of this token for error messages. + @override + String toString() { + var kindText = TokenKind.kindToString(kind); + var actualText = text.trim(); + if (kindText != actualText) { + if (actualText.length > 10) { + actualText = '${actualText.substring(0, 8)}...'; + } + return '$kindText($actualText)'; + } else { + return kindText; + } + } +} + +/// A token containing a parsed literal value. +class LiteralToken extends Token { + dynamic value; + LiteralToken(int kind, FileSpan span, this.value) : super(kind, span); +} + +/// A token containing error information. +class ErrorToken extends Token { + String? message; + ErrorToken(int kind, FileSpan span, this.message) : super(kind, span); +} + +/// CSS ident-token. +/// +/// See <http://dev.w3.org/csswg/css-syntax/#typedef-ident-token> and +/// <http://dev.w3.org/csswg/css-syntax/#ident-token-diagram>. +class IdentifierToken extends Token { + @override + final String text; + + IdentifierToken(this.text, int kind, FileSpan span) : super(kind, span); +} diff --git a/webf/lib/src/css/parser/token_kind.dart b/webf/lib/src/css/parser/token_kind.dart new file mode 100644 index 0000000000..7a9f31f81b --- /dev/null +++ b/webf/lib/src/css/parser/token_kind.dart @@ -0,0 +1,597 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +// TODO(terry): Need to be consistent with tokens either they're ASCII tokens +// e.g., ASTERISK or they're CSS e.g., PSEUDO, COMBINATOR_*. +class TokenKind { + // Common shared tokens used in TokenizerBase. + static const int UNUSED = 0; // Unused place holder... + static const int END_OF_FILE = 1; // EOF + static const int LPAREN = 2; // ( + static const int RPAREN = 3; // ) + static const int LBRACK = 4; // [ + static const int RBRACK = 5; // ] + static const int LBRACE = 6; // { + static const int RBRACE = 7; // } + static const int DOT = 8; // . + static const int SEMICOLON = 9; // ; + + // Unique tokens for CSS. + static const int AT = 10; // @ + static const int HASH = 11; // # + static const int PLUS = 12; // + + static const int GREATER = 13; // > + static const int TILDE = 14; // ~ + static const int ASTERISK = 15; // * + static const int NAMESPACE = 16; // | + static const int COLON = 17; // : + static const int PRIVATE_NAME = 18; // _ prefix private class or id + static const int COMMA = 19; // , + static const int SPACE = 20; + static const int TAB = 21; // /t + static const int NEWLINE = 22; // /n + static const int RETURN = 23; // /r + static const int PERCENT = 24; // % + static const int SINGLE_QUOTE = 25; // ' + static const int DOUBLE_QUOTE = 26; // " + static const int SLASH = 27; // / + static const int EQUALS = 28; // = + static const int CARET = 30; // ^ + static const int DOLLAR = 31; // $ + static const int LESS = 32; // < + static const int BANG = 33; // ! + static const int MINUS = 34; // - + static const int BACKSLASH = 35; // \ + static const int AMPERSAND = 36; // & + + // WARNING: Tokens from this point and above must have the corresponding ASCII + // character in the TokenChar list at the bottom of this file. The + // order of the above tokens should be the same order as TokenChar. + + /// [TokenKind] representing integer tokens. + static const int INTEGER = 60; + + /// [TokenKind] representing hex integer tokens. + static const int HEX_INTEGER = 61; + + /// [TokenKind] representing double tokens. + static const int DOUBLE = 62; + + /// [TokenKind] representing whitespace tokens. + static const int WHITESPACE = 63; + + /// [TokenKind] representing comment tokens. + static const int COMMENT = 64; + + /// [TokenKind] representing error tokens. + static const int ERROR = 65; + + /// [TokenKind] representing incomplete string tokens. + static const int INCOMPLETE_STRING = 66; + + /// [TokenKind] representing incomplete comment tokens. + static const int INCOMPLETE_COMMENT = 67; + + static const int VAR_DEFINITION = 400; // var-NNN-NNN + static const int VAR_USAGE = 401; // var(NNN-NNN [,default]) + + // Synthesized Tokens (no character associated with TOKEN). + static const int STRING = 500; + static const int STRING_PART = 501; + static const int NUMBER = 502; + static const int HEX_NUMBER = 503; + static const int HTML_COMMENT = 504; // <!-- + static const int IMPORTANT = 505; // !important + static const int CDATA_START = 506; // <![CDATA[ + static const int CDATA_END = 507; // ]]> + // U+uNumber[-U+uNumber] + // uNumber = 0..10FFFF | ?[?]* + static const int UNICODE_RANGE = 508; + static const int HEX_RANGE = 509; // ? in the hex range + static const int IDENTIFIER = 511; + + // Uniquely synthesized tokens for CSS. + static const int SELECTOR_EXPRESSION = 512; + static const int COMBINATOR_NONE = 513; + static const int COMBINATOR_DESCENDANT = 514; // Space combinator + static const int COMBINATOR_PLUS = 515; // + combinator + static const int COMBINATOR_GREATER = 516; // > combinator + static const int COMBINATOR_TILDE = 517; // ~ combinator + + static const int UNARY_OP_NONE = 518; // No unary operator present. + + // Attribute match types: + static const int INCLUDES = 530; // '~=' + static const int DASH_MATCH = 531; // '|=' + static const int PREFIX_MATCH = 532; // '^=' + static const int SUFFIX_MATCH = 533; // '$=' + static const int SUBSTRING_MATCH = 534; // '*=' + static const int NO_MATCH = 535; // No operator. + + // Unit types: + static const int UNIT_EM = 600; + static const int UNIT_EX = 601; + static const int UNIT_LENGTH_PX = 602; + static const int UNIT_LENGTH_CM = 603; + static const int UNIT_LENGTH_MM = 604; + static const int UNIT_LENGTH_IN = 605; + static const int UNIT_LENGTH_PT = 606; + static const int UNIT_LENGTH_PC = 607; + static const int UNIT_ANGLE_DEG = 608; + static const int UNIT_ANGLE_RAD = 609; + static const int UNIT_ANGLE_GRAD = 610; + static const int UNIT_ANGLE_TURN = 611; + static const int UNIT_TIME_MS = 612; + static const int UNIT_TIME_S = 613; + static const int UNIT_FREQ_HZ = 614; + static const int UNIT_FREQ_KHZ = 615; + static const int UNIT_PERCENT = 616; + static const int UNIT_FRACTION = 617; + static const int UNIT_RESOLUTION_DPI = 618; + static const int UNIT_RESOLUTION_DPCM = 619; + static const int UNIT_RESOLUTION_DPPX = 620; + static const int UNIT_CH = 621; // Measure of "0" U+0030 glyph. + static const int UNIT_REM = 622; // computed value ‘font-size’ on root elem. + static const int UNIT_VIEWPORT_VW = 623; + static const int UNIT_VIEWPORT_VH = 624; + static const int UNIT_VIEWPORT_VMIN = 625; + static const int UNIT_VIEWPORT_VMAX = 626; + + // Directives (@nnnn) + static const int DIRECTIVE_NONE = 640; + static const int DIRECTIVE_IMPORT = 641; + static const int DIRECTIVE_MEDIA = 642; + static const int DIRECTIVE_PAGE = 643; + static const int DIRECTIVE_CHARSET = 644; + static const int DIRECTIVE_STYLET = 645; + static const int DIRECTIVE_KEYFRAMES = 646; + static const int DIRECTIVE_WEB_KIT_KEYFRAMES = 647; + static const int DIRECTIVE_MOZ_KEYFRAMES = 648; + static const int DIRECTIVE_MS_KEYFRAMES = 649; + static const int DIRECTIVE_O_KEYFRAMES = 650; + static const int DIRECTIVE_FONTFACE = 651; + static const int DIRECTIVE_NAMESPACE = 652; + static const int DIRECTIVE_HOST = 653; + static const int DIRECTIVE_MIXIN = 654; + static const int DIRECTIVE_INCLUDE = 655; + static const int DIRECTIVE_CONTENT = 656; + static const int DIRECTIVE_EXTEND = 657; + static const int DIRECTIVE_MOZ_DOCUMENT = 658; + static const int DIRECTIVE_SUPPORTS = 659; + static const int DIRECTIVE_VIEWPORT = 660; + static const int DIRECTIVE_MS_VIEWPORT = 661; + + // Media query operators + static const int MEDIA_OP_ONLY = 665; // Unary. + static const int MEDIA_OP_NOT = 666; // Unary. + static const int MEDIA_OP_AND = 667; // Binary. + + // Directives inside of a @page (margin sym). + static const int MARGIN_DIRECTIVE_TOPLEFTCORNER = 670; + static const int MARGIN_DIRECTIVE_TOPLEFT = 671; + static const int MARGIN_DIRECTIVE_TOPCENTER = 672; + static const int MARGIN_DIRECTIVE_TOPRIGHT = 673; + static const int MARGIN_DIRECTIVE_TOPRIGHTCORNER = 674; + static const int MARGIN_DIRECTIVE_BOTTOMLEFTCORNER = 675; + static const int MARGIN_DIRECTIVE_BOTTOMLEFT = 676; + static const int MARGIN_DIRECTIVE_BOTTOMCENTER = 677; + static const int MARGIN_DIRECTIVE_BOTTOMRIGHT = 678; + static const int MARGIN_DIRECTIVE_BOTTOMRIGHTCORNER = 679; + static const int MARGIN_DIRECTIVE_LEFTTOP = 680; + static const int MARGIN_DIRECTIVE_LEFTMIDDLE = 681; + static const int MARGIN_DIRECTIVE_LEFTBOTTOM = 682; + static const int MARGIN_DIRECTIVE_RIGHTTOP = 683; + static const int MARGIN_DIRECTIVE_RIGHTMIDDLE = 684; + static const int MARGIN_DIRECTIVE_RIGHTBOTTOM = 685; + + // Simple selector type. + static const int CLASS_NAME = 700; // .class + static const int ELEMENT_NAME = 701; // tagName + static const int HASH_NAME = 702; // #elementId + static const int ATTRIBUTE_NAME = 703; // [attrib] + static const int PSEUDO_ELEMENT_NAME = 704; // ::pseudoElement + static const int PSEUDO_CLASS_NAME = 705; // :pseudoClass + static const int NEGATION = 706; // NOT + + static const List<Map<String, dynamic>> _DIRECTIVES = [ + {'type': TokenKind.DIRECTIVE_IMPORT, 'value': 'import'}, + {'type': TokenKind.DIRECTIVE_MEDIA, 'value': 'media'}, + {'type': TokenKind.DIRECTIVE_PAGE, 'value': 'page'}, + {'type': TokenKind.DIRECTIVE_CHARSET, 'value': 'charset'}, + {'type': TokenKind.DIRECTIVE_STYLET, 'value': 'stylet'}, + {'type': TokenKind.DIRECTIVE_KEYFRAMES, 'value': 'keyframes'}, + {'type': TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES, 'value': '-webkit-keyframes'}, + {'type': TokenKind.DIRECTIVE_MOZ_KEYFRAMES, 'value': '-moz-keyframes'}, + {'type': TokenKind.DIRECTIVE_MS_KEYFRAMES, 'value': '-ms-keyframes'}, + {'type': TokenKind.DIRECTIVE_O_KEYFRAMES, 'value': '-o-keyframes'}, + {'type': TokenKind.DIRECTIVE_FONTFACE, 'value': 'font-face'}, + {'type': TokenKind.DIRECTIVE_NAMESPACE, 'value': 'namespace'}, + {'type': TokenKind.DIRECTIVE_HOST, 'value': 'host'}, + {'type': TokenKind.DIRECTIVE_MIXIN, 'value': 'mixin'}, + {'type': TokenKind.DIRECTIVE_INCLUDE, 'value': 'include'}, + {'type': TokenKind.DIRECTIVE_CONTENT, 'value': 'content'}, + {'type': TokenKind.DIRECTIVE_EXTEND, 'value': 'extend'}, + {'type': TokenKind.DIRECTIVE_MOZ_DOCUMENT, 'value': '-moz-document'}, + {'type': TokenKind.DIRECTIVE_SUPPORTS, 'value': 'supports'}, + {'type': TokenKind.DIRECTIVE_VIEWPORT, 'value': 'viewport'}, + {'type': TokenKind.DIRECTIVE_MS_VIEWPORT, 'value': '-ms-viewport'}, + ]; + + static const List<Map<String, dynamic>> MEDIA_OPERATORS = [ + {'type': TokenKind.MEDIA_OP_ONLY, 'value': 'only'}, + {'type': TokenKind.MEDIA_OP_NOT, 'value': 'not'}, + {'type': TokenKind.MEDIA_OP_AND, 'value': 'and'}, + ]; + + static const List<Map<String, dynamic>> MARGIN_DIRECTIVES = [ + {'type': TokenKind.MARGIN_DIRECTIVE_TOPLEFTCORNER, 'value': 'top-left-corner'}, + {'type': TokenKind.MARGIN_DIRECTIVE_TOPLEFT, 'value': 'top-left'}, + {'type': TokenKind.MARGIN_DIRECTIVE_TOPCENTER, 'value': 'top-center'}, + {'type': TokenKind.MARGIN_DIRECTIVE_TOPRIGHT, 'value': 'top-right'}, + {'type': TokenKind.MARGIN_DIRECTIVE_TOPRIGHTCORNER, 'value': 'top-right-corner'}, + {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFTCORNER, 'value': 'bottom-left-corner'}, + {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMLEFT, 'value': 'bottom-left'}, + {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMCENTER, 'value': 'bottom-center'}, + {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMRIGHT, 'value': 'bottom-right'}, + {'type': TokenKind.MARGIN_DIRECTIVE_BOTTOMRIGHTCORNER, 'value': 'bottom-right-corner'}, + {'type': TokenKind.MARGIN_DIRECTIVE_LEFTTOP, 'value': 'left-top'}, + {'type': TokenKind.MARGIN_DIRECTIVE_LEFTMIDDLE, 'value': 'left-middle'}, + {'type': TokenKind.MARGIN_DIRECTIVE_LEFTBOTTOM, 'value': 'right-bottom'}, + {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTTOP, 'value': 'right-top'}, + {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTMIDDLE, 'value': 'right-middle'}, + {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTBOTTOM, 'value': 'right-bottom'}, + ]; + + static const List<Map<String, dynamic>> _UNITS = [ + {'unit': TokenKind.UNIT_EM, 'value': 'em'}, + {'unit': TokenKind.UNIT_EX, 'value': 'ex'}, + {'unit': TokenKind.UNIT_LENGTH_PX, 'value': 'px'}, + {'unit': TokenKind.UNIT_LENGTH_CM, 'value': 'cm'}, + {'unit': TokenKind.UNIT_LENGTH_MM, 'value': 'mm'}, + {'unit': TokenKind.UNIT_LENGTH_IN, 'value': 'in'}, + {'unit': TokenKind.UNIT_LENGTH_PT, 'value': 'pt'}, + {'unit': TokenKind.UNIT_LENGTH_PC, 'value': 'pc'}, + {'unit': TokenKind.UNIT_ANGLE_DEG, 'value': 'deg'}, + {'unit': TokenKind.UNIT_ANGLE_RAD, 'value': 'rad'}, + {'unit': TokenKind.UNIT_ANGLE_GRAD, 'value': 'grad'}, + {'unit': TokenKind.UNIT_ANGLE_TURN, 'value': 'turn'}, + {'unit': TokenKind.UNIT_TIME_MS, 'value': 'ms'}, + {'unit': TokenKind.UNIT_TIME_S, 'value': 's'}, + {'unit': TokenKind.UNIT_FREQ_HZ, 'value': 'hz'}, + {'unit': TokenKind.UNIT_FREQ_KHZ, 'value': 'khz'}, + {'unit': TokenKind.UNIT_FRACTION, 'value': 'fr'}, + {'unit': TokenKind.UNIT_RESOLUTION_DPI, 'value': 'dpi'}, + {'unit': TokenKind.UNIT_RESOLUTION_DPCM, 'value': 'dpcm'}, + {'unit': TokenKind.UNIT_RESOLUTION_DPPX, 'value': 'dppx'}, + {'unit': TokenKind.UNIT_CH, 'value': 'ch'}, + {'unit': TokenKind.UNIT_REM, 'value': 'rem'}, + {'unit': TokenKind.UNIT_VIEWPORT_VW, 'value': 'vw'}, + {'unit': TokenKind.UNIT_VIEWPORT_VH, 'value': 'vh'}, + {'unit': TokenKind.UNIT_VIEWPORT_VMIN, 'value': 'vmin'}, + {'unit': TokenKind.UNIT_VIEWPORT_VMAX, 'value': 'vmax'}, + ]; + + // Some more constants: + static const int ASCII_UPPER_A = 65; // ASCII value for uppercase A + static const int ASCII_UPPER_Z = 90; // ASCII value for uppercase Z + + // TODO(terry): Should used Dart mirroring for parameter values and types + // especially for enumeration (e.g., counter's second parameter + // is list-style-type which is an enumerated list for ordering + // of a list 'circle', 'decimal', 'lower-roman', 'square', etc. + // see http://www.w3schools.com/cssref/pr_list-style-type.asp + // for list of possible values. + + /// Check if name is a pre-defined CSS name. Used by error handler to report + /// if name is unknown or used improperly. + static bool isPredefinedName(String name) { + var nameLen = name.length; + // TODO(terry): Add more pre-defined names (hidden, bolder, inherit, etc.). + if (matchUnits(name, 0, nameLen) == -1 || matchDirectives(name, 0, nameLen) == -1 || matchMarginDirectives(name, 0, nameLen) == -1) { + return false; + } + + return true; + } + + /// Return the token that matches the unit ident found. + static int matchList(Iterable<Map<String, dynamic>> identList, String tokenField, String text, int offset, int length) { + for (final entry in identList) { + final ident = entry['value'] as String; + + if (length == ident.length) { + var idx = offset; + var match = true; + for (var i = 0; i < ident.length; i++) { + var identChar = ident.codeUnitAt(i); + var char = text.codeUnitAt(idx++); + // Compare lowercase to lowercase then check if char is uppercase. + match = match && (char == identChar || ((char >= ASCII_UPPER_A && char <= ASCII_UPPER_Z) && (char + 32) == identChar)); + if (!match) { + break; + } + } + + if (match) { + // Completely matched; return the token for this unit. + return entry[tokenField] as int; + } + } + } + + return -1; // Not a unit token. + } + + /// Return the token that matches the unit ident found. + static int matchUnits(String text, int offset, int length) { + return matchList(_UNITS, 'unit', text, offset, length); + } + + /// Return the token that matches the directive name found. + static int matchDirectives(String text, int offset, int length) { + return matchList(_DIRECTIVES, 'type', text, offset, length); + } + + /// Return the token that matches the margin directive name found. + static int matchMarginDirectives(String text, int offset, int length) { + return matchList(MARGIN_DIRECTIVES, 'type', text, offset, length); + } + + /// Return the token that matches the media operator found. + static int matchMediaOperator(String text, int offset, int length) { + return matchList(MEDIA_OPERATORS, 'type', text, offset, length); + } + + static String? idToValue(Iterable<Object?> identList, int tokenId) { + for (var entry in identList) { + entry as Map<String, Object?>; + if (tokenId == entry['type']) { + return entry['value'] as String?; + } + } + + return null; + } + + /// Return the unit token as its pretty name. + static String? unitToString(int unitTokenToFind) { + if (unitTokenToFind == TokenKind.PERCENT) { + return '%'; + } else { + for (final entry in _UNITS) { + final unit = entry['unit'] as int; + if (unit == unitTokenToFind) { + return entry['value'] as String?; + } + } + } + + return '<BAD UNIT>'; // Not a unit token. + } + + /// Return RGB value as [int] from a color entry in _EXTENDED_COLOR_NAMES. + static int colorValue(Map entry) { + return entry['value'] as int; + } + + static String decimalToHex(int number, [int minDigits = 1]) { + final _HEX_DIGITS = '0123456789abcdef'; + + var result = <String>[]; + + var dividend = number >> 4; + var remain = number % 16; + result.add(_HEX_DIGITS[remain]); + while (dividend != 0) { + remain = dividend % 16; + dividend >>= 4; + result.add(_HEX_DIGITS[remain]); + } + + var invertResult = StringBuffer(); + var paddings = minDigits - result.length; + while (paddings-- > 0) { + invertResult.write('0'); + } + for (var i = result.length - 1; i >= 0; i--) { + invertResult.write(result[i]); + } + + return invertResult.toString(); + } + + static String kindToString(int kind) { + switch (kind) { + case TokenKind.UNUSED: + return 'ERROR'; + case TokenKind.END_OF_FILE: + return 'end of file'; + case TokenKind.LPAREN: + return '('; + case TokenKind.RPAREN: + return ')'; + case TokenKind.LBRACK: + return '['; + case TokenKind.RBRACK: + return ']'; + case TokenKind.LBRACE: + return '{'; + case TokenKind.RBRACE: + return '}'; + case TokenKind.DOT: + return '.'; + case TokenKind.SEMICOLON: + return ';'; + case TokenKind.AT: + return '@'; + case TokenKind.HASH: + return '#'; + case TokenKind.PLUS: + return '+'; + case TokenKind.GREATER: + return '>'; + case TokenKind.TILDE: + return '~'; + case TokenKind.ASTERISK: + return '*'; + case TokenKind.NAMESPACE: + return '|'; + case TokenKind.COLON: + return ':'; + case TokenKind.PRIVATE_NAME: + return '_'; + case TokenKind.COMMA: + return ','; + case TokenKind.SPACE: + return ' '; + case TokenKind.TAB: + return '\t'; + case TokenKind.NEWLINE: + return '\n'; + case TokenKind.RETURN: + return '\r'; + case TokenKind.PERCENT: + return '%'; + case TokenKind.SINGLE_QUOTE: + return "'"; + case TokenKind.DOUBLE_QUOTE: + return '"'; + case TokenKind.SLASH: + return '/'; + case TokenKind.EQUALS: + return '='; + case TokenKind.CARET: + return '^'; + case TokenKind.DOLLAR: + return '\$'; + case TokenKind.LESS: + return '<'; + case TokenKind.BANG: + return '!'; + case TokenKind.MINUS: + return '-'; + case TokenKind.BACKSLASH: + return '\\'; + default: + throw 'Unknown TOKEN'; + } + } + + static bool isKindIdentifier(int kind) { + switch (kind) { + // Synthesized tokens. + case TokenKind.DIRECTIVE_IMPORT: + case TokenKind.DIRECTIVE_MEDIA: + case TokenKind.DIRECTIVE_PAGE: + case TokenKind.DIRECTIVE_CHARSET: + case TokenKind.DIRECTIVE_STYLET: + case TokenKind.DIRECTIVE_KEYFRAMES: + case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES: + case TokenKind.DIRECTIVE_MOZ_KEYFRAMES: + case TokenKind.DIRECTIVE_MS_KEYFRAMES: + case TokenKind.DIRECTIVE_O_KEYFRAMES: + case TokenKind.DIRECTIVE_FONTFACE: + case TokenKind.DIRECTIVE_NAMESPACE: + case TokenKind.DIRECTIVE_HOST: + case TokenKind.DIRECTIVE_MIXIN: + case TokenKind.DIRECTIVE_INCLUDE: + case TokenKind.DIRECTIVE_CONTENT: + case TokenKind.UNIT_EM: + case TokenKind.UNIT_EX: + case TokenKind.UNIT_LENGTH_PX: + case TokenKind.UNIT_LENGTH_CM: + case TokenKind.UNIT_LENGTH_MM: + case TokenKind.UNIT_LENGTH_IN: + case TokenKind.UNIT_LENGTH_PT: + case TokenKind.UNIT_LENGTH_PC: + case TokenKind.UNIT_ANGLE_DEG: + case TokenKind.UNIT_ANGLE_RAD: + case TokenKind.UNIT_ANGLE_GRAD: + case TokenKind.UNIT_TIME_MS: + case TokenKind.UNIT_TIME_S: + case TokenKind.UNIT_FREQ_HZ: + case TokenKind.UNIT_FREQ_KHZ: + case TokenKind.UNIT_FRACTION: + return true; + default: + return false; + } + } + + static bool isIdentifier(int kind) { + return kind == IDENTIFIER; + } +} + +// Note: these names should match TokenKind names +class TokenChar { + static const int UNUSED = -1; + static const int END_OF_FILE = 0; + static const int LPAREN = 0x28; // "(".codeUnitAt(0) + static const int RPAREN = 0x29; // ")".codeUnitAt(0) + static const int LBRACK = 0x5b; // "[".codeUnitAt(0) + static const int RBRACK = 0x5d; // "]".codeUnitAt(0) + static const int LBRACE = 0x7b; // "{".codeUnitAt(0) + static const int RBRACE = 0x7d; // "}".codeUnitAt(0) + static const int DOT = 0x2e; // ".".codeUnitAt(0) + static const int SEMICOLON = 0x3b; // ";".codeUnitAt(0) + static const int AT = 0x40; // "@".codeUnitAt(0) + static const int HASH = 0x23; // "#".codeUnitAt(0) + static const int PLUS = 0x2b; // "+".codeUnitAt(0) + static const int GREATER = 0x3e; // ">".codeUnitAt(0) + static const int TILDE = 0x7e; // "~".codeUnitAt(0) + static const int ASTERISK = 0x2a; // "*".codeUnitAt(0) + static const int NAMESPACE = 0x7c; // "|".codeUnitAt(0) + static const int COLON = 0x3a; // ":".codeUnitAt(0) + static const int PRIVATE_NAME = 0x5f; // "_".codeUnitAt(0) + static const int COMMA = 0x2c; // ",".codeUnitAt(0) + static const int SPACE = 0x20; // " ".codeUnitAt(0) + static const int TAB = 0x9; // "\t".codeUnitAt(0) + static const int NEWLINE = 0xa; // "\n".codeUnitAt(0) + static const int RETURN = 0xd; // "\r".codeUnitAt(0) + static const int BACKSPACE = 0x8; // "/b".codeUnitAt(0) + static const int FF = 0xc; // "/f".codeUnitAt(0) + static const int VT = 0xb; // "/v".codeUnitAt(0) + static const int PERCENT = 0x25; // "%".codeUnitAt(0) + static const int SINGLE_QUOTE = 0x27; // "'".codeUnitAt(0) + static const int DOUBLE_QUOTE = 0x22; // '"'.codeUnitAt(0) + static const int SLASH = 0x2f; // "/".codeUnitAt(0) + static const int EQUALS = 0x3d; // "=".codeUnitAt(0) + static const int OR = 0x7c; // "|".codeUnitAt(0) + static const int CARET = 0x5e; // "^".codeUnitAt(0) + static const int DOLLAR = 0x24; // "\$".codeUnitAt(0) + static const int LESS = 0x3c; // "<".codeUnitAt(0) + static const int BANG = 0x21; // "!".codeUnitAt(0) + static const int MINUS = 0x2d; // "-".codeUnitAt(0) + static const int BACKSLASH = 0x5c; // "\".codeUnitAt(0) + static const int AMPERSAND = 0x26; // "&".codeUnitAt(0) +} diff --git a/webf/lib/src/css/parser/tokenizer.dart b/webf/lib/src/css/parser/tokenizer.dart new file mode 100644 index 0000000000..a0139ac0bd --- /dev/null +++ b/webf/lib/src/css/parser/tokenizer.dart @@ -0,0 +1,475 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +class Tokenizer extends TokenizerBase { + /// U+ prefix for unicode characters. + final UNICODE_U = 'U'.codeUnitAt(0); + final UNICODE_LOWER_U = 'u'.codeUnitAt(0); + final UNICODE_PLUS = '+'.codeUnitAt(0); + + final QUESTION_MARK = '?'.codeUnitAt(0); + + /// CDATA keyword. + final List<int> CDATA_NAME = 'CDATA'.codeUnits; + + Tokenizer(SourceFile file, String text, bool skipWhitespace, [int index = 0]) : super(file, text, skipWhitespace, index); + + @override + Token next({bool unicodeRange = false}) { + // keep track of our starting position + _startIndex = _index; + + int ch; + ch = _nextChar(); + switch (ch) { + case TokenChar.NEWLINE: + case TokenChar.RETURN: + case TokenChar.SPACE: + case TokenChar.TAB: + return finishWhitespace(); + case TokenChar.END_OF_FILE: + return _finishToken(TokenKind.END_OF_FILE); + case TokenChar.AT: + var peekCh = _peekChar(); + if (TokenizerHelpers.isIdentifierStart(peekCh)) { + var oldIndex = _index; + var oldStartIndex = _startIndex; + + _startIndex = _index; + ch = _nextChar(); + finishIdentifier(); + + // Is it a directive? + var tokId = TokenKind.matchDirectives(_text, _startIndex, _index - _startIndex); + if (tokId == -1) { + // No, is it a margin directive? + tokId = TokenKind.matchMarginDirectives(_text, _startIndex, _index - _startIndex); + } + + if (tokId != -1) { + return _finishToken(tokId); + } else { + // Didn't find a CSS directive or margin directive so the @name is + // probably the Less definition '@name: value_variable_definition'. + _startIndex = oldStartIndex; + _index = oldIndex; + } + } + return _finishToken(TokenKind.AT); + case TokenChar.DOT: + var start = _startIndex; // Start where the dot started. + if (maybeEatDigit()) { + // looks like a number dot followed by digit(s). + var number = finishNumber(); + if (number.kind == TokenKind.INTEGER) { + // It's a number but it's preceeded by a dot, so make it a double. + _startIndex = start; + return _finishToken(TokenKind.DOUBLE); + } else { + // Don't allow dot followed by a double (e.g, '..1'). + return _errorToken(); + } + } + // It's really a dot. + return _finishToken(TokenKind.DOT); + case TokenChar.LPAREN: + return _finishToken(TokenKind.LPAREN); + case TokenChar.RPAREN: + return _finishToken(TokenKind.RPAREN); + case TokenChar.LBRACE: + return _finishToken(TokenKind.LBRACE); + case TokenChar.RBRACE: + return _finishToken(TokenKind.RBRACE); + case TokenChar.LBRACK: + return _finishToken(TokenKind.LBRACK); + case TokenChar.RBRACK: + if (_maybeEatChar(TokenChar.RBRACK) && _maybeEatChar(TokenChar.GREATER)) { + // ]]> + return next(); + } + return _finishToken(TokenKind.RBRACK); + case TokenChar.HASH: + return _finishToken(TokenKind.HASH); + case TokenChar.PLUS: + if (_nextCharsAreNumber(ch)) return finishNumber(); + return _finishToken(TokenKind.PLUS); + case TokenChar.MINUS: + if (inSelectorExpression || unicodeRange) { + // If parsing in pseudo function expression then minus is an operator + // not part of identifier e.g., interval value range (e.g. U+400-4ff) + // or minus operator in selector expression. + return _finishToken(TokenKind.MINUS); + } else if (_nextCharsAreNumber(ch)) { + return finishNumber(); + } else if (TokenizerHelpers.isIdentifierStart(ch)) { + return finishIdentifier(); + } + return _finishToken(TokenKind.MINUS); + case TokenChar.GREATER: + return _finishToken(TokenKind.GREATER); + case TokenChar.TILDE: + if (_maybeEatChar(TokenChar.EQUALS)) { + return _finishToken(TokenKind.INCLUDES); // ~= + } + return _finishToken(TokenKind.TILDE); + case TokenChar.ASTERISK: + if (_maybeEatChar(TokenChar.EQUALS)) { + return _finishToken(TokenKind.SUBSTRING_MATCH); // *= + } + return _finishToken(TokenKind.ASTERISK); + case TokenChar.AMPERSAND: + return _finishToken(TokenKind.AMPERSAND); + case TokenChar.NAMESPACE: + if (_maybeEatChar(TokenChar.EQUALS)) { + return _finishToken(TokenKind.DASH_MATCH); // |= + } + return _finishToken(TokenKind.NAMESPACE); + case TokenChar.COLON: + return _finishToken(TokenKind.COLON); + case TokenChar.COMMA: + return _finishToken(TokenKind.COMMA); + case TokenChar.SEMICOLON: + return _finishToken(TokenKind.SEMICOLON); + case TokenChar.PERCENT: + return _finishToken(TokenKind.PERCENT); + case TokenChar.SINGLE_QUOTE: + return _finishToken(TokenKind.SINGLE_QUOTE); + case TokenChar.DOUBLE_QUOTE: + return _finishToken(TokenKind.DOUBLE_QUOTE); + case TokenChar.SLASH: + if (_maybeEatChar(TokenChar.ASTERISK)) return finishMultiLineComment(); + return _finishToken(TokenKind.SLASH); + case TokenChar.LESS: // <!-- + if (_maybeEatChar(TokenChar.BANG)) { + if (_maybeEatChar(TokenChar.MINUS) && _maybeEatChar(TokenChar.MINUS)) { + return finishHtmlComment(); + } else if (_maybeEatChar(TokenChar.LBRACK) && + _maybeEatChar(CDATA_NAME[0]) && + _maybeEatChar(CDATA_NAME[1]) && + _maybeEatChar(CDATA_NAME[2]) && + _maybeEatChar(CDATA_NAME[3]) && + _maybeEatChar(CDATA_NAME[4]) && + _maybeEatChar(TokenChar.LBRACK)) { + // <![CDATA[ + return next(); + } + } + return _finishToken(TokenKind.LESS); + case TokenChar.EQUALS: + return _finishToken(TokenKind.EQUALS); + case TokenChar.CARET: + if (_maybeEatChar(TokenChar.EQUALS)) { + return _finishToken(TokenKind.PREFIX_MATCH); // ^= + } + return _finishToken(TokenKind.CARET); + case TokenChar.DOLLAR: + if (_maybeEatChar(TokenChar.EQUALS)) { + return _finishToken(TokenKind.SUFFIX_MATCH); // $= + } + return _finishToken(TokenKind.DOLLAR); + case TokenChar.BANG: + return finishIdentifier(); + default: + // TODO(jmesserly): this is used for IE8 detection; I'm not sure it's + // appropriate outside of a few specific places; certainly shouldn't + // be parsed in selectors. + if (!inSelector && ch == TokenChar.BACKSLASH) { + return _finishToken(TokenKind.BACKSLASH); + } + + if (unicodeRange) { + // Three types of unicode ranges: + // - single code point (e.g. U+416) + // - interval value range (e.g. U+400-4ff) + // - range where trailing ‘?’ characters imply ‘any digit value’ + // (e.g. U+4??) + if (maybeEatHexDigit()) { + var t = finishHexNumber(); + // Any question marks then it's a HEX_RANGE not HEX_NUMBER. + if (maybeEatQuestionMark()) finishUnicodeRange(); + return t; + } else if (maybeEatQuestionMark()) { + // HEX_RANGE U+N??? + return finishUnicodeRange(); + } else { + return _errorToken(); + } + } else if (_inString && (ch == UNICODE_U || ch == UNICODE_LOWER_U) && (_peekChar() == UNICODE_PLUS)) { + // `_inString` is misleading. We actually DON'T want to enter this + // block while tokenizing a string, but the parser sets this value to + // false while it IS consuming tokens within a string. + // + // Unicode range: U+uNumber[-U+uNumber] + // uNumber = 0..10FFFF + _nextChar(); // Skip + + _startIndex = _index; // Starts at the number + return _finishToken(TokenKind.UNICODE_RANGE); + } else if (varDef(ch)) { + return _finishToken(TokenKind.VAR_DEFINITION); + } else if (varUsage(ch)) { + return _finishToken(TokenKind.VAR_USAGE); + } else if (TokenizerHelpers.isIdentifierStart(ch)) { + return finishIdentifier(); + } else if (TokenizerHelpers.isDigit(ch)) { + return finishNumber(); + } + return _errorToken(); + } + } + + bool varDef(int ch) { + return ch == 'v'.codeUnitAt(0) && _maybeEatChar('a'.codeUnitAt(0)) && _maybeEatChar('r'.codeUnitAt(0)) && _maybeEatChar('-'.codeUnitAt(0)); + } + + bool varUsage(int ch) { + return ch == 'v'.codeUnitAt(0) && _maybeEatChar('a'.codeUnitAt(0)) && _maybeEatChar('r'.codeUnitAt(0)) && (_peekChar() == '-'.codeUnitAt(0)); + } + + @override + Token _errorToken([String? message]) { + return _finishToken(TokenKind.ERROR); + } + + @override + int getIdentifierKind() { + // Is the identifier a unit type? + var tokId = -1; + + // Don't match units in selectors or selector expressions. + if (!inSelectorExpression && !inSelector) { + tokId = TokenKind.matchUnits(_text, _startIndex, _index - _startIndex); + } + if (tokId == -1) { + tokId = (_text.substring(_startIndex, _index) == '!important') ? TokenKind.IMPORTANT : -1; + } + + return tokId >= 0 ? tokId : TokenKind.IDENTIFIER; + } + + Token finishIdentifier() { + // If we encounter an escape sequence, remember it so we can post-process + // to unescape. + var chars = <int>[]; + + // backup so we can start with the first character + var validateFrom = _index; + _index = _startIndex; + while (_index < _text.length) { + var ch = _text.codeUnitAt(_index); + + // If the previous character was "\" we need to escape. T + // http://www.w3.org/TR/CSS21/syndata.html#characters + // if followed by hexadecimal digits, create the appropriate character. + // otherwise, include the character in the identifier and don't treat it + // specially. + if (ch == 92 /*\*/ && _inString) { + var startHex = ++_index; + eatHexDigits(startHex + 6); + if (_index != startHex) { + // Parse the hex digits and add that character. + chars.add(int.parse('0x' + _text.substring(startHex, _index))); + + if (_index == _text.length) break; + + // if we stopped the hex because of a whitespace char, skip it + ch = _text.codeUnitAt(_index); + if (_index - startHex != 6 && (ch == TokenChar.SPACE || ch == TokenChar.TAB || ch == TokenChar.RETURN || ch == TokenChar.NEWLINE)) { + _index++; + } + } else { + // not a digit, just add the next character literally + if (_index == _text.length) break; + chars.add(_text.codeUnitAt(_index++)); + } + } else if (_index < validateFrom || (inSelectorExpression ? TokenizerHelpers.isIdentifierPartExpr(ch) : TokenizerHelpers.isIdentifierPart(ch))) { + chars.add(ch); + _index++; + } else { + // Not an identifier or escaped character. + break; + } + } + + var span = _file.span(_startIndex, _index); + var text = String.fromCharCodes(chars); + + return IdentifierToken(text, getIdentifierKind(), span); + } + + @override + Token finishNumber() { + eatDigits(); + + if (_peekChar() == 46 /*.*/) { + // Handle the case of 1.toString(). + _nextChar(); + if (TokenizerHelpers.isDigit(_peekChar())) { + eatDigits(); + return _finishToken(TokenKind.DOUBLE); + } else { + _index -= 1; + } + } + + return _finishToken(TokenKind.INTEGER); + } + + bool maybeEatDigit() { + if (_index < _text.length && TokenizerHelpers.isDigit(_text.codeUnitAt(_index))) { + _index += 1; + return true; + } + return false; + } + + Token finishHexNumber() { + eatHexDigits(_text.length); + return _finishToken(TokenKind.HEX_INTEGER); + } + + void eatHexDigits(int end) { + end = math.min(end, _text.length); + while (_index < end) { + if (TokenizerHelpers.isHexDigit(_text.codeUnitAt(_index))) { + _index += 1; + } else { + return; + } + } + } + + bool maybeEatHexDigit() { + if (_index < _text.length && TokenizerHelpers.isHexDigit(_text.codeUnitAt(_index))) { + _index += 1; + return true; + } + return false; + } + + bool maybeEatQuestionMark() { + if (_index < _text.length && _text.codeUnitAt(_index) == QUESTION_MARK) { + _index += 1; + return true; + } + return false; + } + + void eatQuestionMarks() { + while (_index < _text.length) { + if (_text.codeUnitAt(_index) == QUESTION_MARK) { + _index += 1; + } else { + return; + } + } + } + + Token finishUnicodeRange() { + eatQuestionMarks(); + return _finishToken(TokenKind.HEX_RANGE); + } + + Token finishHtmlComment() { + while (true) { + var ch = _nextChar(); + if (ch == 0) { + return _finishToken(TokenKind.INCOMPLETE_COMMENT); + } else if (ch == TokenChar.MINUS) { + /* Check if close part of Comment Definition --> (CDC). */ + if (_maybeEatChar(TokenChar.MINUS)) { + if (_maybeEatChar(TokenChar.GREATER)) { + if (_inString) { + return next(); + } else { + return _finishToken(TokenKind.HTML_COMMENT); + } + } + } + } + } + } + + @override + Token finishMultiLineComment() { + while (true) { + var ch = _nextChar(); + if (ch == 0) { + return _finishToken(TokenKind.INCOMPLETE_COMMENT); + } else if (ch == 42 /*'*'*/) { + if (_maybeEatChar(47 /*'/'*/)) { + if (_inString) { + return next(); + } else { + return _finishToken(TokenKind.COMMENT); + } + } + } + } + } +} + +/// Static helper methods. +class TokenizerHelpers { + static bool isIdentifierStart(int c) { + return isIdentifierStartExpr(c) || c == 45 /*-*/; + } + + static bool isDigit(int c) { + return (c >= 48 /*0*/ && c <= 57 /*9*/); + } + + static bool isHexDigit(int c) { + return (isDigit(c) || (c >= 97 /*a*/ && c <= 102 /*f*/) || (c >= 65 /*A*/ && c <= 70 /*F*/)); + } + + static bool isIdentifierPart(int c) { + return isIdentifierPartExpr(c) || c == 45 /*-*/; + } + + /// Pseudo function expressions identifiers can't have a minus sign. + static bool isIdentifierStartExpr(int c) { + return ((c >= 97 /*a*/ && c <= 122 /*z*/) || + (c >= 65 /*A*/ && c <= 90 /*Z*/) || + // Note: Unicode 10646 chars U+00A0 or higher are allowed, see: + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + // http://www.w3.org/TR/CSS21/syndata.html#characters + // Also, escaped character should be allowed. + c == 95 /*_*/ || + c >= 0xA0 || + c == 92 /*\*/); + } + + /// Pseudo function expressions identifiers can't have a minus sign. + static bool isIdentifierPartExpr(int c) { + return (isIdentifierStartExpr(c) || isDigit(c)); + } +} diff --git a/webf/lib/src/css/parser/tokenizer_base.dart b/webf/lib/src/css/parser/tokenizer_base.dart new file mode 100644 index 0000000000..9f839cdaa1 --- /dev/null +++ b/webf/lib/src/css/parser/tokenizer_base.dart @@ -0,0 +1,444 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +/// Tokenizer state to support look ahead for Less' nested selectors. +class TokenizerState { + final int index; + final int startIndex; + final bool inSelectorExpression; + final bool inSelector; + + TokenizerState(TokenizerBase base) + : index = base._index, + startIndex = base._startIndex, + inSelectorExpression = base.inSelectorExpression, + inSelector = base.inSelector; +} + +/// The base class for our tokenizer. The hand coded parts are in this file, +/// with the generated parts in the subclass Tokenizer. +abstract class TokenizerBase { + final SourceFile _file; + final String _text; + + // TODO: this seems like a bug – this field *is* used + // ignore: prefer_final_fields + bool _inString; + + /// Changes tokenization when in a pseudo function expression. If true then + /// minus signs are handled as operators instead of identifiers. + bool inSelectorExpression = false; + + /// Changes tokenization when in selectors. If true, it prevents identifiers + /// from being treated as units. This would break things like ":lang(fr)" or + /// the HTML (unknown) tag name "px", which is legal to use in a selector. + // TODO(jmesserly): is this a problem elsewhere? "fr" for example will be + // processed as a "fraction" unit token, preventing it from working in + // places where an identifier is expected. This was breaking selectors like: + // :lang(fr) + // The assumption that "fr" always means fraction (and similar issue with + // other units) doesn't seem valid. We probably should defer this + // analysis until we reach places in the parser where units are expected. + // I'm not sure this is tokenizing as described in the specs: + // http://dev.w3.org/csswg/css-syntax/ + // http://dev.w3.org/csswg/selectors4/ + bool inSelector = false; + + int _index = 0; + int _startIndex = 0; + + TokenizerBase(this._file, this._text, this._inString, [this._index = 0]); + + Token next(); + int getIdentifierKind(); + + /// Snapshot of Tokenizer scanning state. + TokenizerState get mark => TokenizerState(this); + + /// Restore Tokenizer scanning state. + void restore(TokenizerState markedData) { + _index = markedData.index; + _startIndex = markedData.startIndex; + inSelectorExpression = markedData.inSelectorExpression; + inSelector = markedData.inSelector; + } + + int _nextChar() { + if (_index < _text.length) { + return _text.codeUnitAt(_index++); + } else { + return 0; + } + } + + int _peekChar([int offset = 0]) { + if (_index + offset < _text.length) { + return _text.codeUnitAt(_index + offset); + } else { + return 0; + } + } + + bool _maybeEatChar(int ch) { + if (_index < _text.length) { + if (_text.codeUnitAt(_index) == ch) { + _index++; + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool _nextCharsAreNumber(int first) { + if (TokenizerHelpers.isDigit(first)) return true; + var second = _peekChar(); + if (first == TokenChar.DOT) return TokenizerHelpers.isDigit(second); + if (first == TokenChar.PLUS || first == TokenChar.MINUS) { + return TokenizerHelpers.isDigit(second) || (second == TokenChar.DOT && TokenizerHelpers.isDigit(_peekChar(1))); + } + return false; + } + + Token _finishToken(int kind) { + return Token(kind, _file.span(_startIndex, _index)); + } + + Token _errorToken([String? message]) { + return ErrorToken(TokenKind.ERROR, _file.span(_startIndex, _index), message); + } + + Token finishWhitespace() { + _index--; + while (_index < _text.length) { + final ch = _text.codeUnitAt(_index++); + if (ch == TokenChar.SPACE || ch == TokenChar.TAB || ch == TokenChar.RETURN) { + // do nothing + } else if (ch == TokenChar.NEWLINE) { + if (!_inString) { + return _finishToken(TokenKind.WHITESPACE); // note the newline? + } + } else { + _index--; + if (_inString) { + return next(); + } else { + return _finishToken(TokenKind.WHITESPACE); + } + } + } + return _finishToken(TokenKind.END_OF_FILE); + } + + Token finishMultiLineComment() { + var nesting = 1; + do { + var ch = _nextChar(); + if (ch == 0) { + return _errorToken(); + } else if (ch == TokenChar.ASTERISK) { + if (_maybeEatChar(TokenChar.SLASH)) { + nesting--; + } + } else if (ch == TokenChar.SLASH) { + if (_maybeEatChar(TokenChar.ASTERISK)) { + nesting++; + } + } + } while (nesting > 0); + + if (_inString) { + return next(); + } else { + return _finishToken(TokenKind.COMMENT); + } + } + + void eatDigits() { + while (_index < _text.length) { + if (TokenizerHelpers.isDigit(_text.codeUnitAt(_index))) { + _index++; + } else { + return; + } + } + } + + static int _hexDigit(int c) { + if (c >= 48 /*0*/ && c <= 57 /*9*/) { + return c - 48; + } else if (c >= 97 /*a*/ && c <= 102 /*f*/) { + return c - 87; + } else if (c >= 65 /*A*/ && c <= 70 /*F*/) { + return c - 55; + } else { + return -1; + } + } + + int readHex([int? hexLength]) { + int maxIndex; + if (hexLength == null) { + maxIndex = _text.length - 1; + } else { + // TODO(jimhug): What if this is too long? + maxIndex = _index + hexLength; + if (maxIndex >= _text.length) return -1; + } + var result = 0; + while (_index < maxIndex) { + final digit = _hexDigit(_text.codeUnitAt(_index)); + if (digit == -1) { + if (hexLength == null) { + return result; + } else { + return -1; + } + } + _hexDigit(_text.codeUnitAt(_index)); + // Multiply by 16 rather than shift by 4 since that will result in a + // correct value for numbers that exceed the 32 bit precision of JS + // 'integers'. + // TODO: Figure out a better solution to integer truncation. Issue 638. + result = (result * 16) + digit; + _index++; + } + + return result; + } + + Token finishNumber() { + eatDigits(); + + if (_peekChar() == TokenChar.DOT) { + // Handle the case of 1.toString(). + _nextChar(); + if (TokenizerHelpers.isDigit(_peekChar())) { + eatDigits(); + return finishNumberExtra(TokenKind.DOUBLE); + } else { + _index--; + } + } + + return finishNumberExtra(TokenKind.INTEGER); + } + + Token finishNumberExtra(int kind) { + if (_maybeEatChar(101 /*e*/) || _maybeEatChar(69 /*E*/)) { + kind = TokenKind.DOUBLE; + _maybeEatChar(TokenKind.MINUS); + _maybeEatChar(TokenKind.PLUS); + eatDigits(); + } + if (_peekChar() != 0 && TokenizerHelpers.isIdentifierStart(_peekChar())) { + _nextChar(); + return _errorToken('illegal character in number'); + } + + return _finishToken(kind); + } + + Token _makeStringToken(List<int> buf, bool isPart) { + final s = String.fromCharCodes(buf); + final kind = isPart ? TokenKind.STRING_PART : TokenKind.STRING; + return LiteralToken(kind, _file.span(_startIndex, _index), s); + } + + Token makeIEFilter(int start, int end) { + var filter = _text.substring(start, end); + return LiteralToken(TokenKind.STRING, _file.span(start, end), filter); + } + + Token _makeRawStringToken(bool isMultiline) { + String s; + if (isMultiline) { + // Skip initial newline in multiline strings + var start = _startIndex + 4; + if (_text[start] == '\n') start++; + s = _text.substring(start, _index - 3); + } else { + s = _text.substring(_startIndex + 2, _index - 1); + } + return LiteralToken(TokenKind.STRING, _file.span(_startIndex, _index), s); + } + + Token finishMultilineString(int quote) { + var buf = <int>[]; + while (true) { + var ch = _nextChar(); + if (ch == 0) { + return _errorToken(); + } else if (ch == quote) { + if (_maybeEatChar(quote)) { + if (_maybeEatChar(quote)) { + return _makeStringToken(buf, false); + } + buf.add(quote); + } + buf.add(quote); + } else if (ch == TokenChar.BACKSLASH) { + var escapeVal = readEscapeSequence(); + if (escapeVal == -1) { + return _errorToken('invalid hex escape sequence'); + } else { + buf.add(escapeVal); + } + } else { + buf.add(ch); + } + } + } + + Token finishString(int quote) { + if (_maybeEatChar(quote)) { + if (_maybeEatChar(quote)) { + // skip an initial newline + _maybeEatChar(TokenChar.NEWLINE); + return finishMultilineString(quote); + } else { + return _makeStringToken(<int>[], false); + } + } + return finishStringBody(quote); + } + + Token finishRawString(int quote) { + if (_maybeEatChar(quote)) { + if (_maybeEatChar(quote)) { + return finishMultilineRawString(quote); + } else { + return _makeStringToken(<int>[], false); + } + } + while (true) { + var ch = _nextChar(); + if (ch == quote) { + return _makeRawStringToken(false); + } else if (ch == 0) { + return _errorToken(); + } + } + } + + Token finishMultilineRawString(int quote) { + while (true) { + var ch = _nextChar(); + if (ch == 0) { + return _errorToken(); + } else if (ch == quote && _maybeEatChar(quote) && _maybeEatChar(quote)) { + return _makeRawStringToken(true); + } + } + } + + Token finishStringBody(int quote) { + var buf = <int>[]; + while (true) { + var ch = _nextChar(); + if (ch == quote) { + return _makeStringToken(buf, false); + } else if (ch == 0) { + return _errorToken(); + } else if (ch == TokenChar.BACKSLASH) { + var escapeVal = readEscapeSequence(); + if (escapeVal == -1) { + return _errorToken('invalid hex escape sequence'); + } else { + buf.add(escapeVal); + } + } else { + buf.add(ch); + } + } + } + + int readEscapeSequence() { + final ch = _nextChar(); + int hexValue; + switch (ch) { + case 110 /*n*/ : + return TokenChar.NEWLINE; + case 114 /*r*/ : + return TokenChar.RETURN; + case 102 /*f*/ : + return TokenChar.FF; + case 98 /*b*/ : + return TokenChar.BACKSPACE; + case 116 /*t*/ : + return TokenChar.TAB; + case 118 /*v*/ : + return TokenChar.FF; + case 120 /*x*/ : + hexValue = readHex(2); + break; + case 117 /*u*/ : + if (_maybeEatChar(TokenChar.LBRACE)) { + hexValue = readHex(); + if (!_maybeEatChar(TokenChar.RBRACE)) { + return -1; + } + } else { + hexValue = readHex(4); + } + break; + default: + return ch; + } + + if (hexValue == -1) return -1; + + // According to the Unicode standard the high and low surrogate halves + // used by UTF-16 (U+D800 through U+DFFF) and values above U+10FFFF + // are not legal Unicode values. + if (hexValue < 0xD800 || hexValue > 0xDFFF && hexValue <= 0xFFFF) { + return hexValue; + } else if (hexValue <= 0x10FFFF) { + // messages.error('unicode values greater than 2 bytes not implemented yet', + // _file.span(_startIndex, _startIndex + 1)); + return -1; + } else { + return -1; + } + } + + Token finishDot() { + if (TokenizerHelpers.isDigit(_peekChar())) { + eatDigits(); + return finishNumberExtra(TokenKind.DOUBLE); + } else { + return _finishToken(TokenKind.DOT); + } + } +} diff --git a/webf/lib/src/css/parser/tree.dart b/webf/lib/src/css/parser/tree.dart new file mode 100644 index 0000000000..0b2f965b92 --- /dev/null +++ b/webf/lib/src/css/parser/tree.dart @@ -0,0 +1,107 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +///////////////////////////////////////////////////////////////////////// +// CSS specific types: +///////////////////////////////////////////////////////////////////////// + +/// The base type for all nodes in a CSS abstract syntax tree. +abstract class TreeNode { + TreeNode clone(); + + /// Classic double-dispatch visitor for implementing passes. + dynamic visit(Visitor visitor) {} +} + +class Identifier extends TreeNode { + String name; + + Identifier(this.name) : super(); + + @override + Identifier clone() => Identifier(name); + + @override + String toString() { + // Try to use the identifier's original lexeme to preserve any escape codes + // as authored. The name, which may include escaped values, may no longer be + // a valid identifier. + return name; + } +} + +class Wildcard extends TreeNode { + Wildcard() : super(); + @override + Wildcard clone() => Wildcard(); + String get name => '*'; +} + +class ThisOperator extends TreeNode { + ThisOperator() : super(); + @override + ThisOperator clone() => ThisOperator(); + String get name => '&'; +} + +class Negation extends TreeNode { + Negation() : super(); + @override + Negation clone() => Negation(); + + String get name => 'not'; +} + +class NoOp extends TreeNode { + NoOp() : super(); + + @override + NoOp clone() => NoOp(); +} + +/// The base type for expressions. +abstract class Expression extends TreeNode { + @override + Expression clone(); +} + +class OperatorSlash extends Expression { + OperatorSlash() : super(); + @override + OperatorSlash clone() => OperatorSlash(); +} + +class OperatorComma extends Expression { + OperatorComma() : super(); + @override + OperatorComma clone() => OperatorComma(); +} diff --git a/webf/lib/src/css/parser/visitor.dart b/webf/lib/src/css/parser/visitor.dart new file mode 100644 index 0000000000..1282eb861e --- /dev/null +++ b/webf/lib/src/css/parser/visitor.dart @@ -0,0 +1,106 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +abstract class Visitor { + dynamic visitSelectorGroup(SelectorGroup node); + dynamic visitSelector(Selector node); + dynamic visitSimpleSelectorSequence(SimpleSelectorSequence node); + dynamic visitSimpleSelector(SimpleSelector node); + dynamic visitElementSelector(ElementSelector node); + dynamic visitAttributeSelector(AttributeSelector node); + dynamic visitIdSelector(IdSelector node); + dynamic visitClassSelector(ClassSelector node); + dynamic visitPseudoClassSelector(PseudoClassSelector node); + dynamic visitPseudoElementSelector(PseudoElementSelector node); + dynamic visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node); + dynamic visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node); + dynamic visitNegationSelector(NegationSelector node); +} + +class SelectorVisitor implements Visitor { + /// Helper function to walk a list of nodes. + void _visitNodeList(List<TreeNode> list) { + // Don't use iterable otherwise the list can't grow while using Visitor. + // It certainly can't have items deleted before the index being iterated + // but items could be added after the index. + for (var index = 0; index < list.length; index++) { + list[index].visit(this); + } + } + + @override + dynamic visitSelectorGroup(SelectorGroup node) { + _visitNodeList(node.selectors); + } + + @override + dynamic visitSelector(Selector node) { + _visitNodeList(node.simpleSelectorSequences); + } + + @override + dynamic visitSimpleSelectorSequence(SimpleSelectorSequence node) { + node.simpleSelector.visit(this); + } + + @override + dynamic visitSimpleSelector(SimpleSelector node) => (node._name as TreeNode).visit(this); + + @override + dynamic visitElementSelector(ElementSelector node) => visitSimpleSelector(node); + + @override + dynamic visitAttributeSelector(AttributeSelector node) { + visitSimpleSelector(node); + } + + @override + dynamic visitIdSelector(IdSelector node) => visitSimpleSelector(node); + + @override + dynamic visitClassSelector(ClassSelector node) => visitSimpleSelector(node); + + @override + dynamic visitPseudoClassSelector(PseudoClassSelector node) => visitSimpleSelector(node); + + @override + dynamic visitPseudoElementSelector(PseudoElementSelector node) => visitSimpleSelector(node); + + @override + dynamic visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) => visitSimpleSelector(node); + + @override + dynamic visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) => visitSimpleSelector(node); + + @override + dynamic visitNegationSelector(NegationSelector node) => visitSimpleSelector(node); +} diff --git a/webf/lib/src/css/rule.dart b/webf/lib/src/css/rule.dart index b1f6dfbb65..57c6238cbb 100644 --- a/webf/lib/src/css/rule.dart +++ b/webf/lib/src/css/rule.dart @@ -1,6 +1,5 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ import 'package:webf/css.dart'; diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart new file mode 100644 index 0000000000..cb951997c1 --- /dev/null +++ b/webf/lib/src/css/rule_set.dart @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'dart:collection'; + +import 'package:webf/css.dart'; + +typedef CSSMap = HashMap<String, List<CSSRule>>; + +class RuleSet { + final List<CSSRule> rules = []; + + final CSSMap idRules = HashMap(); + final CSSMap classRules = HashMap(); + final CSSMap attributeRules = HashMap(); + final CSSMap tagRules = HashMap(); + final List<CSSRule> universalRules = []; + + void addRule(CSSRule rule) { + rules.add(rule); + if (rule is CSSStyleRule) { + findBestRuleSetAndAdd(rule); + } else { + assert(false, 'Unsupported rule type: ${rule.runtimeType}'); + } + } + + void deleteRule(int index) { + CSSRule rule = rules.removeAt(index); + // if (rule is CSSStyleRule) { + // // findBestRuleSetAndRemove(rule); + // } + } + + void reset() { + rules.clear(); + idRules.clear(); + classRules.clear(); + attributeRules.clear(); + tagRules.clear(); + universalRules.clear(); + } + + // indexed by selectorText + void findBestRuleSetAndAdd(CSSStyleRule rule) { + String? id, className, attributeName, tagName, pseudoElementName, pseudoFunctionName; + + for (final selector in rule.selectorGroup.selectors) { + for (final simpleSelectorSequence in selector.simpleSelectorSequences) { + final simpleSelector = simpleSelectorSequence.simpleSelector; + switch (simpleSelector.runtimeType) { + case IdSelector: + id = simpleSelector.name; + break; + case ClassSelector: + className = simpleSelector.name; + break; + case AttributeSelector: + attributeName = simpleSelector.name; + break; + case ElementSelector: + tagName = simpleSelector.name; + break; + } + } + } + + void insertRule(String key, CSSRule rule, CSSMap map) { + List<CSSRule>? rules = map[key] ?? []; + rules.add(rule); + map[key] = rules; + } + + if (id != null && id.isNotEmpty == true) { + insertRule(id, rule, idRules); + return; + } + + if (className != null && className.isNotEmpty == true) { + insertRule(className, rule, classRules); + return; + } + + if (attributeName != null && attributeName.isNotEmpty == true) { + insertRule(attributeName, rule, attributeRules); + return; + } + + if (tagName != null && tagName.isNotEmpty == true) { + insertRule(tagName, rule, tagRules); + return; + } + universalRules.add(rule); + } +} diff --git a/webf/lib/src/css/selector_checker.dart b/webf/lib/src/css/selector_checker.dart new file mode 100644 index 0000000000..3540b3326d --- /dev/null +++ b/webf/lib/src/css/selector_checker.dart @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +import 'package:webf/dom.dart'; +import 'package:webf/css.dart'; + +class SelectorEvaluator extends SelectorVisitor { + Element? _element; + + bool matchSelector(SelectorGroup? selector, Element? element) { + if (selector == null || element == null) { + return false; + } + _element = element; + return visitSelectorGroup(selector); + } + + @override + bool visitSelectorGroup(SelectorGroup node) => node.selectors.any(visitSelector); + + @override + bool visitSelector(Selector node) { + final old = _element; + var result = true; + + // Note: evaluate selectors right-to-left as it's more efficient. + int? combinator; + for (var s in node.simpleSelectorSequences.reversed) { + if (combinator == null) { + result = s.simpleSelector.visit(this) as bool; + } else if (combinator == TokenKind.COMBINATOR_DESCENDANT) { + // descendant combinator + // http://dev.w3.org/csswg/selectors-4/#descendant-combinators + do { + _element = _element!.parentElement; + } while (_element != null && !(s.simpleSelector.visit(this) as bool)); + + if (_element == null) result = false; + } else if (combinator == TokenKind.COMBINATOR_TILDE) { + // Following-sibling combinator + // http://dev.w3.org/csswg/selectors-4/#general-sibling-combinators + do { + _element = _element!.previousElementSibling; + } while (_element != null && !(s.simpleSelector.visit(this) as bool)); + + if (_element == null) result = false; + } + + if (!result) break; + + switch (s.combinator) { + case TokenKind.COMBINATOR_PLUS: + // Next-sibling combinator + // http://dev.w3.org/csswg/selectors-4/#adjacent-sibling-combinators + _element = _element!.previousElementSibling; + break; + case TokenKind.COMBINATOR_GREATER: + // Child combinator + // http://dev.w3.org/csswg/selectors-4/#child-combinators + _element = _element!.parentElement; + break; + case TokenKind.COMBINATOR_DESCENDANT: + case TokenKind.COMBINATOR_TILDE: + // We need to iterate through all siblings or parents. + // For now, just remember what the combinator was. + combinator = s.combinator; + break; + case TokenKind.COMBINATOR_NONE: + break; + default: + throw _unsupported(node); + } + + if (_element == null) { + result = false; + break; + } + } + + _element = old; + return result; + } + + @override + bool visitPseudoClassSelector(PseudoClassSelector node) { + switch (node.name) { + // http://dev.w3.org/csswg/selectors-4/#structural-pseudos + + // http://dev.w3.org/csswg/selectors-4/#the-root-pseudo + case 'root': + // TODO(jmesserly): fix when we have a .ownerDocument pointer + // return _element == _element.ownerDocument.rootElement; + return _element!.nodeName == 'html' && _element!.parentNode == null; + + // http://dev.w3.org/csswg/selectors-4/#the-empty-pseudo + case 'empty': + return _element!.childNodes.any((n) => !(n is Element || n is TextNode && n.data.isNotEmpty)); + + // http://dev.w3.org/csswg/selectors-4/#the-blank-pseudo + case 'blank': + return _element!.childNodes.any((n) => !(n is Element || n is TextNode && n.data.runes.any((r) => !isWhitespaceCC(r)))); + + // http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo + case 'first-child': + return _element!.previousElementSibling == null; + + // http://dev.w3.org/csswg/selectors-4/#the-last-child-pseudo + case 'last-child': + return _element!.nextElementSibling == null; + + // http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo + case 'only-child': + return _element!.previousElementSibling == null && _element!.nextElementSibling == null; + + // http://dev.w3.org/csswg/selectors-4/#link + case 'link': + return _element!.attributes['href'] != null; + + case 'visited': + // Always return false since we aren't a browser. This is allowed per: + // http://dev.w3.org/csswg/selectors-4/#visited-pseudo + return false; + } + + // :before, :after, :first-letter/line can't match DOM elements. + if (_isLegacyPsuedoClass(node.name)) return false; + + throw _unimplemented(node); + } + + @override + bool visitPseudoElementSelector(PseudoElementSelector node) { + // :before, :after, :first-letter/line can't match DOM elements. + if (_isLegacyPsuedoClass(node.name)) return false; + + throw _unimplemented(node); + } + + static bool _isLegacyPsuedoClass(String name) { + switch (name) { + case 'before': + case 'after': + case 'first-line': + case 'first-letter': + return true; + default: + return false; + } + } + + @override + bool visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) => throw _unimplemented(node); + + @override + bool visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) { + switch (node.name) { + // http://dev.w3.org/csswg/selectors-4/#child-index + + // http://dev.w3.org/csswg/selectors-4/#the-nth-child-pseudo + case 'nth-child': + // TODO(jmesserly): support An+B syntax too. + final exprs = node.expression; + if (exprs.length == 1 && _element != null) { + final literal = int.parse(exprs[0]); + final parent = _element!.parentElement; + return parent != null && literal > 0 && parent.children.indexOf(_element!) == literal; + } + break; + } + throw _unimplemented(node); + } + + bool isNumeric(String s) { + return double.tryParse(s) != null; + } + + @override + bool visitElementSelector(ElementSelector node) => _element!.tagName == node.name.toUpperCase(); + + @override + bool visitIdSelector(IdSelector node) => _element!.id == node.name; + + @override + bool visitClassSelector(ClassSelector node) => _element!.classList.contains(node.name); + + // TODO(jmesserly): negation should support any selectors in level 4, + // not just simple selectors. + // http://dev.w3.org/csswg/selectors-4/#negation + @override + bool visitNegationSelector(NegationSelector node) => !(node.negationArg!.visit(this) as bool); + + @override + bool visitAttributeSelector(AttributeSelector node) { + // Match name first + final value = _element!.attributes[node.name.toLowerCase()]; + if (value == null) return false; + + if (node.operatorKind == TokenKind.NO_MATCH) return true; + + final select = '${node.value}'; + switch (node.operatorKind) { + case TokenKind.EQUALS: + return value == select; + case TokenKind.INCLUDES: + return value.split(' ').any((v) => v.isNotEmpty && v == select); + case TokenKind.DASH_MATCH: + return value.startsWith(select) && (value.length == select.length || value[select.length] == '-'); + case TokenKind.PREFIX_MATCH: + return value.startsWith(select); + case TokenKind.SUFFIX_MATCH: + return value.endsWith(select); + case TokenKind.SUBSTRING_MATCH: + return value.contains(select); + default: + throw _unsupported(node); + } + } + + UnimplementedError _unimplemented(SimpleSelector selector) => UnimplementedError("'$selector' selector of type " + '${selector.runtimeType} is not implemented'); + + FormatException _unsupported(selector) => FormatException("'$selector' is not a valid selector"); + + bool isWhitespaceCC(int charCode) { + switch (charCode) { + case TokenChar.TAB: // '\t' + case TokenChar.NEWLINE: // '\n' + case TokenChar.FF: // '\f' + case TokenChar.RETURN: // '\r' + case TokenChar.SPACE: // ' ' + return true; + } + return false; + } +} diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index 0b7c5ca267..b3a5c6b14f 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -46,7 +46,8 @@ List<String> _propertyOrders = [ RegExp _kebabCaseReg = RegExp(r'[A-Z]'); -final LinkedLruHashMap<String, Map<String, String?>> _cachedExpandedShorthand = LinkedLruHashMap(maximumSize: 500); +final LinkedLruHashMap<String, Map<String, String?>> _cachedExpandedShorthand = + LinkedLruHashMap(maximumSize: 500); // CSS Object Model: https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface @@ -70,7 +71,8 @@ class CSSStyleDeclaration { CSSStyleDeclaration(); // ignore: prefer_initializing_formals - CSSStyleDeclaration.computedStyle(this.target, this.defaultStyle, this.onStyleChanged); + CSSStyleDeclaration.computedStyle( + this.target, this.defaultStyle, this.onStyleChanged); /// An empty style declaration. static CSSStyleDeclaration empty = CSSStyleDeclaration(); @@ -92,6 +94,10 @@ class CSSStyleDeclaration { if (css.isNotEmpty) css += ' '; css += '${_kebabize(property)}: $value;'; }); + _sheetStyle.forEach((property, value) { + if (css.isNotEmpty) css += ' '; + css += '${_kebabize(property)}: $value;'; + }); return css; } @@ -110,7 +116,9 @@ class CSSStyleDeclaration { /// If not set, returns the empty string. String getPropertyValue(String propertyName) { // Get the latest pending value first. - return _pendingProperties[propertyName] ?? _properties[propertyName] ?? EMPTY_STRING; + return _pendingProperties[propertyName] ?? + _properties[propertyName] ?? + EMPTY_STRING; } /// Removes a property from the CSS declaration. @@ -123,7 +131,8 @@ class CSSStyleDeclaration { case BACKGROUND: return CSSStyleProperty.removeShorthandBackground(this, isImportant); case BACKGROUND_POSITION: - return CSSStyleProperty.removeShorthandBackgroundPosition(this, isImportant); + return CSSStyleProperty.removeShorthandBackgroundPosition( + this, isImportant); case BORDER_RADIUS: return CSSStyleProperty.removeShorthandBorderRadius(this, isImportant); case OVERFLOW: @@ -142,11 +151,13 @@ class CSSStyleDeclaration { case BORDER_COLOR: case BORDER_STYLE: case BORDER_WIDTH: - return CSSStyleProperty.removeShorthandBorder(this, propertyName, isImportant); + return CSSStyleProperty.removeShorthandBorder( + this, propertyName, isImportant); case TRANSITION: return CSSStyleProperty.removeShorthandTransition(this, isImportant); case TEXT_DECORATION: - return CSSStyleProperty.removeShorthandTextDecoration(this, isImportant); + return CSSStyleProperty.removeShorthandTextDecoration( + this, isImportant); } String present = EMPTY_STRING; @@ -162,7 +173,9 @@ class CSSStyleDeclaration { } // Fallback to default style. - if (isNullOrEmptyValue(present) && defaultStyle != null && defaultStyle!.containsKey(propertyName)) { + if (isNullOrEmptyValue(present) && + defaultStyle != null && + defaultStyle!.containsKey(propertyName)) { present = defaultStyle![propertyName]; } @@ -170,7 +183,8 @@ class CSSStyleDeclaration { _pendingProperties[propertyName] = present; } - void _expandShorthand(String propertyName, String normalizedValue, bool? isImportant) { + void _expandShorthand( + String propertyName, String normalizedValue, bool? isImportant) { Map<String, String?> longhandProperties; String cacheKey = '$propertyName:$normalizedValue'; if (_cachedExpandedShorthand.containsKey(cacheKey)) { @@ -180,31 +194,40 @@ class CSSStyleDeclaration { switch (propertyName) { case PADDING: - CSSStyleProperty.setShorthandPadding(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandPadding( + longhandProperties, normalizedValue); break; case MARGIN: - CSSStyleProperty.setShorthandMargin(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandMargin( + longhandProperties, normalizedValue); break; case BACKGROUND: - CSSStyleProperty.setShorthandBackground(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBackground( + longhandProperties, normalizedValue); break; case BACKGROUND_POSITION: - CSSStyleProperty.setShorthandBackgroundPosition(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBackgroundPosition( + longhandProperties, normalizedValue); break; case BORDER_RADIUS: - CSSStyleProperty.setShorthandBorderRadius(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBorderRadius( + longhandProperties, normalizedValue); break; case OVERFLOW: - CSSStyleProperty.setShorthandOverflow(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandOverflow( + longhandProperties, normalizedValue); break; case FONT: - CSSStyleProperty.setShorthandFont(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFont( + longhandProperties, normalizedValue); break; case FLEX: - CSSStyleProperty.setShorthandFlex(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFlex( + longhandProperties, normalizedValue); break; case FLEX_FLOW: - CSSStyleProperty.setShorthandFlexFlow(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFlexFlow( + longhandProperties, normalizedValue); break; case BORDER: case BORDER_TOP: @@ -214,13 +237,16 @@ class CSSStyleDeclaration { case BORDER_COLOR: case BORDER_STYLE: case BORDER_WIDTH: - CSSStyleProperty.setShorthandBorder(longhandProperties, propertyName, normalizedValue); + CSSStyleProperty.setShorthandBorder( + longhandProperties, propertyName, normalizedValue); break; case TRANSITION: - CSSStyleProperty.setShorthandTransition(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandTransition( + longhandProperties, normalizedValue); break; case TEXT_DECORATION: - CSSStyleProperty.setShorthandTextDecoration(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandTextDecoration( + longhandProperties, normalizedValue); break; } _cachedExpandedShorthand[cacheKey] = longhandProperties; @@ -233,7 +259,9 @@ class CSSStyleDeclaration { } } - String _replacePattern(String string, String lowerCase, String startString, String endString, [int start = 0]) { + String _replacePattern( + String string, String lowerCase, String startString, String endString, + [int start = 0]) { int startIndex = lowerCase.indexOf(startString, start); if (startIndex >= 0) { int? endIndex; @@ -246,7 +274,8 @@ class CSSStyleDeclaration { var replacement = string.substring(startIndex, endIndex); lowerCase = lowerCase.replaceRange(startIndex, endIndex, replacement); if (endIndex < string.length - 1) { - lowerCase = _replacePattern(string, lowerCase, startString, endString, endIndex); + lowerCase = _replacePattern( + string, lowerCase, startString, endString, endIndex); } } } @@ -337,10 +366,12 @@ class CSSStyleDeclaration { if (!CSSColor.isColor(normalizedValue)) return false; break; case BACKGROUND_IMAGE: - if (!CSSBackground.isValidBackgroundImageValue(normalizedValue)) return false; + if (!CSSBackground.isValidBackgroundImageValue(normalizedValue)) + return false; break; case BACKGROUND_REPEAT: - if (!CSSBackground.isValidBackgroundRepeatValue(normalizedValue)) return false; + if (!CSSBackground.isValidBackgroundRepeatValue(normalizedValue)) + return false; break; } return true; @@ -373,7 +404,6 @@ class CSSStyleDeclaration { return; } - // Current only from inline style will mark the property as important. if (isImportant == true) { _importants[propertyName] = true; } @@ -436,6 +466,49 @@ class CSSStyleDeclaration { } } + void merge(CSSStyleDeclaration declaration) { + Map<String, String> properties = {} + ..addAll(_properties) + ..addAll(_pendingProperties); + + for (String propertyName in properties.keys) { + bool isImportant = _importants[propertyName] ?? false; + String? currentValue = properties[propertyName]; + String? otherValue = declaration._pendingProperties[propertyName]; + + if (isNullOrEmptyValue(otherValue) && isNullOrEmptyValue(currentValue)) { + continue; + } else if (!isNullOrEmptyValue(currentValue) && + isNullOrEmptyValue(otherValue)) { + // Remove property. + _pendingProperties.remove(propertyName); + } else if (!isImportant && + otherValue != null && + currentValue != otherValue) { + // Update property. + _pendingProperties[propertyName] = otherValue; + bool otherIsImportant = declaration._importants[propertyName] ?? false; + if (otherIsImportant) { + _importants[propertyName] = true; + } + } + } + + for (String propertyName in declaration._pendingProperties.keys) { + bool isImportant = _importants[propertyName] ?? false; + String? currentValue = properties[propertyName]; + String? otherValue = declaration._pendingProperties[propertyName]; + if (!isImportant && isNullOrEmptyValue(currentValue) && !isNullOrEmptyValue(otherValue)) { + // Add property. + _pendingProperties[propertyName] = otherValue!; + bool otherIsImportant = declaration._importants[propertyName] ?? false; + if (otherIsImportant) { + _importants[propertyName] = true; + } + } + } + } + Map<String, String?> diff(CSSStyleDeclaration other) { Map<String, String?> diffs = {}; @@ -449,7 +522,8 @@ class CSSStyleDeclaration { if (isNullOrEmptyValue(prevValue) && isNullOrEmptyValue(currentValue)) { continue; - } else if (!isNullOrEmptyValue(prevValue) && isNullOrEmptyValue(currentValue)) { + } else if (!isNullOrEmptyValue(prevValue) && + isNullOrEmptyValue(currentValue)) { // Remove property. diffs[propertyName] = null; } else if (prevValue != currentValue) { @@ -524,5 +598,6 @@ class CSSStyleDeclaration { // aB to a-b String _kebabize(String str) { - return str.replaceAllMapped(_kebabCaseReg, (match) => '-${match[0]!.toLowerCase()}'); + return str.replaceAllMapped( + _kebabCaseReg, (match) => '-${match[0]!.toLowerCase()}'); } diff --git a/webf/lib/src/css/style_rule.dart b/webf/lib/src/css/style_rule.dart index f1e990c323..75f9a40506 100644 --- a/webf/lib/src/css/style_rule.dart +++ b/webf/lib/src/css/style_rule.dart @@ -7,8 +7,23 @@ import 'package:webf/css.dart'; /// https://drafts.csswg.org/cssom/#the-cssstylerule-interface class CSSStyleRule extends CSSRule { - final String selectorText; - final Map<String, String> style; + @override + String get cssText => declaration.cssText; - CSSStyleRule(this.selectorText, this.style); + final SelectorGroup selectorGroup; + final CSSStyleDeclaration declaration; + + CSSStyleRule(this.selectorGroup, this.declaration) : super(); + + SimpleSelector? get lastSimpleSelector { + return selectorGroup.selectors.last.simpleSelectorSequences.last.simpleSelector; + } + + String get selectorText { + var sb = StringBuffer(); + selectorGroup.selectors.forEach((selector) { + sb.write(selector.simpleSelectorSequences.map((ss) => ss.simpleSelector.name).join(' ')); + }); + return sb.toString(); + } } diff --git a/webf/lib/src/css/style_sheet.dart b/webf/lib/src/css/style_sheet.dart index c4128e3b08..6c13e041b1 100644 --- a/webf/lib/src/css/style_sheet.dart +++ b/webf/lib/src/css/style_sheet.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 'package:webf/css.dart'; abstract class StyleSheet {} @@ -19,31 +18,29 @@ class CSSStyleSheet implements StyleSheet { /// A string containing the baseURL used to resolve relative URLs in the stylesheet. String? herf; - List<CSSRule> cssRules = []; + final RuleSet ruleSet; - CSSStyleSheet(String text, {this.disabled = false, this.herf}) { - List<CSSRule> rules = CSSParser.parseRules(text, parentStyleSheet: this); - cssRules.addAll(rules); - } + CSSStyleSheet(this.ruleSet, {this.disabled = false, this.herf}); insertRule(String text, int index) { - CSSRule? rule = CSSParser.parseRule(text); - if (rule != null) { - cssRules.insert(index, rule); - } else { - // TODO: throw error + List<CSSRule> rules = CSSParser(text).parseRules(); + for (CSSRule rule in rules) { + ruleSet.addRule(rule); } } /// Removes a rule from the stylesheet object. deleteRule(int index) { - cssRules.removeAt(index); + ruleSet.deleteRule(index); } /// Synchronously replaces the content of the stylesheet with the content passed into it. replaceSync(String text) { - cssRules.clear(); - cssRules.addAll(CSSParser.parseRules(text)); + ruleSet.reset(); + List<CSSRule> rules = CSSParser(text).parseRules(); + for (CSSRule rule in rules) { + ruleSet.addRule(rule); + } } replace(String text) { diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index b2aab26aaf..6545e1d485 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -80,6 +80,8 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // Default to unknown, assign by [createElement], used by inspector. String tagName = UNKNOWN; + String? get id => getAttribute('id'); + // Is element an replaced element. // https://drafts.csswg.org/css-display/#replaced-element final bool _isReplacedElement; @@ -1413,22 +1415,10 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } void _applySheetStyle(CSSStyleDeclaration style) { - if (classList.isNotEmpty) { - const String classSelectorPrefix = '.'; - for (String className in classList) { - for (CSSStyleSheet sheet in ownerDocument.styleSheets) { - List<CSSRule> rules = sheet.cssRules; - for (int i = 0; i < rules.length; i++) { - CSSRule rule = rules[i]; - if (rule is CSSStyleRule && rule.selectorText == (classSelectorPrefix + className)) { - var sheetStyle = rule.style; - for (String propertyName in sheetStyle.keys) { - style.setProperty(propertyName, sheetStyle[propertyName], false); - } - } - } - } - } + for (CSSStyleSheet sheet in ownerDocument.styleSheets) { + RuleSet ruleSet = sheet.ruleSet; + CSSStyleDeclaration? matchRule = ElementRuleCollector().collectionFromRuleSet(ruleSet, this); + style.merge(matchRule); } } diff --git a/webf/lib/src/dom/elements/head.dart b/webf/lib/src/dom/elements/head.dart index 1f64055a3e..cbe01d28b2 100644 --- a/webf/lib/src/dom/elements/head.dart +++ b/webf/lib/src/dom/elements/head.dart @@ -170,7 +170,8 @@ class LinkElement extends Element { } void _addCSSStyleSheet(String css) { - ownerDocument.addStyleSheet(CSSStyleSheet(css)); + final sheet = CSSParser(css).parse(); + ownerDocument.addStyleSheet(sheet); } @override @@ -246,7 +247,8 @@ class StyleElement extends Element { _styleSheet!.replaceSync(text); ownerDocument.recalculateDocumentStyle(); } else { - ownerDocument.addStyleSheet(_styleSheet = CSSStyleSheet(text)); + final sheet = CSSParser(text).parse(); + ownerDocument.addStyleSheet(_styleSheet = sheet); } } } diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index b8db70e756..e53b1208d3 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -100,6 +100,20 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa return null; } + Element? get previousElementSibling { + if (previousSibling != null && previousSibling!.nodeType == NodeType.ELEMENT_NODE) { + return previousSibling as Element; + } + return null; + } + + Element? get nextElementSibling { + if (nextSibling != null && nextSibling!.nodeType == NodeType.ELEMENT_NODE) { + return nextSibling as Element; + } + return null; + } + Node(this.nodeType, [BindingContext? context]) : super(context); // If node is on the tree, the root parent is body. diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index 4bf795e406..779bd49c56 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -6,100 +6,134 @@ import 'package:webf/css.dart'; import 'package:test/test.dart'; +CSSRule? parseSingleRule(String rule) { + CSSStyleSheet sheet = CSSParser(rule).parse(); + return sheet.ruleSet.rules.first; +} + void main() { group('CSSStyleRuleParser', () { - test('1', () { - CSSRule? rule = CSSStyleRuleParser.parse(' .foo { color: red; }'); + test('0', () { + CSSRule? rule = parseSingleRule('div p, #id:first-line { color : red; }'); expect(rule is CSSStyleRule, true); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'first-line'); + expect(styleRule.declaration.getPropertyValue('color'), 'red'); + }); + test('1', () { + CSSRule? rule = parseSingleRule(' .foo { color: red; }'); + expect(rule is CSSStyleRule, true); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.selectorText, '.foo'); - expect(styleRule.style['color'], 'red'); + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('color'), 'red'); }); test('2', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('html{\n color:black;\n}'); - expect(styleRule, isNotNull); - expect(styleRule!.selectorText, 'html'); - expect(styleRule.style['color'], 'black'); + CSSRule? rule = parseSingleRule(' html{\n color:black;\n}'); + expect(rule is CSSStyleRule, true); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'html'); + expect(styleRule.declaration.getPropertyValue('color'), 'black'); }); test('3', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('/*\nSome Comments\nBaby \n*/\nhtml{\n color:black;\n}'); - expect(styleRule!.selectorText, 'html'); - expect(styleRule.style['color'], 'black'); + CSSRule? rule = parseSingleRule('/*\nSome Comments\nBaby \n*/\nhtml{\n color:black;\n}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'html'); + expect(styleRule.declaration.getPropertyValue('color'), 'black'); }); test('4', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('/*\nSome Comments\nBaby \n*/\nhtml{\n color:black;\n}'); - expect(styleRule!.selectorText, 'html'); - expect(styleRule.style['color'], 'black'); + CSSRule? rule = parseSingleRule('/*\nSome Comments\nBaby \n*/\nhtml{\n color:black;\n}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'html'); + expect(styleRule.declaration.getPropertyValue('color'), 'black'); }); test('5', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo{--custom:some\n value;}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['--custom'], 'some value'); + CSSRule? rule = parseSingleRule('.foo{--custom:some\n value;}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('--custom'), 'some value'); }); test('6', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo{zoom;\ncolor: red \n}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['color'], 'red'); + CSSRule? rule = parseSingleRule('.foo{zoom;\ncolor: red \n}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('color'), 'red'); }); test('7', () { - CSSStyleRule? styleRule = - CSSStyleRuleParser.parse('.foo \t {background: url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4) red}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['background'], 'url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4) red'); + CSSRule? rule = parseSingleRule('.foo \t {background: url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4) red}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule. lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('backgroundColor'), 'red'); + expect(styleRule.declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4)'); }); test('8', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo { color: rgb(255, 255, 0)}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['color'], 'rgb(255, 255, 0)'); + CSSRule? rule = parseSingleRule('.foo { color: rgb(255, 255, 0)}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); }); test('9', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo { background : ; color: rgb(255, 255, 0)}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['color'], 'rgb(255, 255, 0)'); + CSSRule? rule = parseSingleRule('.foo { background : ; color: rgb(255, 255, 0)}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); }); test('10', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('th:nth-child(4) {color: rgb(255, 255, 0)}'); - expect(styleRule!.selectorText, 'th:nth-child(4)'); - expect(styleRule.style['color'], 'rgb(255, 255, 0)'); + CSSRule? rule = parseSingleRule('div:nth-child(4) {color: rgb(255, 255, 0)}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + final simpleSelectors = styleRule.selectorGroup.selectors.first.simpleSelectorSequences; + expect(simpleSelectors[0].simpleSelector.name, 'div'); + expect(simpleSelectors[1].simpleSelector.name, 'nth-child'); + expect(simpleSelectors[1].simpleSelector is PseudoClassFunctionSelector, true); + expect((simpleSelectors[1].simpleSelector as PseudoClassFunctionSelector).argument, ['4']); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); }); test('11', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('[hidden] { display: none }'); - expect(styleRule!.selectorText, '[hidden]'); - expect(styleRule.style['display'], 'none'); + CSSRule? rule = parseSingleRule('[hidden] { display: none }'); + CSSStyleRule styleRule = rule as CSSStyleRule; + final simpleSelectors = styleRule.selectorGroup.selectors.first.simpleSelectorSequences; + expect(simpleSelectors[0].simpleSelector is AttributeSelector, true); + expect(simpleSelectors[0].simpleSelector.name, 'hidden'); + expect(styleRule.declaration.getPropertyValue('display'), 'none'); }); test('12', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('/**/ div > p { color: rgb(255, 255, 0); } /**/'); - expect(styleRule!.selectorText, 'div > p'); - expect(styleRule.style['color'], 'rgb(255, 255, 0)'); + CSSRule? rule = parseSingleRule('/**/ div > p { color: rgb(255, 255, 0); } /**/'); + CSSStyleRule styleRule = rule as CSSStyleRule; + final simpleSelectors = styleRule.selectorGroup.selectors.first.simpleSelectorSequences; + expect(simpleSelectors[0].simpleSelector.name, 'div'); + expect(simpleSelectors[1].simpleSelector.name, 'p'); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); }); test('13', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo { background-image: url( "./image (1).jpg" )}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['backgroundImage'], 'url( "./image (1).jpg" )'); + CSSRule? rule = parseSingleRule('.foo { background-image: url( "./image (1).jpg" )}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('background-image'), 'url(./image (1).jpg)'); }); test('14', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo { .foo{ }; color: red}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['color'], 'red'); + CSSRule? rule = parseSingleRule('.foo { .foo{ }; color: red}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('color'), 'red'); }); test('15', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse(' .foo {}'); - expect(styleRule!.selectorText, '.foo'); + CSSRule? rule = parseSingleRule(' .foo {}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); }); }); } diff --git a/webf/test/src/css/style_sheet_parser.dart b/webf/test/src/css/style_sheet_parser.dart index 407e50b29a..ca26b4d5ea 100644 --- a/webf/test/src/css/style_sheet_parser.dart +++ b/webf/test/src/css/style_sheet_parser.dart @@ -6,61 +6,72 @@ import 'package:webf/css.dart'; import 'package:test/test.dart'; +List<CSSRule> parseRules(String rule) { + CSSStyleSheet sheet = CSSParser(rule).parse(); + return sheet.ruleSet.rules; +} + void main() { group('CSSStyleSheetParser', () { test('1', () { - List<CSSRule> rules = CSSStyleSheetParser.parse('.foo {color: red} \n .bar {}'); + List<CSSRule> rules = parseRules('.foo {color: red} \n .bar {}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); + expect((rules[1] as CSSStyleRule).selectorText, 'bar'); }); test('2', () { - List<CSSRule> rules = CSSStyleSheetParser.parse('{} \n .foo {color: red;} ;\n .bar {;;}'); + List<CSSRule> rules = parseRules('{} \n .foo {color: red;} ;\n .bar {;;}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); + expect((rules[1] as CSSStyleRule).selectorText, 'bar'); }); test('3', () { - List<CSSRule> rules = CSSStyleSheetParser.parse('.foo {color: red;} .bar { .x {}; color: #aaa} .baz {}'); + List<CSSRule> rules = parseRules('.foo {color: red;} .bar { .x {}; color: #aaa} .baz {}'); expect(rules.length, 3); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); - expect((rules[1] as CSSStyleRule).style['color'], '#aaa'); - expect((rules[2] as CSSStyleRule).selectorText, '.baz'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); + expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).declaration.getPropertyValue('color'), '#aaa'); + expect((rules[2] as CSSStyleRule).selectorText, 'baz'); }); test('4', () { - List<CSSRule> rules = - CSSStyleSheetParser.parse('.foo {color: red} .bar {background: url(data:image/png;base64...)}'); + List<CSSRule> rules = parseRules('.foo {color: red} .bar {background: url(data:image/png;base64...)}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); - expect((rules[1] as CSSStyleRule).style['background'], 'url(data:image/png;base64...)'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); + expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64...)'); }); test('5', () { - List<CSSRule> rules = CSSStyleSheetParser.parse('@charset "utf-8"; .foo {color: red}'); + List<CSSRule> rules = parseRules('@charset "utf-8"; .foo {color: red}'); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); test('6', () { - List<CSSRule> rules = CSSStyleSheetParser.parse(''' + List<CSSRule> rules = parseRules(''' @media screen and (min-width: 900.5px) { } .foo { color: red } '''); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); + }); + + test('7', () { + List<CSSRule> rules = parseRules('.foo h6{color: red}'); + expect(rules.length, 1); + expect((rules[0] as CSSStyleRule).selectorText, 'foo h6'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); }); } From c90e3a31a9d883fef15b84146d708776201d4384 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Thu, 4 Aug 2022 17:37:57 +0800 Subject: [PATCH 174/498] feat: ignore parse shorthand value --- webf/lib/src/css/element_rule_collector.dart | 2 +- webf/lib/src/css/parser/parser.dart | 159 +++--------------- webf/lib/src/css/parser/token_kind.dart | 116 +------------ webf/lib/src/css/parser/tokenizer.dart | 6 +- ...r_checker.dart => selector_evaluator.dart} | 0 webf/lib/src/css/style_declaration.dart | 91 ++++------ webf/lib/src/dom/element.dart | 13 +- webf/pubspec.yaml | 1 + webf/test/src/css/style_rule_parser.dart | 6 + 9 files changed, 66 insertions(+), 328 deletions(-) rename webf/lib/src/css/{selector_checker.dart => selector_evaluator.dart} (100%) diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index cc3b95717f..3299ffaf35 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -5,7 +5,7 @@ import 'package:webf/css.dart'; import 'package:webf/dom.dart'; -import 'package:webf/src/css/selector_checker.dart'; +import 'package:webf/src/css/selector_evaluator.dart'; class ElementRuleCollector { diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 10a357d3dc..82fb6c54cf 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -561,7 +561,10 @@ class CSSParser { ThisOperator(), ) : simpleSelector(); - if (simpleSel == null && (combinatorType == TokenKind.COMBINATOR_PLUS || combinatorType == TokenKind.COMBINATOR_GREATER || combinatorType == TokenKind.COMBINATOR_TILDE)) { + if (simpleSel == null && + (combinatorType == TokenKind.COMBINATOR_PLUS || + combinatorType == TokenKind.COMBINATOR_GREATER || + combinatorType == TokenKind.COMBINATOR_TILDE)) { // For "+ &", "~ &" or "> &" a selector sequence with no name is needed // so that the & will have a combinator too. This is needed to // disambiguate selector expressions: @@ -703,7 +706,8 @@ class CSSParser { _eat(TokenKind.RPAREN); return NegationSelector(negArg); - } else if (!pseudoElement && (name == 'host' || name == 'host-context' || name == 'global-context' || name == '-acx-global-context')) { + } else if (!pseudoElement && + (name == 'host' || name == 'host-context' || name == 'global-context' || name == '-acx-global-context')) { _eat(TokenKind.LPAREN); var selector = processCompoundSelector(); if (selector == null) { @@ -729,13 +733,17 @@ class CSSParser { // Used during selector look-a-head if not a SelectorExpression is // bad. _eat(TokenKind.RPAREN); - return (pseudoElement) ? PseudoElementFunctionSelector(pseudoName, expr) : PseudoClassFunctionSelector(pseudoName, expr); + return (pseudoElement) + ? PseudoElementFunctionSelector(pseudoName, expr) + : PseudoClassFunctionSelector(pseudoName, expr); } } // Treat CSS2.1 pseudo-elements defined with pseudo class syntax as pseudo- // elements for backwards compatibility. - return pseudoElement || _legacyPseudoElements.contains(name) ? PseudoElementSelector(pseudoName, isLegacy: !pseudoElement) : PseudoClassSelector(pseudoName); + return pseudoElement || _legacyPseudoElements.contains(name) + ? PseudoElementSelector(pseudoName, isLegacy: !pseudoElement) + : PseudoClassSelector(pseudoName); } /// In CSS3, the expressions are identifiers, strings, or of the form "an+b". @@ -880,7 +888,7 @@ class CSSParser { } } - var expr = processExpr().join(' '); + var expr = processExpr(); // Handle !important (prio) var importantPriority = _maybeEat(TokenKind.IMPORTANT); @@ -899,139 +907,16 @@ class CSSParser { // expression: term [ operator? term]* // // operator: '/' | ',' - // term: (see processTerm) - List<String> processExpr([bool ieFilter = false]) { + String processExpr([bool ieFilter = false]) { var start = _peekToken.span; - - var keepGoing = true; - List<String> result = []; - String? expr; - while (keepGoing && (expr = processTerm()) != null) { - Expression? op; - - var opStart = _peekToken.span; - - switch (_peek()) { - case TokenKind.SLASH: - op = OperatorSlash(); - break; - case TokenKind.COMMA: - op = OperatorComma(); - break; - case TokenKind.BACKSLASH: - _next(); - if (_peekKind(TokenKind.INTEGER)) { - _next(); - if (isChecked) { - _warning('\$value is not valid in an expression', _makeSpan(start)); - } - } - break; - } - - if (expr != null) { - result.add(expr); - } else { - keepGoing = false; - } - - if (op != null) { - result.add(_makeSpan(opStart).text); - _next(); - } + FileSpan? end; + while (_peek() != TokenKind.SEMICOLON) { + end = _next().span; } - return result; - } - - String? processTerm() { - var start = _peekToken.span; - Token? t; // token for term's value - dynamic value; // value of term (numeric values) - - var unary = ''; - switch (_peek()) { - case TokenKind.HASH: - _eat(TokenKind.HASH); - if (!_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { - String? hexText; - if (_peekKind(TokenKind.INTEGER)) { - var hexText1 = _peekToken.text; - _next(); - // Append identifier only if there's no delimiting whitespace. - if (_peekIdentifier() && _previousToken!.end == _peekToken.start) { - hexText = '$hexText1${identifier().name}'; - } else { - hexText = hexText1; - } - } else if (_peekIdentifier()) { - hexText = identifier().name; - } - return '#$hexText'; - } - - if (isChecked) { - _warning('Expected hex number', _makeSpan(start)); - } - // Construct the bad hex value with a #<space>number. - return start.text; - case TokenKind.INTEGER: - case TokenKind.DOUBLE: - t = _next(); - return '$unary${t.text}'; - case TokenKind.SINGLE_QUOTE: - value = processQuotedString(false); - value = "'${_escapeString(value as String, single: true)}'"; - return value; - case TokenKind.DOUBLE_QUOTE: - value = processQuotedString(false); - value = '"${_escapeString(value as String)}"'; - return value; - case TokenKind.LPAREN: - _next(); - return null; - case TokenKind.LBRACK: - _next(); - return null; - case TokenKind.IDENTIFIER: - var nameValue = identifier(); // Snarf up the ident we'll remap, maybe. - - if (_maybeEat(TokenKind.LPAREN)) { - return processFunction(nameValue); - } - return nameValue.name; - case TokenKind.UNICODE_RANGE: - String? first; - String? second; - int firstNumber; - int secondNumber; - _eat(TokenKind.UNICODE_RANGE, unicodeRange: true); - if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { - first = _previousToken!.text; - firstNumber = int.parse('0x$first'); - if (firstNumber > MAX_UNICODE) { - _error('unicode range must be less than 10FFFF', _makeSpan(start)); - } - if (_maybeEat(TokenKind.MINUS, unicodeRange: true)) { - if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { - second = _previousToken!.text; - secondNumber = int.parse('0x$second'); - if (secondNumber > MAX_UNICODE) { - _error('unicode range must be less than 10FFFF', _makeSpan(start)); - } - if (firstNumber > secondNumber) { - _error('unicode first range can not be greater than last', _makeSpan(start)); - } - } - } - } else if (_maybeEat(TokenKind.HEX_RANGE, unicodeRange: true)) { - first = _previousToken!.text; - } - return 'U+$first-$second'; - case TokenKind.AT: - break; + if (end != null) { + return start.expand(end).text; } - - return null; + return ''; } static const int MAX_UNICODE = 0x10FFFF; @@ -1141,7 +1026,7 @@ class CSSParser { if (!_maybeEat(TokenKind.RPAREN)) { _error('problem parsing function expected ), ', _peekToken.span); } - return 'rgb(${expr.join()})'; + return 'rgb($expr)'; case 'url': // URI term sucks up everything inside of quotes(' or ") or between // parens. @@ -1176,7 +1061,7 @@ class CSSParser { if (!_maybeEat(TokenKind.RPAREN)) { _error('problem parsing function expected ), ', _peekToken.span); } - return expr.join(); + return expr; } } diff --git a/webf/lib/src/css/parser/token_kind.dart b/webf/lib/src/css/parser/token_kind.dart index 7a9f31f81b..f860279284 100644 --- a/webf/lib/src/css/parser/token_kind.dart +++ b/webf/lib/src/css/parser/token_kind.dart @@ -1,5 +1,5 @@ /* -Copyright 2013, the Dart project authors. +Copyright 2013, the Dart project authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -137,35 +137,6 @@ class TokenKind { static const int SUBSTRING_MATCH = 534; // '*=' static const int NO_MATCH = 535; // No operator. - // Unit types: - static const int UNIT_EM = 600; - static const int UNIT_EX = 601; - static const int UNIT_LENGTH_PX = 602; - static const int UNIT_LENGTH_CM = 603; - static const int UNIT_LENGTH_MM = 604; - static const int UNIT_LENGTH_IN = 605; - static const int UNIT_LENGTH_PT = 606; - static const int UNIT_LENGTH_PC = 607; - static const int UNIT_ANGLE_DEG = 608; - static const int UNIT_ANGLE_RAD = 609; - static const int UNIT_ANGLE_GRAD = 610; - static const int UNIT_ANGLE_TURN = 611; - static const int UNIT_TIME_MS = 612; - static const int UNIT_TIME_S = 613; - static const int UNIT_FREQ_HZ = 614; - static const int UNIT_FREQ_KHZ = 615; - static const int UNIT_PERCENT = 616; - static const int UNIT_FRACTION = 617; - static const int UNIT_RESOLUTION_DPI = 618; - static const int UNIT_RESOLUTION_DPCM = 619; - static const int UNIT_RESOLUTION_DPPX = 620; - static const int UNIT_CH = 621; // Measure of "0" U+0030 glyph. - static const int UNIT_REM = 622; // computed value ‘font-size’ on root elem. - static const int UNIT_VIEWPORT_VW = 623; - static const int UNIT_VIEWPORT_VH = 624; - static const int UNIT_VIEWPORT_VMIN = 625; - static const int UNIT_VIEWPORT_VMAX = 626; - // Directives (@nnnn) static const int DIRECTIVE_NONE = 640; static const int DIRECTIVE_IMPORT = 641; @@ -271,58 +242,10 @@ class TokenKind { {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTBOTTOM, 'value': 'right-bottom'}, ]; - static const List<Map<String, dynamic>> _UNITS = [ - {'unit': TokenKind.UNIT_EM, 'value': 'em'}, - {'unit': TokenKind.UNIT_EX, 'value': 'ex'}, - {'unit': TokenKind.UNIT_LENGTH_PX, 'value': 'px'}, - {'unit': TokenKind.UNIT_LENGTH_CM, 'value': 'cm'}, - {'unit': TokenKind.UNIT_LENGTH_MM, 'value': 'mm'}, - {'unit': TokenKind.UNIT_LENGTH_IN, 'value': 'in'}, - {'unit': TokenKind.UNIT_LENGTH_PT, 'value': 'pt'}, - {'unit': TokenKind.UNIT_LENGTH_PC, 'value': 'pc'}, - {'unit': TokenKind.UNIT_ANGLE_DEG, 'value': 'deg'}, - {'unit': TokenKind.UNIT_ANGLE_RAD, 'value': 'rad'}, - {'unit': TokenKind.UNIT_ANGLE_GRAD, 'value': 'grad'}, - {'unit': TokenKind.UNIT_ANGLE_TURN, 'value': 'turn'}, - {'unit': TokenKind.UNIT_TIME_MS, 'value': 'ms'}, - {'unit': TokenKind.UNIT_TIME_S, 'value': 's'}, - {'unit': TokenKind.UNIT_FREQ_HZ, 'value': 'hz'}, - {'unit': TokenKind.UNIT_FREQ_KHZ, 'value': 'khz'}, - {'unit': TokenKind.UNIT_FRACTION, 'value': 'fr'}, - {'unit': TokenKind.UNIT_RESOLUTION_DPI, 'value': 'dpi'}, - {'unit': TokenKind.UNIT_RESOLUTION_DPCM, 'value': 'dpcm'}, - {'unit': TokenKind.UNIT_RESOLUTION_DPPX, 'value': 'dppx'}, - {'unit': TokenKind.UNIT_CH, 'value': 'ch'}, - {'unit': TokenKind.UNIT_REM, 'value': 'rem'}, - {'unit': TokenKind.UNIT_VIEWPORT_VW, 'value': 'vw'}, - {'unit': TokenKind.UNIT_VIEWPORT_VH, 'value': 'vh'}, - {'unit': TokenKind.UNIT_VIEWPORT_VMIN, 'value': 'vmin'}, - {'unit': TokenKind.UNIT_VIEWPORT_VMAX, 'value': 'vmax'}, - ]; - // Some more constants: static const int ASCII_UPPER_A = 65; // ASCII value for uppercase A static const int ASCII_UPPER_Z = 90; // ASCII value for uppercase Z - // TODO(terry): Should used Dart mirroring for parameter values and types - // especially for enumeration (e.g., counter's second parameter - // is list-style-type which is an enumerated list for ordering - // of a list 'circle', 'decimal', 'lower-roman', 'square', etc. - // see http://www.w3schools.com/cssref/pr_list-style-type.asp - // for list of possible values. - - /// Check if name is a pre-defined CSS name. Used by error handler to report - /// if name is unknown or used improperly. - static bool isPredefinedName(String name) { - var nameLen = name.length; - // TODO(terry): Add more pre-defined names (hidden, bolder, inherit, etc.). - if (matchUnits(name, 0, nameLen) == -1 || matchDirectives(name, 0, nameLen) == -1 || matchMarginDirectives(name, 0, nameLen) == -1) { - return false; - } - - return true; - } - /// Return the token that matches the unit ident found. static int matchList(Iterable<Map<String, dynamic>> identList, String tokenField, String text, int offset, int length) { for (final entry in identList) { @@ -351,11 +274,6 @@ class TokenKind { return -1; // Not a unit token. } - /// Return the token that matches the unit ident found. - static int matchUnits(String text, int offset, int length) { - return matchList(_UNITS, 'unit', text, offset, length); - } - /// Return the token that matches the directive name found. static int matchDirectives(String text, int offset, int length) { return matchList(_DIRECTIVES, 'type', text, offset, length); @@ -382,22 +300,6 @@ class TokenKind { return null; } - /// Return the unit token as its pretty name. - static String? unitToString(int unitTokenToFind) { - if (unitTokenToFind == TokenKind.PERCENT) { - return '%'; - } else { - for (final entry in _UNITS) { - final unit = entry['unit'] as int; - if (unit == unitTokenToFind) { - return entry['value'] as String?; - } - } - } - - return '<BAD UNIT>'; // Not a unit token. - } - /// Return RGB value as [int] from a color entry in _EXTENDED_COLOR_NAMES. static int colorValue(Map entry) { return entry['value'] as int; @@ -525,22 +427,6 @@ class TokenKind { case TokenKind.DIRECTIVE_MIXIN: case TokenKind.DIRECTIVE_INCLUDE: case TokenKind.DIRECTIVE_CONTENT: - case TokenKind.UNIT_EM: - case TokenKind.UNIT_EX: - case TokenKind.UNIT_LENGTH_PX: - case TokenKind.UNIT_LENGTH_CM: - case TokenKind.UNIT_LENGTH_MM: - case TokenKind.UNIT_LENGTH_IN: - case TokenKind.UNIT_LENGTH_PT: - case TokenKind.UNIT_LENGTH_PC: - case TokenKind.UNIT_ANGLE_DEG: - case TokenKind.UNIT_ANGLE_RAD: - case TokenKind.UNIT_ANGLE_GRAD: - case TokenKind.UNIT_TIME_MS: - case TokenKind.UNIT_TIME_S: - case TokenKind.UNIT_FREQ_HZ: - case TokenKind.UNIT_FREQ_KHZ: - case TokenKind.UNIT_FRACTION: return true; default: return false; diff --git a/webf/lib/src/css/parser/tokenizer.dart b/webf/lib/src/css/parser/tokenizer.dart index a0139ac0bd..666ab8acaf 100644 --- a/webf/lib/src/css/parser/tokenizer.dart +++ b/webf/lib/src/css/parser/tokenizer.dart @@ -1,5 +1,5 @@ /* -Copyright 2013, the Dart project authors. +Copyright 2013, the Dart project authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -264,10 +264,6 @@ class Tokenizer extends TokenizerBase { // Is the identifier a unit type? var tokId = -1; - // Don't match units in selectors or selector expressions. - if (!inSelectorExpression && !inSelector) { - tokId = TokenKind.matchUnits(_text, _startIndex, _index - _startIndex); - } if (tokId == -1) { tokId = (_text.substring(_startIndex, _index) == '!important') ? TokenKind.IMPORTANT : -1; } diff --git a/webf/lib/src/css/selector_checker.dart b/webf/lib/src/css/selector_evaluator.dart similarity index 100% rename from webf/lib/src/css/selector_checker.dart rename to webf/lib/src/css/selector_evaluator.dart diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index b3a5c6b14f..b9878a0225 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -46,8 +46,7 @@ List<String> _propertyOrders = [ RegExp _kebabCaseReg = RegExp(r'[A-Z]'); -final LinkedLruHashMap<String, Map<String, String?>> _cachedExpandedShorthand = - LinkedLruHashMap(maximumSize: 500); +final LinkedLruHashMap<String, Map<String, String?>> _cachedExpandedShorthand = LinkedLruHashMap(maximumSize: 500); // CSS Object Model: https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface @@ -65,14 +64,15 @@ final LinkedLruHashMap<String, Map<String, String?>> _cachedExpandedShorthand = /// object as a read-only interface. class CSSStyleDeclaration { Element? target; + // TODO(yuanyan): defaultStyle should be longhand properties. Map<String, dynamic>? defaultStyle; StyleChangeListener? onStyleChanged; CSSStyleDeclaration(); + // ignore: prefer_initializing_formals - CSSStyleDeclaration.computedStyle( - this.target, this.defaultStyle, this.onStyleChanged); + CSSStyleDeclaration.computedStyle(this.target, this.defaultStyle, this.onStyleChanged); /// An empty style declaration. static CSSStyleDeclaration empty = CSSStyleDeclaration(); @@ -116,9 +116,7 @@ class CSSStyleDeclaration { /// If not set, returns the empty string. String getPropertyValue(String propertyName) { // Get the latest pending value first. - return _pendingProperties[propertyName] ?? - _properties[propertyName] ?? - EMPTY_STRING; + return _pendingProperties[propertyName] ?? _properties[propertyName] ?? EMPTY_STRING; } /// Removes a property from the CSS declaration. @@ -131,8 +129,7 @@ class CSSStyleDeclaration { case BACKGROUND: return CSSStyleProperty.removeShorthandBackground(this, isImportant); case BACKGROUND_POSITION: - return CSSStyleProperty.removeShorthandBackgroundPosition( - this, isImportant); + return CSSStyleProperty.removeShorthandBackgroundPosition(this, isImportant); case BORDER_RADIUS: return CSSStyleProperty.removeShorthandBorderRadius(this, isImportant); case OVERFLOW: @@ -151,13 +148,11 @@ class CSSStyleDeclaration { case BORDER_COLOR: case BORDER_STYLE: case BORDER_WIDTH: - return CSSStyleProperty.removeShorthandBorder( - this, propertyName, isImportant); + return CSSStyleProperty.removeShorthandBorder(this, propertyName, isImportant); case TRANSITION: return CSSStyleProperty.removeShorthandTransition(this, isImportant); case TEXT_DECORATION: - return CSSStyleProperty.removeShorthandTextDecoration( - this, isImportant); + return CSSStyleProperty.removeShorthandTextDecoration(this, isImportant); } String present = EMPTY_STRING; @@ -173,9 +168,7 @@ class CSSStyleDeclaration { } // Fallback to default style. - if (isNullOrEmptyValue(present) && - defaultStyle != null && - defaultStyle!.containsKey(propertyName)) { + if (isNullOrEmptyValue(present) && defaultStyle != null && defaultStyle!.containsKey(propertyName)) { present = defaultStyle![propertyName]; } @@ -183,8 +176,7 @@ class CSSStyleDeclaration { _pendingProperties[propertyName] = present; } - void _expandShorthand( - String propertyName, String normalizedValue, bool? isImportant) { + void _expandShorthand(String propertyName, String normalizedValue, bool? isImportant) { Map<String, String?> longhandProperties; String cacheKey = '$propertyName:$normalizedValue'; if (_cachedExpandedShorthand.containsKey(cacheKey)) { @@ -194,40 +186,31 @@ class CSSStyleDeclaration { switch (propertyName) { case PADDING: - CSSStyleProperty.setShorthandPadding( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandPadding(longhandProperties, normalizedValue); break; case MARGIN: - CSSStyleProperty.setShorthandMargin( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandMargin(longhandProperties, normalizedValue); break; case BACKGROUND: - CSSStyleProperty.setShorthandBackground( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBackground(longhandProperties, normalizedValue); break; case BACKGROUND_POSITION: - CSSStyleProperty.setShorthandBackgroundPosition( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBackgroundPosition(longhandProperties, normalizedValue); break; case BORDER_RADIUS: - CSSStyleProperty.setShorthandBorderRadius( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBorderRadius(longhandProperties, normalizedValue); break; case OVERFLOW: - CSSStyleProperty.setShorthandOverflow( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandOverflow(longhandProperties, normalizedValue); break; case FONT: - CSSStyleProperty.setShorthandFont( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFont(longhandProperties, normalizedValue); break; case FLEX: - CSSStyleProperty.setShorthandFlex( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFlex(longhandProperties, normalizedValue); break; case FLEX_FLOW: - CSSStyleProperty.setShorthandFlexFlow( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFlexFlow(longhandProperties, normalizedValue); break; case BORDER: case BORDER_TOP: @@ -237,16 +220,13 @@ class CSSStyleDeclaration { case BORDER_COLOR: case BORDER_STYLE: case BORDER_WIDTH: - CSSStyleProperty.setShorthandBorder( - longhandProperties, propertyName, normalizedValue); + CSSStyleProperty.setShorthandBorder(longhandProperties, propertyName, normalizedValue); break; case TRANSITION: - CSSStyleProperty.setShorthandTransition( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandTransition(longhandProperties, normalizedValue); break; case TEXT_DECORATION: - CSSStyleProperty.setShorthandTextDecoration( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandTextDecoration(longhandProperties, normalizedValue); break; } _cachedExpandedShorthand[cacheKey] = longhandProperties; @@ -259,9 +239,7 @@ class CSSStyleDeclaration { } } - String _replacePattern( - String string, String lowerCase, String startString, String endString, - [int start = 0]) { + String _replacePattern(String string, String lowerCase, String startString, String endString, [int start = 0]) { int startIndex = lowerCase.indexOf(startString, start); if (startIndex >= 0) { int? endIndex; @@ -274,8 +252,7 @@ class CSSStyleDeclaration { var replacement = string.substring(startIndex, endIndex); lowerCase = lowerCase.replaceRange(startIndex, endIndex, replacement); if (endIndex < string.length - 1) { - lowerCase = _replacePattern( - string, lowerCase, startString, endString, endIndex); + lowerCase = _replacePattern(string, lowerCase, startString, endString, endIndex); } } } @@ -366,12 +343,10 @@ class CSSStyleDeclaration { if (!CSSColor.isColor(normalizedValue)) return false; break; case BACKGROUND_IMAGE: - if (!CSSBackground.isValidBackgroundImageValue(normalizedValue)) - return false; + if (!CSSBackground.isValidBackgroundImageValue(normalizedValue)) return false; break; case BACKGROUND_REPEAT: - if (!CSSBackground.isValidBackgroundRepeatValue(normalizedValue)) - return false; + if (!CSSBackground.isValidBackgroundRepeatValue(normalizedValue)) return false; break; } return true; @@ -478,13 +453,10 @@ class CSSStyleDeclaration { if (isNullOrEmptyValue(otherValue) && isNullOrEmptyValue(currentValue)) { continue; - } else if (!isNullOrEmptyValue(currentValue) && - isNullOrEmptyValue(otherValue)) { + } else if (!isNullOrEmptyValue(currentValue) && isNullOrEmptyValue(otherValue)) { // Remove property. _pendingProperties.remove(propertyName); - } else if (!isImportant && - otherValue != null && - currentValue != otherValue) { + } else if (!isImportant && otherValue != null && currentValue != otherValue) { // Update property. _pendingProperties[propertyName] = otherValue; bool otherIsImportant = declaration._importants[propertyName] ?? false; @@ -522,8 +494,7 @@ class CSSStyleDeclaration { if (isNullOrEmptyValue(prevValue) && isNullOrEmptyValue(currentValue)) { continue; - } else if (!isNullOrEmptyValue(prevValue) && - isNullOrEmptyValue(currentValue)) { + } else if (!isNullOrEmptyValue(prevValue) && isNullOrEmptyValue(currentValue)) { // Remove property. diffs[propertyName] = null; } else if (prevValue != currentValue) { @@ -545,6 +516,7 @@ class CSSStyleDeclaration { /// Override [] and []= operator to get/set style properties. operator [](String property) => getPropertyValue(property); + operator []=(String property, value) { setProperty(property, value); } @@ -598,6 +570,5 @@ class CSSStyleDeclaration { // aB to a-b String _kebabize(String str) { - return str.replaceAllMapped( - _kebabCaseReg, (match) => '-${match[0]!.toLowerCase()}'); + return str.replaceAllMapped(_kebabCaseReg, (match) => '-${match[0]!.toLowerCase()}'); } diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 6545e1d485..1599819123 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1448,19 +1448,12 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } void recalculateStyle() { - // TODO: current only support class selector in stylesheet - if (renderBoxModel != null && classList.isNotEmpty) { + if (renderBoxModel != null) { // Diff style. CSSStyleDeclaration newStyle = CSSStyleDeclaration(); applyStyle(newStyle); - Map<String, String?> diffs = style.diff(newStyle); - if (diffs.isNotEmpty) { - // Update render style. - diffs.forEach((String propertyName, String? value) { - style.setProperty(propertyName, value); - }); - style.flushPendingProperties(); - } + style.merge(newStyle); + style.flushPendingProperties(); } } diff --git a/webf/pubspec.yaml b/webf/pubspec.yaml index d6e865a48d..f7018c58c0 100644 --- a/webf/pubspec.yaml +++ b/webf/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: async: ^2.8.2 # Pure dart module. quiver: ^3.1.0 # Pure dart module. vector_math: ^2.1.2 # Pure dart module. + source_span: ^1.8.2 # Pure dart module. connectivity_plus: ^2.3.5 # No AndroidX used. shared_preferences: ^2.0.15 # No AndroidX used. diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index 779bd49c56..3c7f62ecce 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -135,5 +135,11 @@ void main() { CSSStyleRule styleRule = rule as CSSStyleRule; expect(styleRule.lastSimpleSelector?.name, 'foo'); }); + + test('16', () { + CSSRule? rule = parseSingleRule(' .foo { margin: 64px 0 32px; text-align: center;}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + }); }); } From 44aa7acb0e6ad4140fe91a379d989fbe244e3e10 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Fri, 5 Aug 2022 00:17:56 +0800 Subject: [PATCH 175/498] feat: camelize property name example works --- webf/lib/src/css/element_rule_collector.dart | 7 ++-- webf/lib/src/css/parser/parser.dart | 2 +- webf/lib/src/css/parser/tree.dart | 29 ---------------- webf/lib/src/css/selector_evaluator.dart | 35 +++++++++++++++++--- webf/lib/src/css/style_declaration.dart | 9 ++--- webf/lib/src/css/style_property.dart | 10 ++++++ webf/lib/src/css/transition.dart | 23 +------------ webf/lib/src/dom/element.dart | 10 ++++-- 8 files changed, 56 insertions(+), 69 deletions(-) diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index 3299ffaf35..e97b3cba7b 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -1,5 +1,4 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ @@ -8,15 +7,13 @@ import 'package:webf/dom.dart'; import 'package:webf/src/css/selector_evaluator.dart'; class ElementRuleCollector { - CSSStyleDeclaration collectionFromRuleSet(RuleSet ruleSet, Element element) { - List<CSSRule> matchedRules = []; // #id String? id = element.id; if (id != null) { - matchedRules.addAll(_collectMatchingRulesForList(ruleSet.idRules[id], element)); + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.idRules[id], element)); } // .class @@ -35,6 +32,7 @@ class ElementRuleCollector { // universal matchedRules.addAll(_collectMatchingRulesForList(ruleSet.universalRules, element)); + // sort selector matchedRules.sort((leftRule, rightRule) { if (leftRule is! CSSStyleRule || rightRule is! CSSStyleRule) { return 0; @@ -42,6 +40,7 @@ class ElementRuleCollector { return leftRule.selectorGroup.specificity.compareTo(rightRule.selectorGroup.specificity); }); + // Merge all the rules CSSStyleDeclaration declaration = CSSStyleDeclaration(); for (CSSRule rule in matchedRules.reversed) { if (rule is CSSStyleRule) { diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 82fb6c54cf..c18c33b9b0 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -863,7 +863,7 @@ class CSSParser { void processDeclaration(CSSStyleDeclaration style) { // IDENT ':' expr '!important'? if (TokenKind.isIdentifier(_peekToken.kind)) { - var propertyIdent = identifier().name; + var propertyIdent = camelize(identifier().name); var resetProperty = false; var keepGoing = true; diff --git a/webf/lib/src/css/parser/tree.dart b/webf/lib/src/css/parser/tree.dart index 0b2f965b92..9b614fa1bd 100644 --- a/webf/lib/src/css/parser/tree.dart +++ b/webf/lib/src/css/parser/tree.dart @@ -30,10 +30,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. part of 'parser.dart'; -///////////////////////////////////////////////////////////////////////// -// CSS specific types: -///////////////////////////////////////////////////////////////////////// - /// The base type for all nodes in a CSS abstract syntax tree. abstract class TreeNode { TreeNode clone(); @@ -80,28 +76,3 @@ class Negation extends TreeNode { String get name => 'not'; } - -class NoOp extends TreeNode { - NoOp() : super(); - - @override - NoOp clone() => NoOp(); -} - -/// The base type for expressions. -abstract class Expression extends TreeNode { - @override - Expression clone(); -} - -class OperatorSlash extends Expression { - OperatorSlash() : super(); - @override - OperatorSlash clone() => OperatorSlash(); -} - -class OperatorComma extends Expression { - OperatorComma() : super(); - @override - OperatorComma clone() => OperatorComma(); -} diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/selector_evaluator.dart index 3540b3326d..7c8ae50427 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/selector_evaluator.dart @@ -1,7 +1,33 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + import 'package:webf/dom.dart'; import 'package:webf/css.dart'; @@ -99,7 +125,8 @@ class SelectorEvaluator extends SelectorVisitor { // http://dev.w3.org/csswg/selectors-4/#the-blank-pseudo case 'blank': - return _element!.childNodes.any((n) => !(n is Element || n is TextNode && n.data.runes.any((r) => !isWhitespaceCC(r)))); + return _element!.childNodes + .any((n) => !(n is Element || n is TextNode && n.data.runes.any((r) => !isWhitespaceCC(r)))); // http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo case 'first-child': diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index b9878a0225..831e4ad7a8 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -451,14 +451,9 @@ class CSSStyleDeclaration { String? currentValue = properties[propertyName]; String? otherValue = declaration._pendingProperties[propertyName]; - if (isNullOrEmptyValue(otherValue) && isNullOrEmptyValue(currentValue)) { - continue; - } else if (!isNullOrEmptyValue(currentValue) && isNullOrEmptyValue(otherValue)) { - // Remove property. - _pendingProperties.remove(propertyName); - } else if (!isImportant && otherValue != null && currentValue != otherValue) { + if (!isImportant && !isNullOrEmptyValue(otherValue) && currentValue != otherValue) { // Update property. - _pendingProperties[propertyName] = otherValue; + _pendingProperties[propertyName] = otherValue!; bool otherIsImportant = declaration._importants[propertyName] ?? false; if (otherIsImportant) { _importants[propertyName] = true; diff --git a/webf/lib/src/css/style_property.dart b/webf/lib/src/css/style_property.dart index 5576bf55e2..09d36ce7ce 100644 --- a/webf/lib/src/css/style_property.dart +++ b/webf/lib/src/css/style_property.dart @@ -5,6 +5,8 @@ import 'package:webf/css.dart'; +// a-b to aB +final RegExp _camelCaseReg = RegExp(r'-(\w)'); final RegExp _spaceRegExp = RegExp(r'\s+(?![^(]*\))'); final RegExp _commaRegExp = RegExp(r',(?![^\(]*\))'); final RegExp _slashRegExp = RegExp(r'\/(?![^(]*\))'); @@ -15,6 +17,14 @@ const String _0 = '0'; const String _1 = '1'; const String _0Percent = '0%'; +// a-b -> aB +String camelize(String str) { + return str.replaceAllMapped(RegExp(r'-(\w)'), (match) { + String subStr = match[0]!.substring(1); + return subStr.isNotEmpty ? subStr.toUpperCase() : ''; + }); +} + // Origin version: https://github.com/jedmao/css-list-helpers/blob/master/src/index.ts List<String> _splitBySpace(String value) { List<String> array = List.empty(growable: true); diff --git a/webf/lib/src/css/transition.dart b/webf/lib/src/css/transition.dart index 926ed05ac9..c3858d80d7 100644 --- a/webf/lib/src/css/transition.dart +++ b/webf/lib/src/css/transition.dart @@ -11,27 +11,6 @@ import 'package:webf/dom.dart'; // CSS Transitions: https://drafts.csswg.org/css-transitions/ const String _0s = '0s'; -String _toCamelCase(String s) { - var sb = StringBuffer(); - var shouldUpperCase = false; - for (int rune in s.runes) { - // '-' char code is 45 - if (rune == 45) { - shouldUpperCase = true; - } else { - var char = String.fromCharCode(rune); - if (shouldUpperCase) { - sb.write(char.toUpperCase()); - shouldUpperCase = false; - } else { - sb.write(char); - } - } - } - - return sb.toString(); -} - Color? _parseColor(String color, RenderStyle renderStyle, String propertyName) { return CSSColor.resolveColor(color, renderStyle, propertyName); } @@ -279,7 +258,7 @@ mixin CSSTransitionMixin on RenderStyle { Map<String, List> transitions = {}; for (int i = 0; i < transitionProperty.length; i++) { - String property = _toCamelCase(transitionProperty[i]); + String property = camelize(transitionProperty[i]); String duration = transitionDuration.length == 1 ? transitionDuration[0] : transitionDuration[i]; String function = transitionTimingFunction.length == 1 ? transitionTimingFunction[0] : transitionTimingFunction[i]; diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 1599819123..7d64a94237 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1452,8 +1452,14 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // Diff style. CSSStyleDeclaration newStyle = CSSStyleDeclaration(); applyStyle(newStyle); - style.merge(newStyle); - style.flushPendingProperties(); + Map<String, String?> diffs = style.diff(newStyle); + if (diffs.isNotEmpty) { + // Update render style. + diffs.forEach((String propertyName, String? value) { + style.setProperty(propertyName, value); + }); + style.flushPendingProperties(); + } } } From 435fd890621ebcd5eb2540d6342e2ec9a8fb531a Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Mon, 8 Aug 2022 10:55:19 +0800 Subject: [PATCH 176/498] fix: selector intergration test --- .../attribute-selector.ts.0dc1e8a21.png | Bin 0 -> 4108 bytes .../attribute-selector.ts.2abd507c1.png | Bin 0 -> 5553 bytes .../attribute-selector.ts.6e92b4361.png | Bin 0 -> 4119 bytes .../attribute-selector.ts.9342f0c31.png | Bin 0 -> 4041 bytes .../attribute-selector.ts.b2982c691.png | Bin 0 -> 4611 bytes .../attribute-selector.ts.d28ef93e1.png | Bin 0 -> 4629 bytes .../tag-selector.ts.c2987aa51.png | Bin 0 -> 6471 bytes .../css/css-selectors/attribute-selector.ts | 51 ++++++++++++++++++ .../specs/css/css-selectors/tag-selector.ts | 17 ++++++ webf/lib/src/css/element_rule_collector.dart | 2 +- webf/lib/src/css/rule_set.dart | 4 +- webf/lib/src/dom/element.dart | 2 +- 12 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0dc1e8a21.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.2abd507c1.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.6e92b4361.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.9342f0c31.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.b2982c691.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.d28ef93e1.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png create mode 100644 integration_tests/specs/css/css-selectors/attribute-selector.ts create mode 100644 integration_tests/specs/css/css-selectors/tag-selector.ts diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0dc1e8a21.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0dc1e8a21.png new file mode 100644 index 0000000000000000000000000000000000000000..fb9fd614d42b42c13168f1fc69c27822ef8125c9 GIT binary patch literal 4108 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 Q^`j?59E?2B5SEztU&d}<T>t<8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.2abd507c1.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.2abd507c1.png new file mode 100644 index 0000000000000000000000000000000000000000..86df41c688577365c92c79477792469144db692f GIT binary patch literal 5553 zcmeHL>su3N-i~6$R*|}{Qbl09Zlx|2)?!eJA+0Bh79zq32#A&nLO4ic2oREJRnQ`^ z7FXH;A|5zQAQ3PG6O!0+$SQ`QAp{bVa1a877%?Z3kYtB_`|*9R>-__qFLOOJ&-Kjw z?%#cQX09I#3wHTv^G7fk%q8TTgU4a8W#?e9<ri12gswDi9<PH=%dp3T_rqv?TV|la z3he%n$W_phzv}yIFxZCJkb_@G=2U9r#DB))i2V6X&sFHMd*9st_`uVv&jJs2Mz8v4 z=+ooBtgAe8Bz)&qbil{Wf;DSX9&VouKfPt0X3d{(9=RCub^g;2U4Q>&e__^F&C%fZ zNA{nKC?5Ot*3_jdEB-X9FYSCY#k%yjiel1531+HKfK(}ThzgW~V8Xw7k{m7C6}s%> z<OQ!hXV}obODk7>)(@*`3+$(NygS05FzZy3t)=^3nHFa1s%g2pDKQ0(Dag>m6u%X~ zLq%W2wLiK;vdk>=VY67Xm%SEG?}&bMcN_XnoN}RO_iRzZ;&%X`VQaZ?-0T1afzJQ{ z`47E2aL>2b4ct3a1lH9he}jK_-TO}c+1}fnF^kjMJ$H>tvg1>a^`oxo-veT}BrZa7 zilDjpFTMvpPL35K;Ylxkv2=L{g*$uZjB1I_RFKBATPzj{&t9PRW6!k&Iu><ILZ!1; z{-ZFIO>Y@<B}YSpx7nGlrwKPk^uPUnbv0(_(HUYXUY5=T!Oe*5G&#{E=P8l0u5zya zA!TRcVdy>aN+KLP^@26u<F0<&=}HH|oryhbp6#JV(P^I@y*+T$kF)D<GN&O=4{SdY ziS%GXYcN5nc`~GIn^?nCazow8V^`f$jdR0dktm|Gz=g^&DcF5T)5u94iKxW-K6(AK zWr!bi0<XwTHt6*OX9_(6P9q3fj_D$i@{cq7l`YU*Y*8Xn9jgSFR_$~}jWFd5jASqv zWqC;nBIXe%eG(d4g+Xi(3^G4M4?YMfOSLbWWqRH6H4ksbl8%Ubirv-od@njcJ&`Wz zN$#tR!K(V2Vp@~LpB8R8UbJxs2o>`9L}PULZux}>06-cBA`DNRTnC(7_Z{<3dQqQP zvNuUa{;BB`^{t4o20F<=g%d6MJpIdHjH(JxEAr#Ed*)5ouq?Awj2HwM=U(1pnWrk$ zSJsEsf4F|zKzr82N`@coK+V>E2+o;b#`lb0+d6C0k{x#gIxft-TB3#E4w6l4AODFv zsg}^`KJ<<j8pkN*ZodVBv6&o3ZD>VXXO9#D7}Hm|cZo}^ch7y)3lpVuDvbsDvZn7@ z`U`_10YE+2M6Jl=J_$o;9$VfPqW0Ajmwobgk4r(i7ygJ7iu0W=m)Gil{_H3S&i?bj z+6JZF$YlI5`TS(_cZZa>I(0%+!uoCB)`Q^7`@xcD-qri+v)ax!-Y*FRlOW*S;?939 zs6sH;22S6lAPL61{11k11}^7hYosOk{`-fEH|q<kiN;|tRfXLx7K?%P-eHaL*~1SL z&2Pspqmq+idjho`x>*W_*3ez#CaK(uO@$!z)VISB$AdR4(XmAK@0+pkL!20bh7kpk zFlVCJ@WNj>0HGWmDjIk<*{fbs3ynt8$aG(q;LMZdQGmGX`g(OsuP+s+9&{Nd!PY#~ z1d6SAp>HFYXAnnGK}%42GlcXK)A{+jV=`&;l(0HM5|583YA^}Z*o-F+Z+{VUe5pRA zr7Bz607@_4gOSf&CKYukNmlV?*Pv*Vf{+<n70Bo*@rVTgrR@z~{kjZIySz5Az65C; zf?7~C%aEL+ZjY!PcstgK8T<VzBWvQn1sBW}PS4B?k4O7N(HbMrNJj0qzaCdrV#A8J z>}^RBP=Kg-98h)PRS2$YUGPAVbuI$XAm(dRKsKALuqsSzhe^xR%4$<6zxpQ!0OAg# z6eF1WBA)fQVW};9iM<(YO<R(F`ei5pKU(ga9*{QFza{teO=y^0P0WT2t~G7AWvXjE zIt>ITvLR6#(^ZYFeW@%KD**9kY9x#A0nIfj$z=Y!9mVj(o*T6wh3`pd)WR`?jVV2) zUdju2{BHMvv~!f&l)gO^H@K2hWSb$DlK?xA9xYm9Uv1r;F%pfD(3<c(tDM+-qUj{S ze#64>e`C712Mj<8joo_q#!}z74OcwQeBN`CnGxTVz~dT+5=%9dLgyh**@knl>@7-X zmyeI!UAF(jU0;3vhZ(Qf{Fhr_IbF$9ugDW=Z8_#iFjoiKRb)#!(ikn~nCy1b>eJ3M zsPM_ja;j%u{j7vXlO|V#kZ0vd*eki?ZoK-u#d%PUrw8i$L7UON-Tus*dMc+Tb)+3c z*+k$DV)!dG`6T1n*+&+=KC;*=0XI<$kmub1$ofmn70O5rk1S^L;^D+R>&Pa5FoWIX zfv-m{o)<&JoVOfsZIULI)_A$)Bx4|eZu@0YwntEELC2t2yo+Z693f+Lu5_2E4j)tp zrf_vteP#UMQqP6TOp;E3-lKivxj+GHpS*jXZL^tn3$pldK^eNOlx$OH+n78#uNdkT zU$Jf*TCr$1C`j+hYBs4rDkW*Wt7drP+uM?`ti?{$V{I7E!~tF0L6Ti(^w1<YaO7ty zA)Pf=0P@TgM)~UqG!XjhD=s#^wbqf#TRaPSqvlrEj~@We9+8Htev#=jM~1!3--z!W zs$vR>w0OM!C14tfz>Quyu&7S)5mZQc=(=i!uT@O9l!yNM<*tc+ra19<_kq?OqdPKl z;Gs}lMTS<LG&G<A&Ep91nL<EN8DsEsyg~^P$zcTYe7ELScY794eoES7E<sqBL-Kg| z58sQgtpmIkzCysm#Vc)s8=ms8l(am8kRZjOEaBn@AywGE*u-Z)gF;eUs+L17aKSL= z1)14Fs)Y%v-Iz)B<#iAhQ@)oBT$9U4-Szd>a!pI`Z9XN4Qx$OXNeg+HycjJ0%{dCv zySUmGBW6h2dc8wa%N<%rhD^A+qv=K?K$ZU66BntXY%NXGLK1Q^C>?TN*@YnUR<9JT z^7G2j2%NeJh>zC<tb|-XTS4e7Xd(-a`Y}+VSux;tYN(IuSl|O>b2-HOlenHu8r{Y( z&bj)Qc*^a^1IMnUD|t<u)RkE2v|X-bjFBe)1q#XB)b5`G=C%f(KsxdXTSgy?q?!!= zXZ(vy?oGo8R>KQ0KkqN$(-XAxJNYeDsY3%^ws3;ug})8b7ig&?r=ozJWRf-6D%c&B zK0~?8Glx3Z`cq`PsseTF34BUGJ=xY+tmUJip3No88m{lfTAbwSm%+}ct1a-v&^sgc z?yRw^fT)(i`@}Q%fz9Wvej4-_Hd^S8R43#WH-DIdb=WBI(y}lriehz>k(SV!G>Xl; z&(6a+T5xKR>#${KWD6+Sq&7C8m(Y@v<?#M8&m;HssFNH~ZoTCCSmf#x)y6~`KUpYo zJSc0+BoQ!tC<Q>T#k<9(ma{-|bv*9H&dZQ?V%=lt3FmQ7`JzEux|V+<MawVMuE_!j z|9Ave;WhA#I5E!_E5^_nS*+JTgD7*EG2W;y@0K>+FQu{6w7#=E?4&mRm6c{Us5cmA zW;f>?)8fhVbM?)Ki&ItYb)gt9A%#JlSe}XLq?&$`a3VHwAgyW%6A04cQzs(FxjDd1 zG1f_ic44!NIvL+2H%$?~{O%xv$C*h#D*9_?t;wzT#}70l?&1#!Z*G3BSdMokv1R^) zNBsbzxz|PxkGt@)|Bdt8wl6!fWlgq&zsKVl;WIcPQe*47uCdh=w&|XuQF2G6Vmj>3 zV$W9nVPbGx;H<&tTIQhL$}~X%421~7cu_W3Vwm(}IwLZlJhYvgXkwm_=O#NWie7B` zRO-xA1lqb0H`ti%Y0y<?-Y0Z#0Cvj<NIkhiq%4^<wpwGWL&-hYSXJZ<l%Zc9kDF`O zuo4#URVCVH&{Vr2L5BAEwqh_y^`NXEgHCWgtsvsXXwoY?VWd5~&uWy(68`FaXUH=j zVSa5)Ow*~D&bmGnc{K95QA%lE@)rm6bfd_RY>3k6`7z0i>eth@O(1BW_o8~{+^rHi zE1p13NGW!&n9@49y18~?er?JL+T6aTNrF<nFXC;fqdjHgj@bmVLF^=KL0G11%_cHg zCpybwS4<5hDsyajt|$XDKW;zUn#Aa3v1NRRxjNLJl&=~tMp=iSw!(r-Gbrpgvs<hK z5zO;J!fky>ro7K$sOnXwHM+P<{a3J|rMlg$3A)~dHep2&hqy>~NbJ={u6>@*B=IgX ztNMz1xbKcwo7<75{0XNEp(6WsRO-MKkNn-_bK~jp`f$z2+}e&G@k&$T9GBqWHbuWA zy-V!6)~7<6;t+IX(hH*?JyQ^ru8pQfF`Z+S2Ac7K+o;DgfU|5r2gl9$^Qgz+|25A3 zbnfD@<^NaKy}t<s{(BJKgYZ9FzxPad&xH3(fYto*CG=hn@8$6Skb{NHI?(<>tJ@JR R^xG8{awzN|ZU5;X{{x?ang{>@ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.6e92b4361.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.6e92b4361.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec30b41efdf044b59830d1bf19f0311d9fd93ea GIT binary patch literal 4119 zcmeHK>r+#Q5|6KfMWj=&MnOnB*0D1R5h>3=qM#zSf)5TU&w!Xl<PoVrfRG&UwnStI zR$K7_Nv-vw2MEd?c_idS2PL-A1jLX+Ac#;x9wb5%lAK6#nS1|<`+@zkv%539Gr!s0 z-)_zE_*l=6y+1~wP@Zu|4xU7zR-HwmR#&=zwCs80{jPagt-5eBHX6l~Zr3auYcE8{ zCAu$5h5MOHD3sUJxPu20OB?6L!26_PU-5ib2usS7zG*wXbpFM~*3E&>y?;6qU3-an z{9H$CQt~$U^ccy*BO4A}anA1g*~03)a#4dEXd!&j@oC@D6M47DH->@Rxi>GQG!s+| zhoM|p(k0O3n&vC9YNpIuAq$^yh)s<~btBztYjxI_sCV0KMMe}V;hSGqMX&#A|9!@; zf%>Qrr~Ri`C?xMb(ue6c74RQuS%eC$)gu!d{uV^9$CUl9<?A4)O$@1{+O_g_UP^E2 z4324Ofl{e$5#|91Xn``!zl|th^4IdZ&?Nulla_@B89XKfGc51>g4Ckvoq*&zz?kv@ zjC7LRGt?Yn<hN*_V|mD^0vc}9kqNe^5ET??jo`$O^@Lc_2eLfI>y;CM0Dy&o)m<Ku zcm3}}(1Qn1sPMs>u#Piv@_I1s@%Lnc%PPWMB)JfCa4U&aJem`vh}-~BILVI-v*ovf zf#t+{`lFJ3c@s_A2x{&DYL3#aDma&8tDH{^)3xGbxUUkx6w9bW3gQ~llPvP~q$LB* zL_$B5={{%6KzOP`4gk24#B`9s9pumrrGFf|9U;ZIO1a0Jb{QbXObn_z^Ji{}7Jgxy zo;@2|oPM^cr9hC6ObLy1ig<j_8c_ep^r2gj(^ox@Zz}sv=9r_3O>6>FmPa`Uoioiu z{W~C&OUv|(k)z?`#joQ4V`;4XfE&<sK3X127^Mu{EmTRcMhyjj>hakVaub(i8IhGV zw!`H}3bp5+b_eDGfBGs!gugGK?+YA^@x~F@ksTz5)7rwu6(BcEX=7y+`8G<Te|k#s zlT|`C7A!;dQ7f|Lb~Q1*q7ZAXNqCti3lzT`Yu>!O-?R~Lb5V20`F<gz9pp%(Lc}Y7 zz8lG0yxx__(0m=`>YgfhGOTl>>UE2|J+x2Va2@URaWgTg-Jsz*-&7$>M!M>^LsR7V z$VbgM)&<8RL=(1%782r1($u=AZuBv_nB60yI2N7Xq;@5xmuA(sxMpJ+Z^NdqK-S*o z27!GB?hXqabh{TSDpOdg0m=w<BipcxFfz&1Kk|#PD<>lK9$ON}L6GEDY8B9h9%uss zqa1bXf`jD*-@<2R;$fL<p@E}f(!GN9`iFhx<woID89Hs?DXjSyTbedrtg!82Oy^UT zONDArAIJa;rX1Cw=H4Lb?42437UNbv-S0yK-N^yUl%5VzVOnXHT+?i=nymIgQvZB* zQ0|x@6^ng+XH8P2a0#^xgfo;`@#6C#Qn>9chn+F8+!FiToYnwgTPmHObBZ*}e|H&{ zoF?X}BH<m`XDr%rFZ0<wRx(yM$?{J{3T=}?tfMODqP>nPD^6865iL!|qFy5`(8ge( z@+BP&6g*+(AMh<sbeV0+A?JKO$B$8z(BSXGDxDsTrSpq&Kr%_tDefS)8<Z9Z(SZN~ zob(W%0DG?`7-}!$e0;ws5zn?d*I{qq!9T9vL|gp8w<D>_l<tS(?gWC6n-DLh{<nyK z@@qm}^}fSSM8OG29~~k~5NTagUBKxmYY*T;h!a2V4VGsQ*VA4GGPXGN6v8|wQkOg= z?o#I#W%k%-RHD9(N_sZ`!uasw9Dq(wd|Xk+%>j`ifSA!kTU`H_+V|VqEf(MPKB0?3 z$&R~@>%`Vs4Fp8-Dr8YpWah#(4liwl27b^}f-qP{4);(7ZJgtI9>DzLyCCn=JuWA7 zmpv*#kInGgY6G;jO~j0mt`Md!6;PxFF_(V*XF1<QI<{K&ZIo(n_{h|KN|StWX-o~l z_E)gj9xl`DBz&0Uz@{cfp#Ji1gva_@(h?tjj}?&A&asCrykHOKW&}Cj_w&5dpL|eR zk@|xW(kaSULa<{Tf2r5f#+aXbbIK9YdF(cFTD1=Sa{JMn?LB!qMCY(Zgpbffjf@gZ zH~xJUd@b9&4T50Xb11jH4N()Udgf}!^{lPclY_CW$x~eKYCOvz&T01SyYAoj6)`<{ z!=_DB<O~ax)jiia8P`<wdT(p8X<D*}gx}EIxX+=}DXfbjR6p76Mtzy2!D>n^*j7!J zl&cU+lo2i3TK&uJW{_7?ll513A=0VfF&37TBVzopuFN*6F>&)!aU~o2E^ijLvHZM6 z>QIs9@8#{Q_KF-34GlV|!eWDCou1b3s=W(|QWB0?l1+K&+3Gu@Ke1^xv&&U8=n{vS z2RIvJxOD*;W7(rPY-q4NW=||esFqGsl+E^nT-~s92zg%XsL!aR{I03`=(B#<WIViz zleO0Sv!C~``Qg*EmB&|mesc7G1oUOqNA5cZP<K{>D<G_N!v2+USUyTuKv)4`g&kHX f^8fQr&=Oe4iM7d<KU`gY?on}v;t%qoGcW!J$rL`M literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.9342f0c31.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.9342f0c31.png new file mode 100644 index 0000000000000000000000000000000000000000..72870ec0e30544faa6fc30a4ac501609afc458a2 GIT binary patch literal 4041 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 PbsQEKjfrAKp1t;OLu(f7 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.b2982c691.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.b2982c691.png new file mode 100644 index 0000000000000000000000000000000000000000..1fbc55004fb1b0ac80443d5c11917cee44902f95 GIT binary patch literal 4611 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.d28ef93e1.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.d28ef93e1.png new file mode 100644 index 0000000000000000000000000000000000000000..3aca4eafa3aee5ecc5d0d8d6e01de93495b3f4bf GIT binary patch literal 4629 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 Mz~TK!_~&2$7r@AYhyVZp literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png new file mode 100644 index 0000000000000000000000000000000000000000..95535f9efad3204180da0fa8a7a796cfcd5f6815 GIT binary patch literal 6471 zcmeHMX;@R&x(4l$wy4Ne5M@XOEh+*h7X<_%Z2>umfS{ld1_1$;AtEz@L~9FSOj{L^ z2v{rz2_zzOAfS>#CYgjVC1?Ty0TLrgfP^G>Z|Oa!_c`bP{bQboCu{AszqP;j{oeJi zwL{h!cjs+i@BLauMP-}IKODVOR8%jksBF6N<rd&d&0Y>3_)?AYa{fVu_*!os=zJ0P zgUh)uflu0(moO?SIzku6@6RPaSYjdL@BUmYco+1|uRm>3&2l{WwO{#lp4^Les=U71 zrn<Ir$b0tSRYp0%J;I-xTbE57)@l{sxhZTARfHn_2qP8^5A)BJ`&5n`w$UP)8a(WB zawJ~&Xrjz&6h$|<+_)wWOD<79Tft5DwsbIxi|eMc%MNC%d{b=JBKtz+GVZ(0`w#T1 zxI}wD)MZ-HDI}II!W@OnVGt*pVp=@1UnAB_>5SUi5*7=uTI@#~5}XJ)g79PWRXeup zXuFOa$#RIX-A_k_MfpQT*@;-nJPpSQDp|jmED@CC1*>hH*fZ;DJit}6<4{$N>7+iT zm~XE&S9iEiHj^S$bgfL-y%jDSU~jD)PbS!pzL4qZISZ2FMNj5yAH7E4?;nlxN?ZON zLEmXqg4wC|psB?JKXi?Ck@@7odUfYOUE3i;Lt+}QD>;O|6FS2=<Qd?ruZb(OMbKKS zr57W%+grTWOMCBbJEni-3U~0rNZpaWOe;SB?EE=@tI-5<8o3Y8#Yl}fz+cD|Uv|=w z8l0|S!WyAeBIn=E=inJGFq=b%Po8(A&1LIcm;I7FBR_v?1-APBmM5&CZLyw*3~V;G zSEB79-b`;N;3n(o1_w*z4>((tL(M)q<?m)v>;gpVkx3bkTxmV;Y)tDZw}2OSn4XQv zx@o0xL_cXf9Z!2XtZrn;X=zu)_^;2=G&N)2_x0sUYs6562iM3jWW4N(upHUG>zLIW zFpmld9Aagk6gqa?AdU%*Wv?!WP**j}xjgzC8c;uY;^ZK`T)4z&(@zNHonl5QaT4s~ zZEoc<bCifPC<kVtVuVH^!({KJz|wAm_9GdS_WS84V2?$pOjhiLRuS73`hE>Muz(gH zF769JHxU-oHOMm$n|MzMT`|by@eTmHJ=<?}L>qFeH=t4CFYBp~?Kf2e@z24CXhLN3 zG3*t%L5l2GYYrNs2~snh;2kSb+SwA(5-AA4WQ2&pV8*(`o#mraot2kO)=O<eWg+oS zCq#@)LqpE_A+(l548%)v%-)_WeAZWE)V}*7{9NMn+ZM&O^Ll<smyd2mU{Xxp(^%*x z+cwE_W(ZpgR}yUQTf1Mo%I{3-NrmiqJJwaP{F}A-`B`p0iLJR{Sv9c;Yc<tJcjq)x zU%42C&fWMOdUdp7^{KF<N!YB*t+2&rA2@H8RcYp39@n&<)y>Vl)rWD-Z{A;WUwry> z(eXpX<d4}+D^*DDott<nPMw`=9DYI*4@pba8(nyA{cu*bCY8mrR!m{Bba9@Lb-PV> zv&3KrwpIQQ|7Fp!0YF#&D9((gCN#ZMRA_^~Dq0+|PzY<4IsqnRI;aYDNZa4ruD#tI zqNJ=~F_?BaZHoTK{o5STEf4IwTN%z=DSef-IOVjgw5(WDsOPtSld)ZAFXBkD5kGk? z|3Yhc><JW`TWx+OD*JVv(-4OoLCR@|@dcsu-}XR*$A4O##KLwjSP`)~`E3U)$>(Su zL9#3U($R8g{PTY6G6hhT!rC<V*5!A$VGx9{h*ooP_{R&iF}*;sB}6z9CF3Kwv}SWU ztoVb+z#}-eY4$FXQ-mjF6;&3k&hAvE5FkTXWQ-(EC{}AXNWUtjR@`=W3cJ^}+DU+A zeKqv0qa#(Cw*csRDWe{oT<7?eOCq`BlupXvE9=KoP$6&qJeQ!6c1$`f$ysPmPLE$0 zH;pO29=Sd&M{AJ+rC0TQ(bL<Nfj6SP>aHnPmvb&-LWak^O%Y~~%}W9u9d>0-#U|GT zb2A2)W>3VsYi{$by6oq<%gJ+6F}UP()KNIT&IC=6fR0N529NmHKONl*w`rTzNYz%G zjQ?&dTBB9?9WZi2qBJnE&Fn61Y8{ptBro4AZ-k^RzcAceR=ECpdN4Xk!7K*n>xXOH zr6AgC<k>G8x0z7_kfG6@DbZmdIN&DbJ3bik0sobg=Pp|A0gLR*7*3={T6Hg}d`;nM zK>1@)qHgSeA<+%SC!rP0FlN)iY|dm*2^@gZ=+;~vKOrYg1%Vy+=K6T}O#NM&6+{|7 z#%KYk2Pd0Z2{g+N>BfE!XI7imTa9QB?4VU?MBI&m57d=aw6&2Jp_4@n5w}h8mI;yt zNTGZJFhXrRbbl=-<6uFRuh%Y`^HigpE-`5-iLgud0nqmZ9dD1HkeRT}%l2h=qY`{Q zciBpistw+mitVXXO`|ebnzLYVy}Lt#+7GJ|1OvYH;wSYGjwlPB#=0KIup%OI!`!+< zRv*5DBF7d%wSy~&E&sp*H5TPQ#vp%qrQXdpcoW&L4O7C^E#-yiC}n6UXm~AS?Kzov zJ3TwH__Ek${m^EQLzMlyG=)#trP&xPkp^fRu!+Te5shojMFI#}*<aWkAO6X_Vx`mz zwWuMBF%6&eetwEi;*%qGCixg^qB2%fQ@D1s)SM+5^FUb|8WtQgn^<J7$Lgo|Sk8{3 z7zK76L>9r)-Tv)#Atfr&F@7ByEdkV0CNP#35K|l-8;LB=gO1l%mRALO%Hj$Zp+O{p zt50RCA~i61G<tIA3ti|Mt5Z0+I9vA2HLS<CZ9b0wO7OQ)AN|y<N0Il4VTi`u1o-Q< zp5sEg8iML(&OVo#E|B5VU?(*otan_Ta&Vf><eMVXNkBa44pLN^AiOWyb#|{NG-+)T zGb-|nb{)PuEWDo%JDUi1a-xO96v*zW91MocPl!el-^<KXr^|dw2_9~x6;G<hIBS** zBlU2?L=n1ECS0EUE#E#IH$-x7_h~#91nvy_sM9!8z&9mwYFl*-Voztm)ygi?jG74y z1Jtif*`x?7ttCIR*&^P%OY=_&Y+`u^941rq@N6Bc2dNIH)6rMc1k9<N$kJv@Mfj_! zJ9e8Vi?|v*$~>gYH?u@bnK<6=MB)Vw)RH64IWjiY9T(S&fogm_V9vNfQF9a&v7_mT z$fJyMO2pINm8tk~H({Pqm^bImA}so{`GW(Ds5os>;3Tg>%MFLR$*hiS^g3fJEtFL8 z_e9H=6lKFX4ZK##lQCoR`2=i)l0OE7nxryC`9!T55BtRFl}%BEfRClp><Ub%RDb7r zjTRe2!|)^b2i|cbS1R*(UA*MTcW`J0^C1uz*}|Dz@5a~bjugb$UMh7%SATd>sS(mX zE^k<XOyebEqEP;Hk}*P}fSJqVwvHm7o8`nUTdah8v@gfw81*ZK^$D!;<(HGYLx6L~ z-~?#65#e?_!OcLkrWZT^dqO7tM{o5m)XmlyE_sy(LD$e$@-Wta^zEy;CVmO#&LcDH zv*W1w@<0scLUZDbPk_bcD|q``iZvVzo1i;Fdmgx%OPLYS3TGLPRA*9LqG%Out$>6~ zs&hHrFxmCAg$jfdpxBU+XTH$2;!fdcGd5vvvM5NGlF(IMQ!}cQIikOJw_eKk{%ddU z0(7sQOjF(wFz~W88y{l8p1!^md-pxbryd|zo8fYyh5(tY$6oocDtrTR7Vy~6sJBl? zpT;_zPC=Z?HLxegE2GU>+PXP;u^&~9o$qCv38iWz#`w0#(4KpY__>9#y$-w#>T+BO z7GkgLum&7MI5&Z%Z$_Lrlh{*WaI1j`U+&%%eVq6D5huf(y#vsA>@1qK$An4DCAcf* zJmd{CP|7;BJ9MzeCH7@|>5hBF*L)X;6G)$S7FwhqEELhL<v_x?SQyi4k|4Uwtu5aI zEYbjNfVLzg1u2+1lMx@b%L5DRqkOiKQ_h?_@5A6l`Uv_<rK^H!hnq2i`ug`1xS&Td zC|Tzo4?4Iz&Nn?;v9;mbFY|%mYdA=RD<equb0oKeuEBLai{9lSUwbkb3vfxM#Z@nQ zDas9)$|h^>TMO`^P{55=-`zv$Ovxr!IGvgvbl~vC;Z6AmNZLBowwY5{^<|MQq}aw* z`G9im@&j3STqsaLu!yG`gAzV^14r5bvQlynft?6yqr@wd@|Es5CZ0knH;uOQNh(cA zdVrdmFK_>uNUX~L)i^J=rX2!LU2G8P)FrX(iIGmsrn$MC5dO#JSjA7vuCwX$vG?e} zmV7sV;Z^APB6z@&^|irH%EQkcU2oVYeIv>`t6_7@eRcXPVJ#Cq!W-i=NVN0z;=4_2 zA{@68Ps~QZBuj%!^~m9Z{OX0k#*Vh#MQF$lO<;@7M4LY^A(S0V<ST=f<h+HD`J(cH zmd8mr^bpyUfjDAGpPY#c1vmbnCDerY<MXb1lThF!I)H!d;k)Wn6`;B7(~~n!x%wz5 zx_wDX^BBr_T-X>A82@DMzks#V&vWJhh6VcIoZrAP>B<}gnZSy~!I%ZRJgE=D;>s|F zk}0IpvYl9+vUR*ACfc_y)$>TCpSGIVzJe8josbo3ds5#{Y+<b5T@IyHXu;(ubstkq zd7kSdz%CQfzqc5*?Jlq~#R8|9YN?e<Z({1h;D}Z3OBh{rVCWGM8F2c(?EyYg-UjYx z{&tJJrgu43i~ONm`XTtXE!%9njSw&|$)JNH_0{c@Cp$i5u;NcX<)&<L!`J;jx+AWj zHw%0gFi+YuhOt}@RW5yb^gFMT>nfjK!@dIAo3{OO#~YFOHG%z9UX@yV_JTNLDPnlS zCLyya=IO!(af3T+A5nMF#)nd!3Kuu<+`RtT@-f|gY2#w_Fz!TEYU9P>iONQA#DCZD zSU-9r2y7opC$%z39SpFl<+*JE8;vOsz@bGH*tw@43WQjt0FU8C56jG}Qg7#hqt*E} zynxr0RxLT5CN>9vyT7VR9XjMI4)=Jw=V<Y7-U!JTVBUB=6_@|T0K8d-4daSmR$4{D zfPh^-lwg_fU$Z}(gjv|qlE0Bxb?R*ZHJBmL^6vhd??w|Y{#Jv)Hr+@Ecltj->IG7~ z2}VX604xC+mV%uy1{mUI08R%NE7S?>OCYc$LTFX$y;@!`KjdS!duOE;aub0af69k4 zwlf(h(WPOW-Ay3xnL}zDb@=n`7{G~t%G);4S7~MYvo~TCr0Sp9FTBy`kY5nN=>e(< zYykD2txLn#zaQDS2oT@#C-DHJo&OFg$pT0bELRea0k8yQ2%<?EgMK0;bAuWsK;{?- zjK$L-ux9{uv=p|MoqfjZEqPvPwNZwW?>Fl54e(Z<(xv*#oj);vWdU#=s!HvU00g)P z59>AnRQ)Uae+dkzJV2xin8v4;Z@vF7^`Dvmz>iP=T}yv~#OkTE+Vd&J_pxB=wP4M8 z0g1UFg6ZH-)Ck(2^4u)J(MB73Wzum#9cJH>=I#J^jDeAjGDP%m)a5@dkH0_yRrk3I z{)F?Fb@#dZnT`KrV4t<|X<Pkl6rY{pvorkvID;Imdbrlif$Ba1{4+$w#mU`~_`}5? F{|A(5sHp$| literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/attribute-selector.ts b/integration_tests/specs/css/css-selectors/attribute-selector.ts new file mode 100644 index 0000000000..cac43430ee --- /dev/null +++ b/integration_tests/specs/css/css-selectors/attribute-selector.ts @@ -0,0 +1,51 @@ +fdescribe('css attribute selector', () => { + it('001', async () => { + const style = <style>{`[id] { color: red; }`}</style>; + const div = <div id="div1" >001- Filler Text </div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('002', async () => { + const style = <style>{`[id=div1] { color: red; }`}</style>; + const div = <div id="div1" >002 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('003', async () => { + const style = <style>{`[class~=est] { color: red; }`}</style>; + const div1 = <div class="t estDiv" > Filler Text </div>; + const div2 = <div class="t est" > 003 Filler Text </div>; + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + await snapshot(); + }); + it('004', async () => { + const style = <style>{`div[CLASS] { color: red; }`}</style>; + const div = <div class="div1" > 004 Filler Text </div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + // error + it('005', async () => { + const style = <style>{`[class= "class1"][id = "div1"][class= "class1"][id = "div1"][id = "div1"] { color: red;}`}</style>; + const div = <div class="div1" > 005 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + // error + it('006', async () => { + const style = <style>{`[1digit], div { color: red; }`}</style>; + const div = <div class="div1" > 006 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + +}); diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts new file mode 100644 index 0000000000..9b8ce89d0d --- /dev/null +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -0,0 +1,17 @@ +describe('css tag selector', () => { + fit('001', async () => { + const style = <style>{`p { color: green; }`}</style>; + const p1 = <p>This sentence must be green.</p>; + const p2 = <p>This sentence must be green.</p>; + const p3 = <p>This sentence must be green.</p>; + const p4 = <p>This sentence must be green.</p>; + const p5 = <p>This sentence must be green.</p>; + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + await snapshot(); + }); +}); diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index e97b3cba7b..47ea96d6e9 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -23,7 +23,7 @@ class ElementRuleCollector { // attribute selector for (String attribute in element.attributes.keys) { - matchedRules.addAll(_collectMatchingRulesForList(ruleSet.attributeRules[attribute], element)); + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.attributeRules[attribute.toUpperCase()], element)); } // tag diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index cb951997c1..4b99384047 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -84,12 +84,12 @@ class RuleSet { } if (attributeName != null && attributeName.isNotEmpty == true) { - insertRule(attributeName, rule, attributeRules); + insertRule(attributeName.toUpperCase(), rule, attributeRules); return; } if (tagName != null && tagName.isNotEmpty == true) { - insertRule(tagName, rule, tagRules); + insertRule(tagName.toUpperCase(), rule, tagRules); return; } universalRules.add(rule); diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 7d64a94237..4398fcc71e 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -971,12 +971,12 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element @mustCallSuper void setAttribute(String qualifiedName, String value) { + internalSetAttribute(qualifiedName, value); if (_STYLE_PROPERTY == qualifiedName) { // @TODO: Parse inline style css text. } else if (_CLASS_NAME == qualifiedName) { className = value; } - internalSetAttribute(qualifiedName, value); } void internalSetAttribute(String qualifiedName, String value) { From 0716ff4b7653aa6036b9352c9566de8e1a532a06 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Tue, 9 Aug 2022 11:11:51 +0800 Subject: [PATCH 177/498] test: css selector test --- .../attribute-selector.ts.b2982c691.png | Bin 4611 -> 4147 bytes .../child-selectors.ts.6222b33c1.png | Bin 0 -> 4826 bytes .../child-selectors.ts.8daacc0a1.png | Bin 0 -> 4672 bytes .../child-selectors.ts.a21b79841.png | Bin 0 -> 4685 bytes .../class-selector.ts.01eba5b41.png | Bin 0 -> 4119 bytes .../class-selector.ts.094758831.png | Bin 0 -> 6435 bytes .../class-selector.ts.4a4efefc1.png | Bin 0 -> 4481 bytes .../class-selector.ts.4adca9a21.png | Bin 0 -> 5689 bytes .../class-selector.ts.4adca9a22.png | Bin 0 -> 6551 bytes .../class-selector.ts.4ddf9d331.png | Bin 0 -> 6185 bytes .../class-selector.ts.6e63ddeb1.png | Bin 0 -> 4108 bytes .../class-selector.ts.70ea920c1.png | Bin 0 -> 4167 bytes .../class-selector.ts.74cf8d9d1.png | Bin 0 -> 8541 bytes .../class-selector.ts.82cf20d01.png | Bin 0 -> 5614 bytes .../class-selector.ts.82cf20d02.png | Bin 0 -> 6585 bytes .../class-selector.ts.8f160df51.png | Bin 0 -> 4394 bytes .../class-selector.ts.8fc91ac51.png | Bin 0 -> 8643 bytes .../class-selector.ts.903a90f71.png | Bin 0 -> 7991 bytes .../class-selector.ts.903a90f72.png | Bin 0 -> 10426 bytes .../class-selector.ts.903a90f73.png | Bin 0 -> 6663 bytes .../class-selector.ts.a30301261.png | Bin 0 -> 7593 bytes .../class-selector.ts.db8dd49a1.png | Bin 0 -> 7233 bytes .../class-selector.ts.fb97fa6a1.png | Bin 0 -> 8620 bytes .../css-selectors/combinator.ts.06fd22981.png | Bin 0 -> 4522 bytes .../css-selectors/combinator.ts.0e8424a11.png | Bin 0 -> 4695 bytes .../css-selectors/combinator.ts.6d2121b91.png | Bin 0 -> 4759 bytes .../descendent-selector.ts.1b50a0d81.png | Bin 0 -> 4813 bytes .../descendent-selector.ts.1ffa58061.png | Bin 0 -> 4395 bytes .../descendent-selector.ts.21091ed21.png | Bin 0 -> 4384 bytes .../descendent-selector.ts.52ec30781.png | Bin 0 -> 4394 bytes .../descendent-selector.ts.5911c3671.png | Bin 0 -> 4834 bytes .../descendent-selector.ts.c48dfe211.png | Bin 0 -> 4913 bytes .../descendent-selector.ts.c536672a1.png | Bin 0 -> 4393 bytes .../descendent-selector.ts.c56704881.png | Bin 0 -> 4611 bytes .../descendent-selector.ts.c6d8bc4d1.png | Bin 0 -> 4866 bytes .../descendent-selector.ts.d0f8fff41.png | Bin 0 -> 4785 bytes .../css/css-selectors/attribute-selector.ts | 8 +- .../css/css-selectors/child-selectors.ts | 23 +++++ .../specs/css/css-selectors/class-selector.ts | 86 ++++++++++++++++++ .../specs/css/css-selectors/combinator.ts | 35 +++++++ .../css/css-selectors/descendent-selector.ts | 84 +++++++++++++++++ .../specs/css/css-selectors/tag-selector.ts | 2 +- webf/lib/src/css/element_rule_collector.dart | 4 +- webf/lib/src/css/rule_set.dart | 4 - 44 files changed, 234 insertions(+), 12 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.6222b33c1.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.8daacc0a1.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.a21b79841.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.01eba5b41.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.094758831.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.4a4efefc1.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a21.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a22.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.4ddf9d331.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.6e63ddeb1.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.70ea920c1.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.74cf8d9d1.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d01.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d02.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.8f160df51.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.8fc91ac51.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f71.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f72.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f73.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.a30301261.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.db8dd49a1.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.fb97fa6a1.png create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1b50a0d81.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1ffa58061.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.21091ed21.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.52ec30781.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.5911c3671.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c48dfe211.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c536672a1.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c56704881.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c6d8bc4d1.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.d0f8fff41.png create mode 100644 integration_tests/specs/css/css-selectors/child-selectors.ts create mode 100644 integration_tests/specs/css/css-selectors/combinator.ts create mode 100644 integration_tests/specs/css/css-selectors/descendent-selector.ts diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.b2982c691.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.b2982c691.png index 1fbc55004fb1b0ac80443d5c11917cee44902f95..11a7add1c83bedee3943ed25cc5bd052f16e831e 100644 GIT binary patch delta 1868 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 GLSTZj!$$J} delta 2342 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 D%o<iH diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.6222b33c1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.6222b33c1.png new file mode 100644 index 0000000000000000000000000000000000000000..a4d911d734cb6cd7bdb15dd123d38f79c9a225d7 GIT binary patch literal 4826 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.8daacc0a1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.8daacc0a1.png new file mode 100644 index 0000000000000000000000000000000000000000..cbc62ae84be1eec43483ef180fd9411093aca1c8 GIT binary patch literal 4672 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a21b79841.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a21b79841.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe365b66aeae8249ab03ecd93fad08b84b5cd0b GIT binary patch literal 4685 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.01eba5b41.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.01eba5b41.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec30b41efdf044b59830d1bf19f0311d9fd93ea GIT binary patch literal 4119 zcmeHK>r+#Q5|6KfMWj=&MnOnB*0D1R5h>3=qM#zSf)5TU&w!Xl<PoVrfRG&UwnStI zR$K7_Nv-vw2MEd?c_idS2PL-A1jLX+Ac#;x9wb5%lAK6#nS1|<`+@zkv%539Gr!s0 z-)_zE_*l=6y+1~wP@Zu|4xU7zR-HwmR#&=zwCs80{jPagt-5eBHX6l~Zr3auYcE8{ zCAu$5h5MOHD3sUJxPu20OB?6L!26_PU-5ib2usS7zG*wXbpFM~*3E&>y?;6qU3-an z{9H$CQt~$U^ccy*BO4A}anA1g*~03)a#4dEXd!&j@oC@D6M47DH->@Rxi>GQG!s+| zhoM|p(k0O3n&vC9YNpIuAq$^yh)s<~btBztYjxI_sCV0KMMe}V;hSGqMX&#A|9!@; zf%>Qrr~Ri`C?xMb(ue6c74RQuS%eC$)gu!d{uV^9$CUl9<?A4)O$@1{+O_g_UP^E2 z4324Ofl{e$5#|91Xn``!zl|th^4IdZ&?Nulla_@B89XKfGc51>g4Ckvoq*&zz?kv@ zjC7LRGt?Yn<hN*_V|mD^0vc}9kqNe^5ET??jo`$O^@Lc_2eLfI>y;CM0Dy&o)m<Ku zcm3}}(1Qn1sPMs>u#Piv@_I1s@%Lnc%PPWMB)JfCa4U&aJem`vh}-~BILVI-v*ovf zf#t+{`lFJ3c@s_A2x{&DYL3#aDma&8tDH{^)3xGbxUUkx6w9bW3gQ~llPvP~q$LB* zL_$B5={{%6KzOP`4gk24#B`9s9pumrrGFf|9U;ZIO1a0Jb{QbXObn_z^Ji{}7Jgxy zo;@2|oPM^cr9hC6ObLy1ig<j_8c_ep^r2gj(^ox@Zz}sv=9r_3O>6>FmPa`Uoioiu z{W~C&OUv|(k)z?`#joQ4V`;4XfE&<sK3X127^Mu{EmTRcMhyjj>hakVaub(i8IhGV zw!`H}3bp5+b_eDGfBGs!gugGK?+YA^@x~F@ksTz5)7rwu6(BcEX=7y+`8G<Te|k#s zlT|`C7A!;dQ7f|Lb~Q1*q7ZAXNqCti3lzT`Yu>!O-?R~Lb5V20`F<gz9pp%(Lc}Y7 zz8lG0yxx__(0m=`>YgfhGOTl>>UE2|J+x2Va2@URaWgTg-Jsz*-&7$>M!M>^LsR7V z$VbgM)&<8RL=(1%782r1($u=AZuBv_nB60yI2N7Xq;@5xmuA(sxMpJ+Z^NdqK-S*o z27!GB?hXqabh{TSDpOdg0m=w<BipcxFfz&1Kk|#PD<>lK9$ON}L6GEDY8B9h9%uss zqa1bXf`jD*-@<2R;$fL<p@E}f(!GN9`iFhx<woID89Hs?DXjSyTbedrtg!82Oy^UT zONDArAIJa;rX1Cw=H4Lb?42437UNbv-S0yK-N^yUl%5VzVOnXHT+?i=nymIgQvZB* zQ0|x@6^ng+XH8P2a0#^xgfo;`@#6C#Qn>9chn+F8+!FiToYnwgTPmHObBZ*}e|H&{ zoF?X}BH<m`XDr%rFZ0<wRx(yM$?{J{3T=}?tfMODqP>nPD^6865iL!|qFy5`(8ge( z@+BP&6g*+(AMh<sbeV0+A?JKO$B$8z(BSXGDxDsTrSpq&Kr%_tDefS)8<Z9Z(SZN~ zob(W%0DG?`7-}!$e0;ws5zn?d*I{qq!9T9vL|gp8w<D>_l<tS(?gWC6n-DLh{<nyK z@@qm}^}fSSM8OG29~~k~5NTagUBKxmYY*T;h!a2V4VGsQ*VA4GGPXGN6v8|wQkOg= z?o#I#W%k%-RHD9(N_sZ`!uasw9Dq(wd|Xk+%>j`ifSA!kTU`H_+V|VqEf(MPKB0?3 z$&R~@>%`Vs4Fp8-Dr8YpWah#(4liwl27b^}f-qP{4);(7ZJgtI9>DzLyCCn=JuWA7 zmpv*#kInGgY6G;jO~j0mt`Md!6;PxFF_(V*XF1<QI<{K&ZIo(n_{h|KN|StWX-o~l z_E)gj9xl`DBz&0Uz@{cfp#Ji1gva_@(h?tjj}?&A&asCrykHOKW&}Cj_w&5dpL|eR zk@|xW(kaSULa<{Tf2r5f#+aXbbIK9YdF(cFTD1=Sa{JMn?LB!qMCY(Zgpbffjf@gZ zH~xJUd@b9&4T50Xb11jH4N()Udgf}!^{lPclY_CW$x~eKYCOvz&T01SyYAoj6)`<{ z!=_DB<O~ax)jiia8P`<wdT(p8X<D*}gx}EIxX+=}DXfbjR6p76Mtzy2!D>n^*j7!J zl&cU+lo2i3TK&uJW{_7?ll513A=0VfF&37TBVzopuFN*6F>&)!aU~o2E^ijLvHZM6 z>QIs9@8#{Q_KF-34GlV|!eWDCou1b3s=W(|QWB0?l1+K&+3Gu@Ke1^xv&&U8=n{vS z2RIvJxOD*;W7(rPY-q4NW=||esFqGsl+E^nT-~s92zg%XsL!aR{I03`=(B#<WIViz zleO0Sv!C~``Qg*EmB&|mesc7G1oUOqNA5cZP<K{>D<G_N!v2+USUyTuKv)4`g&kHX f^8fQr&=Oe4iM7d<KU`gY?on}v;t%qoGcW!J$rL`M literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.094758831.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.094758831.png new file mode 100644 index 0000000000000000000000000000000000000000..57df7ab5af70407862110f3f5b219e6d08437570 GIT binary patch literal 6435 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 U%#Q<qbeQ-a^7F3y<@9g=1Nj06KmY&$ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.4a4efefc1.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.4a4efefc1.png new file mode 100644 index 0000000000000000000000000000000000000000..ba360b48514e5a64bffcab50a7d6fc5bf3943221 GIT binary patch literal 4481 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 K9?7ob-~SJCVnmey literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a21.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a21.png new file mode 100644 index 0000000000000000000000000000000000000000..55aec11b1a8f6e3946db96942d01bf66063a8c72 GIT binary patch literal 5689 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 W>e>CD`pUn42lgHLyy>&!-~JcM-W!hq literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a22.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a22.png new file mode 100644 index 0000000000000000000000000000000000000000..fed37b16eca73bd46fe59ce74e70fedcf9f2d5b6 GIT binary patch literal 6551 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.4ddf9d331.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.4ddf9d331.png new file mode 100644 index 0000000000000000000000000000000000000000..06686401568295fd935fefa35ea5540f96013952 GIT binary patch literal 6185 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 N9X@f0e(=JLe*?g)OG^L% literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.6e63ddeb1.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.6e63ddeb1.png new file mode 100644 index 0000000000000000000000000000000000000000..fb9fd614d42b42c13168f1fc69c27822ef8125c9 GIT binary patch literal 4108 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 Q^`j?59E?2B5SEztU&d}<T>t<8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.70ea920c1.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.70ea920c1.png new file mode 100644 index 0000000000000000000000000000000000000000..3277b2b99d85c451550fba80ce3c2854a033f337 GIT binary patch literal 4167 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 bB$$!ZTry>*et)y)7ywF6gzjbTK62(igCSrd literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.74cf8d9d1.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.74cf8d9d1.png new file mode 100644 index 0000000000000000000000000000000000000000..d63652918b2d891a5553f2e3893762b312ca9477 GIT binary patch literal 8541 zcmeHN_g|9h-?ut8?5GXPZ;tFFrebR4-er!+tsrhoGgrBB17(>l^@yw7<(AZ(Bnlj} z)bxmZi&$zXisnEBMW37R56^$_{Pe`@#S6G!_kCU0{Tc6Zalf#(G!+yO6X4_H6NKC{ zvgPAD;KRpv@X^u3z>ylUAEm&@flymhL%u4q#3JzHk5EGh^eFI(J$mmsAKwK&h|vvb zWDbK86()ipvevA;|2hsidg33@iK9A4j~ZPUIXup9BVz1g9Zsm4x^ch;6n!#MsPN(s z7YUnlXWA3{ZN4N>`2TbAjL3;`^Akr?`2Kpj{H{6QU_C5_Y#8?*MPPB)BZYFIa2R}_ zP8l8^o*9^37vQ^N`OhlhAm5{xCw=&?yu5zskq!F!^@x_T!uzb;%CYBT;8H<B0q*d~ z?DzA+OIt#?eZP(BPPXpWv0Pan58MmUdv4ndZpN$v7<9XChisqx^gQX?Iw}yx5Vz-N z@0~bvsyH~ey&U;u>$cY0_oGCl#StsX%Ml4Fk|q`2hE};?-C`CIZIv&zH^rtre`Q*B z7uqHu8m7uB%Xr}agH`uyCWwMuq@-8nOfHRty6ct#^Dg8mu@(jo<%9KP2?LA5yR-u` zL$|HJEFYaeLxb&=p-bKpyy-z(cL_u;^SJ(M7nD_FHjsk~6%iAuV7vdj|8?VY;3`hS zU39eBVKCa<?t#$<_48BPSmjlg{V?$^>~By*<*2>$r3W@WGg)Niyie=LD4S#p4vn>+ z2I9vbY*?Ch^>f4(tMtru%lfxw@JG+=>tBFm8Tb&zh8|ddkE=ai*Uhi3bj{LAFp{35 z@ab1~{4nau<N)rQOr`)?1iGWocL$h-5XLa8IOk%wq24>V$sAN`VFW1>18Uq_ej3a6 z*~OT{y77w4`4nZf$i+KqFa3%HJvA1HRV-ifGp*ab5O_X(r!3s(A&LJfe}XTQfkj;0 zH)@LCf5R-SatcWgjbT@I!<LmsKH{E1FfHp9eXb6SBcWX9(fs|he0+rm54s${fB5dz z1@0Uuf~BUxb4uYPBrIDv&_4I0Hd(7820tYY5o-zim_(BtZOm`<-Fh}2@~!wB;r=a5 zxa4H9MS?n;YfwLI-845)xO83-_sheo#-97Ji%n$&cQX(=IQag@i9imJX)rBCA}W=z z4vI;;cGdVr{|oZ!XtGmvs-2ZKPTJ4NhppJ1uBA5E)JvPyhkGd{HbeK%vjXQ0vXldB z;Vb6?dKPs$0<-=uIPs9+kDXGPTE7$n(<E2}^|qCLN&lK#c6)&B_f11@eFMGC)GB{u zu1e3N`-h-c2-nfX<z5uaRV^gFw$uxbPtrF_P#j+LEzM=4!zN8bzQN|XMHRS05Hn!F zIqzO_%QrV*on28N(`0w{Cy@MAZ5wv-&n3q9z|3q`#xdu#jax~E2XBf<ocQqdrl`*3 zVCr&z%8}$U|JYIRXg`htTz^K-zGxnI86I@~m)7dW9o7+qN7u0^j2>|F^+4T3FSX-% z3uAs>f`GMz-GQ&K5|r$_zK8%5N7s9Q8;dQNJfy9~EZX=~SL(F{<eRNn2y}|%cJCqb z2Jig9sb9~isvVc41{<3jVOo~p4f3yu9Gd!m|8B@eW)c6Bvzn6~z{0GAxkc|PpK&bg z!X=`CyF!BCC)F;3T~_cqBjtCZAJ^$s`Gmy_S!plpKJ!dX_O+yHf#0~zMEI?b&3R7f zqUuJbh$cYvy``4k_Su!6wu9|D)NJUke2PhM>ZlK2ai(xDG{I{HN<@A7)m~LdG?VU8 zKVFi1XCLPhvN%~t<#0Xb_tNRpADgv1!bjSlh{JU18!bh1N7h<q$*N|J%Kabo>vk<; z(#`_m)Ja_#da-*t+S8kTBb;aw{-1L0+nokABo_ZA!p0-)eFml89HM*)h?$qILVbn` zSK+36|KAxmH@`i$+BBXJ^|P*P)#|lC2X!C8%l){hIN4xsaBr``{%U2tK`4o;T641I z<+0@ctTZ)nxTt^7tJbU$&#dVwc1fcYMF}!-CH%>$!qW}Xo(bxbQ$=H)4z7diVg#KZ zt_>(+!&crH_PN+efn$H#xZKOx-<9@MpgfMv_|y5l^CY$6-(7P;(}sm{f{Fi~y2k=R z{Bk%=mS~_U1nAMxTG#GP&ynPpuU-!(J`9A0RNs53`^m=NZUl_egpW25Ee}-jMYWuV z*qTSa#(zQB_}NzoP~xM#78JxN!Sw^71I*SuVW{kfqyV4LC+dlmqE4+8n(i59HD%v6 z+7kac?z8HLo-2aBT1LIXD7Oe?B)m9zvHXBld8ca;D}q$5lBa^NV8i@Q51L@0(jT9J z<S)ya|MmDU-sr$YPRurNCB(6?Rq?R@9&7^^RC3)6rwn`7Y%Q&H{vA+BKIosQ$|;g< zgQ1@<w~Q(9=h2~;1ck6)LpKEduHMqXH9IzaZ4%pWjfB{S`P6%S_Jl6Wj{rf|p4BS6 z(Q_nST=tBw1jRwHP>f!Pl#*}0;(XItc({iVVDA>3*N(hGe!H^Kmap*M<f-LczV!}= zF-&qqD>nPS{p=l&9kaW<xU6Xo@CI6ngZmuUmL`&2#0O{lAKFu&3#6xxnYx#vD;>q} z%U{wp$xmdP1Hr7ca;=swO)m4E&5Qoj(S|9~zKh7&F-b7NI>%~6%@6-<%VrLYWQqX1 zgeV-5m%DHaNQNKv1(Gf@389FGj{;pT!bQKIUyH6FNDWmxy7Qt%jcQQ|Z*GrJ)Cfl} z67O1AWAT+B$XoVX6lq(YCJV70oUo8d&#BPN3LOf}s$W)x3>cpjg=ua$BE8E^?g;km zhW+Gs97mKd4Xrky$c)d6@##d(Tej8h1Rn5CS8r8~?0QOjG{`)B6%b7;xm~)}g|$EK zB(J6b!>Ho+T`Y*PwTIQk%NV9O-i^qZ$ohG=TOu6BTAQe2mY&S@24!9X)qEBU{3@-y zp5Y{`05ldem~_xG*1{C=^82+?p#rf_f6zhch}e0UV1?d=qQ%!WTqo&Hl@^ZJ;>k?< z;{f4>B(s%1Dv8|;^6fff2ZHQ#=h0^17xx2#tA6C^H*e^n=4WrOq<?6}tZ37Ys7q%g z2JPv3^@w=s<rL>7#@K5UR?AkcUhNkt4-o%`t_y)f{qDz`PV;*U4EeZ8(;j(_5}H8} zhG=7iTN*7O3#-Rs^rtD5_y9<eHSS#!cQ9^qfn%x`(>7lCeb;n@$BD9iw(Q{bWmvBt zpf|{x<*&_@5ROB})ZNWQP<eoZtCu;s?|HcN?m=#{<bIz)_-<PhA!P2h-~)B*Tn`;z zvx&3b5-WN&@XEYE*y1{9aaxH*Cl~3YXQfvJyut&B;vpEszm04ML1I>^*s};HsAfK@ znmNcBVq%JDhxJNl9^L7g)U`^rw+Ja={~8fSAYNhBss=R`C-4h7OR$t$PuxPyMN-X< z8B5SVjy`-jYAMrsZl4~4|5aavft!}!n-OVxwObM#L%|efsrglWxDGE^S&b%zyb4tw zlP&Fy-Fr=Ce2BYj9B%~E<|db-pqZbPoI-cR2=z=ELfw)i&8PYz5W+CusbVMHuo|~r zjxk9A8Y`5iy|5gHsh?dCf`p9B);`OGVDzVU-ch-3<6AG?h+AH2*uf7kO``%3ZS)GD z4jG+e-~YT0;rLKZhlwG@+LJX835{Z74+=b_J?AlIiG|{3;HGsG%zc!)op4yp_0!E3 zVF!`UJCk#3(W>Z;SYUzjnim`<lLO!#ue8Ac^(OkMg@mvCL}bD7sH@+vicN=qI^`!L zCs#0MC|%tGKxX--KG6Mu`dIklhzWTavgtLEiErtjpD)?J(|ZK9dU#fPHXZ1Jf=3qw zeblc5=YrKQ+Pg*NcAuIY4wQvBaohZu<_qN}4Px@Ze8wvmy_>h8YL?*Cl<e=okN|G< zwb~sS2H{^&!?;;ncDU^T^4>YAbIK61O=+M6kwoK$Dpki6u80i>r}0oGAzm^EiMM+o z3mG`>PMFc)^2+@_^DQhu-_%kn_{Zg3ioNnf<hMZlC<*Bb$9-~w1VaoI`n);<!tXYQ z2DinJX?!?#ThH;@^H{5TE6OiCrl@T^K?dTj>)j=2NM+QTQZbtj)g3&&=_uZ1@YW!6 za(1hkQTe0#WYZc$yaA|ArvHQ$u-d^>y{9Z3Rg3`%hz;T_QSolo?)!L?2#LymNmB&? zE}vuyjg;KXCMyjtk<6)&w$=!<ot-^$8tCNFS=Pue+O4wm3+12+uX&Mq=(dNWJ#2Cf zP%HkCli$zmk!g|B!ASIOjB7~0jDWBZ93Yt=7ia6&W=zh+yhK1KW*`Wp1~L&os!s9( zj--ccSAJ$EEvZ_*s&WNox}dtN=n@{ctZ&7an%_44R9~y}z#-^O+@Uxomq6XA#7@qJ zj|{Uc*Se3nb10*;%rs|9*lf|YGkVpf0#*?4i$GK`(e_e_=IlSX#I67-s;0RCa-~}y zxmu7<U2kz8@{3Q)0bky!V)vEbJVP#BZp{vKW>0(VNN6FA2F8P9iCu{l+ex<JviQg! z46Pbd+izO=1F=>gm1lrn=*ksm>NODu|I!CgUoVV(6C5JC_`)i08)o044KzksBl;m0 za1V)#nkIk@zilNPiyydN4R9HA&(>-!WsMWcGVr%$L7c8VcyWp*Yp;nkj#c&{ihde! zQOe{gc<yrS_Gfnv=2rg4nU5P;aN}6h>T^x!=pR2mN&1<wT%dP4nqGG`pKY--CEDxz zefYRq`3{J7bmd!bUK&WUQ~Lshv$xH{b19RExy_T3J$ljPh$|S?)rzR~XaapNB!X0U zm-RaINL8onT#mO=<$Kz@i}Bt9^Fl$`UMw;#FSQCF5}Hd@?~4|ULP}N4hBhykl3K_w z%6_7T_io*5%4Z8b<+T)`GSjUUZY<f3F4BwPI&dw*l{tP&df3+PZd3bR-&F?6>PeRH zz-u>^dUdbLP>cG&9QEfuGZFmu<Dp;E*B)XIpA^gyeKW9R*LaTBECrn#oz_4JAL!Uf z!U8&Ci#fT2j102q_6I?vq_!O}X#jHe6<8wxlc>^zcHu~2peu$fdAK>xFFEb@{0Mon z%laGWX%dPJN5;;0HqBM6XNUQg4sQ0*6Vl%1%TwhG=<CR_iE3X1Qa%f^ES>2>&P2zS zrimG=g_Il%<F}7S^|I^@7-3m0>m1}MB}k>GlR~WtJ9j6Rokvylb<WW=lYwidF@KCI zSB%+xSj+8LYH!p4m&%!9Q4TWYbDhJpOgR8AcnV=NXA?YoIepoMN7%vV5#P!L4938U z(i%4bZb!kNKRp~@Vdjzv&Xk9AtS8BX<ct=z7PbFI6nqYDRu0Vvl|#Z#GxTa$aUG6e zw!{~fhb#oR@b+uVu%zwD5r2j9xkJ(JIZqD7sTIwiAhDt9LfzW1psuDN1Vhq47wO4b zqhRJlkq`iYpbh5}0-Y&gHTyIzB6C-1RtG{ON#%62SHuD2+pD1J5>Lc|78$G4Bb`Fh zJfj&MwbV_gejsIy`c?tVtuTM@CCGPT!j}UO5p(=auWH1g%U9!eU&DbvfEH$ETA<gm z7p3*2QaYCUZ(^l53@ID=YS;a&a4BjfP){;nWy;Zmd*CopPJD+f;BTz9qHLW98s+xQ ziP0L1mH4{|F8PRk2VKfuLB+t1@}qf?a&pxVpyvwrI^xD6%X3L86@<*)YXJE=WZ%uA zL^J}uf<TY%oDmbDvqq+I+|sQ#V2D#qV0>e7pdD?DJb_qMQshLZ(4UPeM!X&9ZO}&$ zUQQW{^*wmR+hh2w5|3{jZlFs{Hci!^uQtt>*f!#3Z)JiA90nFea?h@{Ka?eCP5%N_ z948vFz1N4|TOM~!35s`8D%)<OxORk>yK=*w!g?+;{v9jFX1NzX?m1%LG|gkxvwu1j z2zsjfu&3w2x^`uMDJDQ8p{^-S*+2b&er?#u%jJbvJB0Hbz(v)M?CXg!=p1#)iK2bZ zCP@`P>!YXg>l`9+wL<co4o@<=CC2!=(zScGBTX7IVCj0S)&9Edd>Ei*SIqHtHxAq& zvre8xHCTJ<Bo5~gL!p4-Ipr@)2P!i|XF*O+2%dPk>7JVA6$iB6t}LZx`98_lfV*Y~ zjQ8yRR$h5!#6#HUmOo_Bm=9NT+ZO;iRtE61b{g^)L5vkL?Fj#htHi{}U@aXak(q0` zJfrNXon({JeCs#~%s%LZU_GH%tPsEN1Q`HjlBLJ$))(cd@k0il&Yw<m(6NE5;tfN0 zdefmH(RXGrbvo@YkqVoeG%FqqW{o$`RlIKbp(_~U|AyM%PRX3E>=a9JYV;rHH~3se z*Ujr#TGNq5+{_lw!6g8BN~jfHY=sHiM}puQEmZ-&=vM&`(bysk?q8j2;XVu5-qniC zz5V?PC)|N*;l6d}%q7mqDDakX&(i|jftEPUr>#{Gb35sZ=mW=so#yFkU%e6t2GB%I zMt|V&yVh~60WX%{;Fqv~|NZ*s^L}8tsx!<3Ja0PjrM_R~P<{&B?V%a9MC`Fz7xE2s z7@q;?c3wwVFs=?eR$q)r!5BbBPQx<{T$x{Ftu!B_c2hgJsE9!w4d40Bbl(0))?Vo% zxmQ8kbU`Jm=N=)rO<auHH23mobqi;71zjGI8tVRHV>g2aBi%<@(Dfl2^-VVb5yIo6 zuPYviB!)}C<qXL~%CXEij7O?!<Ddg6;y9oLa(spFN%lPzzO=w@?bVt6f=lwZ9oeQR z=~%|nEPERmgmwC>dSxguDy>4Kx@96Vs4!rM_7GE4ofKQlGYYab$$94-)*n0GN0ppK z*Es;0Udzr8ln??`63`GqC^wGJ0BbC7cNxO8SjH-3yOhIl)waN{kk7Ay)@UwgbQpkF zz+4vRS`Io>3B@NxlYi+XxcAR05-M*WSr6;?rL0X$lDXJsDb2~6f43B3n(;f&Ry*~s zT=+P3?Q(GX-tnEA2X79QxQQ3%nKN|)p-zt!t}8AzLM?5QLi4(7o&IWo0;w0eCs{V) zynI+qTu$@KcARb_;FbL(2b~P&ccF{BhEEKK-`zL@=o6pO8rO8P0<b?&D^R;jzH1t9 zwD==h)Zc6(!w-EF<_hpQB5=%A_PAwUD4{~S?@Za2;DiF;S9h+lV4PN0Uj`j#=VM(K zE<p))qh3-}{O3%;hlDzjvmzxkZ)tlNGhYjT4H)3ig}kN0$;Fv4<(53Bul2S-{I(>h zp{+P{>cS-+iV?NfVezx-z>a06>L;Ue$8uN_jH($HGd1zHW<xYLeCo>mC()qIJ}+h0 zpAlko7kd1tod?5+p;Kc8EY4oj;=g@HCZ<>`y!%WF>^vp9|6X9Pbb(TY!~DRcW#pFZ z^>CK5GDhV9U)s%58f^%4*@>y1ma=RsdnShXCRcpFxB|c|{JS1O-ag{B5S5{s@|XK$ zNf(D3Fsp;RS6{E{POlC<0abGD4r`GjpLWT7y|nmE--^S1;QmwJZ5ee_;>IlcHDF8- zK7BVtja`avjwfuownvEW4z=y817wK>IExo^f6j=(vZ(%E*-=%#|IDvvaJx?P{qxk| zi$N4W-@f?&?)m;9#`pSSAKx93$A|d}d8Zl|9Qa4>pdg>g?^Axq<##ds_m7d^P4XKV ze#7B!IQ$KVzv1xzN+|7Jw|@gX8?eKB7U2IS)^H63YFhm~Nx@M-82BK@mPS>EcjEpB DhP`pC literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d01.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d01.png new file mode 100644 index 0000000000000000000000000000000000000000..9053853f8a7968004331b4bf03ed889fd52d571a GIT binary patch literal 5614 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d02.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d02.png new file mode 100644 index 0000000000000000000000000000000000000000..d976a8ef602aace3965442ef8f10af0951771534 GIT binary patch literal 6585 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 HPW|y;3TnpE literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.8f160df51.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.8f160df51.png new file mode 100644 index 0000000000000000000000000000000000000000..cd09fc2c45f324eb2942ce104a161df2c7960f4c GIT binary patch literal 4394 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.8fc91ac51.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.8fc91ac51.png new file mode 100644 index 0000000000000000000000000000000000000000..a3d256bcfff248b1e21b3de3953ba81460100725 GIT binary patch literal 8643 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 IfA`t{0Q8gU00000 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f71.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f71.png new file mode 100644 index 0000000000000000000000000000000000000000..81e74267c5c371dd1143358f24faf7006339803a GIT binary patch literal 7991 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f72.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f72.png new file mode 100644 index 0000000000000000000000000000000000000000..647430fe0b62041c5489c03c06448607cba578d5 GIT binary patch literal 10426 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f73.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f73.png new file mode 100644 index 0000000000000000000000000000000000000000..77f047ff288e1800e11c79c1a1d0833ae3b9f19f GIT binary patch literal 6663 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 O*)OBMV4eEvyZ-{%pEZC0 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.a30301261.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.a30301261.png new file mode 100644 index 0000000000000000000000000000000000000000..f595f2c0a124a3034ffd3a1623c3e44656158006 GIT binary patch literal 7593 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.db8dd49a1.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.db8dd49a1.png new file mode 100644 index 0000000000000000000000000000000000000000..f4eb2f3ee513170a213b15d3988ecdf63b764b5f GIT binary patch literal 7233 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 Ap8x;= literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.fb97fa6a1.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.fb97fa6a1.png new file mode 100644 index 0000000000000000000000000000000000000000..4193152a449e2c7c9786298c0cf740dd56981db8 GIT binary patch literal 8620 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png new file mode 100644 index 0000000000000000000000000000000000000000..42286f77aea68b2ab541844e92d065ae699437a0 GIT binary patch literal 4522 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 Sc<KLJAV5IaDf)@?SN;nUjXuu+ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png new file mode 100644 index 0000000000000000000000000000000000000000..f36723fd32403ad26d4683157e1858ebdbf03596 GIT binary patch literal 4695 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 O0DkV`S=x8E?*9h>wZF&! literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png new file mode 100644 index 0000000000000000000000000000000000000000..fc37a56c29245da1207fc1abbf499761a1ff667c GIT binary patch literal 4759 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1b50a0d81.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1b50a0d81.png new file mode 100644 index 0000000000000000000000000000000000000000..92433d5e3faea413800dbfba226e82643c377a58 GIT binary patch literal 4813 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 Am;e9( literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1ffa58061.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1ffa58061.png new file mode 100644 index 0000000000000000000000000000000000000000..7d2952477e9d7fb6a9d435b52767a453aeedea4e GIT binary patch literal 4395 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 b2tIo`e#vUz1n*Y}1{3l3$bGy$ColdNA#@dz literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.21091ed21.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.21091ed21.png new file mode 100644 index 0000000000000000000000000000000000000000..692d94e6d524e0382d63a8db905fd9188d449e3b GIT binary patch literal 4384 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 PIS@w=g&h<gIDPfsonHb8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.52ec30781.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.52ec30781.png new file mode 100644 index 0000000000000000000000000000000000000000..cd09fc2c45f324eb2942ce104a161df2c7960f4c GIT binary patch literal 4394 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.5911c3671.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.5911c3671.png new file mode 100644 index 0000000000000000000000000000000000000000..4af6fe611b5562a400f78b30730b99e4011eb04b GIT binary patch literal 4834 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c48dfe211.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c48dfe211.png new file mode 100644 index 0000000000000000000000000000000000000000..8a9e181af3045afea4ad493f2af1a55f2e18b9fd GIT binary patch literal 4913 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 CDh0v- literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c536672a1.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c536672a1.png new file mode 100644 index 0000000000000000000000000000000000000000..5eff860098acaa79a98332ee1c8fd47967522537 GIT binary patch literal 4393 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c56704881.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c56704881.png new file mode 100644 index 0000000000000000000000000000000000000000..1fbc55004fb1b0ac80443d5c11917cee44902f95 GIT binary patch literal 4611 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c6d8bc4d1.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c6d8bc4d1.png new file mode 100644 index 0000000000000000000000000000000000000000..7b595cef0b1085fac56ad2013cf0ccdea8be3409 GIT binary patch literal 4866 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 O5IeW0e$C!?xavQcj}p=V literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.d0f8fff41.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.d0f8fff41.png new file mode 100644 index 0000000000000000000000000000000000000000..333080e54e88fbd05886de1327baf2b6d48b27f2 GIT binary patch literal 4785 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/attribute-selector.ts b/integration_tests/specs/css/css-selectors/attribute-selector.ts index cac43430ee..91ce4c7642 100644 --- a/integration_tests/specs/css/css-selectors/attribute-selector.ts +++ b/integration_tests/specs/css/css-selectors/attribute-selector.ts @@ -1,4 +1,4 @@ -fdescribe('css attribute selector', () => { +describe('css attribute selector', () => { it('001', async () => { const style = <style>{`[id] { color: red; }`}</style>; const div = <div id="div1" >001- Filler Text </div>; @@ -31,10 +31,9 @@ fdescribe('css attribute selector', () => { document.body.appendChild(div); await snapshot(); }); - // error it('005', async () => { const style = <style>{`[class= "class1"][id = "div1"][class= "class1"][id = "div1"][id = "div1"] { color: red;}`}</style>; - const div = <div class="div1" > 005 Filler Text < /div>; + const div = <div class="class1" id = "div1" > 005 Filler Text </div>; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -42,10 +41,9 @@ fdescribe('css attribute selector', () => { // error it('006', async () => { const style = <style>{`[1digit], div { color: red; }`}</style>; - const div = <div class="div1" > 006 Filler Text < /div>; + const div = <div> 006 Filler Text</div> document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); - }); diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts new file mode 100644 index 0000000000..f4af50717c --- /dev/null +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -0,0 +1,23 @@ +describe("css child selector", () => { + it("001", async () => { + const style = <style>{`div > h1 { color: green; }`}</style>; + const h1 = <h1>Filler Text</h1 >; + document.head.appendChild(style); + document.body.appendChild(h1); + await snapshot(); + }); + it("002", async () => { + const style = <style>{`div > h1 { color: green; }`}</style>; + const div = <div><h1>Filler Text</h1 ></div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it("003", async () => { + const style = <style>{`div > h1 { color: green; }`}</style>; + const div = <div><blockquote><h1>Filler Text</h1 > </blockquote > </div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); +}) diff --git a/integration_tests/specs/css/css-selectors/class-selector.ts b/integration_tests/specs/css/css-selectors/class-selector.ts index 635a5e25a7..fe9843c1a1 100644 --- a/integration_tests/specs/css/css-selectors/class-selector.ts +++ b/integration_tests/specs/css/css-selectors/class-selector.ts @@ -66,4 +66,90 @@ describe('css class selector', () => { document.head.removeChild(style2); await snapshot(); }); + + it('001', async () => { + const style = <style>{`div.div1 { color: red; }`}</style>; + const div = <div class="div11" > 001 Filler Text </div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('002', async () => { + const style = <style>{`div.div1 { color: red; }`}</style>; + const div = <div class="div1" > 002 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('003', async () => { + const style = <style>{`.div1 { color: red; }`}</style>; + const div = <div class="div1" > 003 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('004', async () => { + const style = <style>{`div.bar.foo.bat { color: red; }`}</style>; + const div = <div class="foo bar bat" > 004 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('005', async () => { + const style = <style>{`.teST { color: green; } .TEst { background: red; color: yellow; }`}</style>; + const p = <p class="teST" > 005 This text should be green.</p>; + document.head.appendChild(style); + document.body.appendChild(p); + await snapshot(); + }); + + it('006', async () => { + const style = <style>{`p { background: green; color: white; } .fail.test { background: red; color: yellow; }`}</style>; + const p = <p class="pass test" > 006 This should have a green background.</p>; + document.head.appendChild(style); + document.body.appendChild(p); + await snapshot(); + }); + + it('007', async () => { + const style = <style>{`p { background: red; color: yellow; } .pass.test { background: green; color: white; }`}</style>; + const p = <p class="pass test" > 007 This should have a green background.< /p>; + document.head.appendChild(style); + document.body.appendChild(p); + await snapshot(); + }); + + it('008', async () => { + const style = <style>{`p { background: red; color: yellow; } .pass { background: green; color: white; }`}</style>; + const p = <p class="pass test" > 008 This should have a green background.< /p>; + document.head.appendChild(style); + document.body.appendChild(p); + await snapshot(); + }); + + it('009', async () => { + const style = <style>{`p { background: red; color: yellow; } .test { background: green; color: white; }`}</style>; + const p1 = <p class= "test line" > This line should be green.< /p> + const p2 = <p class= "line test" > This line should be green.< /p> + const p3 = <p class= " test line" > This line should be green.< /p> + const p4 = <p class= " line test" > This line should be green.< /p> + const p5 = <p class= "test line " > This line should be green.< /p> + const p6 = < p class= "line test " > This line should be green.< /p> + const p7 = < p class= " test line " > This line should be green.< /p> + const p8 = < p class= " line test " > This line should be green.< /p> + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-selectors/combinator.ts b/integration_tests/specs/css/css-selectors/combinator.ts new file mode 100644 index 0000000000..2bbae9247a --- /dev/null +++ b/integration_tests/specs/css/css-selectors/combinator.ts @@ -0,0 +1,35 @@ +describe('css combinator selector', () => { + it('001', async () => { + const style = <style>{`#div1 > p { color: green; }`}</style>; + const div = <div id="div1" ><p> 001 Filler Text < /p></div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('002', async () => { + const style = <style>{`#div1 + p { color: green; }`}</style>; + const div = <div id="div1" ></div >; + const p = <p>002 Filler Text < /p> + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p); + await snapshot(); + }); + + it('003', async () => { + const style = <style>{` + #div1 + + + p + { + color: green; + }`}</style>; + const div = <div id="div1" > </div >; + const p = <p>003 Filler Text < /p> + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p); + await snapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-selectors/descendent-selector.ts b/integration_tests/specs/css/css-selectors/descendent-selector.ts new file mode 100644 index 0000000000..fc546629b8 --- /dev/null +++ b/integration_tests/specs/css/css-selectors/descendent-selector.ts @@ -0,0 +1,84 @@ +describe('css descendent selector', () => { + it('001', async () => { + const style = <style>{`div em { color: red; }` }</style>; + const div = <div id="div1" > 001 Filler Text < /div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('002', async () => { + const style = <style>{`div em { color: red; }`}</style>; + const div = <div><em>002 Filler Text</em></div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('003', async () => { + const style = <style>{`div em { color: red; }`}</style>; + const div = <div><span><em>003 Filler Text</em></span > </div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('004', async () => { + const style = <style>{`p em { color: red; }`}</style>; + const div = <div><em>004 Filler Text</em></div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('005', async () => { + const style = <style>{`div * em { color: red; }`}</style>; + const div = <div>005 Filler Text</div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('006', async () => { + const style = <style>{`div * em { color: red; }`}</style>; + const div = <div><em>006 Filler Text</em></div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + // error + it('007', async () => { + const style = <style>{`div * em { color: red; }`}</style>; + const div = <div><span><em>007 Filler Text</em></span></div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('008', async () => { + const style = <style>{`div em[id] { color: red; }`}</style>; + const div = <div><span><em id="em1" > 008 Filler Text < /em></span ></div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + // error + it('009', async () => { + const style = <style>{`#div em { color: red; }`}</style>; + const div = <div id="div1" ><em> 009 Filler Text </em> </div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + // error + it('010', async () => { + const style = <style>{`#div + em { color: red; }`}</style>; + const div = <div id="div1" > <em>010 Filler Text < /em> </div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 9b8ce89d0d..3d4a04226c 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -1,5 +1,5 @@ describe('css tag selector', () => { - fit('001', async () => { + it('001', async () => { const style = <style>{`p { color: green; }`}</style>; const p1 = <p>This sentence must be green.</p>; const p2 = <p>This sentence must be green.</p>; diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index 47ea96d6e9..80f765b9d5 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -42,7 +42,7 @@ class ElementRuleCollector { // Merge all the rules CSSStyleDeclaration declaration = CSSStyleDeclaration(); - for (CSSRule rule in matchedRules.reversed) { + for (CSSRule rule in matchedRules) { if (rule is CSSStyleRule) { declaration.merge(rule.declaration); } @@ -51,7 +51,7 @@ class ElementRuleCollector { } List<CSSRule> _collectMatchingRulesForList(List<CSSRule>? rules, Element element) { - if (rules == null) { + if (rules == null || rules.isEmpty) { return []; } List<CSSRule> matchedRules = []; diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index 4b99384047..a823db14a4 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -75,22 +75,18 @@ class RuleSet { if (id != null && id.isNotEmpty == true) { insertRule(id, rule, idRules); - return; } if (className != null && className.isNotEmpty == true) { insertRule(className, rule, classRules); - return; } if (attributeName != null && attributeName.isNotEmpty == true) { insertRule(attributeName.toUpperCase(), rule, attributeRules); - return; } if (tagName != null && tagName.isNotEmpty == true) { insertRule(tagName.toUpperCase(), rule, tagRules); - return; } universalRules.add(rule); } From 7a96d5ea29c4a67e8a17505d3b341db0cebe0639 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Tue, 9 Aug 2022 22:16:52 +0800 Subject: [PATCH 178/498] test: child selector --- .../child-selectors.ts.2d9c94aa1.png | Bin 0 -> 6522 bytes .../child-selectors.ts.dd63c52a1.png | Bin 0 -> 12885 bytes .../descendent-selector.ts.c48dfe211.png | Bin 4913 -> 4372 bytes .../descendent-selector.ts.d0f8fff41.png | Bin 4785 -> 4298 bytes .../css/css-selectors/child-selectors.ts | 27 ++++++++++++ .../css/css-selectors/descendent-selector.ts | 7 ++-- .../css/css-selectors/sibling-selector.ts | 39 ++++++++++++++++++ .../specs/css/css-selectors/tag-selector.ts | 32 +++++++------- 8 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.2d9c94aa1.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.dd63c52a1.png create mode 100644 integration_tests/specs/css/css-selectors/sibling-selector.ts diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.2d9c94aa1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.2d9c94aa1.png new file mode 100644 index 0000000000000000000000000000000000000000..9ca0af24a201975695d009b6057339439fe8ed3b GIT binary patch literal 6522 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.dd63c52a1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.dd63c52a1.png new file mode 100644 index 0000000000000000000000000000000000000000..17aa1fa9e61a3c83a3924f4d7aacb0e28c855474 GIT binary patch literal 12885 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 PL)cI{M%rbUuHFBCi1;mD literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c48dfe211.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c48dfe211.png index 8a9e181af3045afea4ad493f2af1a55f2e18b9fd..46894d9eca1498c15751deadda8afae2c1784a97 100644 GIT binary patch delta 2094 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 W+Gp2Dud~*yPDs$H<8{YkulyfNRnyG? delta 2640 zcmW-je^`>|8pkVFtkcYM&FiWyHBRk1b7w6*WzHY?qs_JEOlj4dB_bu2OGGLK6o0&{ z&hj)<SDoofft4%s#aB`SLJ`{OO1Gn8P5}i0wTuM<nj!Mj^RV~7_kFJSdG7mrfA7!z z{AX*&``_<|-`1vn_Q|1=Cd&k^fKAu+%|~v#d@!?V)0esV`!*dQKMM|NQSLn2bnM?Z zdNJ2hcTR2jaLX{JI%?&ot2XWa?ZSJ*|0?+E%HM8&bGazD;r{ueU4l1Kj(xZ_*7JBQ z)bb&r-{-Mn((ti_>IE6IFKE{OWrirns5ZMS2_;;LyohLon^albR!o27qTyG3<6OII z_Xp?KylL-x>-BK+UL2fU{Kx*pxkmx^mSk_Mz|zsTna5rB7?*2WS2pc14mh?<`MhpQ zdAW4asv`sh1ki`~Y*b2&9BuTeQ>X4StP@9vlH3KvGrz3<b49CP6t(Z@tHbj9u&|WF zGPP#G(vq_kubZ(bv&T$am+|Lsvtv7KMOfF<Dnu+6tJ;YKv5rAVPfu5t8rhk0my&O~ z&h?zAsi{$hC(U10jBz*|jiks3doD=pGjmGsY%YC%bv4)a@?~pvr0k(0&e#;i%AWj4 znenO_>%C`RTofUug!YHi-?i@uhJ%BtXU^QG3`|ZQ!=0Jjoi`9QlrT~sNzzB(1*a!2 zj*-K$on*5LB|am0m$U|-DvM%qnfWwzc2T;KDf8L+Vk93Ew7CEkejFD!jvz#)j=pZQ z5jLykKKq3Q9-=ZB;LmHLa5(K63`UzCqbvS=5hG2auXYXZg|SpfscJhqvS`t&*KbZ3 zi|C&1JPc8neO^?kS|1ww)a7z%=H||5d&-!NY&JDUUqa>~ivq5<O{miRauK5ik%xqa z7B~zr+}PAq08Y>3Aq<O99T9sYr10S_2r7Gib+5*%>&Dd?mVJ(OK1Jure$B7Heql6X zU~Kt)2nq=aaVPiubKyo))9k?Mk-O<AqS-E<_<dZIH^hlL*Tlv#MM%Cw$27b<j-7Lj zu6^A>?$h`We&?O91p)zkrNOOtWI@9D`FWs^Um6hO>k&M&H^KS*I@nDtv!h_etmboL zu0hHOx#!jD@TmBBShHr$nhoWZS`n36Qt~j-xAY7ZI*01<NzH|*Q0|$Dv{aKkxyCj* zcywp`!iesi9u~dZW#VbWm=nW(MQ=t^HZ<*}pcT(T46!rxST-3Yh}#2$f|&ZbEDF_O z)ltVgPg?Fa7UpLX>5$oGvw5s@b8{5gCo5rEHhcFFTS%RF*g<(@5gYUfVi^~-gvA%% zE4t*(?`Bx?A*B^~8bts2@nio^L~?Ct-0uj261y4t%lq(nV1P_gh5f>HqPUQlS#HD; zlAKpuH}W{AdwP1P$BzAklH@X~=0PiZq~o?ZJul)lqy%Ye0%IoNr<HE4A;N<QRS}Zs z*Hmh?T4*V*wS%N@Ly-3V@e~Rwggwu&D9FF2wpP?Ys+9P*^V?V^lQ9+sW5kU#nItT! zd@0(vO1gu-%KHPq{0^x4)VCYE*0dcJ+=U<<WA;ed<arDzk=rJhQwDl#LI{wM*bOt- zW2YfPNA4c=j}n0i>zk`u$T(5xEgifWO$A0^_o+|<j+{|zd)by;cjd~1^XJdEN0|&> zD-%?;$D!8(h5PHbgBJwiX>O%d`7G6prMaoYkmY~d__LbI_`Z17>#u?6w3L(-DvQ+x z3M0OC>z2ahot-U!c}P2*F*!Rb+4U?u*_m2%<;v*ljoDA_Tc%Tj5)U;uHw(WWzO9j# z7C{i2E$O5TOeo*Jc<~}TIsE<iP2C1>Vc=VDje;1>B5ffLLGZ9^WTE2lxFmks+HGs^ zw#m8$UeK=mAGT$xMn?}>G(ui`x3x=AL?)A?ckYA_fD%DabW&0d1YzA{eF$Q@9L~A7 z7hCb5Q0`s4dGjV><dn7NuwLF_ElpMN5R=YU$%G)Iu_h!&)oAd%C^b6rgM))1NSOQN zJB4T@@h~2bCze@+LWV`NDJ%>Pz^or#S}N)BT4;PExZzEi%dn#93Bbrq0}19h0;J)b z5>V{CK>CRjKL@T~&!~wz_xua9#X{ve2lC>zqC%YpP$0g4P0{k8+=TEpl(+myfC2Wb ztkAe~KMNVAbG@KELlu9Nydqmu6p}j^uMZ6@vz;m~-f;wFA4%hu`KGF{R*eG{>os)c zU(PIM9fH`633?a+f%(J@_<kCKDgahc1PSMscP1_U62zz(msOOd<MC=^^0HGQ@=K`i zDWd>3MdNS;u6x1=)S{5w8Y%7VWtUo>-&Y$(e|e`tb?xtIU7L&4B$`3igCI$ssh~_{ zYhFLp!ucxMK6+58R%haqy~RCC({OckRMbIKsOe_X()aBfoqke{%X`s~eeSnUZO2<m z{<u|z@cH}*>D+&TvT=tYWzkS#Cg_3Ez`#HVQqn3H8V@ce#>Ot)R96S&RqcVGsvvAT z5*$-M;B*_7bNskOb9%a5Znt@?gB+chsH1N*0D9%?y68#(W)kl(8h_Gg16^y@m)X!r z*x7IQ+F-dJ5KV!*EE&rwpMSD0<Ms0;PUx)UsY2IwfWGxoT0;c7E>e15k-Y3-jWp^X zOm^UzsmH<8>+S6|b@oS?{m%Di<#(-qf#dL700gbP6c`fEKTSbJ1>*;>%kWYko)YmW zM0*=S96h!Q2%?)&vFgQOqq#5HcXm5TCIzK8;SA0Uh<yY{6cro=6t1ve#Cm@En5NGL z(IWsu%0$P+L0V})0Wp2{Kuj~__@_X{^Y^f=+xXH>8E~F@qipWm)!{qDzJ&Prc-z7P zd&UYl2O}A6*dc{N1(Mkhg<CIp9%Fs`K`Sgh!kFq{54pn5BcX$hG3xs5g#5>s0*U6a z@G76-2F>d%u8oe1JKB)LI?XP#N`$2gFS0sMq*lC4BOze&@$LO2Unhe2oI^0E+rm&X z2|%7_<ifrOFW!)TGKkVAN{uj^NNM4^Mq4!gi$%Nr`!F1%Sa&jZ*RD|!RjZ0Vm79CJ zD9~n7^6-+3lBE8INO;i9?d$CYB*TE&C7@bg$&vi1s3_{WbDaXKcaT;o5HhAa#aFLh zJ!m~8T~Od~?svn&ymcxrO)^to%7-gaau{eEzqWSRZWrcpD?|%8_oBs|o%nke1>8?; zUG7)P<($dc5j!z-Z{22eBdNSYKVQo%R_!ycWm&Xh{)Wh$Y$Kbg|Hlv(jEbDnnIE#c zieKI+#gqnu%Rn1wPL`Qq$b!OOBu7U>e!GBAiibe0Rxs7~r`3Pd3M7$#uwlDv*<E`t z>TFwUYioR^r(`>^Yt+GdbhJPpnYL<DZlgQt%`5T(1oLYHF@nFY?OM6Pg^jsGtxMI; S9A5#R)IAxWwSRi-!v6tmf$QM_ diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.d0f8fff41.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.d0f8fff41.png index 333080e54e88fbd05886de1327baf2b6d48b27f2..ac8e7eab423f7a4f28694dc8f8b6fcf1087130c3 100644 GIT binary patch delta 2020 zcmWlXiB}T`0*7^7Wh*Ma*F(9x>sDV^Anhszgb?d$vmSsx9U=%uNf#=SLofsg$#hGB zwg{26DpW{BTqA*S6q9h|SOJ|XNI*=G0EwD%C4}M1B+O(otG|EX`@Q#lt)D#o^VR*} z*2{+vet9al+By#2r?Y&aMR^oDTlQgI;pz1|Hf(P53UH&H{M(+2+i2ZEuk{BSf7nlK z-MII=U-zC)La2D{)t09l{~~6M6-7xd22x{ZUi3eIP|qB`fSq9TP*cxM7&l-_c8I4f zi|n^BIae^S%F~2lraTSVo(+F0k;Z~+dh;F<=pWZ?kGZ|}z<Yc5-@mqNu&u2J^xt>N zR_K2UU*z|+PINW!R_X{>-r5MNC;%(@9zy`@c%1De18AVpYPdPtdQs3gJXkC4g6AKO zx14vTSoHu4Rsy8_6WX{GKsnDU^|$|4A*MT%oHDsmlA3hK{L^l+$_Y-2MFMgDYF`5n zX%o8#-Pg+`=0w<QzANdF)|VY%$+~G35{;cW@<`r%`UGpf0}xebCGp*>o%*bcqtErd z2bn<6$R{_i%xB{^Nlo5VCYCoH=&XkPGpu(?zoLl5(!{QhC`f;QH}2L<^_#^KSO8P# z{C$BeTs>w4c?;+0;L;NnZ}NIy=Q#1322?p#0jVri`(xO^P@O~)H>{tgfJu=aKd_xt zpKx|;V%A|TW}w-6-v``0ViR-EYi|SsAc1A7sebk7V2N0@bY0~tXZI6Fj<aU}3&<y8 zgUJ(cgh+(RGPuk|lgE5?hFWPBaEhDBAV++5`4^QnTiAe_X(jNeOkz~V9NW}fD>hp* zDD#spXdsl48yzz70&40=4n_Ltf=bhzA(3U|0!&?XrDokyU=_@O(8Mvb7T=Qo%0h?P zCE@I#0?o4!ds=wTm2zX&QGa48gbMB2i6+{T?%hKdx3w{-Op8v6fMZ6)c8T4$4BtP) zw35IJR+B$G64E}bbfH5UmE(=>i!`%|se2&1WO_3_6wjp5Cny#dMuqt7#u%`Z-lO97 zv~uPzq~RzJat`qk`zAwIdLom91aO1?w`_R+Z<VRsugE*Y$JeOn;8RuxyX^`27mNrW zyi!ij1ixfnyC!)yumws^!Bn@tD5JQBV@OJ+)KA{ASR<IXjLA0J<J2nYX76H)N5r7| zv&OR%de(?EXE+M4=pQHTD;%WR@JMV_$6LPjNK+@(4N%l{Q$k;uE{6AfB%{5ynXqP% zdrzLkiN#I}nsO{!h7^%jMqc+E6}m5g3i;*Rq^`yi25cBwEc3=3tqgZ#TdKBuTEnp; zIH~L6Wv2>59aGOtd`hlMaf#tE_9}SKp79i3!Odl7UQe-Ke+T;odqQi+5IKhGBwSnx zr&$MU7pFtsLbwFK;Ool;VbCu%Rw9Erf{>0_bT$w+8s+|Ey&V){)xM##WmUzWLu~b! za8iY8>r7*ET2vIEJZIQ3=jPvtTIdatNbAsIRh6nh!}$sVaf_;8_|{{8aKX7cbqiy@ z5;uqqs{@ft#z>QRce77=xM6wPNT|#-wOFs{P!k%kX<z1Tig&X(-^#?YT6m^1fRy*d zu?*g_41{A#isfQPA6fU)hky`0B`o`UFdzj%3QSYn$uKH8D_-9c?xk+%V6ESnN5Q$v zP7iR#L`!I0AQ#SNF8?D7R&rOTLipvxLJ9e9X;Dh@y_nf9OViNeHg!8`8e%83WGJO1 z&AtYbn!r^G=aS2-vbr%|-lX0DLWY)FGtle>rY~>4!$C%SS||M)#4+m{#pnc#rflY| zi4S%j2o9Dive!m$EY-Z(?)(qrnR2BZggM+*mz`9gDfi`e@bDRjfK2YqhI>7H?srb! z5PN{_<|0ypBTf@BTu98bX*D_e8Rln1KCb|JrmF<0T>Aw1Drh&-Z4MJxz+!TF_8K<g z@HhavR!d#>fEF%?7!OgL50zCj09&}lgV&O3)vF@GDG0H0k;miTtlOo0w2te@q(@?N znR7!_>}OdisVQEX`kHf3gvlKw?(B?Xk0xN;ew1$Ri+U$)>sYoXapi+;y=-2KTdl7X z{PhUHfBp12`L0N40OOKd86<l}cf}4oAxM$=E}Q(hR#*F!tr><AyJY5K?_#y*XuMV@ zaTf*t;T;}-9D+<rM`CdY1Q=JNwPDjM;oTkC<!&1^La6BXz>NBQbk;R<KKUNl1zM#5 zjh@Jwsxt9GMTP~Mm;@tNj?)b}XS73gz}wz!T&$QMXFzq4LdQ}c#!8#e7q-7(RG2*N z&b+s0mY%qLyX!@WRa6se`!QyPfP}6G@SaMhYoP}Z|GC8ftRZZz20ZHe(;}1ZjZ4}~ zgHo+ApqCoH5onsG$aVTUK2r5wp{A<x8>_c*I|X$A`c-xI4FRD@J3}<i&c1$m4y6N3 zYWa$%&$Vk@yp%qNMJ((W#(i$@+_@8#ZaEfJHn1s7z-;oQ5P9_s+(O5_cI`UDS$O!G zBgjn(<=mC(1Y8*$l1q$R)<>s8#2KHqHhgt<H$AK%8BOpFJ<ghYxdbjC2|f`oQz8y2 z=KjUy=E#g`%?YE>w4l$0<4v}e;cWx|46BIlZ58B;De3VY&yBWkiTC>m#^E(iT1^C) r{=uui?r6K@{;6Br$L@l)9}yeuI@<KdTXu-oyxrkL(FdCjocr$oy-KxP delta 2510 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 H#kc<hKXSKN diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index f4af50717c..8ef9e6c9ce 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -13,6 +13,7 @@ describe("css child selector", () => { document.body.appendChild(div); await snapshot(); }); + it("003", async () => { const style = <style>{`div > h1 { color: green; }`}</style>; const div = <div><blockquote><h1>Filler Text</h1 > </blockquote > </div>; @@ -20,4 +21,30 @@ describe("css child selector", () => { document.body.appendChild(div); await snapshot(); }); + + it("004", async () => { + const style = <style>{`div:first-child { color: green; }`}</style>; + const div1 = <div>Filler Text</div>; + const div2 = <div>Filler Text</div>; + const p = <p>Test passes if the first "Filler Text" above is green and the second one is black.</p>; + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(p); + await snapshot(); + }); + + it("005", async () => { + const style = <style>{`div:first-child { color: green; }`}</style>; + document.body = <body>Filler Text<div>Filler Text</div><div>Filler Text</div></body>; + document.head.appendChild(style); + await snapshot(); + }); + + fit("006", async () => { + const style = <style>{`html { color: red; } :root:first-child { color: green; }`}</style>; + document.body = <body><p>This text should be green.</p></body>; + document.head.appendChild(style); + await snapshot(); + }); }) diff --git a/integration_tests/specs/css/css-selectors/descendent-selector.ts b/integration_tests/specs/css/css-selectors/descendent-selector.ts index fc546629b8..f4a6b91b6c 100644 --- a/integration_tests/specs/css/css-selectors/descendent-selector.ts +++ b/integration_tests/specs/css/css-selectors/descendent-selector.ts @@ -63,20 +63,19 @@ describe('css descendent selector', () => { document.body.appendChild(div); await snapshot(); }); - // error + it('009', async () => { const style = <style>{`#div em { color: red; }`}</style>; - const div = <div id="div1" ><em> 009 Filler Text </em> </div >; + const div = <div id="div" ><em> 009 Filler Text </em> </div >; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); - // error it('010', async () => { const style = <style>{`#div em { color: red; }`}</style>; - const div = <div id="div1" > <em>010 Filler Text < /em> </div >; + const div = <div id="div" > <em>010 Filler Text < /em> </div >; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); diff --git a/integration_tests/specs/css/css-selectors/sibling-selector.ts b/integration_tests/specs/css/css-selectors/sibling-selector.ts new file mode 100644 index 0000000000..f658abad53 --- /dev/null +++ b/integration_tests/specs/css/css-selectors/sibling-selector.ts @@ -0,0 +1,39 @@ +describe("css sibling selector", () => { + it("001", async () => { + const style = <style>{`div { color: red; } + [class=foo] + div { color: green; } + [class=foo] + div + div { color: green; } + [class=foo] + div + div + div { color: green; } + [class=foo] + div + div + div + div { color: green; } + [class=foo] + div + div + div + div + div { color: green; } + [class=foo] + div + div + div + div + div + div { color: green; } + [class=foo] + div + div + div + div + div + div + div { color: green; } + [class=foo] + div + div + div + div + div + div + div + div { color: green; } + [class=foo] + div + div + div + div + div + div + div + div + div { color: green; } + [class=foo] + div + div + div + div + div + div + div + div + div + div { color: green; }`}</style>; + const div = <div id='test' class='foo'></div> + const p1 = <div>This sentence must be green.</div>; + const p2 = <div>This sentence must be green.</div>; + const p3 = <div>This sentence must be green.</div>; + const p4 = <div>This sentence must be green.</div>; + const p5 = <div>This sentence must be green.</div>; + const p6 = <div>This sentence must be green.</div>; + const p7 = <div>This sentence must be green.</div>; + const p8 = <div>This sentence must be green.</div>; + const p9 = <div>This sentence must be green.</div>; + const p10 = <div>This sentence must be green.</div>; + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + document.body.appendChild(p10); + await snapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 3d4a04226c..5a2d8c9d57 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -1,17 +1,17 @@ -describe('css tag selector', () => { - it('001', async () => { - const style = <style>{`p { color: green; }`}</style>; - const p1 = <p>This sentence must be green.</p>; - const p2 = <p>This sentence must be green.</p>; - const p3 = <p>This sentence must be green.</p>; - const p4 = <p>This sentence must be green.</p>; - const p5 = <p>This sentence must be green.</p>; - document.head.appendChild(style); - document.body.appendChild(p1); - document.body.appendChild(p2); - document.body.appendChild(p3); - document.body.appendChild(p4); - document.body.appendChild(p5); - await snapshot(); - }); +describe("css tag selector", () => { + it("001", async () => { + const style = <style>{`p { color: green; }`}</style>; + const p1 = <p>This sentence must be green.</p>; + const p2 = <p>This sentence must be green.</p>; + const p3 = <p>This sentence must be green.</p>; + const p4 = <p>This sentence must be green.</p>; + const p5 = <p>This sentence must be green.</p>; + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + await snapshot(); + }); }); From b4de5ac8bf968b90ad2870c6e261c879e53be4d9 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Tue, 9 Aug 2022 22:40:37 +0800 Subject: [PATCH 179/498] test: id selector --- .../child-selectors.ts.3df2d0281.png | Bin 0 -> 5299 bytes .../id-selector.ts.0c9eb2421.png | Bin 0 -> 5542 bytes .../id-selector.ts.53d002db1.png | Bin 0 -> 5339 bytes .../id-selector.ts.555cba3f1.png | Bin 0 -> 5575 bytes .../id-selector.ts.f3a7cf311.png | Bin 0 -> 5572 bytes .../specs/css/css-selectors/id-selector.ts | 49 ++++++++++++++++++ 6 files changed, 49 insertions(+) create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.3df2d0281.png create mode 100644 integration_tests/snapshots/css/css-selectors/id-selector.ts.0c9eb2421.png create mode 100644 integration_tests/snapshots/css/css-selectors/id-selector.ts.53d002db1.png create mode 100644 integration_tests/snapshots/css/css-selectors/id-selector.ts.555cba3f1.png create mode 100644 integration_tests/snapshots/css/css-selectors/id-selector.ts.f3a7cf311.png create mode 100644 integration_tests/specs/css/css-selectors/id-selector.ts diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.3df2d0281.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.3df2d0281.png new file mode 100644 index 0000000000000000000000000000000000000000..680f5c3fc4f005f03a0b24c776318143ba373013 GIT binary patch literal 5299 zcmeHL{a4cIw%3}nmr2h#oyN?!JGYs-94k&aS&8`4G+CNiRB9+_nXd?>j*tQ}o~E%k zWlBae$t*+9j0%<VMMdB=jxq-mOh*V1(9E|GP*Fg?B8PSFKXHF(|MIN8pS7N6?a$hu zy+3Q;I&(U5%cdQhTwGkXoIG(1;o{<&>Eg2HyAA7|krzAOH8>B~d_?3?7q--A+Ub0f zfAr+}4NfC(xN_6Q<*%rd$0E*OV;VH1yGd!I;!)ms`fr~dySVOl_l4U}p6%NH-#eh! zMkA45z1XlJv%5C@;9s7fXgd}jzHf~-;?*aAyLsY^O+B0byzbte&$itRkH9ny-|oH~ z9S!YYV=~bWRVbCl0S)p!hFo(c5S}4p7Eb1XRe>hvjs09X(&c8Lh2`hs^7Yot@6N2* z@~7-8CB+j}2(|#AiyRcYIux7{9eI{gcf{R$5N@UC*%$RX-1I<@@S8iUc8V*s{Y9E_ z7=i1|wQ<sVRbjU2!RYqsdQYu-M#;(nE<&g)W7PBoN7hcbySI9sCw=}Nt%=GPt+2{W zM!w^JUx?|w7YVfdGCG_W4dIY9zgvQGFGJ2XRK8r4dj~!IDIi)&u_DT0Y<esuyUVhl zsm2A2_kSI|^zYcHLUZ*YqtF%oNp&TLvp22pA@Hq5$x0$P|6M)@Nz8d8k6|WA#%Q!0 zyorJbRcA1Y$^+D5NNi~PD7anvGL5w-;f!Q37QaGS+qt2qQXvx3QG~2yPSJ%#SQdh) z=|#&m%dgy`m3R@N=WbTAsW28|o+uDG%#Rrd3iC`phM{;y-be}SuD{M`E+>8pzn^UX zh0YUl?Mn-T>SAed<6dYoT5@YQFn8DMoCJeDCuzFKYC>y-)VXdf*q+Y^6JI=q%Xf!e zlpL9)AIMKn6ggJ0YH!^3A|K-LSxG~f#xM2t1B7<=hk{9_g6I>KWeU++g+_BtNIRWV z%mpteL_y+FL(&PxK7+ti8hTzb85Xu+3-&8~7IWd-o+YXyUShY3W-04CMt?xm$6&0Q zTrncl+OGBFOx6U9-;Y+=oVm$TMRwJg@JBcQoG_ELtv)7T{2@kV78wAbFO|j}kKP9v zAM?kn3nsd$=4wKS+bX4fyizMpcwS4V6}N)hD|R#HADI?#fmkHzbW3^xPo2gHiu7>o z{Se-gK7@>FZj84!u{}cH${7efnl!ZBhnE)(oWvGN=te?((i^#bNrqiw^HuYom;O;S zbin{-)LblGP>AHq-B7^LaJqSsnV&5%c31>nfvV+HTJxeL00SSV1LUbE1~4G*50x%i z&*saQ7Y4DK7h(Ww>}Zr%<FeJ22z^HApoL-*=*us4-$sJV*_u=uGDF5*{LqMj8(NW` zP(MxXwUH74H}DJz?R+lp-G>p^(t|AJo1r;+KT;FXr()Mo(MXAAyvu`AFx+OG|K$Rf z$!eWLeS20d3<RXP-jdN$9(H*-8@}}35&{#a_uMrS7)b;2n8#@SI73nNYMmi;>#jdK z2!?(T-To5B9*pHIzPrPhkw)6q`-xG+=Ea#748=BzG>Jr{Jy2m=8CC{YcewdwT2X?I zvP;8lNEwV{fAnF+{s7L0Rc_p>>R4R`9QiUsId*pLhck7X7qleB@^HRpG17NP&Ph^M zy16Z{nAP+5zuf8FH^9PzW~rV{ntB2lf7?pp!HsQNv2bYuAoqB2wS^g=`R4`#?{Q#5 zb2W@;6N|>mfj;Juuc_2$;!6{+ftBBP3apQVxy`1|n>m@a)P*{Ns26Gy=)|Ex^Wn)P ztZ8|AkVK8M+YL9eYaiR2ux5R-hrkrT$<aARJxB_F!rT6mngcA->uiG`Z+FcUJF&A3 zJbE6ZCPKJqD#es~stTcp>69qrG4hB`(8$iV+Q*@#eqrBxX$tz?e9lPTx)POH4Fq*n z_VyeGN@Z}8X_%bPKZR}mY)>W+Ul>gyQDksz@*^?%-7NxWBr@ievdX;qf3tkZS0Q|( zs{Whfb!M_OlrPxk7qZy@LOAw!Jest!achz3P(d~y)pqsUu^lr)P-OshI{s?y_2WXC z2f3D5sH3P2T^_v0ga25^>eQpK!S7bV=ytmW;YVr3nrYNe1v2Shdq-rSyB|)OGSCh` zr87jG{17}E{L!{Fson<3pRAlO4Wl2xC0QTzJ=Wof>gKXJDNMcaHZHLlwr%m1N9DXE zp1|0BI)~R7w*~IEp=J7?fFI0qgW*;k)J!)^G08d*ooHR;F!C1O?4-Vy9u$Bk9bd@d z^!?<)+!?lNBX}uSmRLzeS=Q^)dmr=xX;LQQbnwq8c3c#s)HhDT6KVLIMc>jJo1>LD zet}ij-jVj^DPkFnJOq>kL!%P0T%#@W@~__`f|SpQsNGkh8I>=u<$~sT;ear>rwlV@ zB**Q4d;d#=WgBM-6*g>HOC_*c$AFXwlX?3gHeBXQ%;|mPSt`woOiqT7X5R2|v$C|; z51xo!pP+|zU--ZP%3E&cS(qO7{zpXlk@`ozWq&j#Yj7Ddoa1mjJEw&~3)5NxXZ_7p z8G!m^Mby#&BWUJSmJ|L9dp3Ilfz#2@z2mn7j`L_j;S-rKzA2M;{rW+b13hG$GZ1EB zjPc$`f<sz08y*l(unPKu46?us0Ly^lGq}AB#yn5N_4uN6pwRV0h^%~6d+SrVB3?p= z7_Rqx8LfiC_a<w#M<}D4vDH`}mS@$f(I;vO>p|-!Df1rOeT89@t^7iqtgzITXZL!* zzgi$vW92wE^mPA`b49jM1R7LndB3gIT>Tp2i8?P?nq<sh_iqh+Q4xzK>M)A(oY!i+ zu0A~~>+FuQM?@Un3d`v5B@VyA!DK(@$Aw<y-`LE@kChHqA-t{)MM1ifs}9f;@dlKW zr36^+Wmpe|f|Xn<^;KP*IUx<z*?I^tn`a@rJ^f0ms&FzWW`8jmR_9NyOpda)hs_-$ z&1uQzdj`^BSt$4}Nxsqh)l~iA;}|K^;*kOx)LzmXp1@+ot}Oz4Wg@VB#U^HT+6@N( z5gh-gmsTx{S(FXCqfVs}(pUerb}u0I*Gc=+UuWuI%TtITO)EW>hQ-UmRz?Ail-@O? zG~8_ziMlgt`6k@i4<<Z=X<ul-+DCX&eg6A<EV2VM9B3P(;bnSxqWa*(+DVjdp`Jkc zb)%s>Mfs9&YcxfTPnMjE<&Mn*JZ|r7x-@4Hs<Ry@jdqMzBBXHQ`_y5o10h@)w?v=d z>9ksi&((12%<6+w)p^-nFRXo{n&(N=tpX^&Lv*_ZBX*2jSKm<))ZAPYK{-|K6f>=3 zFPsH&YPr5zl|WGa90T{wKLrCC9?6U28soU_AK=y;v$Sr1VIC=h872~hhu^QEeX1aC z6SEi=wnL>T3R^Ofe+{J;CmNHo-Yh)~=2Gsj9^KdRPrm*0($)f6s7fG78Ht~~&)r#` z*=rph)X1Fq#Lj~t2GvsE4F0ye!)iAY6_gnads^eDA<2v67wf{@xj?iqm{Cw5bceTE zH{C$;#hvDZsmGhanREIGw*p?(Ar48IFuCcLH-3h#Px^DNrobQ**}7-N*4Zxgke6;b z&NOz~IcO?k3A{u>hkWbl1nUD6S1}8+xhQu<JCeoWpvnf?k>fqy$+pAp#Nl11GOg1# zjY(zpZR=~xHT<oje05dZyB^cLXcghr?!!2%M%@7#Nu!hNpe6gc!rvXQR;3~DoGg1y z_(I;4m2o!_rvxJg9v;Mok}6_UiL=ty?#w3S&;*XB%AT4Faga$feL>#E!D(Mr?#ap+ zqfolS98NvAq!6ima2dsg&yi{&pzW|WJDz@H*pB_3qu`)5TIJ01u37QCvuCC@q0|%9 zL+>9j^P(Ub%s2JG@#V)`5?($N)V(dzYXkPLN4CGDQ&(Hcvea0G<H_O~CspkTX^-t< z*Kx}`w}+0sy_MXoAB958sFryI8+AoTq1<$<TX-jpFpDPdMXJVsTI)+l01HSd=a~^L zb9>Rl^*O-Xr>K0v=jYCT7ryq{yr^@{hA;bFo_w<7zv$s>zyHhs$>gY7*P|Q13jd#y z^&c-kg7C2tKGI>!pFZ{k=l<X$2p>WCI3oW)>4a7PlB7E)nYUffIv0;FC%-v;jD0la GAO8Wz@A~2Z literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/id-selector.ts.0c9eb2421.png b/integration_tests/snapshots/css/css-selectors/id-selector.ts.0c9eb2421.png new file mode 100644 index 0000000000000000000000000000000000000000..58535e68c5dde16d9b747c2f4de7008dd4a5cb1d GIT binary patch literal 5542 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/id-selector.ts.53d002db1.png b/integration_tests/snapshots/css/css-selectors/id-selector.ts.53d002db1.png new file mode 100644 index 0000000000000000000000000000000000000000..5a001633405e54a3da5518658692fa5c055169cf GIT binary patch literal 5339 zcmeI0`Bzity2qhurPhV$@%CClpsiI-Z>7|wks3_WI-JxZpyDQ6LP%@GC^1AD0)#16 zj>R4a^k7B95YP%FfrN+&Lx3O@QpPZ3074RJkYR@q0)#*U$xYT>_kXxQxYyciPw!sq z-QVZ)e4pptzn%$4d4Ayift#C~=c(^boO5$?zwGAr&h_^<Iq&ej|Eh2v+$rZ!$K1I6 zyJwvj?^2GPI{&_N%Y6UhEjPFSxOwWtx98J}4C;*gN^_RcWNG{0_Wl<W!dvG*{`A$S z7cQL6fyNz^MPGe*uxQ(_<`NnH%lyHY&xU`=zic=Hum)$oV?+l${%G&_4=?0z3pqUY zE9n5f>E!9p_B||h-@fgBd~NF*K9@NP@%!>=%hYOG?^o>>>)QGw_4InS)<~uwBB_KV zjQh3<B?j*9mJ(j-er)rhZ~p#scK&*z-k82$N>9&*uj)N~dVI0IPz%4o+zS#kb1e5V zhEV(2+zeRhZ(qzc-<FFjA9&oU`rKG_F30AnnYf<&2!0-!HBliSs}LZrEqEl%mGVR2 zrXgskein#OZa1K0uOMF8c>fN})wP};eSp+)G1r`xZec}AN@DV=(tf{Z9~(5^)U}*J z7p0v90MAt%1l-u8#Ua;g{i6VRZ4exjoBd{>ab=RIAL=TYA6~1D7C-$@Ui3Ocm238o z%14E3D)8^{a1_AewJ?d<r+h)}@%D*fU_jKgy;74pU}Lir^ulVY)we>~#tfQEx^bkU zkA*~nQlB1)Iz2Wpj$aRJgt6J+FOlR*X_N6Fc@;wYBFiyqR0h~*cZ8^g26`fAqPksQ z3CUx(|Khi@Pg<pdh|^2^8FU3Pk@;2fF*wbX&&wz>7wd_=1rk}jBrb}HoLb`9G8a;m zv4FXQzHuQCQ&-ipQi0>t1{7nxg6Q>#Lzd&zi5pw=CWk2vpo%}ktuEc9_Ww=+JCP0i zC~^$eHNPw?6Nx>zueVb8NXTZxN{#M!3D?o^WI1NJM&lhE(cExvEeD?xja<C`!2D-` zKW)n;83`<l)bnrqaoFsgI2ymaxA@$!<63&wM19bCKz_XiveNZ+F$P97m&?sV<pnEU zYp)GlrEn~&&c3YhMqbRsu9j@!L>Lj&^-7(-X^)f>5QP}5-<RG_A3p;C!S=4Zu8{x3 zb2m^MQG&%{L||bay*8=s=E0o`ZRP?QY{w&;RQV$V4P^*~LkFD%Kanp#FZVu>aYAkD zZH8W0xy3NIR&{1G*E9sMrf@5V3^!GK|75=JZGLkW00iauFl_znwocOq7eo{KketKz zM7(Y!P$iVj<r0*)M)$C1L&F5x-)!;&b-N=&mHSC^6iI&Sd7HpM-Jr+fp0ylh^R=IA zk3NrTevwd+hj*e$Jz?7px1R@Gkr?BVco=%RK9L@_lH8wql!j{7ccucv1&o9+3Bzyt z3h@fDm(W(T833S7)HJ;MvG9OsxBe9l8EsGsmA`q#jq1jS=Zm6@P4;55AwY~ZTDwI~ ziWvU5NY{wxw^t0$z0*Xq0_$&bxj?`vzw=}W-~KQ)B`7G1W%F2W5J=@C-{Luf2qK=j zLsAqC0OGXSojl^-0<<YVsqKFlsBJSlKt?{773l>RluHZaq~x@gs4Cpx2zj(IUNG6Q zm#SE=1tHH6XKL>m?^z`C)@b$OOdymxE^DNLeN?+h%5w?JK#<dQBYMY%)w=#vs+<uH zQrlU<vML=x{w(Nd466UgizJBgd03FV`|cNdiq;Vu_7SX%d(_ko`*C@*;PUL##$}HT z|HA&jV!=<kZg}>kh5!m;Y5pL`k-b4wm}=A4eGDA_Js)ap*zUvpOYpBa*+P&2094zK zIwd@Gp0!0o`8F(pd7cn{w3f}@x3w^O`ZTP<K2wSu)6v{`2^zoFS2j!2C-obfCddj$ zIh!3fvT)3o`uZ$<g@)=0X|&MRf-~Sh6L9H5zc~A~Fdt;~ymbb9*NobVe1WhvOXrs# z7glIucQ#v|vcZ;J4bDkh|Hwd`bu3gk21W?@=}v1WUR&u|?89~{jx{~|0(||mhcYi{ zH3`q;=X5H1MFhpu=eHA_x#OcpTI^CewIUV`0CX%JOLB;+?W9q$VUeF{>mn<4;;bhA z(Zq#%7-{^9XzjO=!IIv!J0^lGm5^!i^wkR;DHz{O^(*tBG4rJ#%u&|(!>jk8&hd@w zBRw2+B+vTSW?YqzW2w`+Fyf`uhOeobOSTMD)6`Q`C;g6Wn`LBxEY5bGbDw2oC>31a zm07uL<I84&FVT|zjw&a`x;mUug(M6e3Gqy(@TRPjXPH;`2hoyF0YI2Nr%RCBHx#qc z4aS(e*<&&Ri_Oj!nd;=@oCXCc4qcIlY<^UYiQ)uTa{(W;oTsp}*=##eO*eeCV)RiT zZj4HdZRn9K=H=P(d2jN)j?j1z^`M-OqO>@_z>-h|-=%L=K#2WUC}7)rI~A)w5;O{p z>SG=do3h;IFnxv%6$g}WBU=`y3OH<AOgsP(nF`-y;|<SiC5Q~Xt;cQ%-c=Xk^fMl< z*XZ%1t=6(+go`DJz`j6ez4kSz9Osp27cv2$FY9WeYsWLE;`*R9##V0yeS}y-C-m^% zl;o+0b?44H1yI8h3C>J*zD@3q-q~ck;Y7AT;o^DncsO_S@qjsVeX9erbjy7Y8<dCc zQjxs0D~971|CpewX=M_GieBRddtKUG(ps9G!%mP8CiEdnqr#-pC8<AG%9oKvail;} zNNkSUX=-eC`CVodo!+fQV?r^WS9e8+$zLj$G*9d{vzDV5ha60HlmX^WDJ?=`1tVHw z^oMzzA2)L4PQbtiALhw2P)(^!>E=q%<)fP{KCzaK^_H<HUUrRoy<!>-$S2yj^4RQW zUa&@&2zi$8gVHX2YY}co8O`r@dipuJw0zl6C$XxS^;vcIkQWC&0RVFkR9xk((BoG= z#@Jp)g;GM{0+8kHjOJL(nCAX~f#r0*xf(8!hz6P)C>k~2#Q@Vj9icqy;Ri&}!P$f> ziL5KB;l%NpxnWTR0FX2<);ymCG^r1vfsGD-$4P98Bc9snOOVk}C1|g}jLH_voCS2E zf2gD^E@dtrYGl!%@$(NG!5p-ljgFG<Mb`hmX~j2scx$0hx#R21kqPLJGyk;C|J$gz zBf2D;e2`D<x*ZDo<YnRO?lHf<dcox&EsIPXt6C*g@ICx7&5U%pE8$>u$qIP@77GAO zm^Bh=n;4}Ikpkoms1i(8hNT+Evr*v-;}F5MsW$AL7e-IAZ`^J4el9JdkHgZHA5I+7 zY}AVMw^sImGJZu1i=rv!yG+$pz4{&51Wz!yGI8zCK(lGhpC`5!h}oQo#>P#{yUz4A zGl-hVB`^n%h<0)f7yY6vYhZ)_?kbQ`h$S4JeUfvR&=GJC!EdoY;032-S{723O5~!s z@$$q;sE)e}kZ)}H$kNxuOAj_{CjJNuQ%hS@rmcpYX?XPXI3+yL8QZ>$ggBQ&Qo;)( zr!|yB-KBVZTSx;}^t$&w=+gWX>hNEMV0lL{RDsJ}?zk()CH?iGK!_)~44>)<GLozO zkr&suPTR!OO&nsEDE&@|EoS|Vzat)-sqJvh!aPS{Rv*_SeOC;honzQW#$ij`gG56- z5vMG&By&aH#Qr^$1k=Ad%&GoX5p2>|&5OAdUi9A-zbBE(^;HHeV`!ARTGOuY-odNt zalBcC8CHf9x*vZ|b&Oh!$wz-sGO`^In@E@<|CjlTgg@=aQ_>`zDXIiabIrs}P$eSk ztR|m{lD!t$%a96lTBw?PRr8|Zk+`YJsS9^I<R6FPesZ+9!oFBgfS!KT@2!DlGO-6W zKF(%E|E4cUd4D7Kkb)#$L6k~4VzuQ`KMi+BK<k-`QSpV&NG&k5s8%lY`Fh@eY1c+m z6RwG030TC)K4*Wc^Il#IePmodUg&10@7yu7!5!x%<y5h4Qxl}&L}F(2j#gFJ=~88K z73)JiTBaajh^JML>z0Qg-`sfo4P+o+JNVxFdk5Ug-u3?Y;Wv<f{_XPhGw*nA`~LrW z=}m7R{{82-A~;u7Z$)@3!vC(X-;(f_gtsKTEs_5}`UJa|=F`)H84*S9{6Xn<>SXu{ J?y(<k{15H07U%!~ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/id-selector.ts.555cba3f1.png b/integration_tests/snapshots/css/css-selectors/id-selector.ts.555cba3f1.png new file mode 100644 index 0000000000000000000000000000000000000000..b6ffee012257d236bb5fee590acbeff7a324fd00 GIT binary patch literal 5575 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/id-selector.ts.f3a7cf311.png b/integration_tests/snapshots/css/css-selectors/id-selector.ts.f3a7cf311.png new file mode 100644 index 0000000000000000000000000000000000000000..79841e517084778eb325f36ceeba848a35214d5d GIT binary patch literal 5572 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/id-selector.ts b/integration_tests/specs/css/css-selectors/id-selector.ts new file mode 100644 index 0000000000..dfdd677988 --- /dev/null +++ b/integration_tests/specs/css/css-selectors/id-selector.ts @@ -0,0 +1,49 @@ +fdescribe("css id selector", () => { + it("001", async () => { + const style = <style>{`#div1 { color: green; }`}</style>; + const div = <div id="div1"> 001 green Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + xit("002", async () => { + const style = <style>{`# div1 { color: green; }`}</style>; + const div = <div id="div1"> 002 black Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it("003", async () => { + const style = <style>{`div { color: red; } #-div1 { color: green; }`}</style>; + const div = <div id="-div1"> 003 green Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + xit("004", async () => { + const style = <style>{`#1digit { color: red; }`}</style>; + const div = <div id="1digit"> 004 black Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it("005", async () => { + const style = <style>{`div[id=div1] { color: red; } div#div1 { color: green; }`}</style>; + const div = <div id="div1"> 005 green Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it("006", async () => { + const style = <style>{`div[id=div1] { color: red; } div#div1 { color: green; }`}</style>; + const div = <div id="div1"> 006 green Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); +}); From b9061e7fd2f943552f210c8fb394cff798e9d316 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Tue, 9 Aug 2022 22:43:04 +0800 Subject: [PATCH 180/498] test: tag selector --- .../tag-selector.ts.0c6a7df41.png | Bin 0 -> 10563 bytes .../tag-selector.ts.40c7c0241.png | Bin 0 -> 3841 bytes .../specs/css/css-selectors/tag-selector.ts | 20 ++++++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png new file mode 100644 index 0000000000000000000000000000000000000000..362ea65742a95071af2fc17805374e8163d79bdc GIT binary patch literal 10563 zcmeHtS6Gv2*KQQS85Eci(Gdhbm5zXjNC!bd0|L^OI*N#Z5FtPmNKg?`sss?F_fDd8 zLUW`^3pIq6&<PL-H9%<jpX~qo_t_l$-?tC;!Eh4t=6#>C*1higUQhDjp3!YCPJT`Z z1i}TqbITM0Ir0<&Ir{1s4shig|5Pq`IRZDmtpmZ}&o6*C$KX0p^IyPUz%P&ALm>Zx zKyPWAzerjp`3D#x>X~Z@^8xo(8Q<7@ToINz=R>s(OkD3Nl*#tvQiKM~yZ&&`o$bY^ zBmY<&$+yYP!-a2Y_nsnT7Fd}@Mw(sIH3;oHB_eV$`c8d=*7~U1Gw6)ImMWGq9j&!B z;)bP@+(fZUbz4erl_^OO$XqNV|2PElQ{XiRB=eVRkUxAH-~P%gwm%TGgYa6L(v6ek z-S=M``sTPh5yZ3E9e8L)rxJ+~O62<!zOuGeX+uNzNiU`&3M`AQ^P@ZucIo<1XzvGJ z@iOF|udc4{!aD&EiGk_Tk`fW<g$ozZS)>|-8wH<?qYv6(cZUL9g^`=lTQpLk0Ib$T z7`2oB<HwJg4Te{-)ky2ax?@1G9SU!vs-wx=hsP%*B&=}CSeM<4ld`}}eni!uk+Uzl zE%fr1n(xxRuro3uP&EW?AQ?fHgsw9JMk?GB`IS6uCn~swHH&uXRJX|=!Mj-IF4k1g zSzkXCOte`ueqeO&+&SyAr_uOSEi5OW)cnrcbZA&u2mNzT&%JWkxIJFQBHvV3P}RE! z=R*~NUeXKiNrcts&&<peYHq!Kdu(~QRD67|&<cxr(v=Xms3}J%u#isHL|XHT8x5ps z2bI0tctY}DsjD88UUq!Wkgxx=u1b**bOQS}Sti%1jq_<TW_Ziph?rYyP(wqDjkzxR zfSIw&(AS5eBZKs%xkQ*{r3cBp+|>d$+#%O+8LH|z^>!Z>m=d+ORN8&*@z39iAC&4D zCSI>woIY7QRrsJZ=jzp~B2Ydl^N%9P-G$smBGw@#3XYc)5xG_8NAHtEt%Vh8;%lFc zRv0!gx?t;d4Q9DUtn}(TIe-M3-M|0&jI8av%*Nh<lock)bs+D8B<1XnZjvz)ABb<S zKiC>2^GwdP#gsp8JGDGg-r=8!+~`t@e<A+m%a>iSVDGukFQ;HszrlPnai7Hj$*04I z2YZBUgZO~y%TVX;6pMg>+P?mN?AC7@s0N40$;tMgKkq$${1~%mRdhdnt}Btk7y@_O zFHg=EI<`iRRKik4Bqb%G?jvQrj!_yp<IKZtbe4XcRNP2Cw~%_?`}gnDoD|&#&4){z z>TWQ{WG81Rx#|=xXD5&9*)xi+19u|$71!(YGcq&loMt=Xt-$;oC;v!$|NfWYw{IoH z#Pl{O-8>^bTg#(#013QJu67W|F0E#TmAr%X_=Z>!4d(tv=!Xxz4u|XU>c<s4r*8Qy z4VfffcQ@Upk@!xX$^htCj0)U$#3&0YKfm$t;X}?}e>J&t=e>2gYxdC4kWPf;p8y%; z!nBVce<LBcN!n4&o#`mTH^<hrJuSveOqvhpNK5whXG3Y9KmP$fN9?w09j1f--9egG z8kqHu9zD9|w>fVc_2sHls9xlGN$6sKZWOp$=F%^=a(FX@d*StQq1DKnt&xIoyEhV@ zj2hd=kFyE@GWiu<XBZvRATwL_w~sm;0X0Ze=vId}FH^G9rVko<v{*3!71THJR}gD6 z5duma?v0d<m(#!6M$z{U$R*$95~Y`=Ra71z#*vjAJVNKomUY9<NP@=_m0*^4?}q!* z#`3{qQs7F%Ast(XWE`MY;^5k^jSLL>g%4Mcv(f+<z;(PHiCOtJGxHeSjkgiMcu@zW z*uvU+gs)OiSQrZ;pYK7!k++$7CYj#rGf=3?xSG7Yymg&l`Su@_Bvs#~JP;K@HQ!Df z)Y6cAb5qk(fWenE>N-3nIy!x!KbK1OU$Fq82NizJW2{O#5<wfQb|n<VZNr(whC_=e zjjhih{S(E!Pn$s62J%cUm4lCaS|y~V4L}r3`*RHI<TX~mv+W-4t<?D()yl*+9O9Hd zef@fcOGy1b6biLE&$O77mG@n`FW#1&o&B<-udk04?F~kN$4Hrr2V;HKqbE(<2yIjQ z;u{=a4X5B$8pc<wE1oR?(pmy!IV)zAG;frmz5vR}M(N>?W8!T-)WI&pFISuE_GmS- za*ZK82z1i;3Zh$MJBc-nz}3g^V`ezG_)nL4=;*vjRB(>8DR+%a)C#N*i;VO|k134@ zm`!|6f_ngdHA4IEOoosERV{pybD)VCCd{}qsn`Zzv~7%-;n?`$mQv-5wMRspkDGYg zGO4EW-e}d>`fNwCNF{7s^Q^G&%9GB|lL4Bktf3X2bUvpJ76Ve0v8i~`I`OSl2yT{h z*VBJ%$uopo&<mX;LQKDTe0#k^u4Zkz)!$LJozc-zh^=3onwlD;k!nb9*iY2XN?T#P z(PM+fHXF!iAXWm(&(je8JFAm;0JKUrM~ABC52GWuv%9N3r&~PMXWD2#qc1Tcrw8)Q zjL?Z!9_sY0|5&0DtQ>sMV?Zq?li;+yl?DXb79h&b+B(61t@&&+h?j|piAQ^^q{rbt z1Fu544kIr;a&)u<jPF19#o5-^*EhxSg!<XkZi=e+{Dl3>Sw1EB+}*S1&Mk;kS5+y) zNZ%QLQy;{~Liwb}QknbC;%!{#u8NF1CZ(j%GBPrfx+!oFL9dQ@ImC2J1ROnPhak<C zl$P3AT7CkI=`r7vPM-E%AukI~8+3Prg+{yC#^d&ry1yv2s%-(G@I7%>)*C$Dy=~i- zqA?2fva9j+0R)5qEbN177>9a+D4+C8dA%0#hy0IX+v<{@)~CH0x<+U!$de7uJD-($ zFqQ1pt9?#So~Tj|=?YS9T!N|tU0@Eb^U6I?FHlomh<z)A*RQ#gKC{YM<0~{q3<IVD zF{yx!(?qHPE2$&Uv9U8E)h_)xcAi&W2QjxsVaH`wz%(&4r+9fckV_z2L8vrN{{GE{ zer#1&^rhRXP#VYxxg<v1SQY9q)$}2Fq-9o?%ln46cNulGa)nR$tbhPjq#E$n%Z{U3 z6LvUn&j$}4kk@-NZ>vFN6%`38R#h8Tr(fUDI@s*@02?C{KgrnC`h@b%|2V($eLCTq z%alfgbemjH@URsKDs><~2GkV|2o46ZLI!B5o^Fluk8&Z`Rs&Spyq^APFj0LCMji_b zf-`rswLHdvvw?^32y^2ESAmwYCsR72JSS?8A3bvg$WfZZS3s7mNY?~{T4jyryF5~z zqT$aWSZYA58Jnfl-i{K&yxf=z))b14j+Rwb9dUa6_(cax`2yJL9yN2q9z2XPF*i5w zsQ~2$G}8#Zx7x^)45mMaoD4quW-u%?)T`L0Qg(AgQpV|oU+_Vf()b-TP+kWKO-*~i zAo}|HrohM4cCZ6K6(0-dQd3Yc+a7}(f56m{0+|QemHwOa>A)8(f+`^U8Yd~I19Xat zingib0&8&xjgZVrlV>NRqXg9~Z@xRp(xNvxmCen~`C#Ggc>EyH+&aqg2BWqT#qK_B zJwI9r>nW@Iz5xi?tT~kLgwJ8<bn{t<lQ@m1J^un~_HAfXx^s>D5BW!Lr7VkHSZrJ5 z+&w3A|LYAd{%buN+hZ&ew1(BL>0#XPqKbchhs4S3VTBuk?P&uAOzfSWo)(27$S-s2 znTP&hp_20Qz5Y}b(0@Xql^oHRjdGCUPy5xHTn%@Ist~(JE8Gk5$!b(O(Si7y?fAgg zhd1j$fENjc@hC=z!{S=2FcA=^d9{}l5Pm3+`s$7JR4Y((<qsP<3=^(FSuz&5-x@fh z#xAF*XaRCY_$=>unxB7B1~&9H9gyu8H)LveE(nBw<q3HB#fukD1BD^i`BOuLQ9WN2 zoMU5Svodc->7!BmYprQW^___z?Mhf3ebAO_dxD)q^-3Pl2M(ESpn!jYf(wm^AOL?z zs6oJ>eV3xtGeD&IRPF<aRL_la{NS(RxQ3wFY8>uNqhl%O`+tPz`)|_{uDd7X7$)Wr z9Rlspn|%gy3p{~4L0L&|9&2lB+C&GWW@>?X{*++Yn+i`ETg}|v_0eFJz~*njdX&5L zx764Tsq`@<L1H8v>h~3gt<241f%@lL*}v39HE4|wON&Mm=?94(IB2d>GFn7X3uSAH zr|`sU0obaJJN~Y`0?deZ2b<Xww(aAneAL#kGcfB36&|DldB>I)!VuVSiEdYdq8Ttn zIPV41d5R)x|3!9I*8H$@DxtJnv+(E7J2DSSM2I0XrudMFml2Aq>b$2;dC4?v?$ust zm3U4<wuGNckav7n8QD!S1MAyn*w~MDQ@6@ZC+yt@izJ~VrOr`6>+%8fy};g=JGh4d z$r>qli;omk%V~=dr<N1#`+@p|si%F1l^L+lxr77<FYGfm{6Jm^nYVesawLHHXlrZR z1tJ62py@kGndOA11gt|@8+Jg_<AhK<4<pVg=<RK<fO7ImyylVt${c{u<gA>%xtCW7 z3zizdGY5~nV-MB{)U`<0VYeu{-@7NZpcVp{r_Xc<$?|U4+VxP-I1~YMUk4hdY*p?B z2T$ApfJ-gUxyHsu(b>*)CIDw#Lc(Hl!@=G*MQu;~Ik`Jo?QEI)YU$}A58y`#Ri2K> z&EDI7%!6&w+1mEV&`?tVT|NPULO=`pXrMj!<|!7y)pw<8Ie;EycP7Tzyw1;uzDSVn z_F;R7cjMj$UgpG@)x$`Wi5HRQRZKz5EWkd1*2E<x6$KsVS*+iqNpHeyK5?G(o{-td zH_JUOB!uyrZMXA00MT^=HI4Qn>p|Da6~}Tm_-6WzwYYExy|;y-?O#-WdvRg}h_(gB zcG!kOLX6AweLi}$l>exL1n8G8OHyRIfYr+dmdB#dayb<H+s3G#%dh7fIy*ZrYE|S> zR;)Sq>0ivtrtM7so2K7!3raPNW4kr??y|ayYXLjY1y#J7Y;tX@y>QPbbw0F|0IEnE z1*q4*b7xgHmEFEqSjFoWuwGJ|*~#Buj(>B!-LoEexRHu}liR&D-+NO)$z$Lhm%u=| zn-whOFo;Jpk7aRKh7<69m}0KlwQJ@e*8Q)Ju*IU$8K9bNspc5l2cW4~so%rM+Sks@ zXnD_ntZ*N-oY3TihXY=d0BZz9O2{|6t(l5#3Z%c-S{hDUv*%*BpPC242cVn@2*V8s zTWDx#A7g`3?%2v*?m@EF@ZTC>9*i?3Wo3<k$Iv%6CaSH}UV@Ts=0Ph0IM4;;&ULh+ z_(x+SRyn`UI91aRSP~1Yy@oppXMwHTO4!|6E(B&g&yT(s8W}kVToH@)g5SM!?f4{< zpaOr=bDfjl?Afy-u=G5TL2A<n-UKxSCM!wu9eQ&<Gs+coIEy3Yg)NbSg@7of0lIA} zp7jD!vS>gZ9IZ65_P{NOK~=E7CBeb+r8XT;zz;DMU?^!(QC(m@EP+ha8d!e&_6@gO zF*;C>+|v;R$p4Hypko6GKuy6;RYArL;d7n6V0)RrU($ODfJ-+23a+{UsVgwad<S}K zo0)IV0C6xGsmy%T!LH<`OE<f<n6)A=%{ssivt;dX^9{_shPdS9VxaB?cfVX6sX<_` zDk#u5`;86Sk>P9<WYhcK!KQN-2a5~Yj&sj1<fd8z69L-2m56j;hFHwWGOtJ;1C{ty z`xle!p!vTeSG%ecok9e$Gq3cp1JShuh)U4#uK+cBelugyw1IiBLI<t!BJld<e%rL- z*x1v6-wN;TNiE6qD?ABriTdw012$gI>Tg*b6u3Vv3{C+|qEp5}SP8kt!oc)3)zrSU zu-*Oe(7sd|n9YrDEd-FAwVc#>rZJCo1F8-fdmzvpB$kWbj&Il(eJa2r4+Il*R7n)^ zPp^$L*vveRI@I}Z`#R&3)&~74$p~9OkcdFAPo@3~SA2?qhHFl81+ct-9b+z0d-?&e z`bq@xRX*TIxdm0t=iP0kR@?mkluCoElq!RBjQADDqoCLpmnpyt)1Mrv1tg_S1v``` zcB0?l9ZZU>%L~4{_yUY-_g|cznyOuJeEs0MvHIHEm>J+;Yw4V-WQGQ1Uqno7P9-ce z4axq5$Mz7Z3L~qFw*l|jsS*#jWgD&X=Fok3ETv<rIm`~6N(8lh1+Y!?i4xLS?0Ssb zq=K`c(n=%yR%Pr4ifD(Z26BAdT}xE7xkgJ<lWc<mhewnRMv}-jgOcj7#;`@<<&Z|e zb!qIcHhK^-b_`8TY3UBb6!qam+0haw10`qtc_Mg4fWcl;xr8;<yN5-BRrFk3TxegM zQ`*XnK(A5jbF@@h_TZck{{I*@LkF%+v4&4^pMIloGI_Rgb8@6ET+rr|wloLv2$D^5 zc6Md~KRrF2QP0%@P8mQOAZ1YyO9>H(KlDEehGw0nEDq%JZnk;6f*S$TXc^4Ow|?-r z4kQMn@h&)+9f?L&H#OZ#*6_CkCD5g)mi={9f{W6fY6Y4OAl;@#NV|o7J9oT_sF+v* zXe7y^!QbgU9ZcMcerruxoVUa9@USX;E?np4&5eWaxGTVH+$KG5<g(fi>kAJ*2?T7+ zW@SA!tTysnEIf&qmzPgSh<4C+SpG331p^v`LU4xAj7xgHyXg`f65<A`W_NV_&@!*} zw0C$`65Mxx#}l*x1%O?fQ+{Wiy`%+JOu9;#reP~m*M$R)toD!8BWqZPbwFaN(@|Q3 z;I-U;+XGniu06MzKqaefQDMmGzRoWS<zBP;E54vR8^qY{!33P!hNS1eZ^r-FhbKZ% z_N+0$A)Pje8*U*d@zDJ+JzrX$?<wSa!>WV*kyj?i_#xpJdmw+F{-4uC-Lxa$C601I z^nQN|xe|JV?SFp@>iTPZC;lX^?#T*2(rbrzWh=V$c9}QPG#RP|FAm{=G@EYa3f*`Z z@N(ZkP}5(TxUO2S>S|IGT)#7UqTIH}w7Wdes2dSrkG?jue8E+4*m{M>&jEdU&#*|Z zPEEf?k7i0(w=nPt@ISRBp(&|3z6axJu<KRKFz`u*pbjx%H;i|^(d6CTV%v^Hf@@B4 z6wSQ2z1*8(h*;c8sAL|?jLC6N&T*=VpY>-7+b~TOYVeoWP}c>=vQUM)3w5to@_imt zPr-Ozxf6BM*y_(SvUN^Rkuc^Mw20)@%cc(DJ5#y*pTrO;MdG8Hl2Gf)z~q#@Rh6zq zRfA-Y1rcb0MYU!AlDMP>)LAacVz+tIbXW*7iYavKF}#>?45^qmc*7J=pMAgZyY>F; z)J1nWaA)Hnm)Xw1^WeKDO@g6(*PeIX$bORJM-3+{TUU11*#(YoAcBp#h+T4|BsVpm z0H0cUlIijpsA<q5SFO{b{^8MW#o1EH(9c5YGUM)R6^Q{C9#rD%Ew-^$4#suN*muPO zV$el~p0V!TsGfv6w@_cm5nC%vp;Zr~?PvQv9oILf`cH*zR*l!wZQNC>ph1b;mWe;F zkHmbvz2V#0zfjgcqWouOR?^OjTDdVB@uWF+)NkQ%elJY1<;|920YSo$@hnux`rspX zluFUMQ-|ATORwt37dH(2WK14eXVmNO=&uOAFm7<&ktUha&kzbtvG*O1JV#bxj%sXd ze9?|qrbXba3@;^|akDud!|mGtmmxnnpvr!kQBbL_c*(-RSEjc*9A03HDX9!VMKSn? zAI(^d#$%I=rG;$Mg{1wSjvb#K$n_MKA(fkFT1T3W;@shb-FFH8$I1P9pE3v1Q6Y~a z*uT@Kah#hmi7B9mnyx?B%)MD}wkHC0U##f;D=SD9d01wYqLp73=7P@R%@?n|5ietA zd5L!3WXbcauLB9Tj6umCZ1z>empvn1Wk@p=59$}1$#So#qdt7|GoEeQ*fiK>f5&f? zK9?DvMHZqFLcdzu*FSqn$h&uwuif$&YkRS*op@?k305*W_o>sEezK8T@fSPif`H-m z|2t0mtkiLKoXCFMK051xopZkNv}S>Iy=#wXM4x#qQe#ca_zv3DJTLu&u;IY!TqAa* z3Nqwt*EJ<kP_$Gu)?te;;UK`DylN6}mcDv<y+H5l*FEb}M8qJ?rf_Y!#cdfzTwDq+ z^?TOnNIpn+(h;2vkl>u4tl<3E?M|0V5&I&AeOXJRzo(_EKJ*cRo<PVBe4T3?D6&)_ zO*Y&tWg;p{>?&v&L}mNi*j^fMO<Z(bdcIxGr;+=#)p3)FdZT2m>?3X*dG>LB2|W+r z=ybewecu0<HO?)cLrJg*Z<1X1KtK)__+VJprZRXuXV-!nKcakO$jh$wjqLm`?9QD# z82X_%F*Oz?Z+9@hh@GZH7TPp=xZbZzpIiQ%dsY*rjV`05`?&HB>%kG7gqwxJrp(G+ zx4zt%H*?wsD#E8enQQOy*Q2WX<Z|468VF?R*<sR?La*xf#nO_ft0h+lGJH??xD6Q! z)RWRY0#|b4Jf#iPWg))7%9Jr<jrWD3m!xmDokjE6%a7UksxFsF1zU3Nt}g2gvI`n& z76|Bg+;ewukE=-^vk)+IS)$susDNTE&<p0zUhiOgWvE5H!S<cLgsW;g0%1n7zM}ch zN%ikXk@8McC{+mZzz)~wrJg@*szJ^y{mv=_=f3V(@;qc>{Qd*z?)px=YQWZWj2d-k zF;92-%<fc(+<=$1fd=tVMPq#JCA9BjWk{aSW4Ai3c_h?yN$DFuohGr&Uu|H~VI@P* zra^}?cd~W@RvI++eRYZNWeLcm-;IllL=69jYZ2EA33t_mN%_`sZVUKC+Q70RtYMBV zHp|5(*JYUI#2_<U2LlH>F$xB?sfNdu^2ZBMey|CDK=8PxQMOlT3IBx%x!b7x--2IM zO2-w7KtoT44V=AHUsZ9brfCKrLw`IfE()b?Vf*32nz^jf=$A<rfdb4*#T|%bi!T6J z>o<J1mtqa}3yy;%k^$|p3UcKVnYDF@1kBj%AS>I~64MuR?HTC`U{kA~O}|-uHrHnH znd_5+O8k68rqABkD1BQa&<c<-X}_<ly27m0s#Tz0hTst`qL@eiZZn6Sk$di7rkR)l z2z+FnbOu%fu;N2qvhiVt?$HPKz*j-Ihv=*n*rs{Y<}by=H92>>&qDOghTm941MtOu zOh{`gYr5#ZUkN*~Oyu_Ho5%?wh?PWai7fTj3T}7<z+LAal8_WX+%sD4|E|bJpjO!9 zv~~|Ow8C_j+{_D264r-LYn1I?5EO#npY8ZWWUc#OYp*_ST4yws-9pB##*x)lQ&5Ce zD_r)XOo3Il{kN8*kn9T*JqlW>=e$2mlk@xu*D_qbT@Sdjkj4_e$*vG8AqDH#VWwbO z{+M_wxkEV@5RrceW&1NWT1L;l9LZ>BluiA`xA-k1qy>4!2+6<Q+h=E>STHM5UOal; z`JmIh4l$t-;i{r9xQLJ#ybnn$JI21-W@yYoQu*I-ZPCGLr@!Y~h^-;`hC(R*{3hpa zONr7M|Hf8y6cjD=k`kRZ3u0jv!KrAT{wz3c)=4LnsidGgOpL{wm0k77Hp)#N1mLr% zf5v#Uau$NgNxNYsYo#L5BRYwli5Fq1H`tR2uogkGF0nZoOVkg{mpW+kOdT%6{ihl4 zl!Q@)7IuiJNro03-;Qsu>zuvpc2C2Z<4%BS?Zw86Sl44GYOsp_#HY*k<^1CwwS$Mg z#dv$=!q)5Rb0lGFF>`PjlL1*ZlODJnSvyc*YoLZ2N(a<KxP%K?d`pNHkr}JL*O<U# zR%+el9m3@9IvgKv_Z`O7kV#U!rg}BTOO|?;?{a*<Si2feaWe(kY19FR8UNQOuNE!x zbAJ^SlPJ5J$D-KmnolgoT|7@1PAp$r--?f0F7dJ1aW8NvN99NGLP9gb%~$E;A3r8_ z2*qQzb88N>Hp3PcnU;ZqS_oSbzpq*2B9|hpRlRO??F2f@ho{&Kc!7i(M+{5hcN+)} zF`CJzukl#7Gd_OJ;v6Ba(i`IdSr_gVZ`n(B)2`dZby!%Y#vx;YGMp*5$ph>?vbxOS zv*)i=9B%Vq%Jaw#Pr$Xdp2A7sj+ql}oY+5TGHS`b)^R%0?8qJ83)DA}V!{Nj3rcwv z*ot=2J<Q|G`Zs+;MBoARyY5<mB<s({ABUX2{Q+3jD!-lW|Jb$uJ;DCHJvM*z^bG{^ z%OvZ!0RNU%{&yz*XQKayy~;lc__y1<e?s?9=>DD>|6!?rSn5AJM*4?e{y*WDJm&EW ZSyE#ae--`;eh3MH>Kfg`==}NSzW~A!KWzX2 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png new file mode 100644 index 0000000000000000000000000000000000000000..67c90840eb2d31f9f844a68fda6398104903956d GIT binary patch literal 3841 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 5a2d8c9d57..d5f3836f89 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -14,4 +14,24 @@ describe("css tag selector", () => { document.body.appendChild(p5); await snapshot(); }); + + fit("002", async () => { + const style = <style>{`div, blockquote { color: green; }`}</style>; + const p = <p>Test passes if the "Filler Text" below is green.</p>; + const blockquote = <blockquote>Filler Text</blockquote>; + const div = <div>Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(blockquote); + document.body.appendChild(div); + await snapshot(); + }); + + fit("003", async () => { + const style = <style>{`DIV { color: green; }`}</style>; + const div = <div>Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); }); From 2bef2a3a0f313463c180efb6febbe2ce60128086 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Tue, 9 Aug 2022 23:45:55 +0800 Subject: [PATCH 181/498] test: fix wildcard selector --- .../descendent-selector.ts.1b50a0d81.png | Bin 4813 -> 4278 bytes .../id-selector.ts.a4da180e1.png | Bin 0 -> 2356 bytes .../tag-selector.ts.5fddfcf51.png | Bin 0 -> 34430 bytes .../css/css-selectors/attribute-selector.ts | 2 +- .../css/css-selectors/child-selectors.ts | 10 +++++- .../css/css-selectors/descendent-selector.ts | 1 - .../specs/css/css-selectors/id-selector.ts | 4 +-- .../specs/css/css-selectors/tag-selector.ts | 30 ++++++++++++++++-- webf/lib/src/css/element_rule_collector.dart | 8 +++-- webf/lib/src/css/parser/selector.dart | 2 ++ webf/lib/src/css/selector_evaluator.dart | 2 +- 11 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/id-selector.ts.a4da180e1.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1b50a0d81.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1b50a0d81.png index 92433d5e3faea413800dbfba226e82643c377a58..d7310c9e1d114d69b0f7fb378aba3b54aed28956 100644 GIT binary patch delta 1999 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 X&)=N?_w$E-ONl;kc)xUS(zX8qPy)I7 delta 2538 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 H&bjt~+l|iV diff --git a/integration_tests/snapshots/css/css-selectors/id-selector.ts.a4da180e1.png b/integration_tests/snapshots/css/css-selectors/id-selector.ts.a4da180e1.png new file mode 100644 index 0000000000000000000000000000000000000000..deaa789156ee8d399f5684d7a693b36cdca89ff2 GIT binary patch literal 2356 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_q9Mo-U3d6?5KRF=S*=;5lG$VE^;PhWT=xO>uXPjkz1<e70t2FmGn- zFv@7q7^OyoU^EqsW`xnQV6-?Ktr13R!$DsguHcSH=lByG#C!?Z@?r3F^>bP0l+XkK DSbYf` literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png new file mode 100644 index 0000000000000000000000000000000000000000..3e942d5feac1306706d98f1874b82dd5231f1fbf GIT binary patch literal 34430 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 F{}-xN?EnA( literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/attribute-selector.ts b/integration_tests/specs/css/css-selectors/attribute-selector.ts index 91ce4c7642..61a1503b96 100644 --- a/integration_tests/specs/css/css-selectors/attribute-selector.ts +++ b/integration_tests/specs/css/css-selectors/attribute-selector.ts @@ -39,7 +39,7 @@ describe('css attribute selector', () => { await snapshot(); }); // error - it('006', async () => { + xit('006', async () => { const style = <style>{`[1digit], div { color: red; }`}</style>; const div = <div> 006 Filler Text</div> document.head.appendChild(style); diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index 8ef9e6c9ce..0deb2442b8 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -41,7 +41,15 @@ describe("css child selector", () => { await snapshot(); }); - fit("006", async () => { + it("006", async () => { + const style = <style>{`div:fiRsT-cHiLd { color: green; }`}</style>; + const div = <div>Filler Text</div>; + document.body.appendChild(div); + document.head.appendChild(style); + await snapshot(); + }); + + it("007", async () => { const style = <style>{`html { color: red; } :root:first-child { color: green; }`}</style>; document.body = <body><p>This text should be green.</p></body>; document.head.appendChild(style); diff --git a/integration_tests/specs/css/css-selectors/descendent-selector.ts b/integration_tests/specs/css/css-selectors/descendent-selector.ts index f4a6b91b6c..55087156df 100644 --- a/integration_tests/specs/css/css-selectors/descendent-selector.ts +++ b/integration_tests/specs/css/css-selectors/descendent-selector.ts @@ -47,7 +47,6 @@ describe('css descendent selector', () => { await snapshot(); }); - // error it('007', async () => { const style = <style>{`div * em { color: red; }`}</style>; const div = <div><span><em>007 Filler Text</em></span></div >; diff --git a/integration_tests/specs/css/css-selectors/id-selector.ts b/integration_tests/specs/css/css-selectors/id-selector.ts index dfdd677988..13c7d853a8 100644 --- a/integration_tests/specs/css/css-selectors/id-selector.ts +++ b/integration_tests/specs/css/css-selectors/id-selector.ts @@ -1,4 +1,4 @@ -fdescribe("css id selector", () => { +describe("css id selector", () => { it("001", async () => { const style = <style>{`#div1 { color: green; }`}</style>; const div = <div id="div1"> 001 green Filler Text</div>; @@ -7,7 +7,7 @@ fdescribe("css id selector", () => { await snapshot(); }); - xit("002", async () => { + it("002", async () => { const style = <style>{`# div1 { color: green; }`}</style>; const div = <div id="div1"> 002 black Filler Text</div>; document.head.appendChild(style); diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index d5f3836f89..7f79a10760 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -15,7 +15,7 @@ describe("css tag selector", () => { await snapshot(); }); - fit("002", async () => { + it("002", async () => { const style = <style>{`div, blockquote { color: green; }`}</style>; const p = <p>Test passes if the "Filler Text" below is green.</p>; const blockquote = <blockquote>Filler Text</blockquote>; @@ -27,11 +27,37 @@ describe("css tag selector", () => { await snapshot(); }); - fit("003", async () => { + it("003", async () => { const style = <style>{`DIV { color: green; }`}</style>; const div = <div>Filler Text</div>; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); + + fit("004", async () => { + const style = <style>{`body * { + margin: 1em 0; + font: inherit; + display: block; + color: white; + background: green; + }`}</style>; + const e1 = <p>This text should be green. (element)</p>; + const e2 =<div>This text should be green. (class)</div> + const e3 =<div>This text should be green. (id)</div> + const e4 =<div>This text should be green. (child)</div> + const e5 =<div>This text should be green. (descendant)</div> + const e6 =<blockquote>This text should be green. (sibling)</blockquote> + const e7 =<div>This text should be green. (attribute)</div> + document.head.appendChild(style); + document.body.appendChild(e1); + document.body.appendChild(e2); + document.body.appendChild(e3); + document.body.appendChild(e4); + document.body.appendChild(e5); + document.body.appendChild(e6); + document.body.appendChild(e7); + await snapshot(); + }); }); diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index 80f765b9d5..91791d1a14 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -60,8 +60,12 @@ class ElementRuleCollector { if (rule is! CSSStyleRule) { continue; } - if (evaluator.matchSelector(rule.selectorGroup, element)) { - matchedRules.add(rule); + try { + if (evaluator.matchSelector(rule.selectorGroup, element)) { + matchedRules.add(rule); + } + } catch(error) { + print('selector evaluator error: $error'); } } return matchedRules; diff --git a/webf/lib/src/css/parser/selector.dart b/webf/lib/src/css/parser/selector.dart index a67bd73527..3bdf96d129 100644 --- a/webf/lib/src/css/parser/selector.dart +++ b/webf/lib/src/css/parser/selector.dart @@ -148,6 +148,8 @@ abstract class SimpleSelector extends TreeNode { // ignore: avoid_dynamic_calls String get name => _name.name as String; + bool get isWildcard => _name is Wildcard; + bool get isThis => _name is ThisOperator; @override diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/selector_evaluator.dart index 7c8ae50427..37455d0e3e 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/selector_evaluator.dart @@ -203,7 +203,7 @@ class SelectorEvaluator extends SelectorVisitor { } @override - bool visitElementSelector(ElementSelector node) => _element!.tagName == node.name.toUpperCase(); + bool visitElementSelector(ElementSelector node) => node.isWildcard || _element!.tagName == node.name.toUpperCase(); @override bool visitIdSelector(IdSelector node) => _element!.id == node.name; From 2dafb8a12a3e9783d42dd53d1135ca7fd1236cd1 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Wed, 10 Aug 2022 10:55:40 +0800 Subject: [PATCH 182/498] test: sibling selector --- .../sibling-selector.ts.0c64aab11.png | Bin 0 -> 15700 bytes .../sibling-selector.ts.0df520ad1.png | Bin 0 -> 14512 bytes .../sibling-selector.ts.bc6540e01.png | Bin 0 -> 7048 bytes .../tag-selector.ts.0c6a7df41.png | Bin 10563 -> 10727 bytes .../css/css-selectors/sibling-selector.ts | 36 +++++++++++++++++- .../specs/css/css-selectors/tag-selector.ts | 6 +-- 6 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0c64aab11.png create mode 100644 integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0df520ad1.png create mode 100644 integration_tests/snapshots/css/css-selectors/sibling-selector.ts.bc6540e01.png diff --git a/integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0c64aab11.png b/integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0c64aab11.png new file mode 100644 index 0000000000000000000000000000000000000000..9580ae71ca28494fbd26d8d189a7f82ecbdc2240 GIT binary patch literal 15700 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0df520ad1.png b/integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0df520ad1.png new file mode 100644 index 0000000000000000000000000000000000000000..f8db56d76d433a680cc0460244e6dbabbe8aba0b GIT binary patch literal 14512 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/sibling-selector.ts.bc6540e01.png b/integration_tests/snapshots/css/css-selectors/sibling-selector.ts.bc6540e01.png new file mode 100644 index 0000000000000000000000000000000000000000..fba54f159709c6dcfa9362aaa422e5d9bd76a02b GIT binary patch literal 7048 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png index 362ea65742a95071af2fc17805374e8163d79bdc..b3849a302e280880002ec08d8b17fa692af205e9 100644 GIT binary patch literal 10727 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 literal 10563 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 ZSyE#ae--`;eh3MH>Kfg`==}NSzW~A!KWzX2 diff --git a/integration_tests/specs/css/css-selectors/sibling-selector.ts b/integration_tests/specs/css/css-selectors/sibling-selector.ts index f658abad53..daacccc170 100644 --- a/integration_tests/specs/css/css-selectors/sibling-selector.ts +++ b/integration_tests/specs/css/css-selectors/sibling-selector.ts @@ -1,4 +1,4 @@ -describe("css sibling selector", () => { +fdescribe("css sibling selector", () => { it("001", async () => { const style = <style>{`div { color: red; } [class=foo] + div { color: green; } @@ -36,4 +36,38 @@ describe("css sibling selector", () => { document.body.appendChild(p10); await snapshot(); }); + + it("002", async () => { + const style = <style>{`p + div { color: green; }`}</style>; + const p = <p>Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.</p>; + const div1 = <div> 002 Filler Text</div>; + const div2 = <div>Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(div1); + document.body.appendChild(div2); + await snapshot(); + }); + + it("003", async () => { + const style = <style>{`p + div { color: green; }`}</style>; + document.head.appendChild(style); + document.body.innerHTML = `<body> + <p>Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.</p> + <!-- This is a comment --> + <div> 003 Filler Text</div> + </body>`; + await snapshot(); + }); + + xit("004", async () => { + const style = <style>{`p + div { color: green; }`}</style>; + document.head.appendChild(style); + document.body.innerHTML = `<body> + <p>Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.</p> + Filler Text + <div> 004 Filler Text</div> + </body>`; + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 7f79a10760..12bc15c401 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -15,8 +15,8 @@ describe("css tag selector", () => { await snapshot(); }); - it("002", async () => { - const style = <style>{`div, blockquote { color: green; }`}</style>; + fit("002", async () => { + const style = <style>{`div, blockquote, p { color: green; }`}</style>; const p = <p>Test passes if the "Filler Text" below is green.</p>; const blockquote = <blockquote>Filler Text</blockquote>; const div = <div>Filler Text</div>; @@ -35,7 +35,7 @@ describe("css tag selector", () => { await snapshot(); }); - fit("004", async () => { + it("004", async () => { const style = <style>{`body * { margin: 1em 0; font: inherit; From ef379d97c8ecacea13b0dd7366bbb10dfa4b671e Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Wed, 10 Aug 2022 23:11:32 +0800 Subject: [PATCH 183/498] test: child selector --- .../child-selectors.ts.6a3b3d991.png | Bin 0 -> 5760 bytes .../child-selectors.ts.9db7989f1.png | Bin 0 -> 5582 bytes .../child-selectors.ts.d1ba354e1.png | Bin 0 -> 5760 bytes .../tag-selector.ts.38aaea691.png | Bin 0 -> 8670 bytes .../specs/css/css-selectors/child-indexed.ts | 188 ++++++++++++++++++ .../css/css-selectors/child-selectors.ts | 146 ++++++++++++++ .../specs/css/css-selectors/tag-selector.ts | 31 +++ webf/lib/src/css/selector_evaluator.dart | 175 ++++++++++++++-- webf/lib/src/dom/element.dart | 10 +- 9 files changed, 534 insertions(+), 16 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.6a3b3d991.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.9db7989f1.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.d1ba354e1.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png create mode 100644 integration_tests/specs/css/css-selectors/child-indexed.ts diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.6a3b3d991.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.6a3b3d991.png new file mode 100644 index 0000000000000000000000000000000000000000..fa9a9fa3187c013578369b790720fdcf7996f7de GIT binary patch literal 5760 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.9db7989f1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.9db7989f1.png new file mode 100644 index 0000000000000000000000000000000000000000..30a63b00f76161115e86c0423136278da5d5e778 GIT binary patch literal 5582 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 Vf0qLJPu6YkUk~gO?D*=^zX4(Q>%;&6 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.d1ba354e1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.d1ba354e1.png new file mode 100644 index 0000000000000000000000000000000000000000..fa9a9fa3187c013578369b790720fdcf7996f7de GIT binary patch literal 5760 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png new file mode 100644 index 0000000000000000000000000000000000000000..336044bbc03e647127b9e19a1e6eb0970b82dd92 GIT binary patch literal 8670 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/child-indexed.ts b/integration_tests/specs/css/css-selectors/child-indexed.ts new file mode 100644 index 0000000000..b2396dbae2 --- /dev/null +++ b/integration_tests/specs/css/css-selectors/child-indexed.ts @@ -0,0 +1,188 @@ +/*auto generated*/ +describe('child-indexed', () => { + it('pseudo-class', async () => { + var check = function (element, selectors, qsRoot) { + for (var i = 0; i < selectors.length; ++i) { + var selector = selectors[i][0]; + var expected = selectors[i][1]; + test(function () { + assert_equals(expected, element.matches(selector)); + + if (qsRoot) { + assert_equals(expected, element === qsRoot.querySelector(selector)); + var qsa = qsRoot.querySelectorAll(selector); + assert_equals(expected, !!qsa.length && element === qsa[0]); + } + }, 'Expected ' + + element.tagName + + ' element to ' + + (expected ? 'match ' : 'not match ') + + selector + + ' with matches' + + (qsRoot ? ', querySelector(), and querySelectorAll()' : '')); + } + }; + + var rootOfSubtreeSelectors = [ + [':first-child', true], + [':last-child', true], + [':only-child', true], + [':first-of-type', true], + [':last-of-type', true], + [':only-of-type', true], + [':nth-child(1)', true], + [':nth-child(n)', true], + [':nth-last-child(1)', true], + [':nth-last-child(n)', true], + [':nth-of-type(1)', true], + [':nth-of-type(n)', true], + [':nth-last-of-type(1)', true], + [':nth-last-of-type(n)', true], + [':nth-child(2)', false], + [':nth-last-child(2)', false], + [':nth-of-type(2)', false], + [':nth-last-of-type(2)', false], + ]; + + check(document.documentElement, rootOfSubtreeSelectors, document); + check(document.createElement('div'), rootOfSubtreeSelectors); + + var fragment = document.createDocumentFragment(); + var div = document.createElement('div'); + fragment.appendChild(div); + check(div, rootOfSubtreeSelectors, fragment); + + await matchViewportSnapshot(); + }); + + it('no-parent-ref', async () => { + let p; + let p_1; + let p_2; + let p_3; + let p_4; + let p_5; + let p_6; + let p_7; + let p_8; + p = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`Should be green +`), + ] + ); + p_1 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`Should be green +`), + ] + ); + p_2 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`Should be green +`), + ] + ); + p_3 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`Should be green +`), + ] + ); + p_4 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`Should be green +`), + ] + ); + p_5 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`Should be green +`), + ] + ); + p_6 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`Should be green +`), + ] + ); + p_7 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`Should be green +`), + ] + ); + p_8 = createElement( + 'p', + { + style: { + 'box-sizing': 'border-box', + }, + }, + [ + createText(`Should be green +`), + ] + ); + BODY.appendChild(p); + BODY.appendChild(p_1); + BODY.appendChild(p_2); + BODY.appendChild(p_3); + BODY.appendChild(p_4); + BODY.appendChild(p_5); + BODY.appendChild(p_6); + BODY.appendChild(p_7); + BODY.appendChild(p_8); + + await matchViewportSnapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index 0deb2442b8..3717224ff9 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -55,4 +55,150 @@ describe("css child selector", () => { document.head.appendChild(style); await snapshot(); }); + + it("008", async () => { + const style = <style>{` :first-child { border: 10px solid blue; }`}</style>; + const div = <div>Filler Text</div>; + const p = <p>Test passes if there is a blue border around the viewport and around "Filler Text" above.</p> + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p); + await snapshot(); + }); + + fit("009", async () => { + const style = <style>{` :root { color: green; }`}</style>; + const p1 = <p>Should be green </p>; + const p2 = <p>Should be green </p>; + const p3 = <p>Should be green </p>; + const p4 = <p>Should be green </p>; + const p5 = <p>Should be green </p>; + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + await snapshot(); + }); + + fit("0010", async () => { + const style = <style>{` + :first-child #a { + color: green; + } + :nth-child(n) #b { + color: green; + } + :first-of-type #c { + color: green; + } + :nth-of-type(1) #d { + color: green; + } + :last-of-type #e { + color: green; + } + :last-child #f { + color: green; + } + :nth-last-child(1) #g { + color: green; + } + :nth-last-of-type(n) #h { + color: green; + } + + #i { + color: green; + } + + /* NB: not matching intentionally */ + :nth-last-child(2) #i { + color: red; + } + `}</style>; + document.head.appendChild(style); + + const p1 = <p id="a">Should be green</p>; + const p2 = <p id="b">Should be green</p>; + const p3 = <p id="c">Should be green</p>; + const p4 = <p id="d">Should be green</p>; + const p5 = <p id="e">Should be green</p>; + const p6 = <p id="f">Should be green</p>; + const p7 = <p id="g">Should be green</p>; + const p8 = <p id="h">Should be green</p>; + const p9 = <p id="i">Should be green</p>; + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + await snapshot(); + }); + + fit("0011", async () => { + const style = <style>{` + :root:first-child #a { + color: green; + } + :root:nth-child(n) #b { + color: green; + } + :root:first-of-type #c { + color: green; + } + :root:nth-of-type(1) #d { + color: green; + } + :root:last-of-type #e { + color: green; + } + :root:last-child #f { + color: green; + } + :root:nth-last-child(1) #g { + color: green; + } + :root:nth-last-of-type(n) #h { + color: green; + } + + #i { + color: green; + } + + /* NB: not matching intentionally */ + :root:nth-last-child(2) #i { + color: red; + } + `}</style>; + + const p1 = <p id="a">Should be green</p>; + const p2 = <p id="b">Should be green</p>; + const p3 = <p id="c">Should be green</p>; + const p4 = <p id="d">Should be green</p>; + const p5 = <p id="e">Should be green</p>; + const p6 = <p id="f">Should be green</p>; + const p7 = <p id="g">Should be green</p>; + const p8 = <p id="h">Should be green</p>; + const p9 = <p id="i">Should be green</p>; + + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + await snapshot(); + }); + }) diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 12bc15c401..64aae20dbb 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -60,4 +60,35 @@ describe("css tag selector", () => { document.body.appendChild(e7); await snapshot(); }); + + it("005", async () => { + const style = <style>{` body { color: green; }`}</style>; + const p = <p>Test passes if all text on this page is green.</p>; + const div = <div>Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(div); + await snapshot(); + }); + + it("006", async () => { + const style = <style>{` * { color: green; }`}</style>; + const p = <p>Test passes if all text on this page is green.</p>; + const div = <div>Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(div); + await snapshot(); + }); + + fit("007", async () => { + const style = <style>{` html, div { border: 10px solid blue; }`}</style>; + const p = <p>Test passes if all text on this page is green.</p>; + const div = <div>Filler Text</div>; + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(div); + await snapshot(); + }); + }); diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/selector_evaluator.dart index 37455d0e3e..20f0d2da6b 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/selector_evaluator.dart @@ -115,13 +115,11 @@ class SelectorEvaluator extends SelectorVisitor { // http://dev.w3.org/csswg/selectors-4/#the-root-pseudo case 'root': - // TODO(jmesserly): fix when we have a .ownerDocument pointer - // return _element == _element.ownerDocument.rootElement; - return _element!.nodeName == 'html' && _element!.parentNode == null; + return _element!.nodeName == HTML && (_element!.parentElement == null || _element!.parentElement is Document); // http://dev.w3.org/csswg/selectors-4/#the-empty-pseudo case 'empty': - return _element!.childNodes.any((n) => !(n is Element || n is TextNode && n.data.isNotEmpty)); + return _element!.childNodes.every((n) => !(n is Element || n is TextNode && n.data.isNotEmpty)); // http://dev.w3.org/csswg/selectors-4/#the-blank-pseudo case 'blank': @@ -130,12 +128,47 @@ class SelectorEvaluator extends SelectorVisitor { // http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo case 'first-child': - return _element!.previousElementSibling == null; + if (_element!.previousElementSibling != null) { + return _element!.previousElementSibling is HeadElement; + } + return true; // http://dev.w3.org/csswg/selectors-4/#the-last-child-pseudo case 'last-child': return _element!.nextElementSibling == null; + //http://drafts.csswg.org/selectors-4/#first-of-type-pseudo + //http://drafts.csswg.org/selectors-4/#last-of-type-pseudo + //http://drafts.csswg.org/selectors-4/#only-of-type-pseudo + case 'first-of-type': + case 'last-of-type': + case 'only-of-type': + var parent = _element!.parentElement; + if (parent != null) { + var children = parent.children.where((Element el) { + return el.nodeName == _element!.nodeName; + }).toList(); + + var index = children.indexOf(_element!); + var isFirst = index == 0; + var isLast = index == children.length - 1; + + if (isFirst && node.name == 'first-of-type') { + return true; + } + + if (isLast && node.name == 'last-of-type') { + return true; + } + + if (isFirst && isLast && node.name == 'only-of-type') { + return true; + } + + return false; + } + + break; // http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo case 'only-child': return _element!.previousElementSibling == null && _element!.nextElementSibling == null; @@ -180,26 +213,138 @@ class SelectorEvaluator extends SelectorVisitor { bool visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) => throw _unimplemented(node); @override - bool visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) { - switch (node.name) { + bool visitPseudoClassFunctionSelector(PseudoClassFunctionSelector selector) { + switch (selector.name) { // http://dev.w3.org/csswg/selectors-4/#child-index // http://dev.w3.org/csswg/selectors-4/#the-nth-child-pseudo case 'nth-child': - // TODO(jmesserly): support An+B syntax too. - final exprs = node.expression; - if (exprs.length == 1 && _element != null) { - final literal = int.parse(exprs[0]); - final parent = _element!.parentElement; - return parent != null && literal > 0 && parent.children.indexOf(_element!) == literal; + case 'nth-last-child': + case 'nth-of-type': + case 'nth-last-of-type': + // i = An + B + var nthData = _parseNthExpressions(selector.expression); + if (nthData == null) { + break; + } + + var A = nthData['A']; + var B = nthData['B'] ?? 0; + + var parent = _element?.parentElement; + + if (parent != null) { + var elIndex; + var children = parent.children; + + if (selector.name == 'nth-of-type' || selector.name == 'nth-last-of-type') { + children = children.where((Element el) { + return el.tagName == _element!.tagName; + }).toList(); + } + + if (selector.name == 'nth-last-child' || selector.name == 'nth-last-of-type') { + elIndex = children.length - children.indexOf(_element!); + } else { + elIndex = children.indexOf(_element!) + 1; + } + + if (A == null) { + return B > 0 && elIndex == B; + } else { + var divideResult = (elIndex - B) / A; + + if (divideResult >= 1) { + return divideResult % divideResult.ceil() == 0; + } else { + return divideResult == 0; + } + } + } else { + return false; } break; } - throw _unimplemented(node); + throw _unimplemented(selector); + } + + num _countExpressionList(List<String> list) { + String first = list[0]; + num sum = 0; + num modulus = 1; + if (first == '-') { + modulus = -1; + list = list.sublist(1); + } + list.forEach((String item) { + num? value = num.tryParse(item); + if (value != null) { + sum += value; + } + }); + return sum * modulus; + } + + Map<String, num?>? _parseNthExpressions(List<String> exprs) { + num? A; + num B = 0; + + if (exprs.isNotEmpty) { + if (exprs.length == 1) { + String value = exprs[0]; + if (isNumeric(value)) { + B = num.parse(value); + } else { + if (value == 'even') { + A = 2; + B = 1; + } else if (value == 'odd') { + A = 2; + B = 0; + } else if (value == 'n') { + A = 1; + B = 0; + } else { + return null; + } + } + } + + List<String> bTerms = []; + List<String> aTerms = []; + var nIndex = exprs.indexWhere((expr) { + return expr.toString() == 'n'; + }); + + if (nIndex > -1) { + bTerms.addAll(exprs.sublist(nIndex + 1)); + aTerms.addAll(exprs.sublist(0, nIndex)); + } else { + bTerms.addAll(exprs); + } + + if (bTerms.isNotEmpty) { + B = _countExpressionList(bTerms); + } + + if (aTerms.isNotEmpty) { + if (aTerms.length == 1 && aTerms[0] == '-') { + A = -1; + } else { + A = _countExpressionList(aTerms); + } + } else { + if (nIndex == 0) { + A = 1; + } + } + } + + return {'A': A, 'B': B}; } bool isNumeric(String s) { - return double.tryParse(s) != null; + return num.tryParse(s) != null; } @override diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 4398fcc71e..43cafef101 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -18,6 +18,7 @@ import 'package:webf/rendering.dart'; final RegExp _splitRegExp = RegExp(r'\s+'); const String _ONE_SPACE = ' '; const String _STYLE_PROPERTY = 'style'; +const String _ID = 'id'; const String _CLASS_NAME = 'class'; /// Defined by W3C Standard, @@ -80,7 +81,12 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // Default to unknown, assign by [createElement], used by inspector. String tagName = UNKNOWN; - String? get id => getAttribute('id'); + String? _id; + String? get id => _id; + set id(String? id) { + _id = id; + recalculateStyle(); + } // Is element an replaced element. // https://drafts.csswg.org/css-display/#replaced-element @@ -976,6 +982,8 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // @TODO: Parse inline style css text. } else if (_CLASS_NAME == qualifiedName) { className = value; + } else if (_ID == qualifiedName) { + id = value; } } From 808d8ee6cb68cc4c4f2bd81c100757f9ea353e21 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Wed, 10 Aug 2022 23:40:02 +0800 Subject: [PATCH 184/498] test: only child selector --- .../child-selectors.ts.a98cfcea1.png | Bin 0 -> 6227 bytes .../css/css-selectors/child-selectors.ts | 381 +++++++++++------- .../css/css-selectors/sibling-selector.ts | 2 +- .../specs/css/css-selectors/tag-selector.ts | 5 +- 4 files changed, 247 insertions(+), 141 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.a98cfcea1.png diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a98cfcea1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a98cfcea1.png new file mode 100644 index 0000000000000000000000000000000000000000..622e896b11dcc60051718f6a325f73c630dc6afe GIT binary patch literal 6227 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 AH~;_u literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index 3717224ff9..4855b241c4 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -1,89 +1,123 @@ describe("css child selector", () => { - it("001", async () => { - const style = <style>{`div > h1 { color: green; }`}</style>; - const h1 = <h1>Filler Text</h1 >; - document.head.appendChild(style); - document.body.appendChild(h1); - await snapshot(); - }); - it("002", async () => { - const style = <style>{`div > h1 { color: green; }`}</style>; - const div = <div><h1>Filler Text</h1 ></div>; - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it("003", async () => { - const style = <style>{`div > h1 { color: green; }`}</style>; - const div = <div><blockquote><h1>Filler Text</h1 > </blockquote > </div>; - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it("004", async () => { - const style = <style>{`div:first-child { color: green; }`}</style>; - const div1 = <div>Filler Text</div>; - const div2 = <div>Filler Text</div>; - const p = <p>Test passes if the first "Filler Text" above is green and the second one is black.</p>; - document.head.appendChild(style); - document.body.appendChild(div1); - document.body.appendChild(div2); - document.body.appendChild(p); - await snapshot(); - }); - - it("005", async () => { - const style = <style>{`div:first-child { color: green; }`}</style>; - document.body = <body>Filler Text<div>Filler Text</div><div>Filler Text</div></body>; - document.head.appendChild(style); - await snapshot(); - }); - - it("006", async () => { - const style = <style>{`div:fiRsT-cHiLd { color: green; }`}</style>; - const div = <div>Filler Text</div>; - document.body.appendChild(div); - document.head.appendChild(style); - await snapshot(); - }); - - it("007", async () => { - const style = <style>{`html { color: red; } :root:first-child { color: green; }`}</style>; - document.body = <body><p>This text should be green.</p></body>; - document.head.appendChild(style); - await snapshot(); - }); - - it("008", async () => { - const style = <style>{` :first-child { border: 10px solid blue; }`}</style>; - const div = <div>Filler Text</div>; - const p = <p>Test passes if there is a blue border around the viewport and around "Filler Text" above.</p> - document.head.appendChild(style); - document.body.appendChild(div); - document.body.appendChild(p); - await snapshot(); - }); - - fit("009", async () => { - const style = <style>{` :root { color: green; }`}</style>; - const p1 = <p>Should be green </p>; - const p2 = <p>Should be green </p>; - const p3 = <p>Should be green </p>; - const p4 = <p>Should be green </p>; - const p5 = <p>Should be green </p>; - document.head.appendChild(style); - document.body.appendChild(p1); - document.body.appendChild(p2); - document.body.appendChild(p3); - document.body.appendChild(p4); - document.body.appendChild(p5); - await snapshot(); - }); - - fit("0010", async () => { - const style = <style>{` + it("001", async () => { + const style = <style>{`div > h1 { color: green; }`}</style>; + const h1 = <h1>Filler Text</h1>; + document.head.appendChild(style); + document.body.appendChild(h1); + await snapshot(); + }); + it("002", async () => { + const style = <style>{`div > h1 { color: green; }`}</style>; + const div = ( + <div> + <h1>Filler Text</h1> + </div> + ); + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it("003", async () => { + const style = <style>{`div > h1 { color: green; }`}</style>; + const div = ( + <div> + <blockquote> + <h1>Filler Text</h1>{" "} + </blockquote>{" "} + </div> + ); + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it("004", async () => { + const style = <style>{`div:first-child { color: green; }`}</style>; + const div1 = <div>Filler Text</div>; + const div2 = <div>Filler Text</div>; + const p = ( + <p> + Test passes if the first "Filler Text" above is green and the second one + is black. + </p> + ); + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(p); + await snapshot(); + }); + + it("005", async () => { + const style = <style>{`div:first-child { color: green; }`}</style>; + document.body = ( + <body> + Filler Text<div>Filler Text</div> + <div>Filler Text</div> + </body> + ); + document.head.appendChild(style); + await snapshot(); + }); + + it("006", async () => { + const style = <style>{`div:fiRsT-cHiLd { color: green; }`}</style>; + const div = <div>Filler Text</div>; + document.body.appendChild(div); + document.head.appendChild(style); + await snapshot(); + }); + + it("007", async () => { + const style = ( + <style>{`html { color: red; } :root:first-child { color: green; }`}</style> + ); + document.body = ( + <body> + <p>This text should be green.</p> + </body> + ); + document.head.appendChild(style); + await snapshot(); + }); + + it("008", async () => { + const style = ( + <style>{` :first-child { border: 10px solid blue; }`}</style> + ); + const div = <div>Filler Text</div>; + const p = ( + <p> + Test passes if there is a blue border around the viewport and around + "Filler Text" above. + </p> + ); + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p); + await snapshot(); + }); + + it("009", async () => { + const style = <style>{` :root { color: green; }`}</style>; + const p1 = <p>Should be green </p>; + const p2 = <p>Should be green </p>; + const p3 = <p>Should be green </p>; + const p4 = <p>Should be green </p>; + const p5 = <p>Should be green </p>; + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + await snapshot(); + }); + + it("010", async () => { + const style = ( + <style>{` :first-child #a { color: green; } @@ -117,32 +151,34 @@ describe("css child selector", () => { :nth-last-child(2) #i { color: red; } - `}</style>; - document.head.appendChild(style); - - const p1 = <p id="a">Should be green</p>; - const p2 = <p id="b">Should be green</p>; - const p3 = <p id="c">Should be green</p>; - const p4 = <p id="d">Should be green</p>; - const p5 = <p id="e">Should be green</p>; - const p6 = <p id="f">Should be green</p>; - const p7 = <p id="g">Should be green</p>; - const p8 = <p id="h">Should be green</p>; - const p9 = <p id="i">Should be green</p>; - document.body.appendChild(p1); - document.body.appendChild(p2); - document.body.appendChild(p3); - document.body.appendChild(p4); - document.body.appendChild(p5); - document.body.appendChild(p6); - document.body.appendChild(p7); - document.body.appendChild(p8); - document.body.appendChild(p9); - await snapshot(); - }); - - fit("0011", async () => { - const style = <style>{` + `}</style> + ); + document.head.appendChild(style); + + const p1 = <p id="a">Should be green</p>; + const p2 = <p id="b">Should be green</p>; + const p3 = <p id="c">Should be green</p>; + const p4 = <p id="d">Should be green</p>; + const p5 = <p id="e">Should be green</p>; + const p6 = <p id="f">Should be green</p>; + const p7 = <p id="g">Should be green</p>; + const p8 = <p id="h">Should be green</p>; + const p9 = <p id="i">Should be green</p>; + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + await snapshot(); + }); + + it("011", async () => { + const style = ( + <style>{` :root:first-child #a { color: green; } @@ -176,29 +212,100 @@ describe("css child selector", () => { :root:nth-last-child(2) #i { color: red; } - `}</style>; - - const p1 = <p id="a">Should be green</p>; - const p2 = <p id="b">Should be green</p>; - const p3 = <p id="c">Should be green</p>; - const p4 = <p id="d">Should be green</p>; - const p5 = <p id="e">Should be green</p>; - const p6 = <p id="f">Should be green</p>; - const p7 = <p id="g">Should be green</p>; - const p8 = <p id="h">Should be green</p>; - const p9 = <p id="i">Should be green</p>; - - document.head.appendChild(style); - document.body.appendChild(p1); - document.body.appendChild(p2); - document.body.appendChild(p3); - document.body.appendChild(p4); - document.body.appendChild(p5); - document.body.appendChild(p6); - document.body.appendChild(p7); - document.body.appendChild(p8); - document.body.appendChild(p9); - await snapshot(); - }); - -}) + `}</style> + ); + + const p1 = <p id="a">Should be green</p>; + const p2 = <p id="b">Should be green</p>; + const p3 = <p id="c">Should be green</p>; + const p4 = <p id="d">Should be green</p>; + const p5 = <p id="e">Should be green</p>; + const p6 = <p id="f">Should be green</p>; + const p7 = <p id="g">Should be green</p>; + const p8 = <p id="h">Should be green</p>; + const p9 = <p id="i">Should be green</p>; + + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + await snapshot(); + }); + + it("012", async () => { + const style = ( + <style>{` + li:only-child { + color: green; + } + `}</style> + ); + const ul = ( + <ul> + <li> 012 Should be green</li> + </ul> + ); + document.head.appendChild(style); + document.body.appendChild(ul); + await snapshot(); + }); + + it("013", async () => { + const style = ( + <style>{` + li:only-child { + color: green; + } + `}</style> + ); + const ul = ( + <ul> + <li> 013 Should not be green</li> + <li> 013 Should not be green</li> + </ul> + ); + document.head.appendChild(style); + document.body.appendChild(ul); + await snapshot(); + }); + + fit("014", async () => { + const style = ( + <style>{` + last-child #f { + color: green; + } + `}</style> + ); + + const p = <p>014</p>; + const p1 = <p id="a">Should be green</p>; + const p2 = <p id="b">Should be green</p>; + const p3 = <p id="c">Should be green</p>; + const p4 = <p id="d">Should be green</p>; + const p5 = <p id="e">Should be green</p>; + const p6 = <p id="f">Should be green</p>; + const p7 = <p id="g">Should be green</p>; + const p8 = <p id="h">Should be green</p>; + const p9 = <p id="i">Should be green</p>; + + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + await snapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-selectors/sibling-selector.ts b/integration_tests/specs/css/css-selectors/sibling-selector.ts index daacccc170..7d9582a2cb 100644 --- a/integration_tests/specs/css/css-selectors/sibling-selector.ts +++ b/integration_tests/specs/css/css-selectors/sibling-selector.ts @@ -1,4 +1,4 @@ -fdescribe("css sibling selector", () => { +describe("css sibling selector", () => { it("001", async () => { const style = <style>{`div { color: red; } [class=foo] + div { color: green; } diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 64aae20dbb..b13a67eae9 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -15,7 +15,7 @@ describe("css tag selector", () => { await snapshot(); }); - fit("002", async () => { + it("002", async () => { const style = <style>{`div, blockquote, p { color: green; }`}</style>; const p = <p>Test passes if the "Filler Text" below is green.</p>; const blockquote = <blockquote>Filler Text</blockquote>; @@ -81,7 +81,7 @@ describe("css tag selector", () => { await snapshot(); }); - fit("007", async () => { + it("007", async () => { const style = <style>{` html, div { border: 10px solid blue; }`}</style>; const p = <p>Test passes if all text on this page is green.</p>; const div = <div>Filler Text</div>; @@ -90,5 +90,4 @@ describe("css tag selector", () => { document.body.appendChild(div); await snapshot(); }); - }); From 878a04c7c7e403170cf1acbddee03d77c703336e Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Wed, 10 Aug 2022 23:58:35 +0800 Subject: [PATCH 185/498] test: supplement attribute the remaining selectors --- .../attribute-selector.ts.ac47caa61.png | Bin 0 -> 8302 bytes .../css/css-selectors/attribute-selector.ts | 62 ++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.ac47caa61.png diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.ac47caa61.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.ac47caa61.png new file mode 100644 index 0000000000000000000000000000000000000000..8d7ac483d67bce11848c6500f134be2d1d61ca01 GIT binary patch literal 8302 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/attribute-selector.ts b/integration_tests/specs/css/css-selectors/attribute-selector.ts index 61a1503b96..5089a15137 100644 --- a/integration_tests/specs/css/css-selectors/attribute-selector.ts +++ b/integration_tests/specs/css/css-selectors/attribute-selector.ts @@ -46,4 +46,66 @@ describe('css attribute selector', () => { document.body.appendChild(div); await snapshot(); }); + + fit('007', async () => { + const style = <style>{`div[class^="a"] { color: green; }`}</style>; + const div1 = <div class="abc">should be green</div> + const div2 = <div class="acb">should be green</div> + const div3 = <div class="bac">should not be green</div> + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + await snapshot(); + }); + + it('007', async () => { + const style = <style>{`div[class^="a"] { color: green; }`}</style>; + const div1 = <div class="abc">should be green</div> + const div2 = <div class="acb">should be green</div> + const div3 = <div class="bac">should not be green</div> + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + await snapshot(); + }); + + it('008', async () => { + const style = <style>{`div[class$="c"] { color: green; }`}</style>; + const div1 = <div class="abc">should be green</div> + const div2 = <div class="acb">should not be green</div> + const div3 = <div class="bac">should be green</div> + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + await snapshot(); + }); + + it('009', async () => { + const style = <style>{`div[class*="c"] { color: green; }`}</style>; + const div1 = <div class="abc">should be green</div> + const div2 = <div class="acb">should be green</div> + const div3 = <div class="bac">should be green</div> + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + await snapshot(); + }); + + it('010', async () => { + const style = <style>{`div[class|="a"] { color: green; }`}</style>; + const div1 = <div class="a">should be green</div> + const div2 = <div class="a-test">should be green</div> + const div3 = <div class="b-test">should not be green</div> + const div4 = <div class="c-test">should not be green</div> + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + document.body.appendChild(div4); + await snapshot(); + }); }); From c27b969b6a9e1aa960753b1518b0bf939e4052ec Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Sat, 13 Aug 2022 20:17:35 +0800 Subject: [PATCH 186/498] feat: inline style --- .../css/css-selectors/attribute-selector.ts | 2 +- .../css/css-selectors/child-selectors.ts | 2 +- webf/lib/src/css/element_rule_collector.dart | 12 ++++- webf/lib/src/css/parser/parser.dart | 50 ++++++++++--------- webf/lib/src/css/rule_set.dart | 14 +++++- webf/lib/src/css/style_property.dart | 3 ++ webf/lib/src/dom/element.dart | 3 +- webf/test/src/css/style_inline_parser.dart | 19 +++++++ webf/test/src/css/style_rule_parser.dart | 29 +++++------ webf/test/webf_test.dart | 2 + 10 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 webf/test/src/css/style_inline_parser.dart diff --git a/integration_tests/specs/css/css-selectors/attribute-selector.ts b/integration_tests/specs/css/css-selectors/attribute-selector.ts index 5089a15137..0ee05e890e 100644 --- a/integration_tests/specs/css/css-selectors/attribute-selector.ts +++ b/integration_tests/specs/css/css-selectors/attribute-selector.ts @@ -47,7 +47,7 @@ describe('css attribute selector', () => { await snapshot(); }); - fit('007', async () => { + it('007', async () => { const style = <style>{`div[class^="a"] { color: green; }`}</style>; const div1 = <div class="abc">should be green</div> const div2 = <div class="acb">should be green</div> diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index 4855b241c4..085f18d3af 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -275,7 +275,7 @@ describe("css child selector", () => { await snapshot(); }); - fit("014", async () => { + it("014", async () => { const style = ( <style>{` last-child #f { diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index 91791d1a14..eae4b1d510 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -32,16 +32,24 @@ class ElementRuleCollector { // universal matchedRules.addAll(_collectMatchingRulesForList(ruleSet.universalRules, element)); + CSSStyleDeclaration declaration = CSSStyleDeclaration(); + if (matchedRules.isEmpty) { + return declaration; + } + // sort selector matchedRules.sort((leftRule, rightRule) { if (leftRule is! CSSStyleRule || rightRule is! CSSStyleRule) { return 0; } - return leftRule.selectorGroup.specificity.compareTo(rightRule.selectorGroup.specificity); + int isCompare = leftRule.selectorGroup.specificity.compareTo(rightRule.selectorGroup.specificity); + if (isCompare == 0) { + return ruleSet.rules.indexOf(leftRule).compareTo(ruleSet.rules.indexOf(rightRule)); + } + return isCompare; }); // Merge all the rules - CSSStyleDeclaration declaration = CSSStyleDeclaration(); for (CSSRule rule in matchedRules) { if (rule is CSSStyleRule) { declaration.merge(rule.declaration); diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index c18c33b9b0..bd7f16b8de 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -28,6 +28,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +import 'dart:io'; import 'dart:math' as math; import 'package:webf/css.dart'; @@ -82,19 +83,16 @@ class CSSParser { /// Main entry point for parsing an entire CSS file. CSSStyleSheet parse() { - var ruleSet = RuleSet(); - while (!_maybeEat(TokenKind.END_OF_FILE)) { - final rule = processRule(); - if (rule != null) { - ruleSet.addRule(rule); - } else { - _next(); - } - } - checkEndOfFile(); + final ruleSet = RuleSet(); + final rules = parseRules(); + ruleSet.addRules(rules); return CSSStyleSheet(ruleSet); } + CSSStyleDeclaration parseInlineStyle() { + return processDeclarations(checkBrace: false, important: true); + } + List<CSSRule> parseRules() { var rules = <CSSRule>[]; while (!_maybeEat(TokenKind.END_OF_FILE)) { @@ -444,25 +442,18 @@ class CSSParser { } } - CSSStyleDeclaration processDeclarations({bool checkBrace = true}) { - var start = _peekToken.span; - + CSSStyleDeclaration processDeclarations({bool checkBrace = true, bool important = false}) { if (checkBrace) _eat(TokenKind.LBRACE); - var decls = <CSSRule>[]; - var declaration = CSSStyleDeclaration(); - do { var selectorGroup = _nestedSelector(); while (selectorGroup != null) { // Nested selector so process as a ruleset. - var ruleset = processRule(selectorGroup)!; - decls.add(ruleset); + processRule(selectorGroup)!; selectorGroup = _nestedSelector(); } - - processDeclaration(declaration); + processDeclaration(declaration, important: important); } while (_maybeEat(TokenKind.SEMICOLON)); if (checkBrace) _eat(TokenKind.RBRACE); @@ -860,7 +851,7 @@ class CSSParser { // property: expr prio? \9; - IE8 and below property, /9 before semi-colon // *IDENT - IE7 or below // _IDENT - IE6 property (automatically a valid ident) - void processDeclaration(CSSStyleDeclaration style) { + void processDeclaration(CSSStyleDeclaration style, {bool important = false}) { // IDENT ':' expr '!important'? if (TokenKind.isIdentifier(_peekToken.kind)) { var propertyIdent = camelize(identifier().name); @@ -891,7 +882,7 @@ class CSSParser { var expr = processExpr(); // Handle !important (prio) - var importantPriority = _maybeEat(TokenKind.IMPORTANT); + var importantPriority = important ? important : _maybeEat(TokenKind.IMPORTANT); style.setProperty(propertyIdent, expr, importantPriority); } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) { _next(); @@ -910,13 +901,24 @@ class CSSParser { String processExpr([bool ieFilter = false]) { var start = _peekToken.span; FileSpan? end; - while (_peek() != TokenKind.SEMICOLON) { + + var parenCount = 0; + while (!_maybeEat(TokenKind.END_OF_FILE)) { + if (_peek() == TokenKind.LPAREN) { + parenCount++; + } + if (_peek() == TokenKind.RPAREN) { + parenCount--; + } + if (parenCount == 0 && (_peek() == TokenKind.SEMICOLON || _peek() == TokenKind.RBRACE)) { + break; + } end = _next().span; } if (end != null) { return start.expand(end).text; } - return ''; + return _peekToken.text; } static const int MAX_UNICODE = 0x10FFFF; diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index a823db14a4..4f5a732c04 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -18,6 +18,12 @@ class RuleSet { final CSSMap tagRules = HashMap(); final List<CSSRule> universalRules = []; + void addRules(List<CSSRule> rules) { + for (CSSRule rule in rules) { + addRule(rule); + } + } + void addRule(CSSRule rule) { rules.add(rule); if (rule is CSSStyleRule) { @@ -66,8 +72,9 @@ class RuleSet { } } } - + bool isInserted = false; void insertRule(String key, CSSRule rule, CSSMap map) { + isInserted = true; List<CSSRule>? rules = map[key] ?? []; rules.add(rule); map[key] = rules; @@ -88,6 +95,9 @@ class RuleSet { if (tagName != null && tagName.isNotEmpty == true) { insertRule(tagName.toUpperCase(), rule, tagRules); } - universalRules.add(rule); + + if (!isInserted) { + universalRules.add(rule); + } } } diff --git a/webf/lib/src/css/style_property.dart b/webf/lib/src/css/style_property.dart index 09d36ce7ce..93cf23d9f1 100644 --- a/webf/lib/src/css/style_property.dart +++ b/webf/lib/src/css/style_property.dart @@ -19,6 +19,9 @@ const String _0Percent = '0%'; // a-b -> aB String camelize(String str) { + if (str.startsWith('--')) { + return str; + } return str.replaceAllMapped(RegExp(r'-(\w)'), (match) { String subStr = match[0]!.substring(1); return subStr.isNotEmpty ? subStr.toUpperCase() : ''; diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 43cafef101..175fba080c 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -979,7 +979,8 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element void setAttribute(String qualifiedName, String value) { internalSetAttribute(qualifiedName, value); if (_STYLE_PROPERTY == qualifiedName) { - // @TODO: Parse inline style css text. + CSSStyleDeclaration declaration = CSSParser(value).parseInlineStyle(); + _applyInlineStyle(declaration); } else if (_CLASS_NAME == qualifiedName) { className = value; } else if (_ID == qualifiedName) { diff --git a/webf/test/src/css/style_inline_parser.dart b/webf/test/src/css/style_inline_parser.dart new file mode 100644 index 0000000000..89792f1310 --- /dev/null +++ b/webf/test/src/css/style_inline_parser.dart @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'package:webf/css.dart'; +import 'package:test/test.dart'; + +CSSStyleDeclaration parseInlineStyle(String style) { + return CSSParser(style).parseInlineStyle(); +} + +void main() { + group('CSSStyleRuleParser', () { + test('0', () { + CSSStyleDeclaration style = parseInlineStyle('color : red; background: red;'); + expect(style.getPropertyValue('color'), 'red'); + }); + }); +} diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index 3c7f62ecce..00d7ee75bf 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -13,7 +13,7 @@ CSSRule? parseSingleRule(String rule) { void main() { group('CSSStyleRuleParser', () { - test('0', () { + test('0', () { CSSRule? rule = parseSingleRule('div p, #id:first-line { color : red; }'); expect(rule is CSSStyleRule, true); CSSStyleRule styleRule = rule as CSSStyleRule; @@ -55,7 +55,7 @@ void main() { CSSRule? rule = parseSingleRule('.foo{--custom:some\n value;}'); CSSStyleRule styleRule = rule as CSSStyleRule; expect(styleRule.lastSimpleSelector?.name, 'foo'); - expect(styleRule.declaration.getPropertyValue('--custom'), 'some value'); + expect(styleRule.declaration.getPropertyValue('--custom'), 'some\n value'); }); test('6', () { @@ -68,23 +68,24 @@ void main() { test('7', () { CSSRule? rule = parseSingleRule('.foo \t {background: url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4) red}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule. lastSimpleSelector?.name, 'foo'); + expect(styleRule.lastSimpleSelector?.name, 'foo'); expect(styleRule.declaration.getPropertyValue('backgroundColor'), 'red'); - expect(styleRule.declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4)'); + expect(styleRule.declaration.getPropertyValue('backgroundImage'), + 'url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4)'); }); test('8', () { CSSRule? rule = parseSingleRule('.foo { color: rgb(255, 255, 0)}'); CSSStyleRule styleRule = rule as CSSStyleRule; expect(styleRule.lastSimpleSelector?.name, 'foo'); - expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255, 255, 0)'); }); test('9', () { CSSRule? rule = parseSingleRule('.foo { background : ; color: rgb(255, 255, 0)}'); CSSStyleRule styleRule = rule as CSSStyleRule; expect(styleRule.lastSimpleSelector?.name, 'foo'); - expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255, 255, 0)'); }); test('10', () { @@ -95,7 +96,7 @@ void main() { expect(simpleSelectors[1].simpleSelector.name, 'nth-child'); expect(simpleSelectors[1].simpleSelector is PseudoClassFunctionSelector, true); expect((simpleSelectors[1].simpleSelector as PseudoClassFunctionSelector).argument, ['4']); - expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255, 255, 0)'); }); test('11', () { @@ -113,14 +114,14 @@ void main() { final simpleSelectors = styleRule.selectorGroup.selectors.first.simpleSelectorSequences; expect(simpleSelectors[0].simpleSelector.name, 'div'); expect(simpleSelectors[1].simpleSelector.name, 'p'); - expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255, 255, 0)'); }); test('13', () { CSSRule? rule = parseSingleRule('.foo { background-image: url( "./image (1).jpg" )}'); CSSStyleRule styleRule = rule as CSSStyleRule; expect(styleRule.lastSimpleSelector?.name, 'foo'); - expect(styleRule.declaration.getPropertyValue('background-image'), 'url(./image (1).jpg)'); + expect(styleRule.declaration.getPropertyValue('backgroundImage'), 'url( "./image (1).jpg" )'); }); test('14', () { @@ -136,10 +137,10 @@ void main() { expect(styleRule.lastSimpleSelector?.name, 'foo'); }); - test('16', () { - CSSRule? rule = parseSingleRule(' .foo { margin: 64px 0 32px; text-align: center;}'); - CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); - }); + test('16', () { + CSSRule? rule = parseSingleRule(' .foo { margin: 64px 0 32px; text-align: center;}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + }); }); } diff --git a/webf/test/webf_test.dart b/webf/test/webf_test.dart index 2a0cf1c037..0b0c936a3a 100644 --- a/webf/test/webf_test.dart +++ b/webf/test/webf_test.dart @@ -13,6 +13,7 @@ import 'package:webf/webf.dart'; import 'local_http_server.dart'; import 'src/css/style_rule_parser.dart' as style_rule_parser; import 'src/css/style_sheet_parser.dart' as style_sheet_parser; +import 'src/css/style_inline_parser.dart' as style_inline_parser; import 'src/css/values.dart' as css_values; import 'src/foundation/bundle.dart' as bundle; import 'src/foundation/convert.dart' as convert; @@ -65,6 +66,7 @@ void main() { group('css', () { style_rule_parser.main(); style_sheet_parser.main(); + style_inline_parser.main(); css_values.main(); }); From 3628d10759c6625e0d5b6d2756b099139efe31af Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Sat, 13 Aug 2022 23:47:11 +0800 Subject: [PATCH 187/498] feat: inline style attribute --- bridge/bindings/qjs/dom/element.cc | 15 +++++++++++-- bridge/bindings/qjs/dom/element.h | 3 ++- bridge/bindings/qjs/html_parser.cc | 2 +- webf/lib/src/css/parser/parser.dart | 10 ++++----- webf/lib/src/css/style_sheet.dart | 35 +++++++++++++++++++++++++++++ webf/lib/src/dom/element.dart | 3 ++- 6 files changed, 58 insertions(+), 10 deletions(-) diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc index cf3533d354..6438d2b533 100644 --- a/bridge/bindings/qjs/dom/element.cc +++ b/bridge/bindings/qjs/dom/element.cc @@ -427,6 +427,17 @@ IMPL_PROPERTY_SETTER(Element, className)(JSContext* ctx, JSValue this_val, int a return JS_DupValue(ctx, value); } +IMPL_PROPERTY_GETTER(Element, style)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + getDartMethod()->flushUICommand(); + return getAttribute(ctx, this_val, argc, argv); +} + +IMPL_PROPERTY_SETTER(Element, style)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { + JSValue value = argv[0]; + JSValue args[] = { JS_NewString(ctx, "style"), value}; + return setAttribute(ctx, this_val, 2, args); +} + IMPL_PROPERTY_GETTER(Element, offsetLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast<ElementInstance*>(JS_GetOpaque(this_val, Element::classId())); return element->getBindingProperty("offsetLeft"); @@ -844,7 +855,7 @@ ElementInstance::ElementInstance(Element* element, std::string tagName, bool sho JSValue style = JS_CallConstructor(m_ctx, CSSStyleDeclaration::instance(m_context)->jsObject, 1, arguments); m_style = static_cast<StyleDeclarationInstance*>(JS_GetOpaque(style, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - JS_DefinePropertyValueStr(m_ctx, jsObject, "style", m_style->jsObject, JS_PROP_C_W_E); + JS_DefinePropertyValueStr(m_ctx, jsObject, "styleDeclaration", m_style->jsObject, JS_PROP_C_W_E); if (shouldAddUICommand) { std::unique_ptr<NativeString> args_01 = stringToNativeString(tagName); @@ -854,7 +865,7 @@ ElementInstance::ElementInstance(Element* element, std::string tagName, bool sho JSClassExoticMethods ElementInstance::exoticMethods{nullptr, nullptr, nullptr, nullptr, hasProperty, getProperty, setProperty}; -StyleDeclarationInstance* ElementInstance::style() { +StyleDeclarationInstance* ElementInstance::styleDeclaration() { return m_style; } diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h index cb7c64a9c7..5128db6f7a 100644 --- a/bridge/bindings/qjs/dom/element.h +++ b/bridge/bindings/qjs/dom/element.h @@ -112,6 +112,7 @@ class Element : public Node { DEFINE_PROTOTYPE_READONLY_PROPERTY(children); DEFINE_PROTOTYPE_READONLY_PROPERTY(attributes); + DEFINE_PROTOTYPE_PROPERTY(style); DEFINE_PROTOTYPE_PROPERTY(className); DEFINE_PROTOTYPE_PROPERTY(innerHTML); DEFINE_PROTOTYPE_PROPERTY(outerHTML); @@ -149,7 +150,7 @@ class ElementInstance : public NodeInstance { std::string getRegisteredTagName(); std::string outerHTML(); std::string innerHTML(); - StyleDeclarationInstance* style(); + StyleDeclarationInstance* styleDeclaration(); static inline JSClassID classID(); diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/bindings/qjs/html_parser.cc index 05dc5e61cd..a734dc98a1 100644 --- a/bridge/bindings/qjs/html_parser.cc +++ b/bridge/bindings/qjs/html_parser.cc @@ -154,7 +154,7 @@ void HTMLParser::parseProperty(ElementInstance* element, GumboElement* gumboElem } arrStyles.push_back(strStyles.substr(prev_pos, pos - prev_pos)); - auto* style = element->style(); + auto* style = element->styleDeclaration(); for (auto& s : arrStyles) { std::string::size_type position = s.find(':'); diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index bd7f16b8de..efe047ec8e 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -90,7 +90,7 @@ class CSSParser { } CSSStyleDeclaration parseInlineStyle() { - return processDeclarations(checkBrace: false, important: true); + return processDeclarations(checkBrace: false); } List<CSSRule> parseRules() { @@ -442,7 +442,7 @@ class CSSParser { } } - CSSStyleDeclaration processDeclarations({bool checkBrace = true, bool important = false}) { + CSSStyleDeclaration processDeclarations({bool checkBrace = true}) { if (checkBrace) _eat(TokenKind.LBRACE); var declaration = CSSStyleDeclaration(); @@ -453,7 +453,7 @@ class CSSParser { processRule(selectorGroup)!; selectorGroup = _nestedSelector(); } - processDeclaration(declaration, important: important); + processDeclaration(declaration); } while (_maybeEat(TokenKind.SEMICOLON)); if (checkBrace) _eat(TokenKind.RBRACE); @@ -851,7 +851,7 @@ class CSSParser { // property: expr prio? \9; - IE8 and below property, /9 before semi-colon // *IDENT - IE7 or below // _IDENT - IE6 property (automatically a valid ident) - void processDeclaration(CSSStyleDeclaration style, {bool important = false}) { + void processDeclaration(CSSStyleDeclaration style) { // IDENT ':' expr '!important'? if (TokenKind.isIdentifier(_peekToken.kind)) { var propertyIdent = camelize(identifier().name); @@ -882,7 +882,7 @@ class CSSParser { var expr = processExpr(); // Handle !important (prio) - var importantPriority = important ? important : _maybeEat(TokenKind.IMPORTANT); + var importantPriority = _maybeEat(TokenKind.IMPORTANT); style.setProperty(propertyIdent, expr, importantPriority); } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) { _next(); diff --git a/webf/lib/src/css/style_sheet.dart b/webf/lib/src/css/style_sheet.dart index 6c13e041b1..3bfe2bbcbc 100644 --- a/webf/lib/src/css/style_sheet.dart +++ b/webf/lib/src/css/style_sheet.dart @@ -48,3 +48,38 @@ class CSSStyleSheet implements StyleSheet { replaceSync(text); } } + +/* +1. append 元素 +2. Node::invalidateStyle 方法 将元素所有祖先都标记需要更新样式 +3. Document updateStyleIfNeeded() + 3.1 flush pending sheet + 比对 style sheet, 得到 ActiveSheetsChange 和 ChangedRuleSet + 通过 ElementRuleCollector ElementRuleCollector::matchesAnyAuthorRules + 对比后将 element 标记为 invalid (Invalidator::invalidateIfNeeded) + 3.2 判断标记 needsStyleRecalc() ,执行 resolveStyle(); +5. TreeResolver::resolveComposedTree +6. TreeResolver::resolveElement + 6.1 TreeResolver::styleForStyleable return CSSStyleDeclaration + 6.1.1 styleForElement + 6.2 Merge CSSStyleDeclaration +----------------------- + + + + + + + +1. 对比 style sheet +enum ActiveSheetsChange { + kNoActiveSheetsChanged, // Nothing changed. + kActiveSheetsChanged, // Sheets were added and/or inserted. + kActiveSheetsAppended // Only additions, and all appended. +}; +2. + +SetNeedsStyleRecalc + + + */ diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 175fba080c..3106fb6626 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -980,7 +980,8 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element internalSetAttribute(qualifiedName, value); if (_STYLE_PROPERTY == qualifiedName) { CSSStyleDeclaration declaration = CSSParser(value).parseInlineStyle(); - _applyInlineStyle(declaration); + style.merge(declaration); + recalculateStyle(); } else if (_CLASS_NAME == qualifiedName) { className = value; } else if (_ID == qualifiedName) { From cae7202c31fc1d361988ec74b2616d7e37a39a9c Mon Sep 17 00:00:00 2001 From: openwebf-bot <openwebf@openwebf.com> Date: Sat, 13 Aug 2022 15:48:00 +0000 Subject: [PATCH 188/498] Committing clang-format changes --- bridge/bindings/qjs/dom/element.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc index 6438d2b533..fee41bf338 100644 --- a/bridge/bindings/qjs/dom/element.cc +++ b/bridge/bindings/qjs/dom/element.cc @@ -434,7 +434,7 @@ IMPL_PROPERTY_GETTER(Element, style)(JSContext* ctx, JSValue this_val, int argc, IMPL_PROPERTY_SETTER(Element, style)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { JSValue value = argv[0]; - JSValue args[] = { JS_NewString(ctx, "style"), value}; + JSValue args[] = {JS_NewString(ctx, "style"), value}; return setAttribute(ctx, this_val, 2, args); } From d4f8f5df67019f7bcb60efdd6ad585e53747beb8 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Sun, 14 Aug 2022 16:19:17 +0800 Subject: [PATCH 189/498] chore: adjust inline style --- .../specs/css/css-inline/change_inline.ts | 8 ++ webf/lib/src/css/parser/parser.dart | 76 ++++++++++--------- webf/lib/src/css/rule_set.dart | 2 +- webf/lib/src/css/selector_evaluator.dart | 1 - webf/lib/src/css/style_property.dart | 2 +- webf/lib/src/dom/element.dart | 4 +- webf/test/src/css/style_inline_parser.dart | 6 +- 7 files changed, 57 insertions(+), 42 deletions(-) create mode 100644 integration_tests/specs/css/css-inline/change_inline.ts diff --git a/integration_tests/specs/css/css-inline/change_inline.ts b/integration_tests/specs/css/css-inline/change_inline.ts new file mode 100644 index 0000000000..7132111cd0 --- /dev/null +++ b/integration_tests/specs/css/css-inline/change_inline.ts @@ -0,0 +1,8 @@ +describe('line-change', () => { + fit('001', async () => { + const div = <div>should be green</div> + document.body.appendChild(div); + div.style = 'color: green'; + await snapshot(); + }); +}); \ No newline at end of file diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index efe047ec8e..cc04472be3 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -28,7 +28,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -import 'dart:io'; import 'dart:math' as math; import 'package:webf/css.dart'; @@ -89,8 +88,44 @@ class CSSParser { return CSSStyleSheet(ruleSet); } - CSSStyleDeclaration parseInlineStyle() { - return processDeclarations(checkBrace: false); + Map<String, dynamic> parseInlineStyle() { + Map<String, dynamic> style = {}; + do { + if (TokenKind.isIdentifier(_peekToken.kind)) { + var propertyIdent = camelize(identifier().name); + var resetProperty = false; + var keepGoing = true; + while (keepGoing) { + switch (_peek()) { + case TokenKind.COLON: + _eat(TokenKind.COLON); + keepGoing = false; + break; + case TokenKind.SEMICOLON: + case TokenKind.NEWLINE: + resetProperty = true; + _next(); + break; + case TokenKind.IDENTIFIER: + if (resetProperty) { + propertyIdent = identifier().name; + } + break; + default: + keepGoing = false; + } + } + var expr = processExpr(); + style[propertyIdent] = expr; + } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) { + _next(); + } else if (_peekToken.kind == TokenKind.DIRECTIVE_INCLUDE) { + // TODO @include mixinName in the declaration area. + } else if (_peekToken.kind == TokenKind.DIRECTIVE_EXTEND) { + _next(); + } + } while (_maybeEat(TokenKind.SEMICOLON)); + return style; } List<CSSRule> parseRules() { @@ -233,10 +268,7 @@ class CSSParser { /// '}' /// supports: '@supports' supports_condition group_rule_body TreeNode? processDirective() { - var start = _peekToken.span; - - var tokId = _peek(); - final tokenId = tokId as int; + var tokenId = _peek(); switch (tokenId) { case TokenKind.DIRECTIVE_IMPORT: _next(); @@ -361,16 +393,6 @@ class CSSParser { return null; } - SourceSpan _makeSpan(FileSpan start) { - // TODO(terry): there are places where we are creating spans before we eat - // the tokens, so using _previousToken is not always valid. - // TODO(nweiz): use < rather than compareTo when SourceSpan supports it. - if (_previousToken == null || _previousToken!.span.compareTo(start) < 0) { - return start; - } - return start.expand(_previousToken!.span); - } - CSSRule? processRule([SelectorGroup? selectorGroup]) { if (selectorGroup == null) { processDirective(); @@ -463,7 +485,6 @@ class CSSParser { SelectorGroup? processSelectorGroup() { var selectors = <Selector>[]; - var start = _peekToken.span; tokenizer.inSelector = true; do { @@ -483,7 +504,6 @@ class CSSParser { /// Return list of selectors Selector? processSelector() { var simpleSequences = <SimpleSelectorSequence>[]; - var start = _peekToken.span; while (true) { // First item is never descendant make sure it's COMBINATOR_NONE. var selectorItem = simpleSelectorSequence(simpleSequences.isEmpty); @@ -517,7 +537,6 @@ class CSSParser { } SimpleSelectorSequence? simpleSelectorSequence(bool forceCombinatorNone) { - var start = _peekToken.span; var combinatorType = TokenKind.COMBINATOR_NONE; var thisOperator = false; @@ -593,11 +612,10 @@ class CSSParser { // code. // TODO(terry): Need to handle attribute namespace too. dynamic first; - var start = _peekToken.span; switch (_peek()) { case TokenKind.ASTERISK: // Mark as universal namespace. - var tok = _next(); + _next(); first = Wildcard(); break; case TokenKind.IDENTIFIER: @@ -746,8 +764,6 @@ class CSSParser { /// DIMENSION {num}{ident} /// NUMBER {num} List<String> /* SelectorExpression | LiteralTerm */ processSelectorExpression() { - var start = _peekToken.span; - var expressions = <String>[]; Token? termToken; @@ -764,10 +780,10 @@ class CSSParser { break; case TokenKind.SINGLE_QUOTE: final value = processQuotedString(false); - return ["'${_escapeString(value as String, single: true)}'"]; + return ["'${_escapeString(value, single: true)}'"]; case TokenKind.DOUBLE_QUOTE: final value = processQuotedString(false); - return ['"${_escapeString(value as String)}"']; + return ['"${_escapeString(value)}"']; case TokenKind.IDENTIFIER: final value = identifier(); // Snarf up the ident we'll remap, maybe. expressions.add(value.name); @@ -798,8 +814,6 @@ class CSSParser { // // SUBSTRMATCH: '*=' AttributeSelector? processAttribute() { - var start = _peekToken.span; - if (_maybeEat(TokenKind.LBRACK)) { var attrName = identifier(); @@ -924,8 +938,6 @@ class CSSParser { static const int MAX_UNICODE = 0x10FFFF; String processQuotedString([bool urlString = false]) { - var start = _peekToken.span; - // URI term sucks up everything inside of quotes(' or ") or between parens var stopToken = urlString ? TokenKind.RPAREN : -1; @@ -938,18 +950,15 @@ class CSSParser { case TokenKind.SINGLE_QUOTE: stopToken = TokenKind.SINGLE_QUOTE; _next(); // Skip the SINGLE_QUOTE. - start = _peekToken.span; break; case TokenKind.DOUBLE_QUOTE: stopToken = TokenKind.DOUBLE_QUOTE; _next(); // Skip the DOUBLE_QUOTE. - start = _peekToken.span; break; default: if (urlString) { if (_peek() == TokenKind.LPAREN) { _next(); // Skip the LPAREN. - start = _peekToken.span; } stopToken = TokenKind.RPAREN; } else { @@ -1019,7 +1028,6 @@ class CSSParser { // function: IDENT '(' expr ')' // dynamic processFunction(Identifier func) { - var start = _peekToken.span; var name = func.name; switch (name) { diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index 4f5a732c04..3ecd1eea25 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -51,7 +51,7 @@ class RuleSet { // indexed by selectorText void findBestRuleSetAndAdd(CSSStyleRule rule) { - String? id, className, attributeName, tagName, pseudoElementName, pseudoFunctionName; + String? id, className, attributeName, tagName; for (final selector in rule.selectorGroup.selectors) { for (final simpleSelectorSequence in selector.simpleSelectorSequences) { diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/selector_evaluator.dart index 20f0d2da6b..308d9a4673 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/selector_evaluator.dart @@ -263,7 +263,6 @@ class SelectorEvaluator extends SelectorVisitor { } else { return false; } - break; } throw _unimplemented(selector); } diff --git a/webf/lib/src/css/style_property.dart b/webf/lib/src/css/style_property.dart index 93cf23d9f1..2362b8edc2 100644 --- a/webf/lib/src/css/style_property.dart +++ b/webf/lib/src/css/style_property.dart @@ -22,7 +22,7 @@ String camelize(String str) { if (str.startsWith('--')) { return str; } - return str.replaceAllMapped(RegExp(r'-(\w)'), (match) { + return str.replaceAllMapped(_camelCaseReg, (match) { String subStr = match[0]!.substring(1); return subStr.isNotEmpty ? subStr.toUpperCase() : ''; }); diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 3106fb6626..203eb0a487 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -979,8 +979,8 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element void setAttribute(String qualifiedName, String value) { internalSetAttribute(qualifiedName, value); if (_STYLE_PROPERTY == qualifiedName) { - CSSStyleDeclaration declaration = CSSParser(value).parseInlineStyle(); - style.merge(declaration); + final map = CSSParser(value).parseInlineStyle(); + inlineStyle.addAll(map); recalculateStyle(); } else if (_CLASS_NAME == qualifiedName) { className = value; diff --git a/webf/test/src/css/style_inline_parser.dart b/webf/test/src/css/style_inline_parser.dart index 89792f1310..1e2de916f8 100644 --- a/webf/test/src/css/style_inline_parser.dart +++ b/webf/test/src/css/style_inline_parser.dart @@ -5,15 +5,15 @@ import 'package:webf/css.dart'; import 'package:test/test.dart'; -CSSStyleDeclaration parseInlineStyle(String style) { +Map parseInlineStyle(String style) { return CSSParser(style).parseInlineStyle(); } void main() { group('CSSStyleRuleParser', () { test('0', () { - CSSStyleDeclaration style = parseInlineStyle('color : red; background: red;'); - expect(style.getPropertyValue('color'), 'red'); + Map style = parseInlineStyle('color : red; background: red;'); + expect(style['color'], 'red'); }); }); } From 781e8b1578dac03df406726f2735fa13e871eb97 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Sun, 14 Aug 2022 16:31:10 +0800 Subject: [PATCH 190/498] fix: dart lint --- webf/lib/src/css/parser/selector.dart | 6 ++---- webf/lib/src/css/rule_set.dart | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/webf/lib/src/css/parser/selector.dart b/webf/lib/src/css/parser/selector.dart index 3bdf96d129..6ba599e526 100644 --- a/webf/lib/src/css/parser/selector.dart +++ b/webf/lib/src/css/parser/selector.dart @@ -102,9 +102,7 @@ class SimpleSelectorSequence extends TreeNode { int combinator; final SimpleSelector simpleSelector; - SimpleSelectorSequence(this.simpleSelector, [int combinator = TokenKind.COMBINATOR_NONE]) - : combinator = combinator, - super(); + SimpleSelectorSequence(this.simpleSelector, [this.combinator = TokenKind.COMBINATOR_NONE]) : super(); bool get isCombinatorNone => combinator == TokenKind.COMBINATOR_NONE; bool get isCombinatorPlus => combinator == TokenKind.COMBINATOR_PLUS; @@ -112,7 +110,7 @@ class SimpleSelectorSequence extends TreeNode { bool get isCombinatorTilde => combinator == TokenKind.COMBINATOR_TILDE; bool get isCombinatorDescendant => combinator == TokenKind.COMBINATOR_DESCENDANT; - String get _combinatorToString { + String get combinatorToString { switch (combinator) { case TokenKind.COMBINATOR_DESCENDANT: return ' '; diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index 3ecd1eea25..8401252489 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -34,7 +34,7 @@ class RuleSet { } void deleteRule(int index) { - CSSRule rule = rules.removeAt(index); + // CSSRule rule = rules.removeAt(index); // if (rule is CSSStyleRule) { // // findBestRuleSetAndRemove(rule); // } From c75930c9696a794c856ace878e4949f4158f309e Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Sun, 14 Aug 2022 21:08:04 +0800 Subject: [PATCH 191/498] feat: adjust ruleset storage location --- webf/lib/src/css/element_rule_collector.dart | 13 ++++- webf/lib/src/css/parser/parser.dart | 5 +- webf/lib/src/css/rule_set.dart | 7 --- webf/lib/src/css/style_sheet.dart | 51 +++----------------- webf/lib/src/dom/document.dart | 3 ++ webf/lib/src/dom/element.dart | 21 ++++++-- webf/test/src/css/style_rule_parser.dart | 2 +- webf/test/src/css/style_sheet_parser.dart | 7 +-- 8 files changed, 44 insertions(+), 65 deletions(-) diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index eae4b1d510..d10fbb2160 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -7,7 +7,11 @@ import 'package:webf/dom.dart'; import 'package:webf/src/css/selector_evaluator.dart'; class ElementRuleCollector { - CSSStyleDeclaration collectionFromRuleSet(RuleSet ruleSet, Element element) { + bool matchedAnyRule(RuleSet ruleSet, Element element) { + return _matchedRules(ruleSet, element).isNotEmpty; + } + + List<CSSRule> _matchedRules(RuleSet ruleSet, Element element) { List<CSSRule> matchedRules = []; // #id @@ -32,6 +36,11 @@ class ElementRuleCollector { // universal matchedRules.addAll(_collectMatchingRulesForList(ruleSet.universalRules, element)); + return matchedRules; + } + + CSSStyleDeclaration collectionFromRuleSet(RuleSet ruleSet, Element element) { + final matchedRules = _matchedRules(ruleSet, element); CSSStyleDeclaration declaration = CSSStyleDeclaration(); if (matchedRules.isEmpty) { return declaration; @@ -72,7 +81,7 @@ class ElementRuleCollector { if (evaluator.matchSelector(rule.selectorGroup, element)) { matchedRules.add(rule); } - } catch(error) { + } catch (error) { print('selector evaluator error: $error'); } } diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index cc04472be3..cf070f02f6 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -82,10 +82,11 @@ class CSSParser { /// Main entry point for parsing an entire CSS file. CSSStyleSheet parse() { - final ruleSet = RuleSet(); final rules = parseRules(); + + final ruleSet = RuleSet(); ruleSet.addRules(rules); - return CSSStyleSheet(ruleSet); + return CSSStyleSheet(rules); } Map<String, dynamic> parseInlineStyle() { diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index 8401252489..a50809be46 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -33,13 +33,6 @@ class RuleSet { } } - void deleteRule(int index) { - // CSSRule rule = rules.removeAt(index); - // if (rule is CSSStyleRule) { - // // findBestRuleSetAndRemove(rule); - // } - } - void reset() { rules.clear(); idRules.clear(); diff --git a/webf/lib/src/css/style_sheet.dart b/webf/lib/src/css/style_sheet.dart index 3bfe2bbcbc..93eec5a0f7 100644 --- a/webf/lib/src/css/style_sheet.dart +++ b/webf/lib/src/css/style_sheet.dart @@ -18,29 +18,25 @@ class CSSStyleSheet implements StyleSheet { /// A string containing the baseURL used to resolve relative URLs in the stylesheet. String? herf; - final RuleSet ruleSet; + final List<CSSRule> cssRules; - CSSStyleSheet(this.ruleSet, {this.disabled = false, this.herf}); + CSSStyleSheet(this.cssRules, {this.disabled = false, this.herf}); insertRule(String text, int index) { List<CSSRule> rules = CSSParser(text).parseRules(); - for (CSSRule rule in rules) { - ruleSet.addRule(rule); - } + cssRules.addAll(rules); } /// Removes a rule from the stylesheet object. deleteRule(int index) { - ruleSet.deleteRule(index); + cssRules.removeAt(index); } /// Synchronously replaces the content of the stylesheet with the content passed into it. replaceSync(String text) { - ruleSet.reset(); + cssRules.clear(); List<CSSRule> rules = CSSParser(text).parseRules(); - for (CSSRule rule in rules) { - ruleSet.addRule(rule); - } + cssRules.addAll(rules); } replace(String text) { @@ -48,38 +44,3 @@ class CSSStyleSheet implements StyleSheet { replaceSync(text); } } - -/* -1. append 元素 -2. Node::invalidateStyle 方法 将元素所有祖先都标记需要更新样式 -3. Document updateStyleIfNeeded() - 3.1 flush pending sheet - 比对 style sheet, 得到 ActiveSheetsChange 和 ChangedRuleSet - 通过 ElementRuleCollector ElementRuleCollector::matchesAnyAuthorRules - 对比后将 element 标记为 invalid (Invalidator::invalidateIfNeeded) - 3.2 判断标记 needsStyleRecalc() ,执行 resolveStyle(); -5. TreeResolver::resolveComposedTree -6. TreeResolver::resolveElement - 6.1 TreeResolver::styleForStyleable return CSSStyleDeclaration - 6.1.1 styleForElement - 6.2 Merge CSSStyleDeclaration ------------------------ - - - - - - - -1. 对比 style sheet -enum ActiveSheetsChange { - kNoActiveSheetsChanged, // Nothing changed. - kActiveSheetsChanged, // Sheets were added and/or inserted. - kActiveSheetsAppended // Only additions, and all appended. -}; -2. - -SetNeedsStyleRecalc - - - */ diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 33bc70a602..87788267da 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -19,6 +19,8 @@ class Document extends Node { GestureListener? gestureListener; WidgetDelegate? widgetDelegate; + RuleSet ruleSet = RuleSet(); + Document( BindingContext context, { required this.controller, @@ -179,6 +181,7 @@ class Document extends Node { void addStyleSheet(CSSStyleSheet sheet) { styleSheets.add(sheet); + ruleSet.addRules(sheet.cssRules); recalculateDocumentStyle(); } diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 203eb0a487..5605adcaf4 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -882,6 +882,20 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element return child; } + void updateStyleIfNeeded() {} + // 2. Node::invalidateStyle 方法 将元素所有祖先都标记需要更新样式 + // 3. Document updateStyleIfNeeded() + // 3.1 flush pending sheet + // 比对 style sheet, 得到 ActiveSheetsChange 和 ChangedRuleSet + // 通过 ElementRuleCollector ElementRuleCollector::matchesAnyAuthorRules + // 对比后将 element 标记为 invalid (Invalidator::invalidateIfNeeded) + // 3.2 判断标记 needsStyleRecalc() ,执行 resolveStyle(); + // 5. TreeResolver::resolveComposedTree + // 6. TreeResolver::resolveElement + // 6.1 TreeResolver::styleForStyleable return CSSStyleDeclaration + // 6.1.1 styleForElement + // 6.2 Merge CSSStyleDeclaration + @override @mustCallSuper Node insertBefore(Node child, Node referenceNode) { @@ -1425,11 +1439,8 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } void _applySheetStyle(CSSStyleDeclaration style) { - for (CSSStyleSheet sheet in ownerDocument.styleSheets) { - RuleSet ruleSet = sheet.ruleSet; - CSSStyleDeclaration? matchRule = ElementRuleCollector().collectionFromRuleSet(ruleSet, this); - style.merge(matchRule); - } + CSSStyleDeclaration? matchRule = ElementRuleCollector().collectionFromRuleSet(ownerDocument.ruleSet, this); + style.merge(matchRule); } void _onStyleChanged(String propertyName, String? prevValue, String currentValue) { diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index 00d7ee75bf..ee9dddb129 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -8,7 +8,7 @@ import 'package:test/test.dart'; CSSRule? parseSingleRule(String rule) { CSSStyleSheet sheet = CSSParser(rule).parse(); - return sheet.ruleSet.rules.first; + return sheet.cssRules.first; } void main() { diff --git a/webf/test/src/css/style_sheet_parser.dart b/webf/test/src/css/style_sheet_parser.dart index ca26b4d5ea..1925b9f7c4 100644 --- a/webf/test/src/css/style_sheet_parser.dart +++ b/webf/test/src/css/style_sheet_parser.dart @@ -8,7 +8,7 @@ import 'package:test/test.dart'; List<CSSRule> parseRules(String rule) { CSSStyleSheet sheet = CSSParser(rule).parse(); - return sheet.ruleSet.rules; + return sheet.cssRules; } void main() { @@ -45,7 +45,8 @@ void main() { expect((rules[0] as CSSStyleRule).selectorText, 'foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); expect((rules[1] as CSSStyleRule).selectorText, 'bar'); - expect((rules[1] as CSSStyleRule).declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64...)'); + expect( + (rules[1] as CSSStyleRule).declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64...)'); }); test('5', () { @@ -67,7 +68,7 @@ void main() { expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); - test('7', () { + test('7', () { List<CSSRule> rules = parseRules('.foo h6{color: red}'); expect(rules.length, 1); expect((rules[0] as CSSStyleRule).selectorText, 'foo h6'); From 0bdee5fc86a585a1a0c80e56f12dfb5dc0430a1d Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Sun, 14 Aug 2022 22:24:43 +0800 Subject: [PATCH 192/498] feat: revert bridge style attribute --- bridge/bindings/qjs/dom/element.cc | 15 ++------------- bridge/bindings/qjs/dom/element.h | 3 +-- bridge/bindings/qjs/html_parser.cc | 2 +- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/bridge/bindings/qjs/dom/element.cc b/bridge/bindings/qjs/dom/element.cc index fee41bf338..cf3533d354 100644 --- a/bridge/bindings/qjs/dom/element.cc +++ b/bridge/bindings/qjs/dom/element.cc @@ -427,17 +427,6 @@ IMPL_PROPERTY_SETTER(Element, className)(JSContext* ctx, JSValue this_val, int a return JS_DupValue(ctx, value); } -IMPL_PROPERTY_GETTER(Element, style)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - getDartMethod()->flushUICommand(); - return getAttribute(ctx, this_val, argc, argv); -} - -IMPL_PROPERTY_SETTER(Element, style)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - JSValue value = argv[0]; - JSValue args[] = {JS_NewString(ctx, "style"), value}; - return setAttribute(ctx, this_val, 2, args); -} - IMPL_PROPERTY_GETTER(Element, offsetLeft)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { auto* element = static_cast<ElementInstance*>(JS_GetOpaque(this_val, Element::classId())); return element->getBindingProperty("offsetLeft"); @@ -855,7 +844,7 @@ ElementInstance::ElementInstance(Element* element, std::string tagName, bool sho JSValue style = JS_CallConstructor(m_ctx, CSSStyleDeclaration::instance(m_context)->jsObject, 1, arguments); m_style = static_cast<StyleDeclarationInstance*>(JS_GetOpaque(style, CSSStyleDeclaration::kCSSStyleDeclarationClassId)); - JS_DefinePropertyValueStr(m_ctx, jsObject, "styleDeclaration", m_style->jsObject, JS_PROP_C_W_E); + JS_DefinePropertyValueStr(m_ctx, jsObject, "style", m_style->jsObject, JS_PROP_C_W_E); if (shouldAddUICommand) { std::unique_ptr<NativeString> args_01 = stringToNativeString(tagName); @@ -865,7 +854,7 @@ ElementInstance::ElementInstance(Element* element, std::string tagName, bool sho JSClassExoticMethods ElementInstance::exoticMethods{nullptr, nullptr, nullptr, nullptr, hasProperty, getProperty, setProperty}; -StyleDeclarationInstance* ElementInstance::styleDeclaration() { +StyleDeclarationInstance* ElementInstance::style() { return m_style; } diff --git a/bridge/bindings/qjs/dom/element.h b/bridge/bindings/qjs/dom/element.h index 5128db6f7a..cb7c64a9c7 100644 --- a/bridge/bindings/qjs/dom/element.h +++ b/bridge/bindings/qjs/dom/element.h @@ -112,7 +112,6 @@ class Element : public Node { DEFINE_PROTOTYPE_READONLY_PROPERTY(children); DEFINE_PROTOTYPE_READONLY_PROPERTY(attributes); - DEFINE_PROTOTYPE_PROPERTY(style); DEFINE_PROTOTYPE_PROPERTY(className); DEFINE_PROTOTYPE_PROPERTY(innerHTML); DEFINE_PROTOTYPE_PROPERTY(outerHTML); @@ -150,7 +149,7 @@ class ElementInstance : public NodeInstance { std::string getRegisteredTagName(); std::string outerHTML(); std::string innerHTML(); - StyleDeclarationInstance* styleDeclaration(); + StyleDeclarationInstance* style(); static inline JSClassID classID(); diff --git a/bridge/bindings/qjs/html_parser.cc b/bridge/bindings/qjs/html_parser.cc index a734dc98a1..05dc5e61cd 100644 --- a/bridge/bindings/qjs/html_parser.cc +++ b/bridge/bindings/qjs/html_parser.cc @@ -154,7 +154,7 @@ void HTMLParser::parseProperty(ElementInstance* element, GumboElement* gumboElem } arrStyles.push_back(strStyles.substr(prev_pos, pos - prev_pos)); - auto* style = element->styleDeclaration(); + auto* style = element->style(); for (auto& s : arrStyles) { std::string::size_type position = s.find(':'); From f4674194c73cc0f48a565bc186cfc0ea4f9a3e17 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Mon, 15 Aug 2022 14:51:35 +0800 Subject: [PATCH 193/498] test: match rule --- .../attribute-selector.ts.0b8369b51.png | Bin 0 -> 11029 bytes .../attribute-selector.ts.0dc1e8a21.png | Bin 4108 -> 6112 bytes .../attribute-selector.ts.2abd507c1.png | Bin 5553 -> 9110 bytes .../attribute-selector.ts.606b1d201.png | Bin 0 -> 7738 bytes .../attribute-selector.ts.6e92b4361.png | Bin 4119 -> 6072 bytes .../attribute-selector.ts.9342f0c31.png | Bin 4041 -> 5899 bytes .../attribute-selector.ts.ac47caa61.png | Bin 8302 -> 10450 bytes .../attribute-selector.ts.b2982c691.png | Bin 4147 -> 6141 bytes .../attribute-selector.ts.d0936f8b1.png | Bin 0 -> 10555 bytes .../attribute-selector.ts.d28ef93e1.png | Bin 4629 -> 0 bytes .../attribute-selector.ts.f8a4e3801.png | Bin 0 -> 10450 bytes .../child-selectors.ts.2d9c94aa1.png | Bin 6522 -> 9102 bytes .../child-selectors.ts.3df2d0281.png | Bin 5299 -> 6138 bytes .../child-selectors.ts.4b1dc45b1.png | Bin 0 -> 5955 bytes .../child-selectors.ts.6222b33c1.png | Bin 4826 -> 10165 bytes .../child-selectors.ts.6a3b3d991.png | Bin 5760 -> 0 bytes .../child-selectors.ts.8daacc0a1.png | Bin 4672 -> 9995 bytes .../child-selectors.ts.9374178f1.png | Bin 0 -> 8275 bytes .../child-selectors.ts.9db7989f1.png | Bin 5582 -> 8548 bytes .../child-selectors.ts.a21b79841.png | Bin 4685 -> 10067 bytes .../child-selectors.ts.a98cfcea1.png | Bin 6227 -> 12495 bytes .../child-selectors.ts.c2e735d01.png | Bin 0 -> 5762 bytes .../child-selectors.ts.d1ba354e1.png | Bin 5760 -> 0 bytes .../child-selectors.ts.dd63c52a1.png | Bin 12885 -> 10279 bytes .../child-selectors.ts.f149b6a61.png | Bin 0 -> 8080 bytes .../child-selectors.ts.fad43fad1.png | Bin 0 -> 8310 bytes .../css-selectors/combinator.ts.06fd22981.png | Bin 4522 -> 0 bytes .../css-selectors/combinator.ts.0e8424a11.png | Bin 4695 -> 0 bytes .../css-selectors/combinator.ts.6d2121b91.png | Bin 4759 -> 0 bytes .../tag-selector.ts.0c6a7df41.png | Bin 10727 -> 0 bytes .../tag-selector.ts.38aaea691.png | Bin 8670 -> 0 bytes .../tag-selector.ts.40c7c0241.png | Bin 3841 -> 0 bytes .../tag-selector.ts.5fddfcf51.png | Bin 34430 -> 0 bytes .../tag-selector.ts.c2987aa51.png | Bin 6471 -> 0 bytes .../specs/css/css-inline/change_inline.ts | 2 +- .../css/css-selectors/attribute-selector.ts | 45 +++-- .../specs/css/css-selectors/child-indexed.ts | 188 ------------------ .../css/css-selectors/child-selectors.ts | 84 ++++---- .../css/css-selectors/descendent-selector.ts | 28 ++- .../specs/css/css-selectors/id-selector.ts | 18 +- .../css/css-selectors/sibling-selector.ts | 12 +- .../specs/css/css-selectors/tag-selector.ts | 39 ++-- webf/lib/src/css/rule_set.dart | 56 +++--- webf/lib/src/css/selector_evaluator.dart | 6 +- webf/lib/src/dom/document.dart | 2 + 45 files changed, 160 insertions(+), 320 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0b8369b51.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.606b1d201.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.d0936f8b1.png delete mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.d28ef93e1.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.f8a4e3801.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.4b1dc45b1.png delete mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.6a3b3d991.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.9374178f1.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.c2e735d01.png delete mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.d1ba354e1.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.f149b6a61.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.fad43fad1.png delete mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png delete mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png delete mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png delete mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png delete mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png delete mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png delete mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png delete mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png delete mode 100644 integration_tests/specs/css/css-selectors/child-indexed.ts diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0b8369b51.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0b8369b51.png new file mode 100644 index 0000000000000000000000000000000000000000..71af354476bcd7f266eb94f08454f5a2b9fc57a6 GIT binary patch literal 11029 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 Fe*s7TT|WQ- literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0dc1e8a21.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0dc1e8a21.png index fb9fd614d42b42c13168f1fc69c27822ef8125c9..501c85cd06eb800d0b8c952d20036d0a4f87dc3b 100644 GIT binary patch literal 6112 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௢tNdU%zin~tyM|mU~@v< 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 literal 4108 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 Q^`j?59E?2B5SEztU&d}<T>t<8 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.2abd507c1.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.2abd507c1.png index 86df41c688577365c92c79477792469144db692f..4a60999c637813d510d63f5a3b4fc5a20acfdfd1 100644 GIT binary patch literal 9110 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 TVLkY54Fs-jaI;+N!PEZ%sU;46 literal 5553 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 R^xG8{awzN|ZU5;X{{x?ang{>@ diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.606b1d201.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.606b1d201.png new file mode 100644 index 0000000000000000000000000000000000000000..086f6237c6061bf88901b380a5a7986e042e0729 GIT binary patch literal 7738 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 LfTOjC&tCpdf@>ke literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.6e92b4361.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.6e92b4361.png index 8ec30b41efdf044b59830d1bf19f0311d9fd93ea..174b7421e792d898370080a7ae92270af7126feb 100644 GIT binary patch literal 6072 zcmeI0i&s)<+s3V?oW{zpuQjh@8oZ5^b4bld%>y`1sY9mJI5XvVn!-b-LWzoqVAdL) zGNs5&JYlb;(^Qy}BB)?vi6&}EiHV5@ii&^=au9)UGw+}Awb$BfuV=4kKkM1|eP6%( z+WYE>@Q|&a?flHf#%3$xhoe8**sMEeW3&F!r<;H~O*=m{0GD;xA49&kp$|IG0fSGl z-y?qd6gYA}jsL^O#^EgD=;5ETs}%3?rPZ0$vL)o6+nqmr|05*+=AP7xe(V3Xxtx7E zD6)QIMsUIXE62VKIWv~%yZ0<%^s>NZ>eI`YyPj=3Yd3uJt3RH*-9Wp1xqI?x{&#k} z3vPTu`O@z8iF-3-zs&nT?*2rC=AyW<GAWroq=M^o<2Q6frHaKoH@~p3u*GItV~9Z7 zTyooS?$Uog*;)JNaz0vhZs>YQlJu$rO>a*ek6TkZuuxOGFn;Li)!Wpi+3Wl8_&%o+ zs-cpKpL}O)E*ESt?>P4fyR!}^CM*+J=O=!)&c`p^c3@@~vWjFg{ak%II`TqjP0rkd z$I9FntDjR>o<CYkpYyOxZg$7x5qrg6zPLcV4%|TweEyUrNFU=1CNjoR9sy;2xUj*F zJ?e=Id};e$S+lLX5fyH)N7nix%IJ|5S1|BHMWTQCcyCDsl^cgT0&PqD_amAgU?bp* zkL@*|O|Q5{(~t|6tFivd<4Gl4g+Ab^=57a;hf6fO&R7T_+b&u)9Fe|MRm5mZyNF)H z_#}4AyyFL{*x{xJE=fe)qn<7*mr8A?b@<=XQLDTngi<Op@K$w!q7=)?3;%`LsBDpV z@J-)?eqB9LVLWKz?b_N!y9uwJ<Gb0R`<{<n^-bY2hPfDvbF2D{p_c>hYb>+;)ubg1 zToFPLbeMANsRq+G*qt?~LDe^)B0@Mzqdqwc8D_N_`x@!~^7q2%^35V-wgAD$Cx(Y} zOwuhC-d}~?IhdfgfF@D~@UM%C(t8IA@6A!_55^3aGUIewejY~_Y3M}0uSCRAOG$TD zLIr!m9EsDjxk^`Be}T~ZU{9`Msxe_f(-hf63L+ah@_Jmta?CBJ-%!ITSamm-l#Qw( z<YlV%oo<~BBR7if>*6XlpF;j%UB{o8N-rk_krj=ZoU%D}eG;%tGW*`wCU^OrBWO4T z*_e~_&{z$mc)|3xQ=n=l&EX=sRk_AK)>?i)boJ%yNXEr5O8iC9hwGg5jQXL<8XZwW zv@RhwuP|>x#Bmx^hl$PfsbRu69U)5M`~--+q<_i7YOdEg@!-G;oikkVR^#+jYQwaK z+ThdEVy2Dovom_U14Fu0UfeeGi;UFy-jnU1x2W!uJ4#*_t1*0m2dug?Sb1AaH>YKR zn+U(IG7t7RH_5jnC_x@>=f_*kZ&!S$DqZ7;ZW3m$)zNz&xhAG%u21o%_cbwhRh~Q& zJZ%?3+<nA$%fRIXy923RPe2lMw!YwaYIS*$J@-(kaNmFsWd1c=KeIV9$2<(_tMdq` zkf8nDU2Kc_FAC$T?rtbZ9FZ^%q$!_1Iv;miUefN45VG~^6dIBcLLi$0SB4`TM*|kO z%2>*`nN-bfL^2>omTgUClc;#WzGn6CAdP7L1;|HL(u`CVN~+=d&SIM1Us9wC)+*Dj z<x)$!vdmPiiXeS&1G#<<e9pgZ$$^I=#89t?qEoVKtW)t~xo#kohX2~Ms*$&dt@?b9 zsV={HB{*^ZZiHaG!8a~$aV1x_q=F#)yfYyPZ%(UcztC-HBg3Qj&)s$D&vrY+b3Kg- z&zd*-nk#up$NS5pNz!S@SGe!!_{9omoakcY1?}YpXb$aBUM6u{+tQ_{xTmtekGS*o zcO_8y)kxN?t3$#C?lXg?9bSD;&b<;+2BVzWP%vG!VF)3v=df0vJ%k$QeZFUfF3YWV zF`|0eXS2f{8DXa;gSUa+p}Kny#{^o?11bunf5g4tjMAz+6OH*ITru8&D;5oMj0=xW zbHBt6Rx!f*ozyfMO<ST}Gri8EMeMC!E|5%k&hN5rQIkB+roS7RC=)qM)U-GW>+k1< zx>K<Tiu5iyur#JnRRQtUbzvFSO8eHHxJN*ya^Bi-sTh+Ob05H+(3uc62wNP7^pzfI zkajXTK7p%3aOa%K-#asY%GDvU;f((t1U$2=p}E@u*L#O#z8_rc(P<f8IGEb7wtxyu z8OP|hx69t-`FSyDHPsT`A&!vLSb_VQB7Mds?Qc)xMp2~DLekzk?jgT4r?H%3u;9R6 zWQ{Ri^1D_lLWWZU(zPy*u=os$v=q@0&)0ae@a6&5%xFrw9K}pj^t2eXd6q1=CJtT- zF-X_Z_@i?u_1id7%l27FvQkhLWJx)h^+m@KN<4FW`?N%C%m&4-KKZUY>P>?YyfjI= zLP-7M4dw)e{y%$T^55ki$L(tu)+U(~6Vb$lk!X|oAw$*+V<xXBTpx8oEaL)A?SKk{ zf1$^*UR-QY5pyI<6#9idF-fh6<_j<WzP|IoU)yD9aJfDo(g%G=dslDhxT%~j-Qij@ zD$z2u{wbl|gm-y-bMYZpM5v|AnJu_mY{yXSQY4FBm38BM0|3~|ZYGf9wE~8dnnYKy z+?2Z43FO2#H%<c2^j+d?Se`<FCqC$rpv69r>hiznr^}a)@RA~A^=s!1jmv^0QUy@* z^(-W|hQN}0I=IJ;W^O_0h9uvDhXOs!vhfvt%sX^f)*LBcl{Gq8==GuMo@b+y>-w9> z3rF+HENm0?JH>`m7Dk&<6nbqc$@79F#mNZ;NJR?JDB-O9xif5PHo)-V7Gn7HvFzm+ zt_<TJ?+Hh}5j|)hZ;Sp}9v`o(7M|V!Cy^ysnWc@vbHs?@M8Y^mZR&WU{e5A7d|@j3 zdF^%tdtx)dmbSO9sFbqCs2ZtGwUb?(?8peyff+HXi;u5F-GVs9if+h+a2mwgnL#P7 z1H=j_Kqes4=ZlQQx!ZSIv&S3BQh6qKF^h0UVP_3wlYpnq=I0M=VDvA>Q0^@`sy3g} zb=2&Cg8$8Ywvan}F)AV=f=u(V5Z1TGs7DoS^O-D<gbaCX>+A%ldvaHEs-hW$V%Hr& z&$a=W{Q+PXD9(_rWoI?U+CLSx5bmcPiz?Xm#aN2y>s#S{){~JoG&2?#UHNTZRsx+c z%dW~>Y<;ZS8?#$UI*IS}a$>tv(m-v@Rsa96h0leZY&y?z_Al+vq6~|JP-l+D>&7=} zcD?#^9<c-Y@@G~1H{G_|hMwei50}=*>g1k{`Q5;4Vl?6LSeH%3y+GNjyFx&{i}~FQ zQ>{Y4d>|{Dnk}O{EESqp?_LM{!A16C@C~cmM(a%*^4j2Trl<qyuX@MTQrH|{Rp+Jl zf!>1L3JyC)ao(@I6u{LQK(yOt<qol8%v*a&qY?$`s!4H;fprDfUxs&Qy|f?FpCLa< z`=^Odhs4%E@Mou^j5CuU7hUgiW<R0x4}MI54`OF1_<hoJBUs0uPnuXP@_o;~^V3WK zw1KB!fW)gouh8l;zI?m7By1PU^3Dh)&FUdBsuP>M?fHLyZf8x^f!xVOpJFV<+Hj`j zWjg&m-owh+fod)XTRGFh>xDI+HC@{RJ`?~{2H}OT!F1q_ojkU;#LfR2bj>z7oMI;? zo~d6ucPxBu<mx|GWc&pdtBTOgJiqGdJs8m7?6(KEufru|;LtaMlB%-L<)@?_#=-t4 z>$=m2MaW~cdbn>Q6O{w&0wN2&C%$2=bhq%|CMbBSWo>Q2E4{H}c<C6&@DQ^XG0`b# z7Z2wHi8fG8ngwT=WHvhI`TgWMF$HXv+pDiMkZxFYpM5c#_a_cA$1XP*y5Y2+6%W&2 zlRf5JxXi7@D66W<HZrBXF=W1@vFhf7z?urE$`6{RNOSbbh*lEjIpfWfWob1Mj!;#Z z91`B&HIvUW-w`GO=%c33miB0oyA~fDr}@BCohzNbSTPh@tGW~*(x#GDMYI`Z1amFy zC6X%_{Ikal$zfkzEmi=ywOryep;)sqNi9pWG%9FC6WEi#+A<RX3(QJ-4KsWoaLZvK zN-B3u*0@Xs6ZeAIk|Tz-Gqj;t7v0OT;uN*VkSTPV$nd0UwIqUR@U>};!BVPli+F?L zFY!!4A?94w=isLP?W{Qu%=s<3vJw?PEDs-H?OnP0LAu=uNnyzltYL!jf<Z@PS~sy~ zUj+HejIx8IiA+7+*D|Sw;%PNB1gXxHAtv$BJZY%P@}o7m@NAx~lSs@p8nLF$LLU+w zNLhN%M3QubhaNyMX>Z!n09wL85|oj+&$2ihD6M0_$MmgIoJEGi&IeXkAjjXY4D@-r zq0)_o=%GT@+Tt&?E1WQ*`ZbPpFf=xn0_9+XDfG+<hDH94nlt;Rr!AD+cF<}tW<t=) zrM?@FA6J9hjZG|tRCHj47l8%*H55>ov)9z0&IgsX<Ex{OI|s9T@<4Hxj!f{W;mlw$ ztTppOiqhZ2se-FDxmq4A?Ygx=0nqQ@T|{#%O4{R~?kJ&VHEvW4SaZN^g~=yJVriqZ zBh<ltQSJKxJ)eudWMq!LVSNp3r6Esj#;~_EyIIkB$}hIBP0`ai6=E7mjlKnlCkX&- zr~6u_{CE;DVM6}cLb-D%{si}MG5Jf*=idq&kBbUgkB^f6c5*+HIzL{z$HSwqN9di% zE88Vr#tu^l7Yx%d1^~iGRCZ#eikbU<*=p$k8|Cf(8yY>(;}ywHOa?4w{PF%Trhd;0 z2{V(wu~>{N*W!|1hZYU{wRzdQ<M)fR7o70%?_3NE1h}<+jS)zD{RTsjW8S7jnCC;@ z@DWP%h2D)U{bG^jD0IE~i>kvG$Lz=ImH?qP`j?i*V?Ye9i_lf~ZS~N9zp@GP9a#U> zvD$Ul{`bG1b6@3e`Z)X;jgQ3u{Mz`ag^wush=PwO_=tl4HzZums|sun21EAc0Ds?Y O5JBNb>EHi$`Tqc1iy}n; literal 4119 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 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.9342f0c31.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.9342f0c31.png index 72870ec0e30544faa6fc30a4ac501609afc458a2..f457547a6b67ca222010ed929ec4abfb7ff4e187 100644 GIT binary patch literal 5899 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 literal 4041 zcmeHK{acc07Dm&w9J8stR?{{2+HIztoy*8^4HX4*G_@w|DxucT8gn!WO&P#Ie5-9z zN6oRe(M{2;E%haFEdjrPK1{|*#Y#kZ1r+=WLi_?uzLvdq|A_s;=a=Vt&iUn>``qWb z&nZ2DiE-cVy&VRFxyQvuorJ+0vS2XBE3RAZmUeGtll^iao{Wivv4(&5-u`eRM#d$$ z+E1bD`4Sk+gBTZeIH90UJ9(ibW!6`?41D{E_nwMxRgqEdyAJQWwr}UQhu(iq`02+V zd-5b_{9TX4oqBcc3;L<VjYn)}#N;3D?@ag&;>6*kyywo*D(T(szUI<^1Eu=4SMM}K z-+x8E_03xK+UnKm!EocGDJ&4qHx7r0CIeC=B<F~_c7@Rib_RWQtLtYYutv_!2tbdu znc#xQiM^fbA%w|P5AfA;n!t3~;o`LbMe;uL%4<-#mr2#Mg5ied(3zjW(%r=<WlIy7 zN^Qw5_5~lsw@GO_J$e<&J<T&Dimj&8+OGM2(bBSziLw<gSS`l3yJ21GZVfmb%2W^R zhYGD$BS<&2bZ@A^r=lETJtbA#%Ei%F2Kq(m!GI}AtQNmipy^aG23cY4e1^+S#$x@G zaHN?k|NQXVpJT(9BE%gRv<68UcO->4G?jU#qgmaz+r+Iz^VL-`QX&p3yY#iVF2o1@ zGK2`q3(^;?3M)r_>zLH7@6(vawpTMf2PrwUVpLs>U+c!S2ZCOwu<_M!poKbn=QwcS zY}p7V&phqnKg^Zk*xwx&?~d=Fv{om-MXy**Np)~u2QS?`k7j;1nDr)=4FZ$TBE;s8 zSb<5lFqA(x2v(=<HIwH4q2*6JWiEC#WeY4va(ms|3-SO=Q-j<PZcGLg<$E3k4nN7j z9{ag`nCOsnV^P!AlXLc-W=D93&-v%qSWI;{LY~E~M6zV5yl!BSXXMZk@^Sa&(NkK! zPmUK=aiiv|hngMe!gZ65-=o<Pd=D++%Cl9ebds%xC?cR$-KhXigF$s^K0lr<^t%)x zvAmD}TV;r{gjI_cXo@Tv@+7=ySxe-tD-FpF0RLP%gM^a|)lHE6=%WhbU|<S@kSIwZ zVI{t#gjzt?{_YV;_9R1r@;qCHVBsET>8=2451!A~2Qdk@<w}rlY^7TCoZJ`JtpS0V z9BsGf3?Kg`u7jw=kCmGP(hA=c$?ts0WOd$W5Cu9u=ysr4T4VwPhqHvdrO2Mn;Daah zs|}|uYfU~VK~*{FO*GcUH$ne84QG#`XCZ`=BaZRIGV+g#JM*xwtGRfmCdODj#k}s` z;P$WRJ;tFfC4;iJFkWY%>DXFk8|U3NjWkclp&MmGh?gLFS|`Q3=#X<Y6=+>=L7w>S zLw`Q{V;n^J^$YE2A6sJBB3m5{^Z~YK>`OmhizCh6@t@ky7)|x6FMN`+#mlQXbiR)K zT;DE~zW?Kt?L}wquwSmgGDYJ+ECS8gmKd~F({B6gLvH@;FEND+jg9T$`cZOYZt$_6 z*G`{b8@$V2zPiYG9lrF8?69J$gd`ZGy9GZro{rF(4Y9(zXo1=QCG}PxXbrWQK|fo8 zO6bffP-RO}Ac~FWq<t3;8C=j=(7^5D8wFY^M@HaQRR)tkjM(cwF$eht(#YZzio0Aw zbC~erQOq=LP=W2AabLsL7Gv4lId8mq9dve+A{RjL^6DW%@_&kGXNE$L$yn>P6+s3i z@^XGsS3AoCl?BNtejiJUeaHBR`VK*#pp3j0Dm)b4J2J;t-K^EsEyfBrlx5%#Zp=sD zT+np@dL~$#D-R<&a$M8#FRp_vry%4g2|d18Pm_|>qZ|f`*J`BCGpqp~sBsRD{Wi|3 zZ=2RZnGKv2ey=@6gwB~;b(4MRT+jIB)gi)!%AcDcyPRpW61W72WW(<o2idoOS)F4O z9}?#|3)VRVR+6R|3)Ve3hFr-+;~`aLu4ivfcT4L<dt0cNUJ9Dtx5em`b217S*wvD4 z8|ci&ihU^^o;43Db37H8;6}9w$ieq|!dcxPo-{U{JS2|orz%Sg$Oc4PYkAkw3-X3) zk_a8WDHol<>V;@rxcQPev!P-!>}r_(GnGl%DU^@11O`E;>D%42sH${<|JtMFSgDgB z3%NeUqJ5mz!>4E)@24fPxg|FC4(glxhlX!`W}(}&pr3lw0m1p>q}%(s^s?Ww>3{{F z6yLij3OhJLqR4^@K7<gDpd`~{hXLpee(ZbMXba&nNm7G0T|_Q^cnWDf&=&<dxhjo% zVP@5l6KfGEo3jBFJZVfG<U#qArI9vhRKiT4KT5Y6Kpt--^ssn7jNrVb=m9Yk5`~?M zM5cw21B;4Av&z}|878i&bT_lVe2*o>F<`5U_Z6=sp|Ha7;tx*VzyEv0mg~E+uAFdm z-+A<&L*%x<MEvQ0^0Dc(Yv?8jn;`smn%ZQ-CKEQ9u&EB468V2OC#V&#$}kzV<wuD2 PbsQEKjfrAKp1t;OLu(f7 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.ac47caa61.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.ac47caa61.png index 8d7ac483d67bce11848c6500f134be2d1d61ca01..b4068950e0ec0ed152f781e167d8f2da0786cd9e 100644 GIT binary patch literal 10450 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 HKc4<S(+LQW literal 8302 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 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.b2982c691.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.b2982c691.png index 11a7add1c83bedee3943ed25cc5bd052f16e831e..bdb0186f9c76a84a4356286af16134d81772e21b 100644 GIT binary patch literal 6141 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 bIwv%DW>G7A4gtUL&7enMhiiT~`|JMz@7+wQ literal 4147 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 P9};mm>QK|cl*|7CFBfB@ diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.d0936f8b1.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.d0936f8b1.png new file mode 100644 index 0000000000000000000000000000000000000000..a6410be466fc34e0c174e5b6bd22bdac74d2afb7 GIT binary patch literal 10555 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.d28ef93e1.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.d28ef93e1.png deleted file mode 100644 index 3aca4eafa3aee5ecc5d0d8d6e01de93495b3f4bf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4629 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 Mz~TK!_~&2$7r@AYhyVZp diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.f8a4e3801.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.f8a4e3801.png new file mode 100644 index 0000000000000000000000000000000000000000..f881d5d8871759d8e18377b69bf0ed18bf17330e GIT binary patch literal 10450 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.2d9c94aa1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.2d9c94aa1.png index 9ca0af24a201975695d009b6057339439fe8ed3b..7e4d7ff48feb416220a468b7d22f6876bd856076 100644 GIT binary patch literal 9102 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%0AI53Z%2lZwn{*B&j=`u*zDm#N)apNR6UsioJA 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 literal 6522 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 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.3df2d0281.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.3df2d0281.png index 680f5c3fc4f005f03a0b24c776318143ba373013..0943af3393e0d726bd52ad49ad6502b63dced1d1 100644 GIT binary patch literal 6138 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 b23+m&@kh}MQi19G-U50m^knn#Uw-=!dvkrv literal 5299 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 GAO8Wz@A~2Z diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.4b1dc45b1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.4b1dc45b1.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed6be064f9dbf14246de834664fe04b7b7782cb GIT binary patch literal 5955 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 YluiM6a(^=&gqwkX2szStDCWxl0kp);qyPW_ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.6222b33c1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.6222b33c1.png index a4d911d734cb6cd7bdb15dd123d38f79c9a225d7..1c13a251d106b8fe79fbf1a557ea87a74ccedb92 100644 GIT binary patch literal 10165 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 Jzj^=pe*qUPQ}X}- literal 4826 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 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.6a3b3d991.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.6a3b3d991.png deleted file mode 100644 index fa9a9fa3187c013578369b790720fdcf7996f7de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5760 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 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.8daacc0a1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.8daacc0a1.png index cbc62ae84be1eec43483ef180fd9411093aca1c8..be59d35a6764777a84760e78df5f90c89cda261e 100644 GIT binary patch literal 9995 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 literal 4672 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 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.9374178f1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.9374178f1.png new file mode 100644 index 0000000000000000000000000000000000000000..771379943b2c49e4ec6357a8a256248dd8782485 GIT binary patch literal 8275 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.9db7989f1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.9db7989f1.png index 30a63b00f76161115e86c0423136278da5d5e778..0cc62d063fec65eb15f5a6213b3b016f61683772 100644 GIT binary patch literal 8548 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 bg`QcQ+b5bg7Z1LafEXK?UoO@E_0fL;LXY=E literal 5582 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 Vf0qLJPu6YkUk~gO?D*=^zX4(Q>%;&6 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a21b79841.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a21b79841.png index cfe365b66aeae8249ab03ecd93fad08b84b5cd0b..a320b7bc35e38dc0cdffb4874dfffbf9ec2e0251 100644 GIT binary patch literal 10067 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 literal 4685 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 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a98cfcea1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a98cfcea1.png index 622e896b11dcc60051718f6a325f73c630dc6afe..97e9ceafd20a4bef646e28a469309c38c99f45f4 100644 GIT binary patch literal 12495 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 literal 6227 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 AH~;_u diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.c2e735d01.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.c2e735d01.png new file mode 100644 index 0000000000000000000000000000000000000000..5a01a665688d7c60166210006a43774ae694ce24 GIT binary patch literal 5762 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� z`(!-seItu7t;92h-bt-Zj?GAMt!w~lndpIfX8!gJWtm?w>56>+B`pi@xeuYjAlkx9 z*(QOW>)2HcPblU${u5c8mV&SrL~-|DP$=0-_UqHZsY$0xCW2=P6vZzK#TWU^%5dcg z&HXUcJgG2}E%=5;%F!9iv2g&xK2=p5c}I2NPB{M=BN)bBw9X%<rG2%iGGDL84E4#p zC`r_4f}W^eF>>9*eje(Wofz|Em^b++&s=My6m9QcyU-|@uhf9j)Z;`UKYABGvR~+6 zMcJz3X^L$Bx%&i<FJj;dGSS0d`B0U^`}j`cXX%q|@iXo=O~gna(RCaQit4Lhuv*!2 zc4j@)$sGLc?@a~gbpI1QrM<i~#rigWYK!UdK6~WNXfL`ZciDzM8AP4j(dY@OH?92- z_A7(nDHv0+(B@p2yp?5{G<HP+3HwGq+y&Q;E%p1E;3&g0x{k-wA0@;SjsY$j)xC0s zI-ReLH0EP?^r*`w#dDUopy9DeU>4^nup-OP5bLzWGpgnKsh-kM!f<*AR&(v^BG)$j zSFU{~`Cf0|M%WAyZFA${(%C6ST{OgnMTFHj%>@9OJ<}Je#;YS#<To2fi_HdVq9M5T zX>Y*R)TDlwUX+chbL1(GvEq2NT`+rFlIFA!g6XitnPQ2hvjIO`sXE@d;W)9;m8R4| zWUKs-;X5$}Y5Mi5BNW|2JyJ}>%2D9e?50r>QLb}k$I;G#DlzA^zpcW#EQPw^SmCTf zD`~xw{Cw)_)OvM|wZLZvdIx0Gd0icT4#Ea}?Pojq@lqUazkT9~HTr4_$W3f}e7)Zz zVy4aK%f1K5_A3mRsMO@Sq!Cy5;V#EkG&a6J%jPh53?LR>Xigux=`C%jDR*CK0V9Yx z9ti7LZ~)o%0OKbRolF$Z`#T!^kQc2kfFc*9^twB%=f*u~WiuZ8AQKg_!Nen&q8B5p z8&sQ11BX2U$t9&+9o1H^nkZ~P>b@mWa^Pxvo+}9k#Y5;&b*t8dFuP)@s_fzm1MxJC zbYTYqfa5{>6AlJ*sUsQbfw|d=1=fD380!LB<U?8m1AjX$N0ca(`F{+jkZ<ouZhFdK zCO@Cl?dZ>u15C|gZ$zCc{_0$J8BByR<dx@*8|(#8a&A#?Y`9VV;3{*aEyn45e~xZa zCDhmOuiS`xc?nKZ(!%I*x1L(fLZA>&_5D#u&F%_hX~VGN29irJ=|V^Z-v_GFyHRTb zdDZyCSgVfXh?fvujBG56U`g<gwUoJ@(58`gFTbd)Fqe{#$ZG5G?C3%W+Io*4z9fuo zbyy<yHaxp~6O@Qm85lvJ9h$&pcwz!!VGggjADgn$BgZX9R_u=vFPpM6_~ju;PfT&( zER!Ohk@=G7t%*LK+Z6-$*+SeBjHF&dp1mpQg$g};HsBSK+x5Tk?i8Z*@E~4&Z}BRE z)7t5E@^_*eu}{Hs=}Bq;y0y|1(p{2n+h$H$;<5T2!~NIIzkRX%hyVNW6#csN_y0O~ z`H0K%-xZ(j`EvUw<0mJ4iiA&a_{0gHi1FJ`*}+v5{N#lHS57br1D}452cB(w?5ZQX O9o!eUm$B#6mH!37jwfaS literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.d1ba354e1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.d1ba354e1.png deleted file mode 100644 index fa9a9fa3187c013578369b790720fdcf7996f7de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5760 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 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.dd63c52a1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.dd63c52a1.png index 17aa1fa9e61a3c83a3924f4d7aacb0e28c855474..6c39e8d6224b8cf54794d3f4a8922148c14e1209 100644 GIT binary patch literal 10279 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 literal 12885 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 PL)cI{M%rbUuHFBCi1;mD diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.f149b6a61.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.f149b6a61.png new file mode 100644 index 0000000000000000000000000000000000000000..51cb4ed8e97e42492cd93d3933a46b0ddce894eb GIT binary patch literal 8080 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 HmtX$}&W0=@ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.fad43fad1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.fad43fad1.png new file mode 100644 index 0000000000000000000000000000000000000000..48fdce4032cbd658f9abd02f68b6f4445ecf8873 GIT binary patch literal 8310 zcmeHN`8S(uyH44yRzG|l)JD;Ps&><f+C}N0G`h`V9;yRvQnOGoq_gg7tD=U+*qRf> z90`)5YLiM3q=XoQgor8T$a(Xvb=ErP{0V0@zq~8&^gPe~+}C|w*S+4qZLG{?q?M#W zAdn3Brl}nWwBrE?B=KbTF5pbL(pUj-*b!o9ehpMgJ<I|A{1$Qz?64d7MeV-#8U#85 z0-IiSc%04~ig5oZV2Kv=|1AEJo&6-$Efvj16LMYb(9AZg?Lj0ukKI-`XLqZ_Fa$i4 z>Xz)Nqm5R*kW%$Sy`$UWGI}hmBFE=5A^XXZe`HtH1}_*$NEZq&2@uAr#;R03hC8F> zY`xDU(Wew=?&;~72?9+%-exI-Kqn7B-37|otqb~Hpmt!oc<>K~h+avrzP&0T{9Tj- z8!$l%3zFx&HX<LPwaUTeu4R!=srgw#OGe#Tto*<-CI2)Pf5t!S(5&ak%4WT@H`#53 z);_vrjR+%>);N1r-kKC**w|AT$B)m1&-M=94w?07Nq=kmB>c&M11#sCHwil@`-7rl zNhS*ob{Fh>k5oPV0=?08UG;|lw^zrNKcYU_-9|j`+=Wqeu&3j#7-jJ0s)r*ds*C!} zT2b*2zqU&1<_Qn>K6Eih=>{#CMu*K`5EKsQID~&=KiZ1OL0v$_)srl%FJh*h7SK4n zuCZ{nKxw1jhNoKLc9nFM+>eloGP<ITsp(V@iQ*_!d?4+b<#t#Fv5$k1Cdo^?d;R1e zTB{7c*}V|b+8Z2mtK~!sv2-53#+`{J_3`%MZ=r&hg5e_6rhr2>fxCTm^I&z~wNwl7 zm?KryC5y@i{W3R_xUkk7!-<)B?a<?@Tk}^n?yJkE8WRN%EZ?AWfe=6s_tkc4z8$_c z%#Ma1W1A*0aJ)Ll)f=qv-kJ5RGa~Z<-s4-onU_O)DH8Kpw4>m=WF_p+T;~p+fs;^q zg^_&MEtK{wQul%22ydOK3SJNJHW9D0Gw=y(dim8DeGEQT7tSRcZ=Mkp58DIl1~%@5 zw#b`6?B(V6XZTVWqu3OEa<LOR5>kn$^_3+zzZv|1zN#k(!o?5ezufi00!3u?Ks*At zh=#f6rw7(L&F<gxy@@g_RV$*&MVI<LuOVaxN=GW#DrwqToix_oM*p$*E7F)|#O4g8 zhC}poOLyrhs`JmYpl%H%yDlmfHUvd_Y)bh0r)hx^Ido;|d0895S1x)W22&AIDQL`; zQwK+muUMcwA8meVl5B!B;-7tI={eeMlUgZzfSZAw@E2X9OiCfzNE*@^`(4L`C@JdU z&dYn}5{6ApBHk|9On<bo958D0%RCXF-*p_T)&(TbgnpOxb4R&TW$6ZuaCfFd8yTg} zNp8p2=C4Ptdj)xT916EBVL&-Q*7xGx;}0{xoU+GGvjaRG<07=H08oJKfVV<Ex#s=j z#9IoZ(lhuhE!-Z@#owns58xO&XK4f1VUTDuI57SP#&2ifYuV4OvP^hHW6d+>^qRlu zGV}oP`3TE-J7AcH3#=A@3Wr=lb|upX{0548?180V2!>OKh|yMADqDZOV?ifeV;IO# z?G@5O&kqNIC(G?<;`NDh)a!6oVNP{yAF(4bHM8(4R*EpWkTzWqb9zHd@3f$T;wczE zrT4h<DXeaLW7JE~ou6{hK*M3`JPk~oPj#yZ_?ugAYpyzO^bvSEz5cM}(0K?X-!W(i zUowgB9{tw5GiC)*$t4Ki>#(+gTzhG?r+3NA80Wa;)^;~N*64dgA*&cM5OK%1eD3(x z!lf}<>uE!MH(R}&!P3_l`8;3^+`uO9-aVx`$9>~ZR@}*zsF!qK8uVE52X{BVc#g{T zfuO-Cw-@`n_p5_tnw63f#}9PxKbWF4g^+hUJXJ}>_7D14(5x62N-}MG7a`T$RtRF7 zGR5YzaRshM?12>z&G5ESPUlxM$==OAmIjM7Rt)=#c@AofBd7`|Y)hsI*ZK+l-0I>K z>itamu>WRSy-~x#tV6i#dt!C7M?qkewPKdU%*NbwxYAbJ?Hj#syJxADNlA&2BeE!6 zsPH8G?QCJA<t`Xdvu>!p@0*7=SC2g1(tP@f=>vVd9H=J?EB4lo)jF+ttFne>H!L3w z>8mTA!-bI5Ia8G;GhaVR`ctU?P;K0y=}i8*om;#gWU3`Ye)>S6ZHc*f>V|bJt+Ke{ zlLszZ+T3fo`R;+<wIY`ybUFN$fi^ccBcA6lUiKZ8>q$XqZ3I?r=+EMkA#TusLC+4B zvD?DK1y^Rc-)G9u;X0S0<WT!EuQD^ds&;I_b$&~IL6VfC1q#5DhR5R{;;ebstR#*O z0JKh+n-~4{^ggIdb)Fesv80mYtM_n8T@Qgx*l%{?9}4}}InBF05kbB$rC!YBg`H_D zcpLBIpH^fQXQ1VC^R3dWiw-_k`D1qDy1-MtGDX<(@OQnqHZpXMO5j%1#=Vy_@hsll zgM9RUd}>i`cjNZlW_ZSCB=YhqR-JRN9jqr4z`Yp1bcBX$TVK+Rdyrp<w&m6q=BUCp zQlkzVqsbv8fGc{1lXEFUAI7KN!v>Nc`qOvmD%o&_)V-^JI9T|+G2TF`5&DFWD_bG= zq4EucaZ9J{j61Ks6~^RkF#J65=7Xi0X(mPDo#K729sn5TPU0PLHItuE&fQmHHXVDx zWZI_bs3j>pIAO*$B=DX3L-4AG^n#pLFbPPnJdR3!olfjuvR(S+!?il`o6vM%PSxXG zDvQhWp?)DR$xfYd%3h&6iB<Ul9bT)+SKS=W<YuZrG{x?dPTTJ|y<_y&%bhPC(uP;W z0Y&wktH_yqJSE#e5L$L{49sH>$ejkZbeFzj@g<L`TK^REC(LR%8TawWLA=5ND{AY{ znnq^m-IvN5;Nx!b4%67rFIA;Dj`%dk6?YENXnSS>bI93p`0`=~%oWHSfeXvLD>kQ> z5a#<%gs4}4$oSw<8veAwpOZIVG%Aj$PgpvK$SX+-fc*IIJA;4ZsP<I~(%ExjWt`SB z8fCK)xnAcnI>pumTUgM@#)kTJUyZ0zNlSjPF#nokr_G?v2{P#F=;XthviGNauo0#q z3^i9<fLZj65Vg#z$bfo$&r_Bf-V4=JWnr)KXUG*_YF5{$C0_?>L3+Big;n@qpn1`_ zI|>d&yux@ulz@opxvup|3z0o1sUbV6ofMGpkv!u)&AToSxq#_>j-xt_TIoH5*4AiT zHuL~1g1I3uH3r|NeYm4Xx4tMWYrx}+->Dze1BUXm3bSu2)sqCLu&RQ{=?o9P`@)K( zl5Ak)k1FElBw)sj((_Z;jb#0*9-ywhF;T<Or>swF8(P|J`=>TZ;5R4yAv$jT^1fQ4 z3=5hY0!m#Ck^W%<@H0b{vC0&}`A(1Qip4ByAgGEl-mFyTJm|IscW=`-A=Ob3_9R;j zNyd01fatD?9DfR*k)6flk9>c#&P+llS!5rLT~cHz-qD<DZEuojQfG@6Jc**S7c}X9 znNFmL8v#gBO@2)l<|tk!AzU#aI%49Mm^U^`rz890Z};LHyxgSx;Avr;1J}TZZyBEe zV+5|+cn(Q+SmIomv7!ebzW#Bc>u23}Zn^c6iD1#LF2$n0G*b`><rg)eNpK=8YcjRw zuI^lEkpGg$mN}Ahe~QFwGFJM1Ozq3^Y$#icU!nG{9z`!IfD}E-lb|<CHpRg==Yw}n z?Oomzs$vQ!!6F~`Hv2EF8AJim{=>w4luUn7_2L?y67gu%PQhgT5+A>_PNDX_!c9~& z{qvQqD?mV9P@$*TA5rH~4gf;&5m726%sz)^zxDe@lv=NqK=NA?ee<N?b-80xrT@~L zQhTzrVX>VQdY+xj1TycD>H2PPH%1Z*sHR>del36@St5ML>n*mp5G`18N*VgMwHyt$ z@uqQo&j;RfJQli+!HhV4?u;gq*+i;u24k<Xmrb^HHZc71#DHcsNwl%QghFq&PyBM? z;hi2+o<aF7TFgT6cjRSHxxPpnnQO`iVT#n>HS#qM)#_Y2?gB&?3uM*Kja{eoDn}ZL zW2)+*s&%!_P)E$M=p~bL<8McoW$EG~CA}zfs&8DgQ@^Z}R^{c7BqlD!L;_ij?(;@J zi+X-=TG@Go1n5uOZO`oi-M~cPoI@i{V)RDQ<6V!XPX0XO4BKuniy#KxsgpM;{t!B< z4X=FdV~`Ns#H;MaBHc5PLn4*K%R1vFvxATF?&lFoCLFj$GZhO|NrRU~9_2N#A@3dD z3~j203xhtB=?Jim<BDt1IltBi0BBvS!V_i?Q*Ij+p}UppqD|ub2}Ef}2r3sK!8Z*J zJ6Jmw;cHNU;qaVG5tjzw!6}$z92Y$~K5uJhh6-K`cBfz5t}pLoPfmEI7Wmj$X575= zxwhv|e1xO07URrZ8zspN>8JsK=k#-kgzgTM&aCvB)`BKP+pGGg^&I><_mqwvmG>3u z=%1V{g!Rh@%+wFBWU+8cSM?4~C_79aaN&&%7({PZiGww!b}ii&DhozTzW4nP1-o@y zN-E3rO#bX}jeJl7=+#L?-g%7Rr1lWm!FKN#-SQ#R3}d8gbDxYou-&SKt54K9XW-jk zv|DQmhc<c3HaW2l04p0C&zFlCAT+K3*KcMh_drL)2-e%}m;F9L1%7>9`C~>}N%&nB zqz=vNA$ud=xtrJxrGzm6*f?m`zPCGXzs&0j=&eDx2?*6RJ!a-u@eDSL+?~#!DnvGK z6YIVYLG4T4vEm}c>ax3`dJe*_NP=jWp>wya&km{v)eSfXxE(PP{als1&%RfLuz?HF zTi6rGG~|`8byHXO>uEB*J=*rYFqboefS+Cw?et<K)ea!KSNQtCKeD|aTrUwlY*6(S zlp%WfpQD2|f#4KMQ1<TKO<x&jR)>o0;H09;UHU4;IOMvk-!KfTz$Bdn^ftO7x&{{B znKWzC;FWO*-u?BvWKu%L=Pu>N)tzz#zt_22&tZ5+5(Fn&f&-Nd1F!A_7_KMNKJw$I zM3;%DePS}4Ztj8_;|ZA$wqMj!9?vwSuD%hjhfE2>_)U=lAi@*&-_6PD!Y}X4FLLR| z>sdC6tl-)&a^_f0dOm{Gu>{bnm<y^VN0b^6d@NCGE&oo*-oTsPfYAt5UwF;h-u7c) z$Le5%B@xI^Q|R&_=er%76eF4S(ANl^87(lzm4xYAb{R*!oS#*podRn2trhLAPTDJ? zKa-uWql8>tvkS-#G06xqf$kiZ<_?#U0Y{q6q9A5w3o8-1ZO0MF)lCFY#7YD7OK0#_ zLdRu#h;nA#ykw+7sMEVToj;g4e?1jeYsinO1^2`6Pu|09cF##K@98PFs&r>W{}f)Y zP9L_5FOD(ITHZE&j{aA7njx!xUh7F~Sdp#saXh@lu*!<hj~Rv&#FOV36ns?^CdK40 zgvT@bmn+d^7|D1LIoiv$*Us$Du~dV5sP-drff&{C!lOs23cB9(-|%pw$ks|kRL3}% zP+TB;IpSt?01qqaD?6PO;AzWeH+V2NRwK7d6K2dk7a;7nn?4WP!|i*<bGC4EQjevk z8~phMQvWG1HWKHU#g5j6ZT`_QU`){yF8IVN5C(x%ub#%m=^~mRyc0r;0X-uRIc?E# z91R=Ie7Tb)bVrzYMv+TIoTj-V;!(JVC;)jkvZFTOm05`z+kiE)rKl7BXk?7yw3+%% z?Rjg9<}8ic(FgcqAZB(GJBWbS1JoVBMTFUPO@J<wGBZj$D?SH5Hh7cHhyF=+u%Xta z!<c|@l&&u^N{O&)rKvapjR3&j;bxwTT#HWwqJ$7ZkY16~S=J5(>cxKgen=y(N!~;y zL<6{B5HnY6blFJWQ>Xr)Xj*}tH7BYs0uZSR>q){N>3?bXs@FwtozXwzWKLDJH6^@0 z77%{PJTV^g)?ATM{-NpK#Jy?f#dC}(*8TchRqui6<}(7i+dd{5=x?}}`zNNQVDGlI z0%>1V_N44sc=@-y?^@?B+cw5DrU;$YZGM+Cb7;)!AX|xUNga?q_07bT`QDE%6=~dA znpn5&o;C&4qHn{IOQ4jQrS3y#AN9s#2g7cF*S)?3WhR5lo;h`{(c9W6U0u037Jpeg zyhgr0)_$Xmrf_241JKzwDKenj2miYjb0d4l*TOqe8{M|4XH;6&SCuT4K--0fQ3pfL zn*lhp8)<(XypLGY?k5(u=nlVpg}JCkK@#tlc@QZx2h4xR#AfYBo?6^M92NXY-tolb zaICuJpiy~eWrWzYh>eBwl~nXAeMp0aK>Ej$+hYOKsXCC>bPR_iw$K6Qa!p_;I4zGp z5Udr(8uM2Pt`2@dy%fnO*wGcsrqrY7{HN}mobdqcI5;89h&YZPs)(zPqEVE(pf7T_ z?9)kQ3p*|OXFYh(n#54=uTQW^fXmE%tDfgu;FZYU@wGRQDHY8r9H4qXO+Wr`vhtGu z--kTYZBw$GH(zi&Fa<Xv6}0XJzsArCJ#Syy$36x)^~!6O;PolVFe|Kk&7wR*(5{a= z8ER2TGFr@o5!w{nFhYbM+-2|vEo$K{%#>d$n#Oc$eJ%~TBl(s8*?DR#@ck9{pC=Vk z?15qKCf<RM{MaFI>#`o=hS{g25kGDfocvCF#WL=Jb17b#8#hR@UYE;P_NZ9=nY&uo zfO}E(8!wJoAntW+9^>10WTQ)$E&y+C)b0dqeF(E<9{?qp>&nT{MhzACdIvRtX!Skj zavuap`9mhP&KU~febX{IV!a@4p}o4OjB(VjYR@t`4arj55SM?rp|37zOLb7<@-f5< zW|BSAS{t5y>pZujnLZkZTAQgZwW>BOG8d|f?ct$0&%*Zmr(qVGT6vylK<?K5Ppwol zW>=&KT5UIiwk!;|i@oWV#p*K%cBWGmzz!`1Eil*Ehj|8n8-cbC4N9nts08-rTrx}w z>tW9{l#b3Uwlepw?Cq<?eSa93*vRm!1C2o4dz~82sJ8U$@kC}w9bO?N&vp=AvgZBL zQPsI<YQU%<sloA-)^Tt=j&L?6BV?`cNBWPMni7zWd!DK-yW2MXc)R&c)Fdyx;Hu@W zq9EsSyLUu)r544}^&jgOxOHD}n4OMvK)1G2=oG#AN1|6kxOGXSd)7cI!7m?ZtBKo2 z(BFD%eVBVd8lzes5h7fy`L=&Nkmgo2D<|6h%Wyz08kmJS02WAWi4Tx6k7yx#*kYI5 zx8lNWsK*2JQS^73CNWypwOdcoVZ~xb?Dc^>ghj-&OP$(r;ZYud2;u_Wy7M9D;ciJx zu~q9k?4I*J&H&2U&U0$;0NJ=Y>3y;A&iS*opaom(<br=5;AF@R2D`_>^X}!J)H@9U ziUh;YG#FI7|5d#NDCbW#prI;8@kt38klB9#ens+$?Qcq;x5qmF572+z@+&U?X~6%= zmp8va@;@cOugdVNGW@Cxzv%OqX88ZEr3`;wJP3Rna8Ud$!2cq_9+AZL*w&pjcKLz8 PbrAT5m1*g<+t2<BP`_=% literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png deleted file mode 100644 index 42286f77aea68b2ab541844e92d065ae699437a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4522 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 Sc<KLJAV5IaDf)@?SN;nUjXuu+ diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png deleted file mode 100644 index f36723fd32403ad26d4683157e1858ebdbf03596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4695 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 O0DkV`S=x8E?*9h>wZF&! diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png deleted file mode 100644 index fc37a56c29245da1207fc1abbf499761a1ff667c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4759 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 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png deleted file mode 100644 index b3849a302e280880002ec08d8b17fa692af205e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10727 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 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png deleted file mode 100644 index 336044bbc03e647127b9e19a1e6eb0970b82dd92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8670 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 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png deleted file mode 100644 index 67c90840eb2d31f9f844a68fda6398104903956d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3841 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 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png deleted file mode 100644 index 3e942d5feac1306706d98f1874b82dd5231f1fbf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34430 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 F{}-xN?EnA( diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png deleted file mode 100644 index 95535f9efad3204180da0fa8a7a796cfcd5f6815..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6471 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 F{|A(5sHp$| diff --git a/integration_tests/specs/css/css-inline/change_inline.ts b/integration_tests/specs/css/css-inline/change_inline.ts index 7132111cd0..b74e3e9e03 100644 --- a/integration_tests/specs/css/css-inline/change_inline.ts +++ b/integration_tests/specs/css/css-inline/change_inline.ts @@ -1,5 +1,5 @@ describe('line-change', () => { - fit('001', async () => { + it('001', async () => { const div = <div>should be green</div> document.body.appendChild(div); div.style = 'color: green'; diff --git a/integration_tests/specs/css/css-selectors/attribute-selector.ts b/integration_tests/specs/css/css-selectors/attribute-selector.ts index 0ee05e890e..53a6716866 100644 --- a/integration_tests/specs/css/css-selectors/attribute-selector.ts +++ b/integration_tests/specs/css/css-selectors/attribute-selector.ts @@ -1,46 +1,46 @@ describe('css attribute selector', () => { it('001', async () => { - const style = <style>{`[id] { color: red; }`}</style>; - const div = <div id="div1" >001- Filler Text </div>; + const style = <style>{`[id] { color: green; }`}</style>; + const div = <div id="div1" >001 should be green </div>; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it('002', async () => { - const style = <style>{`[id=div1] { color: red; }`}</style>; - const div = <div id="div1" >002 Filler Text < /div>; + const style = <style>{`[id=div1] { color: green; }`}</style>; + const div = <div id="div1" >002 should be green < /div>; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it('003', async () => { - const style = <style>{`[class~=est] { color: red; }`}</style>; - const div1 = <div class="t estDiv" > Filler Text </div>; - const div2 = <div class="t est" > 003 Filler Text </div>; + const style = <style>{`[class~=est] { color: green; }`}</style>; + const div1 = <div class="t estDiv" > should not be green </div>; + const div2 = <div class="t est" > 003 should be green </div>; document.head.appendChild(style); document.body.appendChild(div1); document.body.appendChild(div2); await snapshot(); }); it('004', async () => { - const style = <style>{`div[CLASS] { color: red; }`}</style>; - const div = <div class="div1" > 004 Filler Text </div>; + const style = <style>{`div[CLASS] { color: green; }`}</style>; + const div = <div class="div1" > 004 should be green </div>; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it('005', async () => { - const style = <style>{`[class= "class1"][id = "div1"][class= "class1"][id = "div1"][id = "div1"] { color: red;}`}</style>; - const div = <div class="class1" id = "div1" > 005 Filler Text </div>; + const style = <style>{`[class= "class1"][id = "div1"][class= "class1"][id = "div1"][id = "div1"] { color: green;}`}</style>; + const div = <div class="class1" id = "div1" > 005 should be green </div>; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); // error xit('006', async () => { - const style = <style>{`[1digit], div { color: red; }`}</style>; + const style = <style>{`[1digit], div { color: green; }`}</style>; const div = <div> 006 Filler Text</div> document.head.appendChild(style); document.body.appendChild(div); @@ -49,7 +49,7 @@ describe('css attribute selector', () => { it('007', async () => { const style = <style>{`div[class^="a"] { color: green; }`}</style>; - const div1 = <div class="abc">should be green</div> + const div1 = <div class="abc">7 should be green</div> const div2 = <div class="acb">should be green</div> const div3 = <div class="bac">should not be green</div> document.head.appendChild(style); @@ -59,9 +59,9 @@ describe('css attribute selector', () => { await snapshot(); }); - it('007', async () => { + it('008', async () => { const style = <style>{`div[class^="a"] { color: green; }`}</style>; - const div1 = <div class="abc">should be green</div> + const div1 = <div class="abc"> 8 should be green</div> const div2 = <div class="acb">should be green</div> const div3 = <div class="bac">should not be green</div> document.head.appendChild(style); @@ -71,11 +71,14 @@ describe('css attribute selector', () => { await snapshot(); }); - it('008', async () => { + it('009', async () => { const style = <style>{`div[class$="c"] { color: green; }`}</style>; - const div1 = <div class="abc">should be green</div> + const div1 = <div class="abc">9 should be green</div> const div2 = <div class="acb">should not be green</div> const div3 = <div class="bac">should be green</div> + for (var oldStyle in document.getElementsByTagName("style")) { + document.head.removeChild(oldStyle); + } document.head.appendChild(style); document.body.appendChild(div1); document.body.appendChild(div2); @@ -83,9 +86,9 @@ describe('css attribute selector', () => { await snapshot(); }); - it('009', async () => { + it('010', async () => { const style = <style>{`div[class*="c"] { color: green; }`}</style>; - const div1 = <div class="abc">should be green</div> + const div1 = <div class="abc">10 should be green</div> const div2 = <div class="acb">should be green</div> const div3 = <div class="bac">should be green</div> document.head.appendChild(style); @@ -95,9 +98,9 @@ describe('css attribute selector', () => { await snapshot(); }); - it('010', async () => { + it('011', async () => { const style = <style>{`div[class|="a"] { color: green; }`}</style>; - const div1 = <div class="a">should be green</div> + const div1 = <div class="a"> 11 should be green</div> const div2 = <div class="a-test">should be green</div> const div3 = <div class="b-test">should not be green</div> const div4 = <div class="c-test">should not be green</div> diff --git a/integration_tests/specs/css/css-selectors/child-indexed.ts b/integration_tests/specs/css/css-selectors/child-indexed.ts deleted file mode 100644 index b2396dbae2..0000000000 --- a/integration_tests/specs/css/css-selectors/child-indexed.ts +++ /dev/null @@ -1,188 +0,0 @@ -/*auto generated*/ -describe('child-indexed', () => { - it('pseudo-class', async () => { - var check = function (element, selectors, qsRoot) { - for (var i = 0; i < selectors.length; ++i) { - var selector = selectors[i][0]; - var expected = selectors[i][1]; - test(function () { - assert_equals(expected, element.matches(selector)); - - if (qsRoot) { - assert_equals(expected, element === qsRoot.querySelector(selector)); - var qsa = qsRoot.querySelectorAll(selector); - assert_equals(expected, !!qsa.length && element === qsa[0]); - } - }, 'Expected ' + - element.tagName + - ' element to ' + - (expected ? 'match ' : 'not match ') + - selector + - ' with matches' + - (qsRoot ? ', querySelector(), and querySelectorAll()' : '')); - } - }; - - var rootOfSubtreeSelectors = [ - [':first-child', true], - [':last-child', true], - [':only-child', true], - [':first-of-type', true], - [':last-of-type', true], - [':only-of-type', true], - [':nth-child(1)', true], - [':nth-child(n)', true], - [':nth-last-child(1)', true], - [':nth-last-child(n)', true], - [':nth-of-type(1)', true], - [':nth-of-type(n)', true], - [':nth-last-of-type(1)', true], - [':nth-last-of-type(n)', true], - [':nth-child(2)', false], - [':nth-last-child(2)', false], - [':nth-of-type(2)', false], - [':nth-last-of-type(2)', false], - ]; - - check(document.documentElement, rootOfSubtreeSelectors, document); - check(document.createElement('div'), rootOfSubtreeSelectors); - - var fragment = document.createDocumentFragment(); - var div = document.createElement('div'); - fragment.appendChild(div); - check(div, rootOfSubtreeSelectors, fragment); - - await matchViewportSnapshot(); - }); - - it('no-parent-ref', async () => { - let p; - let p_1; - let p_2; - let p_3; - let p_4; - let p_5; - let p_6; - let p_7; - let p_8; - p = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_1 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_2 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_3 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_4 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_5 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_6 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_7 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_8 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - BODY.appendChild(p); - BODY.appendChild(p_1); - BODY.appendChild(p_2); - BODY.appendChild(p_3); - BODY.appendChild(p_4); - BODY.appendChild(p_5); - BODY.appendChild(p_6); - BODY.appendChild(p_7); - BODY.appendChild(p_8); - - await matchViewportSnapshot(); - }); -}); diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index 085f18d3af..406ec70741 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -1,18 +1,21 @@ describe("css child selector", () => { it("001", async () => { const style = <style>{`div > h1 { color: green; }`}</style>; - const h1 = <h1>Filler Text</h1>; + const h1 = <h1> 001 Text should not be green </h1>; + document.head.appendChild(style); document.body.appendChild(h1); await snapshot(); }); + it("002", async () => { const style = <style>{`div > h1 { color: green; }`}</style>; const div = ( <div> - <h1>Filler Text</h1> + <h1>002 Text should be green </h1> </div> ); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -23,10 +26,11 @@ describe("css child selector", () => { const div = ( <div> <blockquote> - <h1>Filler Text</h1>{" "} + <h1>003 Text should not be green </h1>{" "} </blockquote>{" "} </div> ); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -34,14 +38,10 @@ describe("css child selector", () => { it("004", async () => { const style = <style>{`div:first-child { color: green; }`}</style>; - const div1 = <div>Filler Text</div>; - const div2 = <div>Filler Text</div>; - const p = ( - <p> - Test passes if the first "Filler Text" above is green and the second one - is black. - </p> - ); + const div1 = <div>004 Text should be green </div>; + const div2 = <div>Text should not be green </div>; + const p = <p>Text should not be green </p>; + document.head.appendChild(style); document.body.appendChild(div1); document.body.appendChild(div2); @@ -53,17 +53,18 @@ describe("css child selector", () => { const style = <style>{`div:first-child { color: green; }`}</style>; document.body = ( <body> - Filler Text<div>Filler Text</div> - <div>Filler Text</div> + 005 <div>should be green</div> + <div>should not be green</div> </body> ); + document.head.appendChild(style); await snapshot(); }); it("006", async () => { const style = <style>{`div:fiRsT-cHiLd { color: green; }`}</style>; - const div = <div>Filler Text</div>; + const div = <div> 006 should be green</div>; document.body.appendChild(div); document.head.appendChild(style); await snapshot(); @@ -75,7 +76,7 @@ describe("css child selector", () => { ); document.body = ( <body> - <p>This text should be green.</p> + <p> 007 This text should be green.</p> </body> ); document.head.appendChild(style); @@ -83,16 +84,10 @@ describe("css child selector", () => { }); it("008", async () => { - const style = ( - <style>{` :first-child { border: 10px solid blue; }`}</style> - ); - const div = <div>Filler Text</div>; - const p = ( - <p> - Test passes if there is a blue border around the viewport and around - "Filler Text" above. - </p> - ); + const style = <style>{` :first-child { color: green; }`}</style>; + const div = <div> 008 Filler Text should be green </div>; + const p = <p> Filler Text </p>; + document.head.appendChild(style); document.body.appendChild(div); document.body.appendChild(p); @@ -101,11 +96,12 @@ describe("css child selector", () => { it("009", async () => { const style = <style>{` :root { color: green; }`}</style>; - const p1 = <p>Should be green </p>; + const p1 = <p>009 Should be green </p>; const p2 = <p>Should be green </p>; const p3 = <p>Should be green </p>; const p4 = <p>Should be green </p>; const p5 = <p>Should be green </p>; + document.head.appendChild(style); document.body.appendChild(p1); document.body.appendChild(p2); @@ -153,9 +149,10 @@ describe("css child selector", () => { } `}</style> ); + document.head.appendChild(style); - const p1 = <p id="a">Should be green</p>; + const p1 = <p id="a">10 Should be green</p>; const p2 = <p id="b">Should be green</p>; const p3 = <p id="c">Should be green</p>; const p4 = <p id="d">Should be green</p>; @@ -215,7 +212,7 @@ describe("css child selector", () => { `}</style> ); - const p1 = <p id="a">Should be green</p>; + const p1 = <p id="a">11 Should be green</p>; const p2 = <p id="b">Should be green</p>; const p3 = <p id="c">Should be green</p>; const p4 = <p id="d">Should be green</p>; @@ -225,6 +222,7 @@ describe("css child selector", () => { const p8 = <p id="h">Should be green</p>; const p9 = <p id="i">Should be green</p>; + document.head.appendChild(style); document.body.appendChild(p1); document.body.appendChild(p2); @@ -251,12 +249,13 @@ describe("css child selector", () => { <li> 012 Should be green</li> </ul> ); + document.head.appendChild(style); document.body.appendChild(ul); await snapshot(); }); - it("013", async () => { + xit("013", async () => { const style = ( <style>{` li:only-child { @@ -270,31 +269,28 @@ describe("css child selector", () => { <li> 013 Should not be green</li> </ul> ); + document.head.appendChild(style); document.body.appendChild(ul); await snapshot(); }); + // error it("014", async () => { const style = ( - <style>{` - last-child #f { - color: green; - } - `}</style> + <style>{`:last-child #f { color: green; }`}</style> ); - const p = <p>014</p>; - const p1 = <p id="a">Should be green</p>; - const p2 = <p id="b">Should be green</p>; - const p3 = <p id="c">Should be green</p>; - const p4 = <p id="d">Should be green</p>; - const p5 = <p id="e">Should be green</p>; + const p1 = <p id="a">Should not be green</p>; + const p2 = <p id="b">Should not be green</p>; + const p3 = <p id="c">Should not be green</p>; + const p4 = <p id="d">Should not be green</p>; + const p5 = <p id="e">Should not be green</p>; const p6 = <p id="f">Should be green</p>; - const p7 = <p id="g">Should be green</p>; - const p8 = <p id="h">Should be green</p>; - const p9 = <p id="i">Should be green</p>; - + const p7 = <p id="g">Should not be green</p>; + const p8 = <p id="h">Should not be green</p>; + const p9 = <p id="i">Should not be green</p>; + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(p1); diff --git a/integration_tests/specs/css/css-selectors/descendent-selector.ts b/integration_tests/specs/css/css-selectors/descendent-selector.ts index 55087156df..ec8f0afbd3 100644 --- a/integration_tests/specs/css/css-selectors/descendent-selector.ts +++ b/integration_tests/specs/css/css-selectors/descendent-selector.ts @@ -2,7 +2,8 @@ describe('css descendent selector', () => { it('001', async () => { const style = <style>{`div em { color: red; }` }</style>; const div = <div id="div1" > 001 Filler Text < /div >; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -10,7 +11,8 @@ describe('css descendent selector', () => { it('002', async () => { const style = <style>{`div em { color: red; }`}</style>; const div = <div><em>002 Filler Text</em></div >; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -18,7 +20,8 @@ describe('css descendent selector', () => { it('003', async () => { const style = <style>{`div em { color: red; }`}</style>; const div = <div><span><em>003 Filler Text</em></span > </div >; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -26,7 +29,8 @@ describe('css descendent selector', () => { it('004', async () => { const style = <style>{`p em { color: red; }`}</style>; const div = <div><em>004 Filler Text</em></div >; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -34,7 +38,8 @@ describe('css descendent selector', () => { it('005', async () => { const style = <style>{`div * em { color: red; }`}</style>; const div = <div>005 Filler Text</div >; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -42,7 +47,8 @@ describe('css descendent selector', () => { it('006', async () => { const style = <style>{`div * em { color: red; }`}</style>; const div = <div><em>006 Filler Text</em></div >; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -50,7 +56,8 @@ describe('css descendent selector', () => { it('007', async () => { const style = <style>{`div * em { color: red; }`}</style>; const div = <div><span><em>007 Filler Text</em></span></div >; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -58,6 +65,7 @@ describe('css descendent selector', () => { it('008', async () => { const style = <style>{`div em[id] { color: red; }`}</style>; const div = <div><span><em id="em1" > 008 Filler Text < /em></span ></div >; + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -66,7 +74,8 @@ describe('css descendent selector', () => { it('009', async () => { const style = <style>{`#div em { color: red; }`}</style>; const div = <div id="div" ><em> 009 Filler Text </em> </div >; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -75,7 +84,8 @@ describe('css descendent selector', () => { const style = <style>{`#div em { color: red; }`}</style>; const div = <div id="div" > <em>010 Filler Text < /em> </div >; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); diff --git a/integration_tests/specs/css/css-selectors/id-selector.ts b/integration_tests/specs/css/css-selectors/id-selector.ts index 13c7d853a8..210db3f8ba 100644 --- a/integration_tests/specs/css/css-selectors/id-selector.ts +++ b/integration_tests/specs/css/css-selectors/id-selector.ts @@ -2,7 +2,8 @@ describe("css id selector", () => { it("001", async () => { const style = <style>{`#div1 { color: green; }`}</style>; const div = <div id="div1"> 001 green Filler Text</div>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -10,7 +11,8 @@ describe("css id selector", () => { it("002", async () => { const style = <style>{`# div1 { color: green; }`}</style>; const div = <div id="div1"> 002 black Filler Text</div>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -18,7 +20,8 @@ describe("css id selector", () => { it("003", async () => { const style = <style>{`div { color: red; } #-div1 { color: green; }`}</style>; const div = <div id="-div1"> 003 green Filler Text</div>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -26,7 +29,8 @@ describe("css id selector", () => { xit("004", async () => { const style = <style>{`#1digit { color: red; }`}</style>; const div = <div id="1digit"> 004 black Filler Text</div>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -34,7 +38,8 @@ describe("css id selector", () => { it("005", async () => { const style = <style>{`div[id=div1] { color: red; } div#div1 { color: green; }`}</style>; const div = <div id="div1"> 005 green Filler Text</div>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -42,7 +47,8 @@ describe("css id selector", () => { it("006", async () => { const style = <style>{`div[id=div1] { color: red; } div#div1 { color: green; }`}</style>; const div = <div id="div1"> 006 green Filler Text</div>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); diff --git a/integration_tests/specs/css/css-selectors/sibling-selector.ts b/integration_tests/specs/css/css-selectors/sibling-selector.ts index 7d9582a2cb..9567985d8b 100644 --- a/integration_tests/specs/css/css-selectors/sibling-selector.ts +++ b/integration_tests/specs/css/css-selectors/sibling-selector.ts @@ -22,7 +22,8 @@ describe("css sibling selector", () => { const p8 = <div>This sentence must be green.</div>; const p9 = <div>This sentence must be green.</div>; const p10 = <div>This sentence must be green.</div>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); document.body.appendChild(p1); document.body.appendChild(p2); @@ -42,7 +43,8 @@ describe("css sibling selector", () => { const p = <p>Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.</p>; const div1 = <div> 002 Filler Text</div>; const div2 = <div>Filler Text</div>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(div1); document.body.appendChild(div2); @@ -51,7 +53,8 @@ describe("css sibling selector", () => { it("003", async () => { const style = <style>{`p + div { color: green; }`}</style>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.innerHTML = `<body> <p>Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.</p> <!-- This is a comment --> @@ -62,7 +65,8 @@ describe("css sibling selector", () => { xit("004", async () => { const style = <style>{`p + div { color: green; }`}</style>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.innerHTML = `<body> <p>Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.</p> Filler Text diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index b13a67eae9..6f34fde38e 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -1,12 +1,13 @@ describe("css tag selector", () => { it("001", async () => { const style = <style>{`p { color: green; }`}</style>; - const p1 = <p>This sentence must be green.</p>; + const p1 = <p>001 This sentence must be green.</p>; const p2 = <p>This sentence must be green.</p>; const p3 = <p>This sentence must be green.</p>; const p4 = <p>This sentence must be green.</p>; const p5 = <p>This sentence must be green.</p>; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(p1); document.body.appendChild(p2); document.body.appendChild(p3); @@ -19,8 +20,9 @@ describe("css tag selector", () => { const style = <style>{`div, blockquote, p { color: green; }`}</style>; const p = <p>Test passes if the "Filler Text" below is green.</p>; const blockquote = <blockquote>Filler Text</blockquote>; - const div = <div>Filler Text</div>; - document.head.appendChild(style); + const div = <div> 002 Filler Text</div>; + + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(blockquote); document.body.appendChild(div); @@ -29,20 +31,15 @@ describe("css tag selector", () => { it("003", async () => { const style = <style>{`DIV { color: green; }`}</style>; - const div = <div>Filler Text</div>; - document.head.appendChild(style); + const div = <div> 003 Filler Text</div>; + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it("004", async () => { - const style = <style>{`body * { - margin: 1em 0; - font: inherit; - display: block; - color: white; - background: green; - }`}</style>; + const style = <style>{`body * { color: green; }`}</style>; const e1 = <p>This text should be green. (element)</p>; const e2 =<div>This text should be green. (class)</div> const e3 =<div>This text should be green. (id)</div> @@ -50,7 +47,8 @@ describe("css tag selector", () => { const e5 =<div>This text should be green. (descendant)</div> const e6 =<blockquote>This text should be green. (sibling)</blockquote> const e7 =<div>This text should be green. (attribute)</div> - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(e1); document.body.appendChild(e2); document.body.appendChild(e3); @@ -64,8 +62,9 @@ describe("css tag selector", () => { it("005", async () => { const style = <style>{` body { color: green; }`}</style>; const p = <p>Test passes if all text on this page is green.</p>; - const div = <div>Filler Text</div>; - document.head.appendChild(style); + const div = <div> 005 Filler Text</div>; + + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(div); await snapshot(); @@ -74,7 +73,8 @@ describe("css tag selector", () => { it("006", async () => { const style = <style>{` * { color: green; }`}</style>; const p = <p>Test passes if all text on this page is green.</p>; - const div = <div>Filler Text</div>; + const div = <div> 006 Filler Text</div>; + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(div); @@ -82,9 +82,10 @@ describe("css tag selector", () => { }); it("007", async () => { - const style = <style>{` html, div { border: 10px solid blue; }`}</style>; + const style = <style>{` html, div { color: green; }`}</style>; const p = <p>Test passes if all text on this page is green.</p>; - const div = <div>Filler Text</div>; + const div = <div> 007 Filler Text</div>; + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(div); diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index a50809be46..d825bbaeff 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -27,7 +27,9 @@ class RuleSet { void addRule(CSSRule rule) { rules.add(rule); if (rule is CSSStyleRule) { - findBestRuleSetAndAdd(rule); + for (final selector in rule.selectorGroup.selectors) { + findBestRuleSetAndAdd(selector, rule); + } } else { assert(false, 'Unsupported rule type: ${rule.runtimeType}'); } @@ -43,31 +45,33 @@ class RuleSet { } // indexed by selectorText - void findBestRuleSetAndAdd(CSSStyleRule rule) { - String? id, className, attributeName, tagName; - - for (final selector in rule.selectorGroup.selectors) { - for (final simpleSelectorSequence in selector.simpleSelectorSequences) { - final simpleSelector = simpleSelectorSequence.simpleSelector; - switch (simpleSelector.runtimeType) { - case IdSelector: - id = simpleSelector.name; - break; - case ClassSelector: - className = simpleSelector.name; - break; - case AttributeSelector: - attributeName = simpleSelector.name; - break; - case ElementSelector: - tagName = simpleSelector.name; - break; + void findBestRuleSetAndAdd(Selector selector, CSSRule rule) { + String? id, className, attributeName, tagName, pseudoName; + + for (final simpleSelectorSequence in selector.simpleSelectorSequences.reversed) { + final simpleSelector = simpleSelectorSequence.simpleSelector; + if (simpleSelector.runtimeType == IdSelector) { + id = simpleSelector.name; + } else if (simpleSelector.runtimeType == ClassSelector) { + className = simpleSelector.name; + } else if (simpleSelector.runtimeType == AttributeSelector) { + attributeName = simpleSelector.name; + } else if (simpleSelector.runtimeType == ElementSelector) { + if (simpleSelector.isWildcard) { + break; } + tagName = simpleSelector.name; + } else if (simpleSelector.runtimeType == PseudoClassSelector || + simpleSelector.runtimeType == PseudoElementSelector) { + pseudoName = simpleSelector.name; + } + + if (id != null || className != null || attributeName != null || tagName != null || pseudoName != null) { + break; } } - bool isInserted = false; + void insertRule(String key, CSSRule rule, CSSMap map) { - isInserted = true; List<CSSRule>? rules = map[key] ?? []; rules.add(rule); map[key] = rules; @@ -75,22 +79,24 @@ class RuleSet { if (id != null && id.isNotEmpty == true) { insertRule(id, rule, idRules); + return; } if (className != null && className.isNotEmpty == true) { insertRule(className, rule, classRules); + return; } if (attributeName != null && attributeName.isNotEmpty == true) { insertRule(attributeName.toUpperCase(), rule, attributeRules); + return; } if (tagName != null && tagName.isNotEmpty == true) { insertRule(tagName.toUpperCase(), rule, tagRules); + return; } - if (!isInserted) { - universalRules.add(rule); - } + universalRules.add(rule); } } diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/selector_evaluator.dart index 308d9a4673..479b2b2801 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/selector_evaluator.dart @@ -110,7 +110,7 @@ class SelectorEvaluator extends SelectorVisitor { @override bool visitPseudoClassSelector(PseudoClassSelector node) { - switch (node.name) { + switch (node.name.toLowerCase()) { // http://dev.w3.org/csswg/selectors-4/#structural-pseudos // http://dev.w3.org/csswg/selectors-4/#the-root-pseudo @@ -135,7 +135,7 @@ class SelectorEvaluator extends SelectorVisitor { // http://dev.w3.org/csswg/selectors-4/#the-last-child-pseudo case 'last-child': - return _element!.nextElementSibling == null; + return _element!.nextSibling == null; //http://drafts.csswg.org/selectors-4/#first-of-type-pseudo //http://drafts.csswg.org/selectors-4/#last-of-type-pseudo @@ -171,7 +171,7 @@ class SelectorEvaluator extends SelectorVisitor { break; // http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo case 'only-child': - return _element!.previousElementSibling == null && _element!.nextElementSibling == null; + return _element!.previousSibling == null && _element!.nextSibling == null; // http://dev.w3.org/csswg/selectors-4/#link case 'link': diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 87788267da..d7d8a42458 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -139,6 +139,8 @@ class Document extends Node { if (documentElement == child) { documentElement = null; } + ruleSet.reset(); + styleSheets.clear(); return super.removeChild(child); } From 6e6e759a260cce4aafbc0fa2c91b552f5649fa64 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Fri, 19 Aug 2022 01:17:25 +0800 Subject: [PATCH 194/498] feat: update sheet style --- webf/lib/dom.dart | 1 + webf/lib/src/css/parser/parser.dart | 5 +- webf/lib/src/css/style_sheet.dart | 43 +++++-- webf/lib/src/dom/document.dart | 25 ++-- webf/lib/src/dom/element.dart | 4 +- webf/lib/src/dom/elements/head.dart | 57 ++++++--- webf/lib/src/dom/node.dart | 100 +++++++++++++++- webf/lib/src/dom/style_node_manager.dart | 141 +++++++++++++++++++++++ 8 files changed, 341 insertions(+), 35 deletions(-) create mode 100644 webf/lib/src/dom/style_node_manager.dart diff --git a/webf/lib/dom.dart b/webf/lib/dom.dart index 8e2ea6e846..2fa036b76d 100644 --- a/webf/lib/dom.dart +++ b/webf/lib/dom.dart @@ -20,6 +20,7 @@ export 'src/dom/screen.dart'; export 'src/dom/element_registry.dart'; // Elements +export 'src/dom/style_node_manager.dart'; export 'src/dom/elements/semantics_text.dart'; export 'src/dom/elements/grouping_content.dart'; export 'src/dom/elements/sections.dart'; diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index cf070f02f6..8d308f09af 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -83,10 +83,7 @@ class CSSParser { /// Main entry point for parsing an entire CSS file. CSSStyleSheet parse() { final rules = parseRules(); - - final ruleSet = RuleSet(); - ruleSet.addRules(rules); - return CSSStyleSheet(rules); + return CSSStyleSheet(tokenizer._text, rules); } Map<String, dynamic> parseInlineStyle() { diff --git a/webf/lib/src/css/style_sheet.dart b/webf/lib/src/css/style_sheet.dart index 93eec5a0f7..08a57f8590 100644 --- a/webf/lib/src/css/style_sheet.dart +++ b/webf/lib/src/css/style_sheet.dart @@ -9,18 +9,25 @@ abstract class StyleSheet {} const String _CSSStyleSheetType = 'text/css'; // https://drafts.csswg.org/cssom-1/#cssstylesheet -class CSSStyleSheet implements StyleSheet { +class CSSStyleSheet implements StyleSheet, Comparable { String type = _CSSStyleSheetType; /// A Boolean indicating whether the stylesheet is disabled. False by default. bool disabled = false; /// A string containing the baseURL used to resolve relative URLs in the stylesheet. - String? herf; + String? href; - final List<CSSRule> cssRules; + final String content; - CSSStyleSheet(this.cssRules, {this.disabled = false, this.herf}); + List<CSSRule> get cssRules => _cssRules; + late List<CSSRule> _cssRules; + + CSSStyleSheet(this.content, this._cssRules, {this.disabled = false, this.href}); + + CSSStyleSheet.from(this.content, {this.disabled = false, this.href}) { + _cssRules = CSSParser(content).parseRules(); + } insertRule(String text, int index) { List<CSSRule> rules = CSSParser(text).parseRules(); @@ -39,8 +46,30 @@ class CSSStyleSheet implements StyleSheet { cssRules.addAll(rules); } - replace(String text) { - // TODO: put in next frame and return a future - replaceSync(text); + Future replace(String text) async { + return Future(() { + replaceSync(text); + }); + } + + @override + bool operator ==(Object other) { + return other is CSSStyleSheet && other.content == content; + } + + @override + int get hashCode => content.hashCode; + + CSSStyleSheet clone() { + CSSStyleSheet sheet = CSSStyleSheet(content, List.from(cssRules), disabled: disabled, href: href); + return sheet; + } + + @override + int compareTo(other) { + if (other is! CSSStyleSheet) { + return 0; + } + return hashCode.compareTo(other.hashCode); } } diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index d7d8a42458..b33d8a63a4 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -19,7 +19,10 @@ class Document extends Node { GestureListener? gestureListener; WidgetDelegate? widgetDelegate; - RuleSet ruleSet = RuleSet(); + StyleNodeManager get styleNodeManager => _styleNodeManager; + late StyleNodeManager _styleNodeManager; + + final RuleSet ruleSet = RuleSet(); Document( BindingContext context, { @@ -29,6 +32,7 @@ class Document extends Node { this.widgetDelegate, }) : _viewport = viewport, super(NodeType.DOCUMENT_NODE, context) { + _styleNodeManager = StyleNodeManager(this); _scriptRunner = ScriptRunner(this, context.contextId); } @@ -181,20 +185,27 @@ class Document extends Node { // The styleSheets attribute is readonly attribute. final List<CSSStyleSheet> styleSheets = []; - void addStyleSheet(CSSStyleSheet sheet) { - styleSheets.add(sheet); - ruleSet.addRules(sheet.cssRules); - recalculateDocumentStyle(); + void handleStyleSheets(List<CSSStyleSheet> sheets) { + styleSheets.clear(); + styleSheets.addAll(sheets.map((e) => e.clone())); + ruleSet.reset(); + for (var sheet in sheets) { + ruleSet.addRules(sheet.cssRules); + } } - void removeStyleSheet(CSSStyleSheet sheet) { - styleSheets.remove(sheet); + void updateStyleIfNeeded() { + styleNodeManager.updateActiveStyleSheets(); recalculateDocumentStyle(); } void recalculateDocumentStyle() { + if (!needsStyleRecalculate) { + return; + } // Recalculate style for all nodes sync. documentElement?.recalculateNestedStyle(); + needsStyleRecalculate = false; } @override diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 5605adcaf4..c09141815b 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -882,7 +882,6 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element return child; } - void updateStyleIfNeeded() {} // 2. Node::invalidateStyle 方法 将元素所有祖先都标记需要更新样式 // 3. Document updateStyleIfNeeded() // 3.1 flush pending sheet @@ -1485,6 +1484,9 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } void recalculateNestedStyle() { + if (!needsStyleRecalculate) { + return; + } recalculateStyle(); // Update children style. children.forEach((Element child) { diff --git a/webf/lib/src/dom/elements/head.dart b/webf/lib/src/dom/elements/head.dart index cbe01d28b2..c3230771f4 100644 --- a/webf/lib/src/dom/elements/head.dart +++ b/webf/lib/src/dom/elements/head.dart @@ -31,6 +31,12 @@ const String _REL_STYLESHEET = 'stylesheet'; class LinkElement extends Element { LinkElement([BindingContext? context]) : super(context, defaultStyle: _defaultStyle); + CSSStyleSheet? get styleSheet => _styleSheet; + CSSStyleSheet? _styleSheet; + + bool _loading = false; + bool get loading => _loading; + Uri? _resolvedHyperlink; final Map<String, bool> _stylesheetLoaded = {}; @@ -103,15 +109,13 @@ class LinkElement extends Element { set href(String value) { internalSetAttribute('href', value); _resolveHyperlink(); - // Should waiting for all properties had set up. - Future.microtask(() { - _fetchAndApplyCSSStyle(); - }); + _process(); } String get rel => getAttribute('rel') ?? ''; set rel(String value) { internalSetAttribute('rel', value); + _process(); } String get type => getAttribute('type') ?? ''; @@ -132,6 +136,15 @@ class LinkElement extends Element { } } + void _process() { + if (_resolvedHyperlink != null && _stylesheetLoaded.containsKey(_resolvedHyperlink.toString())) { + return; + } + Future.microtask(() { + _fetchAndApplyCSSStyle(); + }); + } + void _fetchAndApplyCSSStyle() async { if (_resolvedHyperlink != null && rel == _REL_STYLESHEET && @@ -141,12 +154,13 @@ class LinkElement extends Element { WebFBundle bundle = WebFBundle.fromUrl(url); _stylesheetLoaded[url] = true; try { + _loading = true; // Increment count when request. ownerDocument.incrementRequestCount(); await bundle.resolve(contextId); assert(bundle.isResolved, 'Failed to obtain $url'); - + _loading = false; // Decrement count when response. ownerDocument.decrementRequestCount(); @@ -170,16 +184,26 @@ class LinkElement extends Element { } void _addCSSStyleSheet(String css) { - final sheet = CSSParser(css).parse(); - ownerDocument.addStyleSheet(sheet); + _styleSheet = CSSParser(css).parse(); } @override - void connectedCallback() async { - super.connectedCallback(); + void connectedCallback() { + if (rel == _REL_STYLESHEET) { + ownerDocument.styleNodeManager.addStyleSheetCandidateNode(this); + } if (_resolvedHyperlink != null) { _fetchAndApplyCSSStyle(); } + super.connectedCallback(); + } + + @override + void disconnectedCallback() { + if (rel == _REL_STYLESHEET) { + ownerDocument.styleNodeManager.removeStyleSheetCandidateNode(this); + } + super.disconnectedCallback(); } } @@ -201,6 +225,8 @@ const String _CSS_MIME = 'text/css'; class StyleElement extends Element { StyleElement([BindingContext? context]) : super(context, defaultStyle: _defaultStyle); final String _type = _CSS_MIME; + + CSSStyleSheet? get styleSheet => _styleSheet; CSSStyleSheet? _styleSheet; // Bindings. @@ -244,11 +270,9 @@ class StyleElement extends Element { String? text = collectElementChildText(); if (text != null) { if (_styleSheet != null) { - _styleSheet!.replaceSync(text); - ownerDocument.recalculateDocumentStyle(); + _styleSheet!.replace(text); } else { - final sheet = CSSParser(text).parse(); - ownerDocument.addStyleSheet(_styleSheet = sheet); + _styleSheet = CSSParser(text).parse(); } } } @@ -277,7 +301,10 @@ class StyleElement extends Element { @override void connectedCallback() { if (_type == _CSS_MIME) { - _recalculateStyle(); + if (_styleSheet == null) { + _recalculateStyle(); + } + ownerDocument.styleNodeManager.addStyleSheetCandidateNode(this); } super.connectedCallback(); } @@ -285,7 +312,7 @@ class StyleElement extends Element { @override void disconnectedCallback() { if (_styleSheet != null) { - ownerDocument.removeStyleSheet(_styleSheet!); + ownerDocument.styleNodeManager.removeStyleSheetCandidateNode(this); } super.disconnectedCallback(); } diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index e53b1208d3..f5a46c4fab 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -2,8 +2,11 @@ * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'dart:math' as math; + import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; import 'package:webf/dom.dart'; import 'package:webf/foundation.dart'; import 'package:webf/widget.dart'; @@ -16,8 +19,20 @@ enum NodeType { DOCUMENT_FRAGMENT_NODE, } +enum DocumentPosition { + EQUIVALENT, + DISCONNECTED, + PRECEDING, + FOLLOWING, + CONTAINS, + CONTAINED_BY, + IMPLEMENTATION_SPECIFIC, +} + enum RenderObjectManagerType { FLUTTER_ELEMENT, WEBF_NODE } +typedef NoteVisitor = void Function(Node node); + /// [RenderObjectNode] provide the renderObject related abstract life cycle for /// [Node] or [Element]s, which wrap [RenderObject]s, which provide the actual /// rendering of the application. @@ -85,12 +100,31 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa // Children changed steps for node. // https://dom.spec.whatwg.org/#concept-node-children-changed-ext - void childrenChanged() {} + void childrenChanged() { + if (!this.isConnected) { + return; + } + Node parent = this; + // invalidate + while (parent.parentNode != null) { + parent.needsStyleRecalculate = true; + parent = parent.parentNode!; + } + + SchedulerBinding.instance.addPostFrameCallback((_) { + if (!ownerDocument.needsStyleRecalculate) { + return; + } + ownerDocument.updateStyleIfNeeded(); + }); + } // FIXME: The ownerDocument getter steps are to return null, if this is a document; otherwise this’s node document. // https://dom.spec.whatwg.org/#dom-node-ownerdocument late Document ownerDocument; + bool needsStyleRecalculate = false; + /// The Node.parentElement read-only property returns the DOM node's parent Element, /// or null if the node either has no parent, or its parent isn't a DOM Element. Element? get parentElement { @@ -182,6 +216,13 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa @override void didDetachRenderer() {} + void visitChild(NoteVisitor visitor) { + childNodes.forEach((node) { + node.visitChild(visitor); + visitor(node); + }); + } + @mustCallSuper Node appendChild(Node child) { child._ensureOrphan(); @@ -197,6 +238,13 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa child.connectedCallback(); } + // invalid style + Node parent = this; + while (parent.parentNode != null) { + parent.needsStyleRecalculate = true; + parent = parent.parentNode!; + } + ownerDocument.needsStyleRecalculate = true; return child; } @@ -309,6 +357,56 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa // Whether Kraken Node need to manage render object. RenderObjectManagerType get renderObjectManagerType => RenderObjectManagerType.WEBF_NODE; + + DocumentPosition compareDocumentPosition(Node other) { + if (this == other) { + return DocumentPosition.EQUIVALENT; + } + + // We need to find a common ancestor container, and then compare the indices of the two immediate children. + List<Node> chain1 = []; + List<Node> chain2 = []; + Node? current = this; + while (current != null && current.parentNode != null) { + chain1.add(current); + current = current.parentNode; + } + current = other; + while (current != null && current.parentNode != null) { + chain2.add(current); + current = current.parentNode; + } + + // If the two elements don't have a common root, they're not in the same tree. + if (chain1.first != chain2.first) { + return DocumentPosition.DISCONNECTED; + } + + // Walk the two chains backwards and look for the first difference. + for (int i = 0; i < math.min(chain1.length, chain2.length) - 1; i++) { + if (chain1[i] != chain2[i]) { + if (chain2[i].nextSibling == null) { + return DocumentPosition.FOLLOWING; + } + if (chain1[i].nextSibling == null) { + return DocumentPosition.PRECEDING; + } + + // Otherwise we need to see which node occurs first. Crawl backwards from child2 looking for child1. + Node? previousSibling = chain2[i].previousSibling; + while (previousSibling != null) { + if (chain1[i] == previousSibling) { + return DocumentPosition.FOLLOWING; + } + previousSibling = previousSibling.previousSibling; + } + return DocumentPosition.PRECEDING; + } + } + // There was no difference between the two parent chains, i.e., one was a subset of the other. The shorter + // chain is the ancestor. + return chain1.length < chain2.length ? DocumentPosition.FOLLOWING : DocumentPosition.PRECEDING; + } } /// https://dom.spec.whatwg.org/#dom-node-nodetype diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart new file mode 100644 index 0000000000..02cd414781 --- /dev/null +++ b/webf/lib/src/dom/style_node_manager.dart @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'dart:math' as math; +import 'package:collection/collection.dart'; + +import 'package:webf/css.dart'; +import 'package:webf/dom.dart'; + +/* + Handling element style updates + 1. log all style element + 2. + */ +class StyleNodeManager { + final List<Node> _styleSheetCandidateNodes = []; + + final Document document; + + StyleNodeManager(this.document); + + void addStyleSheetCandidateNode(Node node) { + if (!node.isConnected) { + return; + } + if (_styleSheetCandidateNodes.isEmpty) { + _styleSheetCandidateNodes.add(node); + return; + } + + // Determine an appropriate insertion point. + for (int i = _styleSheetCandidateNodes.length - 1; i >= 0; i--) { + DocumentPosition position = _styleSheetCandidateNodes[i].compareDocumentPosition(node); + if (position == DocumentPosition.FOLLOWING) { + _styleSheetCandidateNodes.insert(i + 1, node); + return; + } + } + + _styleSheetCandidateNodes.insert(0, node); + } + + void removeStyleSheetCandidateNode(Node node) { + _styleSheetCandidateNodes.remove(node); + } + + void updateActiveStyleSheets() { + List<CSSStyleSheet> newSheets = _collectActiveStyleSheets(); + if (newSheets.isEmpty) { + return; + } + newSheets = _collectActiveStyleSheets().where((element) => element.cssRules.isNotEmpty).toList(); + RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); + invalidateElementStyle(changedRuleSet); + document.handleStyleSheets(newSheets); + } + + List<CSSStyleSheet> _collectActiveStyleSheets() { + List<CSSStyleSheet> styleSheetsForStyleSheetsList = []; + for (Node node in _styleSheetCandidateNodes) { + if (node is LinkElement && !node.disabled && !node.loading && node.styleSheet != null) { + styleSheetsForStyleSheetsList.add(node.styleSheet!); + } else if (node is StyleElement && node.styleSheet != null) { + styleSheetsForStyleSheetsList.add(node.styleSheet!); + } + } + return styleSheetsForStyleSheetsList; + } + + void invalidateElementStyle(RuleSet changedRuleSet) { + ElementRuleCollector collector = ElementRuleCollector(); + document.visitChild((node) { + if (node.childNodes.where((node) => node.needsStyleRecalculate).isNotEmpty) { + node.needsStyleRecalculate = true; + } else if (node is Element && node.isConnected) { + if (collector.matchedAnyRule(changedRuleSet, node)) { + node.needsStyleRecalculate = true; + } + } + }); + } + + RuleSet analyzeStyleSheetChangeRuleSet(List<CSSStyleSheet> oldSheets, List<CSSStyleSheet> newSheets) { + RuleSet ruleSet = RuleSet(); + + final oldSheetsCount = oldSheets.length; + final newSheetsCount = newSheets.length; + + final minCount = math.min(oldSheetsCount, newSheetsCount); + + Function equals = ListEquality().equals; + + int index = 0; + for (; index < minCount && oldSheets[index] == newSheets[index]; index++) { + if (equals(oldSheets[index].cssRules, newSheets[index].cssRules)) { + continue; + } + ruleSet.addRules(newSheets[index].cssRules); + ruleSet.addRules(oldSheets[index].cssRules); + } + + if (index == oldSheetsCount) { + for (; index < newSheetsCount; index++) { + ruleSet.addRules(newSheets[index].cssRules); + } + return ruleSet; + } + + if (index == newSheetsCount) { + for (; index < oldSheetsCount; index++) { + ruleSet.addRules(oldSheets[index].cssRules); + } + return ruleSet; + } + + List<CSSStyleSheet> mergeSorted = []; + mergeSorted.addAll(oldSheets.sublist(index)); + mergeSorted.addAll(newSheets.sublist(index)); + mergeSorted.sort(); + + for (int index = 0; index < mergeSorted.length; index++) { + CSSStyleSheet sheet = mergeSorted[index]; + CSSStyleSheet sheet1 = mergeSorted[++index]; + if (index == mergeSorted.length || sheet != sheet1) { + ruleSet.addRules(sheet1.cssRules); + continue; + } + + CSSStyleSheet sheet2 = mergeSorted[++index]; + if (equals(sheet1.cssRules, sheet2.cssRules)) { + continue; + } + + ruleSet.addRules(sheet1.cssRules); + ruleSet.addRules(sheet2.cssRules); + } + + return ruleSet; + } +} From e77a2f1030a2bc9c8c73dc06fa084d841f4e43c4 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Sun, 21 Aug 2022 12:05:21 +0800 Subject: [PATCH 195/498] feat: udpate Link Element --- integration_tests/assets/bad.css | 4 +++ integration_tests/assets/good.css | 3 ++ .../dom/elements/link.ts.4a71e55a1.png | Bin 4408 -> 4467 bytes .../dom/elements/link.ts.c15dbee01.png | Bin 0 -> 6396 bytes .../dom/elements/link.ts.c15dbee02.png | Bin 0 -> 8937 bytes .../dom/elements/link.ts.c15dbee03.png | Bin 0 -> 8937 bytes integration_tests/specs/dom/elements/link.ts | 21 ++++++++++- webf/lib/src/css/element_rule_collector.dart | 4 +++ webf/lib/src/css/parser/parser.dart | 4 ++- webf/lib/src/css/rule.dart | 2 ++ webf/lib/src/css/rule_set.dart | 5 ++- webf/lib/src/dom/document.dart | 3 ++ webf/lib/src/dom/elements/head.dart | 33 +++++++++++------- webf/lib/src/dom/style_node_manager.dart | 15 ++++++-- 14 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 integration_tests/assets/bad.css create mode 100644 integration_tests/assets/good.css create mode 100644 integration_tests/snapshots/dom/elements/link.ts.c15dbee01.png create mode 100644 integration_tests/snapshots/dom/elements/link.ts.c15dbee02.png create mode 100644 integration_tests/snapshots/dom/elements/link.ts.c15dbee03.png diff --git a/integration_tests/assets/bad.css b/integration_tests/assets/bad.css new file mode 100644 index 0000000000..4e1fe36165 --- /dev/null +++ b/integration_tests/assets/bad.css @@ -0,0 +1,4 @@ +p { + background-color: red; + color: black; +} \ No newline at end of file diff --git a/integration_tests/assets/good.css b/integration_tests/assets/good.css new file mode 100644 index 0000000000..1da5e2b8cf --- /dev/null +++ b/integration_tests/assets/good.css @@ -0,0 +1,3 @@ +p { + color: green; +} \ No newline at end of file diff --git a/integration_tests/snapshots/dom/elements/link.ts.4a71e55a1.png b/integration_tests/snapshots/dom/elements/link.ts.4a71e55a1.png index 662d311d86f4ba295c1aac3450425f9b6e9d2734..028376ab09a064939d9ad73d1cbe682252012a58 100644 GIT binary patch delta 2150 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 delta 2087 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 R3U&Yh002ovPDHLkV1jdek^%q# diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee01.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee01.png new file mode 100644 index 0000000000000000000000000000000000000000..6c1b3b5455fb2b391422bf20246a1736f26785ec GIT binary patch literal 6396 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee02.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee02.png new file mode 100644 index 0000000000000000000000000000000000000000..266137a02c2af9a7942b5b9c57d53c4d0b62772f GIT binary patch literal 8937 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee03.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee03.png new file mode 100644 index 0000000000000000000000000000000000000000..266137a02c2af9a7942b5b9c57d53c4d0b62772f GIT binary patch literal 8937 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/dom/elements/link.ts b/integration_tests/specs/dom/elements/link.ts index f3da0b1ee7..4a621d3872 100644 --- a/integration_tests/specs/dom/elements/link.ts +++ b/integration_tests/specs/dom/elements/link.ts @@ -16,4 +16,23 @@ describe('Link Element', () => { div.appendChild(document.createTextNode('helloworld')); BODY.appendChild(div); }); -}); \ No newline at end of file + + fit('should work with local css', async (done) => { + let link = document.createElement('link'); + link.setAttribute('href', 'assets:assets/bad.css'); + link.setAttribute('rel', 'stylesheet'); + link.addEventListener('load', async () => { + await snapshot(); + link.setAttribute('href', 'assets:assets/good.css'); + await snapshot(0.5); + done(); + }); + document.head.appendChild(link); + + let p = document.createElement('p'); + p.appendChild(document.createTextNode('This text should be green on a white background')); + BODY.appendChild(p); + }); + + +}); diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index d10fbb2160..3951e7dda8 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -14,6 +14,10 @@ class ElementRuleCollector { List<CSSRule> _matchedRules(RuleSet ruleSet, Element element) { List<CSSRule> matchedRules = []; + if (ruleSet.rules.isEmpty) { + return matchedRules; + } + // #id String? id = element.id; if (id != null) { diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 8d308f09af..df8c53d432 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -126,10 +126,12 @@ class CSSParser { return style; } - List<CSSRule> parseRules() { + List<CSSRule> parseRules({int startPosition = 0}) { var rules = <CSSRule>[]; + int position = startPosition; while (!_maybeEat(TokenKind.END_OF_FILE)) { final rule = processRule(); + rule?.position = position++; if (rule != null) { rules.add(rule); } else { diff --git a/webf/lib/src/css/rule.dart b/webf/lib/src/css/rule.dart index 57c6238cbb..f8887c112e 100644 --- a/webf/lib/src/css/rule.dart +++ b/webf/lib/src/css/rule.dart @@ -8,6 +8,8 @@ abstract class CSSRule { CSSStyleSheet? parentStyleSheet; CSSRule? parentRule; + int position = -1; + // https://drafts.csswg.org/cssom/#dom-cssrule-type // The following attribute and constants are historical. int? type; diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index d825bbaeff..fac03debcd 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -10,7 +10,8 @@ import 'package:webf/css.dart'; typedef CSSMap = HashMap<String, List<CSSRule>>; class RuleSet { - final List<CSSRule> rules = []; + bool get isEmpty => + idRules.isEmpty && classRules.isEmpty && attributeRules.isEmpty && tagRules.isEmpty && universalRules.isEmpty; final CSSMap idRules = HashMap(); final CSSMap classRules = HashMap(); @@ -25,7 +26,6 @@ class RuleSet { } void addRule(CSSRule rule) { - rules.add(rule); if (rule is CSSStyleRule) { for (final selector in rule.selectorGroup.selectors) { findBestRuleSetAndAdd(selector, rule); @@ -36,7 +36,6 @@ class RuleSet { } void reset() { - rules.clear(); idRules.clear(); classRules.clear(); attributeRules.clear(); diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index b33d8a63a4..76706f9ff3 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -195,6 +195,9 @@ class Document extends Node { } void updateStyleIfNeeded() { + if (!styleNodeManager.hasPendingStyleSheet) { + return; + } styleNodeManager.updateActiveStyleSheets(); recalculateDocumentStyle(); } diff --git a/webf/lib/src/dom/elements/head.dart b/webf/lib/src/dom/elements/head.dart index c3230771f4..7efaf80498 100644 --- a/webf/lib/src/dom/elements/head.dart +++ b/webf/lib/src/dom/elements/head.dart @@ -140,6 +140,12 @@ class LinkElement extends Element { if (_resolvedHyperlink != null && _stylesheetLoaded.containsKey(_resolvedHyperlink.toString())) { return; } + if (_resolvedHyperlink != null) { + _stylesheetLoaded.remove(_resolvedHyperlink.toString()); + } + if (_styleSheet != null) { + ownerDocument.styleNodeManager.removePendingStyleSheet(_styleSheet!); + } Future.microtask(() { _fetchAndApplyCSSStyle(); }); @@ -165,10 +171,12 @@ class LinkElement extends Element { ownerDocument.decrementRequestCount(); final String cssString = await resolveStringFromData(bundle.data!); - _addCSSStyleSheet(cssString); + _styleSheet = CSSParser(cssString).parse(); + ownerDocument.styleNodeManager.appendPendingStyleSheet(_styleSheet!); // Successful load. SchedulerBinding.instance.addPostFrameCallback((_) { + ownerDocument.updateStyleIfNeeded(); dispatchEvent(Event(EVENT_LOAD)); }); } catch (e) { @@ -183,27 +191,22 @@ class LinkElement extends Element { } } - void _addCSSStyleSheet(String css) { - _styleSheet = CSSParser(css).parse(); - } - @override void connectedCallback() { - if (rel == _REL_STYLESHEET) { - ownerDocument.styleNodeManager.addStyleSheetCandidateNode(this); - } + super.connectedCallback(); + ownerDocument.styleNodeManager.addStyleSheetCandidateNode(this); if (_resolvedHyperlink != null) { _fetchAndApplyCSSStyle(); } - super.connectedCallback(); } @override void disconnectedCallback() { - if (rel == _REL_STYLESHEET) { - ownerDocument.styleNodeManager.removeStyleSheetCandidateNode(this); - } super.disconnectedCallback(); + if (_styleSheet != null) { + ownerDocument.styleNodeManager.removePendingStyleSheet(_styleSheet!); + } + ownerDocument.styleNodeManager.removeStyleSheetCandidateNode(this); } } @@ -274,6 +277,9 @@ class StyleElement extends Element { } else { _styleSheet = CSSParser(text).parse(); } + if (_styleSheet != null) { + ownerDocument.styleNodeManager.appendPendingStyleSheet(_styleSheet!); + } } } @@ -300,18 +306,19 @@ class StyleElement extends Element { @override void connectedCallback() { + super.connectedCallback(); if (_type == _CSS_MIME) { if (_styleSheet == null) { _recalculateStyle(); } ownerDocument.styleNodeManager.addStyleSheetCandidateNode(this); } - super.connectedCallback(); } @override void disconnectedCallback() { if (_styleSheet != null) { + ownerDocument.styleNodeManager.removePendingStyleSheet(_styleSheet!); ownerDocument.styleNodeManager.removeStyleSheetCandidateNode(this); } super.disconnectedCallback(); diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 02cd414781..0a837c8b5a 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -16,6 +16,10 @@ import 'package:webf/dom.dart'; class StyleNodeManager { final List<Node> _styleSheetCandidateNodes = []; + final List<CSSStyleSheet> _pendingStyleSheets = []; + + bool get hasPendingStyleSheet => _pendingStyleSheets.isNotEmpty; + final Document document; StyleNodeManager(this.document); @@ -41,8 +45,14 @@ class StyleNodeManager { _styleSheetCandidateNodes.insert(0, node); } - void removeStyleSheetCandidateNode(Node node) { - _styleSheetCandidateNodes.remove(node); + void removeStyleSheetCandidateNode(Node node) {} + + void appendPendingStyleSheet(CSSStyleSheet styleSheet) { + _pendingStyleSheets.add(styleSheet); + } + + void removePendingStyleSheet(CSSStyleSheet styleSheet) { + _pendingStyleSheets.removeWhere((element) => element == styleSheet); } void updateActiveStyleSheets() { @@ -79,6 +89,7 @@ class StyleNodeManager { } } }); + document.needsStyleRecalculate = true; } RuleSet analyzeStyleSheetChangeRuleSet(List<CSSStyleSheet> oldSheets, List<CSSStyleSheet> newSheets) { From 14af1013425f36328189a5684c68b27e59684e10 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Sun, 21 Aug 2022 18:33:17 +0800 Subject: [PATCH 196/498] feat: remove & append link element --- .../class-selector.ts.e3342df01.png | Bin 0 -> 8441 bytes .../specs/css/css-selectors/class-selector.ts | 8 +++ integration_tests/specs/dom/elements/link.ts | 46 +++++++++++++++++- webf/lib/src/css/element_rule_collector.dart | 4 +- webf/lib/src/css/parser/parser.dart | 2 - webf/lib/src/dom/document.dart | 2 +- webf/lib/src/dom/element.dart | 13 ----- webf/lib/src/dom/node.dart | 27 +++++----- webf/lib/src/dom/style_node_manager.dart | 19 ++++++-- 9 files changed, 85 insertions(+), 36 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.e3342df01.png diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.e3342df01.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.e3342df01.png new file mode 100644 index 0000000000000000000000000000000000000000..d2ce5d73c6d9ec075ac7394a2014d6b1514db70d GIT binary patch literal 8441 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/class-selector.ts b/integration_tests/specs/css/css-selectors/class-selector.ts index fe9843c1a1..55e6b34e3a 100644 --- a/integration_tests/specs/css/css-selectors/class-selector.ts +++ b/integration_tests/specs/css/css-selectors/class-selector.ts @@ -152,4 +152,12 @@ describe('css class selector', () => { document.body.appendChild(p8); await snapshot(); }); + + it('010', async () => { + const style = <style>{`.rule1 { background: red; color: yellow; } .rule2 { background: green; color: white; }`}</style>; + const p = <p class="rule2 rule1" > 010 This should have a green background.< /p>; + document.head.appendChild(style); + document.body.appendChild(p); + await snapshot(); + }); }); diff --git a/integration_tests/specs/dom/elements/link.ts b/integration_tests/specs/dom/elements/link.ts index 4a621d3872..7510818478 100644 --- a/integration_tests/specs/dom/elements/link.ts +++ b/integration_tests/specs/dom/elements/link.ts @@ -17,7 +17,7 @@ describe('Link Element', () => { BODY.appendChild(div); }); - fit('should work with local css', async (done) => { + it('should work with local css', async (done) => { let link = document.createElement('link'); link.setAttribute('href', 'assets:assets/bad.css'); link.setAttribute('rel', 'stylesheet'); @@ -34,5 +34,47 @@ describe('Link Element', () => { BODY.appendChild(p); }); - + it('insert style sheet', async (done) => { + let link1 = document.createElement('link'); + link1.setAttribute('href', 'assets:assets/bad.css'); + link1.setAttribute('rel', 'stylesheet'); + document.head.appendChild(link1); + + let link2 = document.createElement('link'); + link2.setAttribute('href', 'assets:assets/good.css'); + link2.setAttribute('rel', 'stylesheet'); + document.head.appendChild(link2); + + link2.addEventListener('load', async () => { + await snapshot(); + done(); + }); + + let p = document.createElement('p'); + p.appendChild(document.createTextNode('This text should be green on a red background')); + BODY.appendChild(p); + }); + + it('remove style sheet', async (done) => { + let link1 = document.createElement('link'); + link1.setAttribute('href', 'assets:assets/bad.css'); + link1.setAttribute('rel', 'stylesheet'); + document.head.appendChild(link1); + + let link2 = document.createElement('link'); + link2.setAttribute('href', 'assets:assets/good.css'); + link2.setAttribute('rel', 'stylesheet'); + document.head.appendChild(link2); + + link1.addEventListener('load', async () => { + document.head.removeChild(link1); + await sleep(0.5); + await snapshot(); + done(); + }); + + let p = document.createElement('p'); + p.appendChild(document.createTextNode('remove: This text should be green on a red background')); + BODY.appendChild(p); + }); }); diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index 3951e7dda8..e49b27c618 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -14,7 +14,7 @@ class ElementRuleCollector { List<CSSRule> _matchedRules(RuleSet ruleSet, Element element) { List<CSSRule> matchedRules = []; - if (ruleSet.rules.isEmpty) { + if (ruleSet.isEmpty) { return matchedRules; } @@ -57,7 +57,7 @@ class ElementRuleCollector { } int isCompare = leftRule.selectorGroup.specificity.compareTo(rightRule.selectorGroup.specificity); if (isCompare == 0) { - return ruleSet.rules.indexOf(leftRule).compareTo(ruleSet.rules.indexOf(rightRule)); + return leftRule.position.compareTo(rightRule.position); } return isCompare; }); diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index df8c53d432..46716806f5 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -128,10 +128,8 @@ class CSSParser { List<CSSRule> parseRules({int startPosition = 0}) { var rules = <CSSRule>[]; - int position = startPosition; while (!_maybeEat(TokenKind.END_OF_FILE)) { final rule = processRule(); - rule?.position = position++; if (rule != null) { rules.add(rule); } else { diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 76706f9ff3..5dd0aa5d9f 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -195,7 +195,7 @@ class Document extends Node { } void updateStyleIfNeeded() { - if (!styleNodeManager.hasPendingStyleSheet) { + if (!ownerDocument.needsStyleRecalculate) { return; } styleNodeManager.updateActiveStyleSheets(); diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index c09141815b..7a81b28812 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -882,19 +882,6 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element return child; } - // 2. Node::invalidateStyle 方法 将元素所有祖先都标记需要更新样式 - // 3. Document updateStyleIfNeeded() - // 3.1 flush pending sheet - // 比对 style sheet, 得到 ActiveSheetsChange 和 ChangedRuleSet - // 通过 ElementRuleCollector ElementRuleCollector::matchesAnyAuthorRules - // 对比后将 element 标记为 invalid (Invalidator::invalidateIfNeeded) - // 3.2 判断标记 needsStyleRecalc() ,执行 resolveStyle(); - // 5. TreeResolver::resolveComposedTree - // 6. TreeResolver::resolveElement - // 6.1 TreeResolver::styleForStyleable return CSSStyleDeclaration - // 6.1.1 styleForElement - // 6.2 Merge CSSStyleDeclaration - @override @mustCallSuper Node insertBefore(Node child, Node referenceNode) { diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index f5a46c4fab..508b4e6b2c 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -100,6 +100,9 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa // Children changed steps for node. // https://dom.spec.whatwg.org/#concept-node-children-changed-ext + + int _frameCallbackId = 0; + void childrenChanged() { if (!this.isConnected) { return; @@ -110,11 +113,11 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa parent.needsStyleRecalculate = true; parent = parent.parentNode!; } - - SchedulerBinding.instance.addPostFrameCallback((_) { - if (!ownerDocument.needsStyleRecalculate) { - return; - } + ownerDocument.needsStyleRecalculate = true; + if (_frameCallbackId > 0) { + SchedulerBinding.instance.cancelFrameCallbackWithId(_frameCallbackId); + } + _frameCallbackId = SchedulerBinding.instance.scheduleFrameCallback((_) { ownerDocument.updateStyleIfNeeded(); }); } @@ -284,14 +287,14 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa childNodes.remove(child); child.parentNode = null; + if (isConnected) { + child.disconnectedCallback(); + } + // To remove a node, run step 21 from the spec: // 21. Run the children changed steps for parent. // https://dom.spec.whatwg.org/#concept-node-remove childrenChanged(); - - if (isConnected) { - child.disconnectedCallback(); - } } return child; } @@ -368,12 +371,12 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa List<Node> chain2 = []; Node? current = this; while (current != null && current.parentNode != null) { - chain1.add(current); + chain1.insert(0, current); current = current.parentNode; } current = other; while (current != null && current.parentNode != null) { - chain2.add(current); + chain2.insert(0, current); current = current.parentNode; } @@ -383,7 +386,7 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa } // Walk the two chains backwards and look for the first difference. - for (int i = 0; i < math.min(chain1.length, chain2.length) - 1; i++) { + for (int i = 0; i < math.min(chain1.length, chain2.length); i++) { if (chain1[i] != chain2[i]) { if (chain2[i].nextSibling == null) { return DocumentPosition.FOLLOWING; diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 0a837c8b5a..56e9721790 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -45,7 +45,9 @@ class StyleNodeManager { _styleSheetCandidateNodes.insert(0, node); } - void removeStyleSheetCandidateNode(Node node) {} + void removeStyleSheetCandidateNode(Node node) { + _styleSheetCandidateNodes.remove(node); + } void appendPendingStyleSheet(CSSStyleSheet styleSheet) { _pendingStyleSheets.add(styleSheet); @@ -62,6 +64,9 @@ class StyleNodeManager { } newSheets = _collectActiveStyleSheets().where((element) => element.cssRules.isNotEmpty).toList(); RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); + if (changedRuleSet.isEmpty) { + return; + } invalidateElementStyle(changedRuleSet); document.handleStyleSheets(newSheets); } @@ -132,13 +137,19 @@ class StyleNodeManager { for (int index = 0; index < mergeSorted.length; index++) { CSSStyleSheet sheet = mergeSorted[index]; - CSSStyleSheet sheet1 = mergeSorted[++index]; - if (index == mergeSorted.length || sheet != sheet1) { + if (index + 1 < mergeSorted.length) { + ++index; + } + CSSStyleSheet sheet1 = mergeSorted[index]; + if (index == mergeSorted.length - 1 || sheet != sheet1) { ruleSet.addRules(sheet1.cssRules); continue; } - CSSStyleSheet sheet2 = mergeSorted[++index]; + if (index + 1 < mergeSorted.length) { + ++index; + } + CSSStyleSheet sheet2 = mergeSorted[index]; if (equals(sheet1.cssRules, sheet2.cssRules)) { continue; } From 553387d9e9ab30fe10a066367bb2754a830219e4 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Sun, 21 Aug 2022 21:45:22 +0800 Subject: [PATCH 197/498] feat: query selector for dart --- webf/lib/src/css/element_rule_collector.dart | 2 +- ...tor_evaluator.dart => query_selector.dart} | 32 +++++++++++++++++++ webf/lib/src/css/rule_set.dart | 3 ++ webf/lib/src/dom/document.dart | 26 +++++++++++++++ webf/lib/src/dom/node.dart | 2 +- 5 files changed, 63 insertions(+), 2 deletions(-) rename webf/lib/src/css/{selector_evaluator.dart => query_selector.dart} (91%) diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index e49b27c618..95b9da2baf 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -4,7 +4,7 @@ import 'package:webf/css.dart'; import 'package:webf/dom.dart'; -import 'package:webf/src/css/selector_evaluator.dart'; +import 'package:webf/src/css/query_selector.dart'; class ElementRuleCollector { bool matchedAnyRule(RuleSet ruleSet, Element element) { diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/query_selector.dart similarity index 91% rename from webf/lib/src/css/selector_evaluator.dart rename to webf/lib/src/css/query_selector.dart index 479b2b2801..cca55d21c1 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/query_selector.dart @@ -31,6 +31,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import 'package:webf/dom.dart'; import 'package:webf/css.dart'; +Element? querySelector(Node node, String selector) => + SelectorEvaluator().querySelector(node, _parseSelectorGroup(selector)); + +List<Element> querySelectorAll(Node node, String selector) { + final group = _parseSelectorGroup(selector); + final results = <Element>[]; + SelectorEvaluator().querySelectorAll(node, group, results); + return results; +} + +// http://dev.w3.org/csswg/selectors-4/#grouping +SelectorGroup? _parseSelectorGroup(String selector) { + CSSParser parser = CSSParser(selector)..tokenizer.inSelector = true; + return parser.processSelectorGroup(); +} + class SelectorEvaluator extends SelectorVisitor { Element? _element; @@ -42,6 +58,22 @@ class SelectorEvaluator extends SelectorVisitor { return visitSelectorGroup(selector); } + Element? querySelector(Node root, SelectorGroup? selector) { + for (var element in root.childNodes.whereType<Element>()) { + if (matchSelector(selector, element)) return element; + final result = querySelector(element, selector); + if (result != null) return result; + } + return null; + } + + void querySelectorAll(Node root, SelectorGroup? selector, List<Element> results) { + for (var element in root.childNodes.whereType<Element>()) { + if (matchSelector(selector, element)) results.add(element); + querySelectorAll(element, selector, results); + } + } + @override bool visitSelectorGroup(SelectorGroup node) => node.selectors.any(visitSelector); diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index fac03debcd..a7e5154353 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -19,6 +19,8 @@ class RuleSet { final CSSMap tagRules = HashMap(); final List<CSSRule> universalRules = []; + int _lastPosition = 0; + void addRules(List<CSSRule> rules) { for (CSSRule rule in rules) { addRule(rule); @@ -26,6 +28,7 @@ class RuleSet { } void addRule(CSSRule rule) { + rule.position = _lastPosition++; if (rule is CSSStyleRule) { for (final selector in rule.selectorGroup.selectors) { findBestRuleSetAndAdd(selector, rule); diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 5dd0aa5d9f..aeb6372a97 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -9,6 +9,7 @@ import 'package:webf/foundation.dart'; import 'package:webf/gesture.dart'; import 'package:webf/launcher.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/widget.dart'; @@ -91,6 +92,31 @@ class Document extends Node { } } + @override + getBindingProperty(String key) { + switch (key) { + case 'querySelectorAll': + return querySelectorAll; + case 'querySelector': + return querySelector; + } + return super.getBindingProperty(key); + } + + dynamic querySelector(List<dynamic> args) { + if (args.isEmpty || args.first is! String) { + return null; + } + return QuerySelector.querySelector(this, args.first); + } + + dynamic querySelectorAll(List<dynamic> args) { + if (args.isEmpty || args.first is! String) { + return null; + } + return QuerySelector.querySelectorAll(this, args.first); + } + Element? _documentElement; Element? get documentElement => _documentElement; set documentElement(Element? element) { diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index 508b4e6b2c..34a95d8f1f 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -104,7 +104,7 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa int _frameCallbackId = 0; void childrenChanged() { - if (!this.isConnected) { + if (!isConnected) { return; } Node parent = this; From 6d97173b6ad55ef277e7384253f6e3c3596bd5d0 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Mon, 22 Aug 2022 15:04:11 +0800 Subject: [PATCH 198/498] fix(test): dom link element --- .../dom/elements/link.ts.9f7a30e31.png | Bin 0 -> 7170 bytes .../dom/elements/link.ts.c15dbee01.png | Bin 6396 -> 7040 bytes .../dom/elements/link.ts.c15dbee02.png | Bin 8937 -> 9983 bytes .../dom/elements/link.ts.c15dbee03.png | Bin 8937 -> 9983 bytes .../dom/elements/link.ts.c15dbee04.png | Bin 0 -> 9983 bytes .../specs/css/css-inline/change_inline.ts | 2 +- integration_tests/specs/dom/elements/link.ts | 2 +- webf/lib/src/dom/document.dart | 3 --- webf/lib/src/dom/node.dart | 3 +++ 9 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 integration_tests/snapshots/dom/elements/link.ts.9f7a30e31.png create mode 100644 integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png diff --git a/integration_tests/snapshots/dom/elements/link.ts.9f7a30e31.png b/integration_tests/snapshots/dom/elements/link.ts.9f7a30e31.png new file mode 100644 index 0000000000000000000000000000000000000000..fae5497665d0a303d8be35415e363914cba0aead GIT binary patch literal 7170 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee01.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee01.png index 6c1b3b5455fb2b391422bf20246a1736f26785ec..bc5ceea4d630cb7ff6ca9c58f9f0e4bb72104b61 100644 GIT binary patch literal 7040 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 literal 6396 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 diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee02.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee02.png index 266137a02c2af9a7942b5b9c57d53c4d0b62772f..42b998a1e1e2d408baa0e3ba7c330ff32e703316 100644 GIT binary patch literal 9983 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 Dh>Zz7 literal 8937 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 diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee03.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee03.png index 266137a02c2af9a7942b5b9c57d53c4d0b62772f..42b998a1e1e2d408baa0e3ba7c330ff32e703316 100644 GIT binary patch literal 9983 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 Dh>Zz7 literal 8937 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 diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png new file mode 100644 index 0000000000000000000000000000000000000000..42b998a1e1e2d408baa0e3ba7c330ff32e703316 GIT binary patch literal 9983 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 Dh>Zz7 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-inline/change_inline.ts b/integration_tests/specs/css/css-inline/change_inline.ts index b74e3e9e03..399554786d 100644 --- a/integration_tests/specs/css/css-inline/change_inline.ts +++ b/integration_tests/specs/css/css-inline/change_inline.ts @@ -1,4 +1,4 @@ -describe('line-change', () => { +xdescribe('line-change', () => { it('001', async () => { const div = <div>should be green</div> document.body.appendChild(div); diff --git a/integration_tests/specs/dom/elements/link.ts b/integration_tests/specs/dom/elements/link.ts index 7510818478..d55683877d 100644 --- a/integration_tests/specs/dom/elements/link.ts +++ b/integration_tests/specs/dom/elements/link.ts @@ -30,7 +30,7 @@ describe('Link Element', () => { document.head.appendChild(link); let p = document.createElement('p'); - p.appendChild(document.createTextNode('This text should be green on a white background')); + p.appendChild(document.createTextNode('002 This text should be green on a white background')); BODY.appendChild(p); }); diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index aeb6372a97..105cdb6c03 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -221,9 +221,6 @@ class Document extends Node { } void updateStyleIfNeeded() { - if (!ownerDocument.needsStyleRecalculate) { - return; - } styleNodeManager.updateActiveStyleSheets(); recalculateDocumentStyle(); } diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index 34a95d8f1f..386f6700df 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -118,6 +118,9 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa SchedulerBinding.instance.cancelFrameCallbackWithId(_frameCallbackId); } _frameCallbackId = SchedulerBinding.instance.scheduleFrameCallback((_) { + if (!ownerDocument.needsStyleRecalculate) { + return; + } ownerDocument.updateStyleIfNeeded(); }); } From 19510b165c9259da81852a491733598e35200348 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Tue, 23 Aug 2022 01:30:45 +0800 Subject: [PATCH 199/498] feat: support keyframes rule --- webf/lib/css.dart | 2 +- webf/lib/src/css/css_rule.dart | 69 ++++++++++++++ webf/lib/src/css/parser/parser.dart | 92 ++++++++++++++----- webf/lib/src/css/parser/selector.dart | 39 +------- webf/lib/src/css/parser/token_kind.dart | 70 +++++++++++++- webf/lib/src/css/parser/tree.dart | 13 +-- webf/lib/src/css/rule.dart | 3 +- webf/lib/src/css/rule_set.dart | 4 + webf/lib/src/css/style_rule.dart | 29 ------ webf/lib/src/dom/style_node_manager.dart | 4 +- .../test/src/css/style_animations_parser.dart | 21 +++++ webf/test/webf_test.dart | 2 + 12 files changed, 240 insertions(+), 108 deletions(-) create mode 100644 webf/lib/src/css/css_rule.dart delete mode 100644 webf/lib/src/css/style_rule.dart create mode 100644 webf/test/src/css/style_animations_parser.dart diff --git a/webf/lib/css.dart b/webf/lib/css.dart index 851483723a..ea043ef7d8 100644 --- a/webf/lib/css.dart +++ b/webf/lib/css.dart @@ -22,7 +22,7 @@ export 'src/css/border.dart'; export 'src/css/gradient.dart'; export 'src/css/render_style.dart'; export 'src/css/rule.dart'; -export 'src/css/style_rule.dart'; +export 'src/css/css_rule.dart'; export 'src/css/style_declaration.dart'; export 'src/css/style_property.dart'; export 'src/css/style_sheet.dart'; diff --git a/webf/lib/src/css/css_rule.dart b/webf/lib/src/css/css_rule.dart new file mode 100644 index 0000000000..d7a1114789 --- /dev/null +++ b/webf/lib/src/css/css_rule.dart @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'package:webf/css.dart'; + +/// https://drafts.csswg.org/cssom/#the-cssstylerule-interface +class CSSStyleRule extends CSSRule { + @override + String get cssText => declaration.cssText; + + @override + int get type => CSSRule.STYLE_RULE; + + final SelectorGroup selectorGroup; + final CSSStyleDeclaration declaration; + + CSSStyleRule(this.selectorGroup, this.declaration) : super(); + + SimpleSelector? get lastSimpleSelector { + return selectorGroup.selectors.last.simpleSelectorSequences.last.simpleSelector; + } + + String get selectorText { + var sb = StringBuffer(); + selectorGroup.selectors.forEach((selector) { + sb.write(selector.simpleSelectorSequences.map((ss) => ss.simpleSelector.name).join(' ')); + }); + return sb.toString(); + } +} + +class KeyFrameBlock { + final List<String> blockSelectors; + final CSSStyleDeclaration declarations; + + KeyFrameBlock(this.blockSelectors, this.declarations); +} + +class CSSKeyframesRule extends CSSRule { + final int _keyframeName; + final String name; + final List<KeyFrameBlock> blocks = []; + + @override + int get type => CSSRule.KEYFRAMES_RULE; + + CSSKeyframesRule(this._keyframeName, this.name) : super(); + + void add(KeyFrameBlock block) { + blocks.add(block); + } + + String? get keyFrameName { + switch (_keyframeName) { + case TokenKind.DIRECTIVE_KEYFRAMES: + case TokenKind.DIRECTIVE_MS_KEYFRAMES: + return '@keyframes'; + case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES: + return '@-webkit-keyframes'; + case TokenKind.DIRECTIVE_MOZ_KEYFRAMES: + return '@-moz-keyframes'; + case TokenKind.DIRECTIVE_O_KEYFRAMES: + return '@-o-keyframes'; + } + return null; + } +} diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 46716806f5..dd012b38ad 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -159,7 +159,7 @@ class CSSParser { /// Generate an error if [file] has not been completely consumed. void checkEndOfFile() { if (!(_peekKind(TokenKind.END_OF_FILE) || _peekKind(TokenKind.INCOMPLETE_COMMENT))) { - _error('premature end of file unknown CSS', _peekToken.span); + _error('premature end of file unknown CSS'); } } @@ -169,7 +169,7 @@ class CSSParser { // the danger here. bool isPrematureEndOfFile() { if (_maybeEat(TokenKind.END_OF_FILE)) { - _error('unexpected end of file', _peekToken.span); + _error('unexpected end of file'); return true; } else { return false; @@ -232,17 +232,27 @@ class CSSParser { } catch (e) { message = 'parsing error expected $expected'; } - _error(message, tok.span); + _error(message); } - void _error(String message, SourceSpan? location) { + void _error(String message, {SourceSpan? location}) { location ??= _peekToken.span; - // messages.error(message, location); + print(location.message(message, color: '\u001b[31m')); } - void _warning(String message, SourceSpan? location) { - location ??= _peekToken.span; - // messages.warning(message, location); + void _warning(String message, {SourceSpan? location}) { + location ??= _makeSpan(_peekToken.span); + print(location.message(message, color: '\u001b[35m')); + } + + SourceSpan _makeSpan(FileSpan start) { + // TODO(terry): there are places where we are creating spans before we eat + // the tokens, so using _previousToken is not always valid. + // TODO(nweiz): use < rather than compareTo when SourceSpan supports it. + if (_previousToken == null || _previousToken!.span.compareTo(start) < 0) { + return start; + } + return start.expand(_previousToken!.span); } /// Directive grammar: @@ -265,7 +275,7 @@ class CSSParser { /// declarations /// '}' /// supports: '@supports' supports_condition group_rule_body - TreeNode? processDirective() { + CSSRule? processDirective() { var tokenId = _peek(); switch (tokenId) { case TokenKind.DIRECTIVE_IMPORT: @@ -331,7 +341,7 @@ class CSSParser { // TODO(terry): Remove workaround when bug 8270 is fixed. case TokenKind.DIRECTIVE_MS_KEYFRAMES: if (tokenId == TokenKind.DIRECTIVE_MS_KEYFRAMES && isChecked) { - // _warning('@-ms-keyframes should be @keyframes'); + _warning('@-ms-keyframes should be @keyframes'); } // TODO(terry): End of workaround. @@ -348,7 +358,30 @@ class CSSParser { // ['from'|'to'|PERCENTAGE] [',' ['from'|'to'|PERCENTAGE] ]* ; _next(); - return null; + String name = ''; + if (_peekIdentifier()) { + name = identifier().name; + } + assert(name.isNotEmpty, 'keyframes rule name must not be null'); + _eat(TokenKind.LBRACE); + + var keyframe = CSSKeyframesRule(tokenId, name); + do { + List<String> selectors = []; + do { + final selector = _next().text; + final text = _peekToken.text; + // ignore unit type + if (TokenKind.matchUnits(text, 0, text.length) != -1) { + _next(); + } + selectors.add(selector); + } while (_maybeEat(TokenKind.COMMA)); + + keyframe.add(KeyFrameBlock(selectors, processDeclarations())); + } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile()); + + return keyframe; case TokenKind.DIRECTIVE_FONTFACE: _next(); @@ -378,7 +411,7 @@ class CSSParser { return null; case TokenKind.DIRECTIVE_CONTENT: // TODO(terry): TBD - // _warning('@content not implemented.'); + _warning('@content not implemented.'); return null; case TokenKind.DIRECTIVE_MOZ_DOCUMENT: return null; @@ -393,7 +426,11 @@ class CSSParser { CSSRule? processRule([SelectorGroup? selectorGroup]) { if (selectorGroup == null) { - processDirective(); + final directive = processDirective(); + if (directive != null) { + _maybeEat(TokenKind.SEMICOLON); + return directive; + } selectorGroup = processSelectorGroup(); } if (selectorGroup != null) { @@ -527,7 +564,7 @@ class CSSParser { if (selector != null) { for (var sequence in selector.simpleSelectorSequences) { if (!sequence.isCombinatorNone) { - // _error('compound selector can not contain combinator'); + _error('compound selector can not contain combinator - ${sequence.combinatorToString}'); } } } @@ -661,7 +698,7 @@ class CSSParser { _eat(TokenKind.HASH); if (_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { - // _error('Not a valid ID selector expected #id'); + _error('Not a valid ID selector expected #id', location: _makeSpan(start)); return null; } return IdSelector(identifier()); @@ -669,7 +706,7 @@ class CSSParser { _eat(TokenKind.DOT); if (_anyWhiteSpaceBeforePeekToken(TokenKind.DOT)) { - // _error('Not a valid class selector expected .className'); + _error('Not a valid class selector expected .className', location: _makeSpan(start)); return null; } return ClassSelector(identifier()); @@ -679,7 +716,7 @@ class CSSParser { case TokenKind.LBRACK: return processAttribute(); case TokenKind.DOUBLE: - _error('name must start with a alpha character, but found a number', _peekToken.span); + _error('name must start with a alpha character, but found a number'); _next(); break; } @@ -840,7 +877,7 @@ class CSSParser { } if (value == null) { - _error('expected attribute value string or ident', _peekToken.span); + _error('expected attribute value string or ident'); } } @@ -936,6 +973,8 @@ class CSSParser { static const int MAX_UNICODE = 0x10FFFF; String processQuotedString([bool urlString = false]) { + var start = _peekToken.span; + // URI term sucks up everything inside of quotes(' or ") or between parens var stopToken = urlString ? TokenKind.RPAREN : -1; @@ -948,19 +987,22 @@ class CSSParser { case TokenKind.SINGLE_QUOTE: stopToken = TokenKind.SINGLE_QUOTE; _next(); // Skip the SINGLE_QUOTE. + start = _peekToken.span; break; case TokenKind.DOUBLE_QUOTE: stopToken = TokenKind.DOUBLE_QUOTE; _next(); // Skip the DOUBLE_QUOTE. + start = _peekToken.span; break; default: if (urlString) { if (_peek() == TokenKind.LPAREN) { _next(); // Skip the LPAREN. + start = _peekToken.span; } stopToken = TokenKind.RPAREN; } else { - // _error('unexpected string'); + _error('unexpected string', location: _makeSpan(start)); } break; } @@ -1013,7 +1055,7 @@ class CSSParser { } if (!matchingParens) { - _error('problem parsing function expected ), ', _peekToken.span); + _error('problem parsing function expected ), '); } tokenizer._inString = inString; @@ -1032,7 +1074,7 @@ class CSSParser { case 'rgb': var expr = processExpr(); if (!_maybeEat(TokenKind.RPAREN)) { - _error('problem parsing function expected ), ', _peekToken.span); + _error('problem parsing function expected ), '); } return 'rgb($expr)'; case 'url': @@ -1042,7 +1084,7 @@ class CSSParser { // TODO(terry): Better error message and checking for mismatched quotes. if (_peek() == TokenKind.END_OF_FILE) { - _error('problem parsing URI', _peekToken.span); + _error('problem parsing URI'); } if (_peek() == TokenKind.RPAREN) { @@ -1061,13 +1103,13 @@ class CSSParser { // (GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); var expr = processExpr(); if (!_maybeEat(TokenKind.RPAREN)) { - _error('problem parsing var expected ), ', _peekToken.span); + _error('problem parsing var expected'); } return expr; default: var expr = processExpr(); if (!_maybeEat(TokenKind.RPAREN)) { - _error('problem parsing function expected ), ', _peekToken.span); + _error('problem parsing function expected'); } return expr; } @@ -1078,7 +1120,7 @@ class CSSParser { if (!TokenKind.isIdentifier(tok.kind) && !TokenKind.isKindIdentifier(tok.kind)) { if (isChecked) { - _warning('expected identifier, but found $tok', tok.span); + _warning('expected identifier, but found $tok', location: tok.span); } return Identifier(''); } diff --git a/webf/lib/src/css/parser/selector.dart b/webf/lib/src/css/parser/selector.dart index 6ba599e526..872e5a2c39 100644 --- a/webf/lib/src/css/parser/selector.dart +++ b/webf/lib/src/css/parser/selector.dart @@ -49,9 +49,6 @@ class SelectorGroup extends TreeNode { SelectorGroup(this.selectors) : super(); - @override - SelectorGroup clone() => SelectorGroup(selectors); - @override dynamic visit(Visitor visitor) => visitor.visitSelectorGroup(this); @@ -86,13 +83,6 @@ class Selector extends TreeNode { int get length => simpleSelectorSequences.length; - @override - Selector clone() { - var simpleSequences = simpleSelectorSequences.map((ss) => ss.clone()).toList(); - - return Selector(simpleSequences); - } - @override dynamic visit(Visitor visitor) => visitor.visitSelector(this); } @@ -125,9 +115,6 @@ class SimpleSelectorSequence extends TreeNode { } } - @override - SimpleSelectorSequence clone() => SimpleSelectorSequence(simpleSelector, combinator); - @override dynamic visit(Visitor visitor) => visitor.visitSimpleSelectorSequence(this); @@ -158,9 +145,6 @@ abstract class SimpleSelector extends TreeNode { class ElementSelector extends SimpleSelector { ElementSelector(name) : super(name); - @override - ElementSelector clone() => ElementSelector(_name); - @override String toString() => name; @@ -228,9 +212,6 @@ class AttributeSelector extends SimpleSelector { } } - @override - AttributeSelector clone() => AttributeSelector(_name as Identifier, _op, value); - @override String toString() => '[$name${matchOperator()}${valueToString()}]'; @@ -241,8 +222,6 @@ class AttributeSelector extends SimpleSelector { // #id class IdSelector extends SimpleSelector { IdSelector(Identifier name) : super(name); - @override - IdSelector clone() => IdSelector(_name as Identifier); @override String toString() => '#$_name'; @@ -254,8 +233,7 @@ class IdSelector extends SimpleSelector { // .class class ClassSelector extends SimpleSelector { ClassSelector(Identifier name) : super(name); - @override - ClassSelector clone() => ClassSelector(_name as Identifier); + @override String toString() => '.$_name'; @@ -267,9 +245,6 @@ class ClassSelector extends SimpleSelector { class PseudoClassSelector extends SimpleSelector { PseudoClassSelector(Identifier name) : super(name); - @override - PseudoClassSelector clone() => PseudoClassSelector(_name as Identifier); - @override String toString() => ':$name'; @@ -284,9 +259,6 @@ class PseudoElementSelector extends SimpleSelector { PseudoElementSelector(Identifier name, {this.isLegacy = false}) : super(name); - @override - PseudoElementSelector clone() => PseudoElementSelector(_name as Identifier); - @override String toString() => "${isLegacy ? ':' : '::'}$name"; @@ -300,9 +272,6 @@ class PseudoClassFunctionSelector extends PseudoClassSelector { PseudoClassFunctionSelector(Identifier name, this.argument) : super(name); - @override - PseudoClassFunctionSelector clone() => PseudoClassFunctionSelector(_name as Identifier, argument); - Selector get selector => argument as Selector; List<String> get expression => argument as List<String>; @@ -317,9 +286,6 @@ class PseudoElementFunctionSelector extends PseudoElementSelector { PseudoElementFunctionSelector(Identifier name, this.expression) : super(name); - @override - PseudoElementFunctionSelector clone() => PseudoElementFunctionSelector(_name as Identifier, expression); - @override dynamic visit(Visitor visitor) => visitor.visitPseudoElementFunctionSelector(this); } @@ -330,9 +296,6 @@ class NegationSelector extends SimpleSelector { NegationSelector(this.negationArg) : super(Negation()); - @override - NegationSelector clone() => NegationSelector(negationArg); - @override dynamic visit(Visitor visitor) => visitor.visitNegationSelector(this); } diff --git a/webf/lib/src/css/parser/token_kind.dart b/webf/lib/src/css/parser/token_kind.dart index f860279284..2761ff2736 100644 --- a/webf/lib/src/css/parser/token_kind.dart +++ b/webf/lib/src/css/parser/token_kind.dart @@ -137,6 +137,35 @@ class TokenKind { static const int SUBSTRING_MATCH = 534; // '*=' static const int NO_MATCH = 535; // No operator. + // Unit types: + static const int UNIT_EM = 600; + static const int UNIT_EX = 601; + static const int UNIT_LENGTH_PX = 602; + static const int UNIT_LENGTH_CM = 603; + static const int UNIT_LENGTH_MM = 604; + static const int UNIT_LENGTH_IN = 605; + static const int UNIT_LENGTH_PT = 606; + static const int UNIT_LENGTH_PC = 607; + static const int UNIT_ANGLE_DEG = 608; + static const int UNIT_ANGLE_RAD = 609; + static const int UNIT_ANGLE_GRAD = 610; + static const int UNIT_ANGLE_TURN = 611; + static const int UNIT_TIME_MS = 612; + static const int UNIT_TIME_S = 613; + static const int UNIT_FREQ_HZ = 614; + static const int UNIT_FREQ_KHZ = 615; + static const int UNIT_PERCENT = 616; + static const int UNIT_FRACTION = 617; + static const int UNIT_RESOLUTION_DPI = 618; + static const int UNIT_RESOLUTION_DPCM = 619; + static const int UNIT_RESOLUTION_DPPX = 620; + static const int UNIT_CH = 621; // Measure of "0" U+0030 glyph. + static const int UNIT_REM = 622; // computed value ‘font-size’ on root elem. + static const int UNIT_VIEWPORT_VW = 623; + static const int UNIT_VIEWPORT_VH = 624; + static const int UNIT_VIEWPORT_VMIN = 625; + static const int UNIT_VIEWPORT_VMAX = 626; + // Directives (@nnnn) static const int DIRECTIVE_NONE = 640; static const int DIRECTIVE_IMPORT = 641; @@ -242,12 +271,43 @@ class TokenKind { {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTBOTTOM, 'value': 'right-bottom'}, ]; + static const List<Map<String, dynamic>> _UNITS = [ + {'unit': TokenKind.UNIT_EM, 'value': 'em'}, + {'unit': TokenKind.UNIT_EX, 'value': 'ex'}, + {'unit': TokenKind.UNIT_LENGTH_PX, 'value': 'px'}, + {'unit': TokenKind.UNIT_LENGTH_CM, 'value': 'cm'}, + {'unit': TokenKind.UNIT_LENGTH_MM, 'value': 'mm'}, + {'unit': TokenKind.UNIT_LENGTH_IN, 'value': 'in'}, + {'unit': TokenKind.UNIT_LENGTH_PT, 'value': 'pt'}, + {'unit': TokenKind.UNIT_LENGTH_PC, 'value': 'pc'}, + {'unit': TokenKind.UNIT_ANGLE_DEG, 'value': 'deg'}, + {'unit': TokenKind.UNIT_ANGLE_RAD, 'value': 'rad'}, + {'unit': TokenKind.UNIT_ANGLE_GRAD, 'value': 'grad'}, + {'unit': TokenKind.UNIT_ANGLE_TURN, 'value': 'turn'}, + {'unit': TokenKind.UNIT_TIME_MS, 'value': 'ms'}, + {'unit': TokenKind.UNIT_TIME_S, 'value': 's'}, + {'unit': TokenKind.UNIT_FREQ_HZ, 'value': 'hz'}, + {'unit': TokenKind.UNIT_FREQ_KHZ, 'value': 'khz'}, + {'unit': TokenKind.UNIT_FRACTION, 'value': 'fr'}, + {'unit': TokenKind.UNIT_RESOLUTION_DPI, 'value': 'dpi'}, + {'unit': TokenKind.UNIT_RESOLUTION_DPCM, 'value': 'dpcm'}, + {'unit': TokenKind.UNIT_RESOLUTION_DPPX, 'value': 'dppx'}, + {'unit': TokenKind.UNIT_CH, 'value': 'ch'}, + {'unit': TokenKind.UNIT_REM, 'value': 'rem'}, + {'unit': TokenKind.UNIT_VIEWPORT_VW, 'value': 'vw'}, + {'unit': TokenKind.UNIT_VIEWPORT_VH, 'value': 'vh'}, + {'unit': TokenKind.UNIT_VIEWPORT_VMIN, 'value': 'vmin'}, + {'unit': TokenKind.UNIT_VIEWPORT_VMAX, 'value': 'vmax'}, + {'unit': TokenKind.UNIT_PERCENT, 'value': '%'}, + ]; + // Some more constants: static const int ASCII_UPPER_A = 65; // ASCII value for uppercase A static const int ASCII_UPPER_Z = 90; // ASCII value for uppercase Z /// Return the token that matches the unit ident found. - static int matchList(Iterable<Map<String, dynamic>> identList, String tokenField, String text, int offset, int length) { + static int matchList( + Iterable<Map<String, dynamic>> identList, String tokenField, String text, int offset, int length) { for (final entry in identList) { final ident = entry['value'] as String; @@ -258,7 +318,8 @@ class TokenKind { var identChar = ident.codeUnitAt(i); var char = text.codeUnitAt(idx++); // Compare lowercase to lowercase then check if char is uppercase. - match = match && (char == identChar || ((char >= ASCII_UPPER_A && char <= ASCII_UPPER_Z) && (char + 32) == identChar)); + match = match && + (char == identChar || ((char >= ASCII_UPPER_A && char <= ASCII_UPPER_Z) && (char + 32) == identChar)); if (!match) { break; } @@ -289,6 +350,11 @@ class TokenKind { return matchList(MEDIA_OPERATORS, 'type', text, offset, length); } + /// Return the token that matches the unit ident found. + static int matchUnits(String text, int offset, int length) { + return matchList(_UNITS, 'unit', text, offset, length); + } + static String? idToValue(Iterable<Object?> identList, int tokenId) { for (var entry in identList) { entry as Map<String, Object?>; diff --git a/webf/lib/src/css/parser/tree.dart b/webf/lib/src/css/parser/tree.dart index 9b614fa1bd..c91a4fdc79 100644 --- a/webf/lib/src/css/parser/tree.dart +++ b/webf/lib/src/css/parser/tree.dart @@ -32,8 +32,6 @@ part of 'parser.dart'; /// The base type for all nodes in a CSS abstract syntax tree. abstract class TreeNode { - TreeNode clone(); - /// Classic double-dispatch visitor for implementing passes. dynamic visit(Visitor visitor) {} } @@ -43,9 +41,6 @@ class Identifier extends TreeNode { Identifier(this.name) : super(); - @override - Identifier clone() => Identifier(name); - @override String toString() { // Try to use the identifier's original lexeme to preserve any escape codes @@ -57,22 +52,18 @@ class Identifier extends TreeNode { class Wildcard extends TreeNode { Wildcard() : super(); - @override - Wildcard clone() => Wildcard(); + String get name => '*'; } class ThisOperator extends TreeNode { ThisOperator() : super(); - @override - ThisOperator clone() => ThisOperator(); + String get name => '&'; } class Negation extends TreeNode { Negation() : super(); - @override - Negation clone() => Negation(); String get name => 'not'; } diff --git a/webf/lib/src/css/rule.dart b/webf/lib/src/css/rule.dart index f8887c112e..fd567c319e 100644 --- a/webf/lib/src/css/rule.dart +++ b/webf/lib/src/css/rule.dart @@ -12,13 +12,14 @@ abstract class CSSRule { // https://drafts.csswg.org/cssom/#dom-cssrule-type // The following attribute and constants are historical. - int? type; + int get type; static const int STYLE_RULE = 1; static const int CHARSET_RULE = 2; static const int IMPORT_RULE = 3; static const int MEDIA_RULE = 4; static const int FONT_FACE_RULE = 5; static const int PAGE_RULE = 6; + static const int KEYFRAMES_RULE = 7; static const int MARGIN_RULE = 9; static const int NAMESPACE_RULE = 10; } diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index a7e5154353..e9d1dfb710 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -19,6 +19,8 @@ class RuleSet { final CSSMap tagRules = HashMap(); final List<CSSRule> universalRules = []; + final Map<String, CSSKeyframesRule> keyframesRules = {}; + int _lastPosition = 0; void addRules(List<CSSRule> rules) { @@ -33,6 +35,8 @@ class RuleSet { for (final selector in rule.selectorGroup.selectors) { findBestRuleSetAndAdd(selector, rule); } + } else if (rule is CSSKeyframesRule) { + keyframesRules[rule.name] = rule; } else { assert(false, 'Unsupported rule type: ${rule.runtimeType}'); } diff --git a/webf/lib/src/css/style_rule.dart b/webf/lib/src/css/style_rule.dart deleted file mode 100644 index 75f9a40506..0000000000 --- a/webf/lib/src/css/style_rule.dart +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -import 'package:webf/css.dart'; - -/// https://drafts.csswg.org/cssom/#the-cssstylerule-interface -class CSSStyleRule extends CSSRule { - @override - String get cssText => declaration.cssText; - - final SelectorGroup selectorGroup; - final CSSStyleDeclaration declaration; - - CSSStyleRule(this.selectorGroup, this.declaration) : super(); - - SimpleSelector? get lastSimpleSelector { - return selectorGroup.selectors.last.simpleSelectorSequences.last.simpleSelector; - } - - String get selectorText { - var sb = StringBuffer(); - selectorGroup.selectors.forEach((selector) { - sb.write(selector.simpleSelectorSequences.map((ss) => ss.simpleSelector.name).join(' ')); - }); - return sb.toString(); - } -} diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 56e9721790..453545b03f 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -11,7 +11,9 @@ import 'package:webf/dom.dart'; /* Handling element style updates 1. log all style element - 2. + 2. collect stylesheets + 3. calculate changed rule set + 4. invalidated element */ class StyleNodeManager { final List<Node> _styleSheetCandidateNodes = []; diff --git a/webf/test/src/css/style_animations_parser.dart b/webf/test/src/css/style_animations_parser.dart new file mode 100644 index 0000000000..39ef5121c6 --- /dev/null +++ b/webf/test/src/css/style_animations_parser.dart @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'package:webf/css.dart'; +import 'package:test/test.dart'; + +CSSRule? parseSingleRule(String rule) { + CSSStyleSheet sheet = CSSParser(rule).parse(); + return sheet.cssRules.first; +} + +void main() { + group('CSSStyleRuleParser', () { + test('0', () { + CSSRule? style = parseSingleRule('@keyframes ping { 75%, 100% { transform: scale(2); opacity: 0; } }'); + expect(style is CSSKeyframesRule, true); + expect((style as CSSKeyframesRule).blocks.first.blockSelectors, ['75', '100']); + }); + }); +} diff --git a/webf/test/webf_test.dart b/webf/test/webf_test.dart index 0b0c936a3a..7b36b2c52d 100644 --- a/webf/test/webf_test.dart +++ b/webf/test/webf_test.dart @@ -11,6 +11,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:webf/webf.dart'; import 'local_http_server.dart'; +import 'src/css/style_animations_parser.dart' as style_animations_parser; import 'src/css/style_rule_parser.dart' as style_rule_parser; import 'src/css/style_sheet_parser.dart' as style_sheet_parser; import 'src/css/style_inline_parser.dart' as style_inline_parser; @@ -67,6 +68,7 @@ void main() { style_rule_parser.main(); style_sheet_parser.main(); style_inline_parser.main(); + style_animations_parser.main(); css_values.main(); }); From 441eb0029a04e2dd6a057c9ebc2c5abe1d718abe Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Wed, 24 Aug 2022 00:32:46 +0800 Subject: [PATCH 200/498] feat: optimize post frame callback --- .../css-selectors/combinator.ts.06fd22981.png | Bin 0 -> 4522 bytes .../css-selectors/combinator.ts.0e8424a11.png | Bin 0 -> 4695 bytes .../css-selectors/combinator.ts.6d2121b91.png | Bin 0 -> 4759 bytes .../tag-selector.ts.0c6a7df41.png | Bin 0 -> 11508 bytes .../tag-selector.ts.38aaea691.png | Bin 0 -> 9320 bytes .../tag-selector.ts.4083eb5f1.png | Bin 0 -> 9330 bytes .../tag-selector.ts.40c7c0241.png | Bin 0 -> 4761 bytes .../tag-selector.ts.5fddfcf51.png | Bin 0 -> 20821 bytes .../tag-selector.ts.c2987aa51.png | Bin 0 -> 9500 bytes .../tag-selector.ts.e05cf6221.png | Bin 0 -> 9398 bytes webf/lib/src/dom/document.dart | 21 ++++++++++++++++-- webf/lib/src/dom/node.dart | 16 ++----------- webf/lib/src/dom/style_node_manager.dart | 14 +++++++----- 13 files changed, 29 insertions(+), 22 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.4083eb5f1.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.e05cf6221.png diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png new file mode 100644 index 0000000000000000000000000000000000000000..42286f77aea68b2ab541844e92d065ae699437a0 GIT binary patch literal 4522 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 Sc<KLJAV5IaDf)@?SN;nUjXuu+ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png new file mode 100644 index 0000000000000000000000000000000000000000..f36723fd32403ad26d4683157e1858ebdbf03596 GIT binary patch literal 4695 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 O0DkV`S=x8E?*9h>wZF&! literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png new file mode 100644 index 0000000000000000000000000000000000000000..fc37a56c29245da1207fc1abbf499761a1ff667c GIT binary patch literal 4759 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png new file mode 100644 index 0000000000000000000000000000000000000000..bb0b607db0d461891ea666e2908180d486e6029e GIT binary patch literal 11508 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 ZnTeCZv~4R*;G4@5@Jp7a6&L=v{~w*vFZ}=j literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png new file mode 100644 index 0000000000000000000000000000000000000000..e8c533d0878de1a9b8ee04dd4886f93f25f47293 GIT binary patch literal 9320 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.4083eb5f1.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.4083eb5f1.png new file mode 100644 index 0000000000000000000000000000000000000000..c17ee81b4bb791a316ae20496b95497ddc426b26 GIT binary patch literal 9330 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png new file mode 100644 index 0000000000000000000000000000000000000000..362444bd3d79a5a89e39ddfbc047994322323e26 GIT binary patch literal 4761 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png new file mode 100644 index 0000000000000000000000000000000000000000..86d3c38b4df1576dd9e6e5ec90f3b874ecc04e04 GIT binary patch literal 20821 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png new file mode 100644 index 0000000000000000000000000000000000000000..f9ee17105fa60106fb2d899e8a4889893dc1ea73 GIT binary patch literal 9500 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 X^nk43J}K}ECq5&6^Q)N4x1RnVfuTwM literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.e05cf6221.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.e05cf6221.png new file mode 100644 index 0000000000000000000000000000000000000000..326d0c01f984629266a0d303555c916eda398d71 GIT binary patch literal 9398 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 A+5i9m literal 0 HcmV?d00001 diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 105cdb6c03..d073a9db5e 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -3,6 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ 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'; @@ -221,8 +222,25 @@ class Document extends Node { } void updateStyleIfNeeded() { - styleNodeManager.updateActiveStyleSheets(); + if (styleSheets.isEmpty && !styleNodeManager.hasPendingStyleSheet) { + return; + } + if (styleSheets.isEmpty && styleNodeManager.hasPendingStyleSheet) { + flushStyle(rebuild: true); + return; + } + SchedulerBinding.instance.addPostFrameCallback((_) { + flushStyle(); + }); + } + + void flushStyle({bool rebuild = false}) { + if (!needsStyleRecalculate) { + return; + } + styleNodeManager.updateActiveStyleSheets(rebuild: rebuild); recalculateDocumentStyle(); + needsStyleRecalculate = false; } void recalculateDocumentStyle() { @@ -231,7 +249,6 @@ class Document extends Node { } // Recalculate style for all nodes sync. documentElement?.recalculateNestedStyle(); - needsStyleRecalculate = false; } @override diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index 386f6700df..6ba3f9ca30 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -6,7 +6,6 @@ import 'dart:math' as math; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter/scheduler.dart'; import 'package:webf/dom.dart'; import 'package:webf/foundation.dart'; import 'package:webf/widget.dart'; @@ -100,9 +99,6 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa // Children changed steps for node. // https://dom.spec.whatwg.org/#concept-node-children-changed-ext - - int _frameCallbackId = 0; - void childrenChanged() { if (!isConnected) { return; @@ -114,22 +110,14 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa parent = parent.parentNode!; } ownerDocument.needsStyleRecalculate = true; - if (_frameCallbackId > 0) { - SchedulerBinding.instance.cancelFrameCallbackWithId(_frameCallbackId); - } - _frameCallbackId = SchedulerBinding.instance.scheduleFrameCallback((_) { - if (!ownerDocument.needsStyleRecalculate) { - return; - } - ownerDocument.updateStyleIfNeeded(); - }); + ownerDocument.updateStyleIfNeeded(); } // FIXME: The ownerDocument getter steps are to return null, if this is a document; otherwise this’s node document. // https://dom.spec.whatwg.org/#dom-node-ownerdocument late Document ownerDocument; - bool needsStyleRecalculate = false; + bool needsStyleRecalculate = true; /// The Node.parentElement read-only property returns the DOM node's parent Element, /// or null if the node either has no parent, or its parent isn't a DOM Element. diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 453545b03f..fc492cc1a0 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -59,17 +59,19 @@ class StyleNodeManager { _pendingStyleSheets.removeWhere((element) => element == styleSheet); } - void updateActiveStyleSheets() { + void updateActiveStyleSheets({bool rebuild = false}) { List<CSSStyleSheet> newSheets = _collectActiveStyleSheets(); if (newSheets.isEmpty) { return; } - newSheets = _collectActiveStyleSheets().where((element) => element.cssRules.isNotEmpty).toList(); - RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); - if (changedRuleSet.isEmpty) { - return; + if (rebuild == false) { + newSheets = _collectActiveStyleSheets().where((element) => element.cssRules.isNotEmpty).toList(); + RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); + if (changedRuleSet.isEmpty) { + return; + } + invalidateElementStyle(changedRuleSet); } - invalidateElementStyle(changedRuleSet); document.handleStyleSheets(newSheets); } From 326d4c823e0a3e673dffc105dc014bb119b33dd0 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Wed, 24 Aug 2022 15:09:37 +0800 Subject: [PATCH 201/498] feat: document --- webf/lib/src/css/style_property.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/webf/lib/src/css/style_property.dart b/webf/lib/src/css/style_property.dart index 2362b8edc2..b764e5ca97 100644 --- a/webf/lib/src/css/style_property.dart +++ b/webf/lib/src/css/style_property.dart @@ -19,6 +19,7 @@ const String _0Percent = '0%'; // a-b -> aB String camelize(String str) { + // variables if (str.startsWith('--')) { return str; } From 4e1829f5eb24da822a26219cc04d77de8deb6e89 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Wed, 24 Aug 2022 16:51:28 +0800 Subject: [PATCH 202/498] feat: add html anchor element. --- bridge/CMakeLists.txt | 4 ++- bridge/bindings/qjs/atomic_string.cc | 21 +++++++++++++ bridge/bindings/qjs/atomic_string.h | 1 + bridge/bindings/qjs/qjs_engine_patch.cc | 7 +++++ bridge/bindings/qjs/qjs_engine_patch.h | 1 + bridge/bindings/qjs/wrapper_type_info.h | 1 + bridge/core/dom/binding_call_methods.json5 | 20 ++++++++++++- ...llection.d.ts => html_all_collection.d.ts} | 2 +- bridge/core/html/html_all_collection.h | 12 +++++++- bridge/core/html/html_anchor_element.cc | 2 +- bridge/core/html/html_anchor_element.d.ts | 30 ++++++++++++------- bridge/core/html/html_anchor_element.h | 4 ++- bridge/core/html/html_tag_names.json5 | 1 + bridge/core/html/legacy/html_collection.h | 2 -- bridge/foundation/native_type.h | 2 +- bridge/foundation/native_value_converter.h | 4 +-- 16 files changed, 92 insertions(+), 22 deletions(-) rename bridge/core/html/{legacy/html_collection.d.ts => html_all_collection.d.ts} (78%) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 306bde75ef..4e0bf25841 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -222,6 +222,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") 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_anchor_element.cc # core/html/html_template_element.cc # core/html/forms/html_input_element.cc @@ -291,7 +292,8 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/qjs_scroll_options.cc out/qjs_scroll_to_options.cc out/qjs_html_element.cc - out/qjs_html_collection.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 diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index 7415ef0b0f..bb4d7a1e2f 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -45,6 +45,20 @@ AtomicString::StringKind GetStringKind(JSValue stringValue) { return GetStringKind(reinterpret_cast<const char*>(p->u.str8)); } +AtomicString::StringKind GetStringKind(const NativeString* native_string) { + 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; + } + } +} + } // namespace AtomicString::AtomicString(JSContext* ctx, const std::string& string) @@ -54,6 +68,13 @@ AtomicString::AtomicString(JSContext* ctx, const std::string& string) 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_ValueToAtom(ctx, value)) { if (JS_IsString(value)) { diff --git a/bridge/bindings/qjs/atomic_string.h b/bridge/bindings/qjs/atomic_string.h index 03b18ca32b..8ca312bfd9 100644 --- a/bridge/bindings/qjs/atomic_string.h +++ b/bridge/bindings/qjs/atomic_string.h @@ -36,6 +36,7 @@ class AtomicString { 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_); }; diff --git a/bridge/bindings/qjs/qjs_engine_patch.cc b/bridge/bindings/qjs/qjs_engine_patch.cc index 773a28d318..1d10c10287 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.cc +++ b/bridge/bindings/qjs/qjs_engine_patch.cc @@ -302,6 +302,13 @@ JSValue JS_NewUnicodeString(JSContext* ctx, const uint16_t* code, uint32_t lengt 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) diff --git a/bridge/bindings/qjs/qjs_engine_patch.h b/bridge/bindings/qjs/qjs_engine_patch.h index 1e8aac2a2f..04f524024c 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -115,6 +115,7 @@ static inline bool __JS_AtomIsConst(JSAtom v) { 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); diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index eec44ed9c7..0d891ab10e 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -51,6 +51,7 @@ enum { JS_CLASS_HTML_BODY_ELEMENT, JS_CLASS_HTML_HEAD_ELEMENT, JS_CLASS_HTML_HTML_ELEMENT, + JS_CLASS_HTML_ANCHOR_ELEMENT, JS_CLASS_HTML_TEMPLATE_ELEMENT, JS_CLASS_HTML_UNKNOWN_ELEMENT, JS_CLASS_CSS_STYLE_DECLARATION, diff --git a/bridge/core/dom/binding_call_methods.json5 b/bridge/core/dom/binding_call_methods.json5 index 9999771cdd..929bb2f903 100644 --- a/bridge/core/dom/binding_call_methods.json5 +++ b/bridge/core/dom/binding_call_methods.json5 @@ -37,6 +37,24 @@ "availHeight", "width", "height", - "screen" + "screen", + "target", + "accessKey", + "download", + "ping", + "rel", + "type", + "text", + "href", + "origin", + "protocol", + "username", + "password", + "host", + "hostname", + "port", + "pathname", + "search", + "hash", ] } diff --git a/bridge/core/html/legacy/html_collection.d.ts b/bridge/core/html/html_all_collection.d.ts similarity index 78% rename from bridge/core/html/legacy/html_collection.d.ts rename to bridge/core/html/html_all_collection.d.ts index 975d0cfdde..6cecec5461 100644 --- a/bridge/core/html/legacy/html_collection.d.ts +++ b/bridge/core/html/html_all_collection.d.ts @@ -1,4 +1,4 @@ -import {Element} from "../../dom/element"; +import {Element} from "../dom/element"; interface HTMLCollection { readonly length: double; diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h index fb251bf71b..2d1bbd9d49 100644 --- a/bridge/core/html/html_all_collection.h +++ b/bridge/core/html/html_all_collection.h @@ -6,6 +6,16 @@ #ifndef BRIDGE_CORE_HTML_HTML_ALL_COLLECTION_H_ #define BRIDGE_CORE_HTML_HTML_ALL_COLLECTION_H_ -namespace webf {} +#include "legacy/html_collection.h" + +namespace webf { + +class HTMLAllCollection : public HTMLCollection { + DEFINE_WRAPPERTYPEINFO(); + public: + private: +}; + +} #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 index 1615384da8..35157cde9c 100644 --- a/bridge/core/html/html_anchor_element.cc +++ b/bridge/core/html/html_anchor_element.cc @@ -7,6 +7,6 @@ namespace webf { -HTMLAnchorElement::HTMLAnchorElement(Document& document) : HTMLElement(html_names::ka, &document) {} +HTMLAnchorElement::HTMLAnchorElement(Document& document): HTMLElement(html_names::ka, &document) {} } // namespace webf diff --git a/bridge/core/html/html_anchor_element.d.ts b/bridge/core/html/html_anchor_element.d.ts index 9b45b80a9f..3b8ea28c11 100644 --- a/bridge/core/html/html_anchor_element.d.ts +++ b/bridge/core/html/html_anchor_element.d.ts @@ -1,15 +1,23 @@ import {Element} from "../dom/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; + 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 index 55c93dc688..4b862f7b38 100644 --- a/bridge/core/html/html_anchor_element.h +++ b/bridge/core/html/html_anchor_element.h @@ -10,8 +10,10 @@ namespace webf { class HTMLAnchorElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); public: - explicit HTMLAnchorElement(Document&); + explicit HTMLAnchorElement(Document& document); + private: }; } // namespace webf diff --git a/bridge/core/html/html_tag_names.json5 b/bridge/core/html/html_tag_names.json5 index 4869b1fd59..565b13c0f6 100644 --- a/bridge/core/html/html_tag_names.json5 +++ b/bridge/core/html/html_tag_names.json5 @@ -32,6 +32,7 @@ "body", "head", "div", + "a", // { // "name": "input", // "interfaceHeaderDir": "core/html/forms" diff --git a/bridge/core/html/legacy/html_collection.h b/bridge/core/html/legacy/html_collection.h index cb5c91471b..9030957d82 100644 --- a/bridge/core/html/legacy/html_collection.h +++ b/bridge/core/html/legacy/html_collection.h @@ -10,8 +10,6 @@ namespace webf { class HTMLCollection : public ScriptWrappable { - DEFINE_WRAPPERTYPEINFO(); - public: HTMLCollection(ContainerNode* base, CollectionType); diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h index b04122698f..bd20d1532d 100644 --- a/bridge/foundation/native_type.h +++ b/bridge/foundation/native_type.h @@ -28,7 +28,7 @@ struct NativeTypeNull final : public NativeTypeBaseHelper<ScriptValue> {}; struct NativeTypeBool final : public NativeTypeBaseHelper<bool> {}; // String -struct NativeTypeString final : public NativeTypeBaseHelper<NativeString*> {}; +struct NativeTypeString final : public NativeTypeBaseHelper<AtomicString> {}; // Int64 struct NativeTypeInt64 final : public NativeTypeBaseHelper<int64_t> {}; diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index e78382541f..04022db9b4 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -32,9 +32,9 @@ struct NativeValueConverter<NativeTypeNull> : public NativeValueConverterBase<Na template <> struct NativeValueConverter<NativeTypeString> : public NativeValueConverterBase<NativeTypeString> { - static NativeValue ToNativeValue(ImplType value) { return Native_NewString(value); } + static NativeValue ToNativeValue(const ImplType& value) { return Native_NewString(value.ToNativeString().release()); } - static ImplType FromNativeValue(NativeValue value) { return static_cast<NativeString*>(value.u.ptr); } + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { return AtomicString(ctx, static_cast<NativeString*>(value.u.ptr));; } }; template <> From 8ad081e6b27b4d7197e360b2e33936a28b1d63e2 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Wed, 24 Aug 2022 21:56:12 +0800 Subject: [PATCH 203/498] fix: element style update --- .../snapshots/dom/elements/link.ts.c15dbee04.png | Bin 9983 -> 0 bytes integration_tests/specs/dom/elements/link.ts | 2 +- webf/lib/src/dom/elements/head.dart | 3 ++- webf/lib/src/dom/style_node_manager.dart | 9 +++++++-- 4 files changed, 10 insertions(+), 4 deletions(-) delete mode 100644 integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png deleted file mode 100644 index 42b998a1e1e2d408baa0e3ba7c330ff32e703316..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9983 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 Dh>Zz7 diff --git a/integration_tests/specs/dom/elements/link.ts b/integration_tests/specs/dom/elements/link.ts index d55683877d..3102c2a94b 100644 --- a/integration_tests/specs/dom/elements/link.ts +++ b/integration_tests/specs/dom/elements/link.ts @@ -24,7 +24,7 @@ describe('Link Element', () => { link.addEventListener('load', async () => { await snapshot(); link.setAttribute('href', 'assets:assets/good.css'); - await snapshot(0.5); + await snapshot(0.1); done(); }); document.head.appendChild(link); diff --git a/webf/lib/src/dom/elements/head.dart b/webf/lib/src/dom/elements/head.dart index 7efaf80498..213969d5eb 100644 --- a/webf/lib/src/dom/elements/head.dart +++ b/webf/lib/src/dom/elements/head.dart @@ -172,11 +172,12 @@ class LinkElement extends Element { final String cssString = await resolveStringFromData(bundle.data!); _styleSheet = CSSParser(cssString).parse(); + ownerDocument.needsStyleRecalculate = true; ownerDocument.styleNodeManager.appendPendingStyleSheet(_styleSheet!); + ownerDocument.updateStyleIfNeeded(); // Successful load. SchedulerBinding.instance.addPostFrameCallback((_) { - ownerDocument.updateStyleIfNeeded(); dispatchEvent(Event(EVENT_LOAD)); }); } catch (e) { diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index fc492cc1a0..0e2277233c 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -64,15 +64,21 @@ class StyleNodeManager { if (newSheets.isEmpty) { return; } + newSheets = newSheets.where((element) => element.cssRules.isNotEmpty).toList(); if (rebuild == false) { - newSheets = _collectActiveStyleSheets().where((element) => element.cssRules.isNotEmpty).toList(); RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); if (changedRuleSet.isEmpty) { return; } invalidateElementStyle(changedRuleSet); + } else { + document.visitChild((node) { + node.needsStyleRecalculate = true; + }); } + document.needsStyleRecalculate = true; document.handleStyleSheets(newSheets); + _pendingStyleSheets.clear(); } List<CSSStyleSheet> _collectActiveStyleSheets() { @@ -98,7 +104,6 @@ class StyleNodeManager { } } }); - document.needsStyleRecalculate = true; } RuleSet analyzeStyleSheetChangeRuleSet(List<CSSStyleSheet> oldSheets, List<CSSStyleSheet> newSheets) { From 67b9e0691ce5f3d130e0f818e4b7200034c71390 Mon Sep 17 00:00:00 2001 From: andycall <dongtiangche@outlook.com> Date: Thu, 25 Aug 2022 00:54:22 +0800 Subject: [PATCH 204/498] feat: add image and script element. --- bridge/CMakeLists.txt | 5 + bridge/bindings/qjs/atomic_string.cc | 2 + bridge/bindings/qjs/binding_initializer.cc | 4 + bridge/bindings/qjs/script_value.cc | 3 +- bridge/bindings/qjs/script_wrappable.cc | 12 +- bridge/bindings/qjs/script_wrappable.h | 7 + bridge/bindings/qjs/wrapper_type_info.h | 2 + bridge/core/dom/binding_call_methods.json5 | 13 + bridge/core/events/close_event_init.d.ts | 6 +- bridge/core/events/focus_event_init.d.ts | 2 +- bridge/core/events/gesture_event_init.d.ts | 16 +- bridge/core/events/input_event_init.d.ts | 4 +- .../intersection_change_event_init.d.ts | 2 +- bridge/core/events/keyboard_event.cc | 21 +- bridge/core/events/keyboard_event.d.ts | 8 +- bridge/core/events/keyboard_event.h | 10 +- bridge/core/events/keyboard_event_init.d.ts | 27 +- bridge/core/events/message_event_init.d.ts | 8 +- bridge/core/events/mouse_event.d.ts | 2 + bridge/core/events/mouse_event_init.d.ts | 18 ++ bridge/core/events/pointer_event.d.ts | 2 + bridge/core/events/pointer_event_init.d.ts | 16 ++ bridge/core/events/touch_event.cc | 263 ------------------ bridge/core/events/touch_event.d.ts | 2 + bridge/core/events/touch_event.h | 118 -------- bridge/core/events/touch_event_init.d.ts | 14 + bridge/core/events/transition_event.d.ts | 2 + bridge/core/events/transition_event_init.d.ts | 9 + bridge/core/events/wheel_event.d.ts | 8 +- bridge/core/events/wheel_event_init.d.ts | 10 + bridge/core/executing_context.cc | 9 + bridge/core/executing_context.h | 4 + bridge/core/frame/window.cc | 2 +- bridge/core/frame/window_event_handlers.d.ts | 2 + .../core/html/canvas/html_canvas_element.d.ts | 2 + .../core/html/forms/html_input_element.d.ts | 7 +- .../html/forms/html_textarea_element.d.ts | 1 + bridge/core/html/html_image_element.cc | 11 + bridge/core/html/html_image_element.d.ts | 20 ++ bridge/core/html/html_image_element.h | 5 +- bridge/core/html/html_script_element.cc | 9 + bridge/core/html/html_script_element.d.ts | 11 + bridge/core/html/html_script_element.h | 2 + bridge/core/html/html_tag_names.json5 | 23 +- bridge/core/html/html_text_area_element.d.ts | 22 -- bridge/core/html/media/html_audio_element.cc | 5 - bridge/core/html/media/html_audio_element.h | 10 - bridge/core/html/script_type_names.json5 | 17 ++ .../code_generator/src/idl/analyzer.ts | 6 + .../code_generator/src/idl/generateSource.ts | 15 + .../static/idl_templates/dictionary.cc.tpl | 15 +- .../static/idl_templates/interface.cc.tpl | 7 +- 52 files changed, 318 insertions(+), 503 deletions(-) create mode 100644 bridge/core/events/mouse_event_init.d.ts create mode 100644 bridge/core/events/pointer_event_init.d.ts delete mode 100644 bridge/core/events/touch_event.cc delete mode 100644 bridge/core/events/touch_event.h create mode 100644 bridge/core/events/touch_event_init.d.ts create mode 100644 bridge/core/events/transition_event_init.d.ts create mode 100644 bridge/core/events/wheel_event_init.d.ts create mode 100644 bridge/core/html/html_image_element.d.ts create mode 100644 bridge/core/html/html_script_element.d.ts delete mode 100644 bridge/core/html/html_text_area_element.d.ts delete mode 100644 bridge/core/html/media/html_audio_element.cc delete mode 100644 bridge/core/html/media/html_audio_element.h create mode 100644 bridge/core/html/script_type_names.json5 diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 4e0bf25841..612724c72f 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -223,6 +223,8 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") 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_anchor_element.cc # core/html/html_template_element.cc # core/html/forms/html_input_element.cc @@ -298,12 +300,15 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") 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_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/html_element_factory.cc out/html_names.cc + out/script_type_names.cc ) # Quickjs use __builtin_frame_address() to get stack pointer, we should add follow options to get it work with -O2 diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index bb4d7a1e2f..f7aef069af 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -57,6 +57,8 @@ AtomicString::StringKind GetStringKind(const NativeString* native_string) { return AtomicString::StringKind::kIsMixed; } } + + return predictKind; } } // namespace diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index cbfabc0143..7c8a587955 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -28,6 +28,8 @@ #include "qjs_html_element.h" #include "qjs_html_head_element.h" #include "qjs_html_html_element.h" +#include "qjs_html_script_element.h" +#include "qjs_html_image_element.h" #include "qjs_html_template_element.h" #include "qjs_html_unknown_element.h" #include "qjs_input_event.h" @@ -82,6 +84,8 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLHeadElement::Install(context); QJSHTMLBodyElement::Install(context); QJSHTMLHtmlElement::Install(context); + QJSHTMLImageElement::Install(context); + QJSHTMLScriptElement::Install(context); QJSHTMLUnknownElement::Install(context); QJSHTMLTemplateElement::Install(context); QJSCSSStyleDeclaration::Install(context); diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 4d6e4c329c..34210c7035 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -98,8 +98,7 @@ NativeValue ScriptValue::ToNative() const { } } else if (JS_IsString(value_)) { // NativeString owned by NativeValue will be freed by users. - NativeString* string = this->ToString().ToNativeString().release(); - return NativeValueConverter<NativeTypeString>::ToNativeValue(string); + return NativeValueConverter<NativeTypeString>::ToNativeValue(ToString()); } // else if (JS_IsFunction(ctx_, value_)) { diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index e920608621..5844976699 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -10,7 +10,8 @@ namespace webf { ScriptWrappable::ScriptWrappable(JSContext* ctx) - : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), context_(ExecutingContext::From(ctx)) {} + : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), context_(ExecutingContext::From(ctx)) { +} JSValue ScriptWrappable::ToQuickJS() const { return JS_DupValue(ctx_, jsObject_); @@ -200,9 +201,18 @@ void ScriptWrappable::InitializeQuickJSObject() { 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 index 249bfb2027..fd7a3e377c 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -57,6 +57,13 @@ class ScriptWrappable : public GarbageCollected<ScriptWrappable> { 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}; diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 0d891ab10e..d425c90e01 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -51,6 +51,8 @@ enum { 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_TEMPLATE_ELEMENT, JS_CLASS_HTML_UNKNOWN_ELEMENT, diff --git a/bridge/core/dom/binding_call_methods.json5 b/bridge/core/dom/binding_call_methods.json5 index 929bb2f903..b749a876f4 100644 --- a/bridge/core/dom/binding_call_methods.json5 +++ b/bridge/core/dom/binding_call_methods.json5 @@ -56,5 +56,18 @@ "pathname", "search", "hash", + "alt", + "src", + "srcset", + "sizes", + "naturalWidth", + "naturalHeight", + "complete", + "currentSrc", + "decoding", + "fetchPriority", + "loading", + "noModule", + "async" ] } diff --git a/bridge/core/events/close_event_init.d.ts b/bridge/core/events/close_event_init.d.ts index 2f75f42e91..8f1aa052ae 100644 --- a/bridge/core/events/close_event_init.d.ts +++ b/bridge/core/events/close_event_init.d.ts @@ -3,7 +3,7 @@ import { EventInit } from "../dom/events/event_init"; // @ts-ignore @Dictionary() export interface CloseEventInit extends EventInit { - code: int64; - reason: string; - wasClean: boolean; + code?: int64; + reason?: string; + wasClean?: boolean; } diff --git a/bridge/core/events/focus_event_init.d.ts b/bridge/core/events/focus_event_init.d.ts index 2c46aa9804..ea3284e9ab 100644 --- a/bridge/core/events/focus_event_init.d.ts +++ b/bridge/core/events/focus_event_init.d.ts @@ -4,5 +4,5 @@ import {UIEventInit} from "./ui_event_init"; // @ts-ignore @Dictionary() export interface FocusEventInit extends UIEventInit { - relatedTarget: EventTarget | null; + relatedTarget?: EventTarget | null; } diff --git a/bridge/core/events/gesture_event_init.d.ts b/bridge/core/events/gesture_event_init.d.ts index 384026f4d5..949b1aecad 100644 --- a/bridge/core/events/gesture_event_init.d.ts +++ b/bridge/core/events/gesture_event_init.d.ts @@ -3,12 +3,12 @@ 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; + state?: string; + direction?: string; + deltaX?: number; + deltaY?: number; + velocityX?: number; + velocityY?: number; + scale?: number; + rotation?: number; } diff --git a/bridge/core/events/input_event_init.d.ts b/bridge/core/events/input_event_init.d.ts index c83eac68ab..4ba210395f 100644 --- a/bridge/core/events/input_event_init.d.ts +++ b/bridge/core/events/input_event_init.d.ts @@ -3,6 +3,6 @@ import {UIEventInit} from "./ui_event_init"; // @ts-ignore @Dictionary() export interface InputEventInit extends UIEventInit { - inputType: string; - data: string; + inputType?: string; + data?: string; } diff --git a/bridge/core/events/intersection_change_event_init.d.ts b/bridge/core/events/intersection_change_event_init.d.ts index 1072d9ac8d..4178e2073d 100644 --- a/bridge/core/events/intersection_change_event_init.d.ts +++ b/bridge/core/events/intersection_change_event_init.d.ts @@ -3,5 +3,5 @@ import {UIEventInit} from "./ui_event_init"; // @ts-ignore @Dictionary() export interface IntersectionChangeEventInit extends UIEventInit { - intersectionRatio: number; + intersectionRatio?: number; } diff --git a/bridge/core/events/keyboard_event.cc b/bridge/core/events/keyboard_event.cc index 875513b333..d78053ba89 100644 --- a/bridge/core/events/keyboard_event.cc +++ b/bridge/core/events/keyboard_event.cc @@ -7,6 +7,11 @@ 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) { @@ -44,22 +49,6 @@ bool KeyboardEvent::getModifierState(const AtomicString& key_args, ExceptionStat return false; } -double KeyboardEvent::DOM_KEY_LOCATION_LEFT() const { - return KeyLocationCode::kDomKeyLocationLeft; -} - -double KeyboardEvent::DOM_KEY_LOCATION_NUMPAD() const { - return KeyLocationCode::kDomKeyLocationNumpad; -} - -double KeyboardEvent::DOM_KEY_LOCATION_RIGHT() const { - return KeyLocationCode::kDomKeyLocationRight; -} - -double KeyboardEvent::DOM_KEY_LOCATION_STANDARD() const { - return KeyLocationCode::kDomKeyLocationStandard; -} - bool KeyboardEvent::altKey() const { return alt_key_; } diff --git a/bridge/core/events/keyboard_event.d.ts b/bridge/core/events/keyboard_event.d.ts index 3008a3826c..79b03b0ff3 100644 --- a/bridge/core/events/keyboard_event.d.ts +++ b/bridge/core/events/keyboard_event.d.ts @@ -17,10 +17,10 @@ interface KeyboardEvent extends UIEvent { readonly repeat: boolean; readonly shiftKey: boolean; // getModifierState(keyArg: string): boolean; - readonly DOM_KEY_LOCATION_LEFT: number; - readonly DOM_KEY_LOCATION_NUMPAD: number; - readonly DOM_KEY_LOCATION_RIGHT: number; - readonly DOM_KEY_LOCATION_STANDARD: number; + 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 index d7b0dc95a9..d490d04eef 100644 --- a/bridge/core/events/keyboard_event.h +++ b/bridge/core/events/keyboard_event.h @@ -25,6 +25,11 @@ class KeyboardEvent : public UIEvent { }; 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, @@ -51,11 +56,6 @@ class KeyboardEvent : public UIEvent { bool repeat() const; bool shiftKey() const; - double DOM_KEY_LOCATION_LEFT() const; - double DOM_KEY_LOCATION_NUMPAD() const; - double DOM_KEY_LOCATION_RIGHT() const; - double DOM_KEY_LOCATION_STANDARD() const; - bool getModifierState(const AtomicString& key_args, ExceptionState& exception_state); bool IsKeyboardEvent() const override; diff --git a/bridge/core/events/keyboard_event_init.d.ts b/bridge/core/events/keyboard_event_init.d.ts index d24d8ec608..18c5cb8d81 100644 --- a/bridge/core/events/keyboard_event_init.d.ts +++ b/bridge/core/events/keyboard_event_init.d.ts @@ -3,22 +3,17 @@ import {UIEventInit} from "./ui_event_init"; // @ts-ignore @Dictionary() export interface KeyboardEventInit extends UIEventInit { - altKey: boolean; + altKey?: boolean; /** @deprecated */ - charCode: number; - code: string; - ctrlKey: boolean; - isComposing: boolean; - key: string; + charCode?: number; + code?: string; + ctrlKey?: boolean; + isComposing?: boolean; + key?: string; /** @deprecated */ - keyCode: number; - location: number; - metaKey: boolean; - repeat: boolean; - shiftKey: boolean; - getModifierState(keyArg: string): boolean; - DOM_KEY_LOCATION_LEFT: number; - DOM_KEY_LOCATION_NUMPAD: number; - DOM_KEY_LOCATION_RIGHT: number; - DOM_KEY_LOCATION_STANDARD: number; + keyCode?: number; + location?: number; + metaKey?: boolean; + repeat?: boolean; + shiftKey?: boolean; } diff --git a/bridge/core/events/message_event_init.d.ts b/bridge/core/events/message_event_init.d.ts index 480e3b6075..90e35c08bc 100644 --- a/bridge/core/events/message_event_init.d.ts +++ b/bridge/core/events/message_event_init.d.ts @@ -3,9 +3,9 @@ import { EventInit } from "../dom/events/event_init"; // @ts-ignore @Dictionary() export interface MessageEventInit extends EventInit { - data: any; - origin: string; - lastEventId: string; - source: string; + data?: any; + origin?: string; + lastEventId?: string; + source?: string; // TODO: add ports property. } diff --git a/bridge/core/events/mouse_event.d.ts b/bridge/core/events/mouse_event.d.ts index 913c141d06..d57d425356 100644 --- a/bridge/core/events/mouse_event.d.ts +++ b/bridge/core/events/mouse_event.d.ts @@ -1,6 +1,7 @@ import {UIEvent} from "./ui_event"; import {EventTarget} from "../dom/events/event_target"; import {Window} from "../frame/window"; +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 { @@ -25,4 +26,5 @@ interface MouseEvent extends UIEvent { readonly y: number; getModifierState(keyArg: string): boolean; initMouseEvent(typeArg: string, canBubbleArg: boolean, cancelableArg: boolean, viewArg: Window, detailArg: number, screenXArg: number, screenYArg: number, clientXArg: number, clientYArg: number, ctrlKeyArg: boolean, altKeyArg: boolean, shiftKeyArg: boolean, metaKeyArg: boolean, buttonArg: number, relatedTargetArg: EventTarget | null): void; + new(type: string, init: MouseEventInit): MouseEvent; } \ No newline at end of file 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..d1207ed0bb --- /dev/null +++ b/bridge/core/events/mouse_event_init.d.ts @@ -0,0 +1,18 @@ +import { EventInit } from "../dom/events/event_init"; +import {EventTarget} from "../dom/events/event_target"; + +// @ts-ignore +@Dictionary() +export interface MouseEventInit extends EventInit { + 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.d.ts b/bridge/core/events/pointer_event.d.ts index 69d6126127..64f104e265 100644 --- a/bridge/core/events/pointer_event.d.ts +++ b/bridge/core/events/pointer_event.d.ts @@ -1,4 +1,5 @@ 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 { @@ -12,4 +13,5 @@ interface PointerEvent extends MouseEvent { 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_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/touch_event.cc b/bridge/core/events/touch_event.cc deleted file mode 100644 index c441fbe6f4..0000000000 --- a/bridge/core/events/touch_event.cc +++ /dev/null @@ -1,263 +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_engine_patch.h" -#include "page.h" - -namespace webf { - -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<TouchList*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<Touch*>(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<int64_t>(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<NativeTouchEvent*>(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<NativeTouch*>(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<NativeTouch*>(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<NativeTouch*>(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*>(nativeEvent)); - return event->jsObject; -} -IMPL_PROPERTY_GETTER(TouchEvent, touches)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast<TouchEventInstance*>(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast<NativeTouchEvent*>(event->nativeEvent); - auto* 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<TouchEventInstance*>(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast<NativeTouchEvent*>(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<TouchEventInstance*>(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast<NativeTouchEvent*>(event->nativeEvent); - auto* changedTouchList = - new TouchList(event->m_context, nativeEvent->changedTouches, nativeEvent->changedTouchesLength); - return changedTouchList->jsObject; -} - -IMPL_PROPERTY_GETTER(TouchEvent, altKey)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* event = static_cast<TouchEventInstance*>(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast<NativeTouchEvent*>(event->nativeEvent); - 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<TouchEventInstance*>(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast<NativeTouchEvent*>(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<TouchEventInstance*>(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast<NativeTouchEvent*>(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<TouchEventInstance*>(JS_GetOpaque(this_val, Event::kEventClassID)); - auto* nativeEvent = reinterpret_cast<NativeTouchEvent*>(event->nativeEvent); - return JS_NewBool(ctx, nativeEvent->shiftKey ? 1 : 0); -} - -TouchEventInstance::TouchEventInstance(TouchEvent* event, NativeEvent* nativeEvent) - : EventInstance(event, nativeEvent) {} - -} // namespace webf diff --git a/bridge/core/events/touch_event.d.ts b/bridge/core/events/touch_event.d.ts index eebcf82317..8e56c6cf0b 100644 --- a/bridge/core/events/touch_event.d.ts +++ b/bridge/core/events/touch_event.d.ts @@ -1,6 +1,7 @@ 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 { @@ -11,4 +12,5 @@ interface TouchEvent extends UIEvent { readonly shiftKey: boolean; readonly targetTouches: TouchList; readonly touches: TouchList; + 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 deleted file mode 100644 index 99c99b3e4e..0000000000 --- a/bridge/core/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 { - -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 - -#endif // BRIDGE_TOUCH_EVENTT_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.d.ts b/bridge/core/events/transition_event.d.ts index 6bae2a8a2f..f8a6532791 100644 --- a/bridge/core/events/transition_event.d.ts +++ b/bridge/core/events/transition_event.d.ts @@ -1,8 +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_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/wheel_event.d.ts b/bridge/core/events/wheel_event.d.ts index fe994e89bb..5107e172ab 100644 --- a/bridge/core/events/wheel_event.d.ts +++ b/bridge/core/events/wheel_event.d.ts @@ -1,11 +1,13 @@ import {MouseEvent} from "./mouse_event"; +import {WheelEventInit} from "./wheel_event_init"; /** Events that occur due to the user moving a mouse wheel or similar input device. */ interface WheelEvent extends MouseEvent { readonly deltaMode: number; readonly deltaX: number; readonly deltaY: number; readonly deltaZ: number; - readonly DOM_DELTA_LINE: number; - readonly DOM_DELTA_PAGE: number; - readonly DOM_DELTA_PIXEL: number; + readonly DOM_DELTA_LINE: StaticMember<number>; + readonly DOM_DELTA_PAGE: StaticMember<number>; + readonly DOM_DELTA_PIXEL: StaticMember<number>; + new(type: string, init?: WheelEventInit): WheelEvent; } diff --git a/bridge/core/events/wheel_event_init.d.ts b/bridge/core/events/wheel_event_init.d.ts new file mode 100644 index 0000000000..37ad6a5000 --- /dev/null +++ b/bridge/core/events/wheel_event_init.d.ts @@ -0,0 +1,10 @@ +import {EventInit} from "../dom/events/event_init"; + +// @ts-ignore +@Dictionary() +export interface WheelEventInit extends EventInit { + deltaMode?: number; + deltaX?: number; + deltaY?: number; + deltaZ?: number; +} \ No newline at end of file diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 221ef91576..49d4bd2aac 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -88,6 +88,11 @@ ExecutingContext::~ExecutingContext() { } 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) { @@ -373,6 +378,10 @@ void ExecutingContext::InstallGlobal() { 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) diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index a7e28f2f7c..5162f3dca1 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -77,6 +77,9 @@ class ExecutingContext { // 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 @@ -155,6 +158,7 @@ class ExecutingContext { RejectedPromises rejected_promises_; PendingPromises pending_promises_; MemberMutationScope* active_mutation_scope{nullptr}; + std::vector<ScriptWrappable*> active_wrappers_; }; class ObjectProperty { diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index ea2d71af88..10fc5f6b47 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -23,7 +23,7 @@ Window* Window::open(ExceptionState& exception_state) { Window* Window::open(const AtomicString& url, ExceptionState& exception_state) { const NativeValue args[] = { - NativeValueConverter<NativeTypeString>::ToNativeValue(url.ToNativeString().release()), + NativeValueConverter<NativeTypeString>::ToNativeValue(url), }; InvokeBindingMethod(binding_call_methods::kopen, 1, args, exception_state); return this; diff --git a/bridge/core/frame/window_event_handlers.d.ts b/bridge/core/frame/window_event_handlers.d.ts index c5194d1abf..35bc1d1247 100644 --- a/bridge/core/frame/window_event_handlers.d.ts +++ b/bridge/core/frame/window_event_handlers.d.ts @@ -1,5 +1,7 @@ type IDLEventHandler = Function; +// @ts-ignore +@Mixin() export interface WindowEventHandlers { onbeforeunload: IDLEventHandler | null; onhashchange: IDLEventHandler | null; diff --git a/bridge/core/html/canvas/html_canvas_element.d.ts b/bridge/core/html/canvas/html_canvas_element.d.ts index 0bd9f91131..ab34cf216c 100644 --- a/bridge/core/html/canvas/html_canvas_element.d.ts +++ b/bridge/core/html/canvas/html_canvas_element.d.ts @@ -44,10 +44,12 @@ interface CanvasRenderingContext2D { transform(a: number, b: number, c: number, d: number, e: number, f: number): void; translate(x: number, y: number): void; reset(): void; + new(): void; } interface HTMLCanvasElement extends HTMLElement { width: int64; height: int64; getContext: (contextType: string) => CanvasRenderingContext2D; + new(): void; } diff --git a/bridge/core/html/forms/html_input_element.d.ts b/bridge/core/html/forms/html_input_element.d.ts index 5a3117a410..ffd542b5d9 100644 --- a/bridge/core/html/forms/html_input_element.d.ts +++ b/bridge/core/html/forms/html_input_element.d.ts @@ -12,9 +12,9 @@ interface HTMLInputElement extends HTMLElement { disabled: boolean; min: string; max: string; - minLength: long; - maxLength: long; - size: long; + minLength: double; + maxLength: double; + size: double; multiple: boolean; name: string; step: string; @@ -26,4 +26,5 @@ interface HTMLInputElement extends HTMLElement { inputMode: string; focus(): void; blur(): void; + new(): void; } diff --git a/bridge/core/html/forms/html_textarea_element.d.ts b/bridge/core/html/forms/html_textarea_element.d.ts index 15a84ca12b..5f74c982ab 100644 --- a/bridge/core/html/forms/html_textarea_element.d.ts +++ b/bridge/core/html/forms/html_textarea_element.d.ts @@ -19,4 +19,5 @@ interface HTMLTextAreaElement extends HTMLElement { inputMode: string; focus(): void; blur(): void; + new(): void; } diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index 8dee2ac34d..e384d0ac0b 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -9,4 +9,15 @@ namespace webf { HTMLImageElement::HTMLImageElement(Document& document) : HTMLElement(html_names::kimg, &document) {} +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..8578787375 --- /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<double>; + height: DartImpl<double>; + readonly naturalWidth: DartImpl<double>; + readonly naturalHeight: DartImpl<double>; + 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 index 215f7a8d17..6ac0a2d307 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -11,11 +11,14 @@ namespace webf { class HTMLImageElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); - public: explicit HTMLImageElement(Document& document); + bool KeepAlive() const override; + + ScriptPromise decode(ExceptionState& exception_state) const; private: + }; } // namespace webf diff --git a/bridge/core/html/html_script_element.cc b/bridge/core/html/html_script_element.cc index 308e253c95..5ea117a4f7 100644 --- a/bridge/core/html/html_script_element.cc +++ b/bridge/core/html/html_script_element.cc @@ -4,9 +4,18 @@ */ #include "html_script_element.h" #include "html_names.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; +} + } // 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 index 08ed8f6a30..4e28efbb31 100644 --- a/bridge/core/html/html_script_element.h +++ b/bridge/core/html/html_script_element.h @@ -13,6 +13,8 @@ class HTMLScriptElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: + static bool supports(const AtomicString& type, ExceptionState& exception_state); + explicit HTMLScriptElement(Document& document); private: diff --git a/bridge/core/html/html_tag_names.json5 b/bridge/core/html/html_tag_names.json5 index 565b13c0f6..a4cf2a2cab 100644 --- a/bridge/core/html/html_tag_names.json5 +++ b/bridge/core/html/html_tag_names.json5 @@ -23,16 +23,15 @@ // "name": "canvas", // "interfaceHeaderDir": "core/html/canvas" // }, -// { -// "name": "a", -// "interfaceName": "HTMLAnchorElement", -// "filename": "html_anchor_element" -// }, + { + "name": "a", + "interfaceName": "HTMLAnchorElement", + "filename": "html_anchor_element" + }, "html", "body", "head", "div", - "a", // { // "name": "input", // "interfaceHeaderDir": "core/html/forms" @@ -43,11 +42,11 @@ // "interfaceHeaderDir": "core/html/forms" // }, "template", -// { -// "name": "img", -// "interfaceName": "HTMLImageElement", -// "filename": "html_image_element" -// }, -// "script" + { + "name": "img", + "interfaceName": "HTMLImageElement", + "filename": "html_image_element" + }, + "script" ] } diff --git a/bridge/core/html/html_text_area_element.d.ts b/bridge/core/html/html_text_area_element.d.ts deleted file mode 100644 index 05798d6a38..0000000000 --- a/bridge/core/html/html_text_area_element.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {Element} from "../dom/element"; - -// https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element -interface HTMLTextAreaElement extends Element { - defaultValue: string; - value: string; - cols: double; - rows: double; - wrap: string; - autofocus: boolean; - autocomplete: string; - disabled: boolean; - minLength: double; - maxLength: double; - name: string; - placeholder: string; - readonly: boolean; - required: boolean; - inputMode: string; - focus(): void; - blur(): void; -} diff --git a/bridge/core/html/media/html_audio_element.cc b/bridge/core/html/media/html_audio_element.cc deleted file mode 100644 index 5200d779e5..0000000000 --- a/bridge/core/html/media/html_audio_element.cc +++ /dev/null @@ -1,5 +0,0 @@ -// -// Created by yhtree on 2022/4/15. -// - -#include "html_audio_element.h" diff --git a/bridge/core/html/media/html_audio_element.h b/bridge/core/html/media/html_audio_element.h deleted file mode 100644 index 5a65e44f5a..0000000000 --- a/bridge/core/html/media/html_audio_element.h +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by yhtree on 2022/4/15. -// - -#ifndef BRIDGE_CORE_HTML_MEDIA_HTML_AUDIO_ELEMENT_H_ -#define BRIDGE_CORE_HTML_MEDIA_HTML_AUDIO_ELEMENT_H_ - -class html_audio_element {}; - -#endif // BRIDGE_CORE_HTML_MEDIA_HTML_AUDIO_ELEMENT_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/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index fd33e32ed7..5c89c699e3 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -154,6 +154,7 @@ function walkProgram(statement: ts.Statement) { 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); @@ -249,11 +250,16 @@ function walkProgram(statement: ts.Statement) { }); 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; diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index d683fe1ac4..25a98ccf1c 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -36,6 +36,20 @@ function generateMethodArgumentsCheck(m: FunctionDeclaration) { `; } +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 generateTypeValue(type: ParameterType[]): string { switch (type[0]) { case FunctionArgumentType.int64: { @@ -447,6 +461,7 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass generateFunctionBody, generateTypeValue, generateOverLoadSwitchBody, + isTypeNeedAllocate, overloadMethods, filtedMethods, generateIDLTypeConverter, diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl index b2935c6bb9..960a51eba2 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl @@ -36,11 +36,24 @@ bool <%= className %>::FillMembersWithQJSObject(JSContext* ctx, JSValue value, E } <% _.forEach(props, function(prop, index) { %> + + <% if (prop.optional) { %> { - JSValue v = JS_GetPropertyStr(ctx, value, "<%= prop.name %>"); + 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); + }; + JS_FreeAtom(ctx, key); + } + <% } else { %> + { + JSValue v = JS_GetPropertyStr(ctx, value, "<%= prop.name %>"); + <%= prop.name %>_ = Converter<<%= generateIDLTypeConverter(prop.type, prop.optional) %>>::FromValue(ctx, v, exception_state); + JS_FreeValue(ctx, v); } + <% } %> <% }); %> diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index 31ee06904b..2b0f87a296 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -123,7 +123,12 @@ static JSValue <%= prop.name %>AttributeGetCallback(JSContext* ctx, JSValueConst <% if (prop.typeMode && prop.typeMode.dartImpl) { %> ExceptionState exception_state; - typename <%= generateNativeValueTypeConverter(prop.type) %>::ImplType v = NativeValueConverter<<%= generateNativeValueTypeConverter(prop.type) %>>::FromNativeValue(<%= blob.filename %>->GetBindingProperty(binding_call_methods::k<%= prop.name %>, exception_state)); + 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(); } From 72877f44cecffcf8d73880e6f5da44d8dcd80704 Mon Sep 17 00:00:00 2001 From: jiangzhou <jiangzhoubai@gmail.com> Date: Thu, 25 Aug 2022 18:28:43 +0800 Subject: [PATCH 205/498] feat: variable test --- integration_tests/scripts/html_loader.js | 7 +- ...erty-case-sensitive-001.html.afbab4d91.png | Bin 0 -> 8633 bytes ...om-property-inheritance.html.63fae6de1.png | Bin 0 -> 8642 bytes ...variable-declaration-01.html.45fe06df1.png | Bin 0 -> 6033 bytes ...variable-declaration-02.html.8cd858df1.png | Bin 0 -> 6033 bytes ...variable-declaration-03.html.73b28c271.png | Bin 0 -> 6033 bytes ...variable-declaration-04.html.6b76ecbc1.png | Bin 0 -> 6033 bytes ...variable-declaration-05.html.92548ee01.png | Bin 0 -> 5823 bytes ...variable-declaration-06.html.f4e5ebdd1.png | Bin 0 -> 6033 bytes ...variable-declaration-07.html.e54ecb071.png | Bin 0 -> 6033 bytes ...variable-declaration-08.html.2ee5de2f1.png | Bin 0 -> 6033 bytes ...variable-declaration-09.html.bb23c5ba1.png | Bin 0 -> 6033 bytes ...variable-declaration-10.html.1bd4944c1.png | Bin 0 -> 6033 bytes ...variable-declaration-13.html.2e4a404b1.png | Bin 0 -> 5940 bytes ...variable-declaration-14.html.d5341a8a1.png | Bin 0 -> 6033 bytes ...able-declaration-15-ref.html.13de75671.png | Bin 0 -> 5566 bytes ...variable-declaration-15.html.771703731.png | Bin 0 -> 5566 bytes ...able-declaration-16-ref.html.9e4852411.png | Bin 0 -> 5566 bytes ...variable-declaration-16.html.947fc0031.png | Bin 0 -> 5566 bytes ...able-declaration-17-ref.html.6a121adf1.png | Bin 0 -> 5855 bytes ...variable-declaration-17.html.1578222c1.png | Bin 0 -> 5855 bytes ...able-declaration-18-ref.html.2b364bdd1.png | Bin 0 -> 5566 bytes ...variable-declaration-18.html.74b52e161.png | Bin 0 -> 5855 bytes ...variable-declaration-19.html.66f8ee771.png | Bin 0 -> 5823 bytes ...variable-declaration-20.html.2e850f5e1.png | Bin 0 -> 5823 bytes ...variable-declaration-21.html.73214c1b1.png | Bin 0 -> 5823 bytes ...variable-declaration-22.html.823bc2641.png | Bin 0 -> 5823 bytes ...variable-declaration-23.html.424206ac1.png | Bin 0 -> 5823 bytes ...variable-declaration-24.html.1d9a68901.png | Bin 0 -> 5175 bytes ...variable-declaration-25.html.499f04671.png | Bin 0 -> 6033 bytes ...variable-declaration-26.html.a69cc9571.png | Bin 0 -> 5175 bytes ...variable-declaration-29.html.ba373c8d1.png | Bin 0 -> 5940 bytes ...variable-declaration-30.html.8e9b99541.png | Bin 0 -> 6033 bytes ...variable-declaration-31.html.d3c61d7c1.png | Bin 0 -> 5823 bytes ...variable-declaration-32.html.195b62b21.png | Bin 0 -> 6033 bytes ...variable-declaration-33.html.cef687511.png | Bin 0 -> 5823 bytes ...variable-declaration-34.html.f3e1cff11.png | Bin 0 -> 5823 bytes ...variable-declaration-35.html.e45d7f911.png | Bin 0 -> 5823 bytes ...variable-declaration-36.html.61d31a411.png | Bin 0 -> 5823 bytes ...variable-declaration-37.html.ba254d591.png | Bin 0 -> 5175 bytes ...variable-declaration-38.html.549f86881.png | Bin 0 -> 6033 bytes ...variable-declaration-39.html.237efc0f1.png | Bin 0 -> 6033 bytes ...variable-declaration-40.html.1e4067991.png | Bin 0 -> 6033 bytes ...variable-declaration-41.html.dc0de31e1.png | Bin 0 -> 3202 bytes .../specs/css/css-variables/css-variables.ts | 162 +++++++++++------- ...rs-custom-property-case-sensitive-001.html | 31 ++++ .../css-vars-custom-property-inheritance.html | 30 ++++ .../specs/css/css-variables/environment.ts | 6 +- .../variable-declaration-01.html | 17 ++ .../variable-declaration-02.html | 17 ++ .../variable-declaration-03.html | 18 ++ .../variable-declaration-04.html | 18 ++ .../variable-declaration-05.html | 18 ++ .../variable-declaration-06.html | 18 ++ .../variable-declaration-07.html | 19 ++ .../variable-declaration-08.html | 19 ++ .../variable-declaration-09.html | 19 ++ .../variable-declaration-10.html | 19 ++ .../variable-declaration-13.html | 19 ++ .../variable-declaration-14.html | 21 +++ .../variable-declaration-15-ref.html | 15 ++ .../variable-declaration-15.html | 22 +++ .../variable-declaration-16-ref.html | 15 ++ .../variable-declaration-16.html | 23 +++ .../variable-declaration-17-ref.html | 15 ++ .../variable-declaration-17.html | 23 +++ .../variable-declaration-18-ref.html | 15 ++ .../variable-declaration-18.html | 23 +++ .../variable-declaration-19.html | 21 +++ .../variable-declaration-20.html | 21 +++ .../variable-declaration-21.html | 23 +++ .../variable-declaration-22.html | 20 +++ .../variable-declaration-23.html | 22 +++ .../variable-declaration-24.html | 25 +++ .../variable-declaration-25.html | 25 +++ .../variable-declaration-26.html | 20 +++ .../variable-declaration-29.html | 17 ++ .../variable-declaration-30.html | 21 +++ .../variable-declaration-31.html | 20 +++ .../variable-declaration-32.html | 20 +++ .../variable-declaration-33.html | 20 +++ .../variable-declaration-34.html | 20 +++ .../variable-declaration-35.html | 20 +++ .../variable-declaration-36.html | 20 +++ .../variable-declaration-37.html | 20 +++ .../variable-declaration-38.html | 21 +++ .../variable-declaration-39.html | 21 +++ .../variable-declaration-40.html | 21 +++ .../variable-declaration-41.html | 20 +++ .../variable-declaration-42.html | 21 +++ .../variable-declaration-43.html | 20 +++ .../variable-declaration-44.html | 20 +++ .../variable-declaration-45.html | 24 +++ .../variable-declaration-46.html | 23 +++ .../variable-declaration-47.html | 25 +++ .../variable-declaration-48.html | 25 +++ .../variable-declaration-49.html | 26 +++ .../variable-declaration-50.html | 25 +++ .../variable-declaration-51.html | 24 +++ .../variable-declaration-52.html | 24 +++ .../variable-declaration-53.html | 22 +++ .../variable-declaration-54.html | 21 +++ .../variable-declaration-55.html | 21 +++ .../variable-declaration-56.html | 20 +++ .../variable-declaration-57.html | 24 +++ .../variable-declaration-58.html | 25 +++ .../variable-declaration-59.html | 17 ++ webf/test/src/css/style_rule_parser.dart | 12 ++ 108 files changed, 1402 insertions(+), 64 deletions(-) create mode 100644 integration_tests/snapshots/css/css-variables/css-vars-custom-property-case-sensitive-001.html.afbab4d91.png create mode 100644 integration_tests/snapshots/css/css-variables/css-vars-custom-property-inheritance.html.63fae6de1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-01.html.45fe06df1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-02.html.8cd858df1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-03.html.73b28c271.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-04.html.6b76ecbc1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-05.html.92548ee01.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-06.html.f4e5ebdd1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-07.html.e54ecb071.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-08.html.2ee5de2f1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-09.html.bb23c5ba1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-10.html.1bd4944c1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-13.html.2e4a404b1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-14.html.d5341a8a1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-15-ref.html.13de75671.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-15.html.771703731.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-16-ref.html.9e4852411.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-16.html.947fc0031.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-17-ref.html.6a121adf1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-17.html.1578222c1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-18-ref.html.2b364bdd1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-18.html.74b52e161.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-19.html.66f8ee771.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-20.html.2e850f5e1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-24.html.1d9a68901.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-25.html.499f04671.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-26.html.a69cc9571.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-29.html.ba373c8d1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-30.html.8e9b99541.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-31.html.d3c61d7c1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-32.html.195b62b21.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-33.html.cef687511.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-38.html.549f86881.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-39.html.237efc0f1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-40.html.1e4067991.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-41.html.dc0de31e1.png create mode 100644 integration_tests/specs/css/css-variables/css-vars-custom-property-case-sensitive-001.html create mode 100644 integration_tests/specs/css/css-variables/css-vars-custom-property-inheritance.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-01.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-02.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-03.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-04.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-05.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-06.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-07.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-08.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-09.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-10.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-13.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-14.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-15-ref.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-15.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-16-ref.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-16.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-17-ref.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-17.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-18-ref.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-18.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-19.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-20.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-21.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-22.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-23.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-24.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-25.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-26.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-29.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-30.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-31.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-32.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-33.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-34.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-35.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-36.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-37.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-38.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-39.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-40.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-41.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-42.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-43.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-44.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-45.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-46.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-47.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-48.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-49.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-50.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-51.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-52.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-53.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-54.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-55.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-56.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-57.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-58.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-59.html diff --git a/integration_tests/scripts/html_loader.js b/integration_tests/scripts/html_loader.js index ab5aaf209a..441d6ad6b1 100644 --- a/integration_tests/scripts/html_loader.js +++ b/integration_tests/scripts/html_loader.js @@ -54,9 +54,12 @@ const loader = function(source) { // Use html_parse to parser html in html file. const html_parse = () => __webf_parse_html__('${htmlString}'); - ${isFit ? 'fit' : 'it'}("should work", async () => {\ + ${isFit ? 'fit' : 'it'}("should work", async (done) => {\ html_parse();\ - ${scripts.length === 0 ? `await snapshot(null, '${snapshotFilepath}', false);` : scripts.join('\n')} + requestAnimationFrame(async () => { + ${scripts.length === 0 ? `await snapshot(null, '${snapshotFilepath}', false);` : scripts.join('\n')} + done(); + }); }) }); `; diff --git a/integration_tests/snapshots/css/css-variables/css-vars-custom-property-case-sensitive-001.html.afbab4d91.png b/integration_tests/snapshots/css/css-variables/css-vars-custom-property-case-sensitive-001.html.afbab4d91.png new file mode 100644 index 0000000000000000000000000000000000000000..d09311f3a55d103e369fee3d5e94e2adb390ead3 GIT binary patch literal 8633 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 Ns7po{^K|e2@gJap8{_~0 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/css-vars-custom-property-inheritance.html.63fae6de1.png b/integration_tests/snapshots/css/css-variables/css-vars-custom-property-inheritance.html.63fae6de1.png new file mode 100644 index 0000000000000000000000000000000000000000..77ddc92a362bdc4dc6e8c0b5a7f40a5e822e63ac GIT binary patch literal 8642 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 Uy4G*P=P(c$4V}v*wc8K>1vE4Y8UO$Q literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-01.html.45fe06df1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-01.html.45fe06df1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 zcmeHL`&&}k{<f)Xy6KsjGc~2@G}_hFQj-vs>8h6HG|gzHLi0eP;blhhhFofDvW}&R zC(S^uG(FuUQSk<%W2uNjV!2fmDiu^fL{LNozMH=1uQ*TqvY)-zdU)68vp(<pe%8AA z)2V><Yc{X3va(tq6nNxkE30KOR#snJ{?Z!!QnmSG1^8Ho`#Io{RVCkk5`0;KI~4TG zm*9`^CHjVy)kav*k%Paa(Uc<@cOsLEbZUWf-rm)%E57#l4psl%ituSh=h4v6pZ@W1 zL)gEv&i}OLcxeCb6aQGL>s=lG73%w)f8$FdXMKNobZzC$A2+Q2bLV@v&O`qUx)ZVY zj!Q#sA@^|!{O3N8y~|hMAYDMx$rOJLwU47u7BumpBSke{n{OYp+KFjF;=i!co_w(E zUth;ue&oITO%WF6sWipvM?Ol}ih=!dfHf7Q7J84XtxWh@K0YoqKh;&0+H!Ezri=3K zMA9soM%$$-q7=<bo=B?BsP5kn8CiNVZ0Z%^-qc@3Wa%kr@oc)?T$&Ym<Xi|#`l=fV z<w%x6iqlZ-iTg>VDc+=K>Om}(?UB^k8Rk{^?T8D*_*x{ZRxQQxecnG0tI08S6VTDw z#t&5ulVwzfWWZ=DGnmdS7DSl0Nfzjd=-$CBlkm(k)8FSXM099lf6BHXe;f~!)Hh5` z(mhG7cv0)hy*^?3?X{!#MN9yD?mzZ({iO&sx)DgmsHa|sGw@s&aR50##jS@?d0{eA zq~9UYl?a#aYO2%bK612Pm%p%9cqLUR#LXEgi4wmE+Yz52hI}H5D!Oj7dC1-uqo&#a zu3r{gI6%q2>6$KjoXBHqRPiL^(^d52hJVi^fMXn-Ds?thVr*J>-)pDqHxZ1{ZLoQ0 zuAGz_mC}fAn_LVdsqfZ=#0m;M1;LE1l@$o`#bYNO?t=ffY4-J2XzXH8cy08rl(9cc zkay;HDI4Z`BdCT5ajh>5!9ZoRW;+RsaCp%fI(&an(pz1Q1vc2M)cVV+bPyf?8g|Fn zg5#Ewtw?*y!945nkuVla6UG+=^@U8$;fD2NMLnvF^R3HcON7Oa@TF0N0fv=^vq{p* z<S3}FLKY41Qcm(-<%6sL546tMT#YOrfx89?{U7_G-0U?J4CGm3*!VT<shCRMDWqf< zgwL)5?iEBdnSvXMKQq~BqMG11?gr%056F7uTBvjil{8Uvn2N$XsGd2ncEgCo3d;l` z|8Y2QwqPwe{kQhW1Lb;82QaPa=t(E`^ym&8iBcD}$V6Zlm-O%|#Z61LWC_OM6aK3A zb5E1cSd%pzPGM1il9qiHHk<h^xwFn>h=9&^IcYLIA@;wyhqIm6hPuW$<eEQnkz+NE zCHaS$aOVQHnejZlPI{D6=9e;jv4i$fv|7LDhZH1~rt%)b?C%rX6?x83Q$Pns>g4J9 zTh8i1vci-OCaXaaR&W6<pa%6TJ6wA+=F{GMQwiOgL!MRF)H27vfslT{6?FG}8-UE+ z_N-v6b)~Jeya6&kqt3(_z7k=EXZ|4aQG5!tK{aXc0C@m4v{ZYJe%MqvH}L!x)yFOV zMZA8DRMOp+<;b7oH2G(mIc?Qro+mtFS{^({RQrJ8bpHM8+6<+NJ02pf!Z>2Z#RS<H zVN!<df~yPyz4l58|B-CNg+TcoXYRfx`@GlBHe#kJ4bb$QygX;>`r+r24wO)t_X%8n zxtejY=V>GXcn$rS5ilms8KJV7gV@skN_Ux2!6JNMx}$UE{gM{_D=^_QMsPDO$p=W8 zKUY`WVF35O6CqOE6>#L5P2Oo|oAE#xuD`pQVIDBz_y=WUgM19mv_*yyBq|0Y=}5Ry z&N6rDWVhSI_sB#YC%XCybsU>Yb`2n?ZY(Wokr)t3$gYH0V$`S7Bm}Wv?a~v1L7b?= zi(BQPqv`&>#<ts+%Br4bywgA+(mlm}fdQMFqL>k=k;UX5WruO5{+N&OY;WzO-WadV z1!7#R)0Ws_v(|X~SZVJ8u&TX<x$R*3?CemzxU9{Sfupntery7@37r5=q@@^R+3m#y zC6_&&(cb218P9_d8EVZAk{ATSbRQ^%nm~qMEOWXYp2Np;q3dAev#0@m+vaD$^MnLD z<K13xdHXV26;kGE;8YAaPqJs}WGT3s>EhH>5F?VL8fVX^Dr|G&Yl?v(mT^9@nZ0lR zQI%0_-USp10|OR~_)>L82TE}iWj7*kq>5C8k9-8Swh9XXn{O@6b{+$UDy7W9wb2kT znEEH9_eCJ5O6fk0fX>z|oI#9&GC$h2sxl5lw{vKIXi0@*{nT=BWom6#1r;Tw-;;ph zlq_&};EgnQf|xk7%8|g;x5-~`XWK58jf8Yv&WDpu2(mJbMQ$n|JmAa)<pkYM+eDvO zR2|N8I8ITRrAAGsFd#{AMrRoaXQw1n#tMHGI0t19sBhRCARW?=O>TTR!R+XP*Yp<7 zig!^7<RFWf-v{33p#|~t!Sc)f3dWpUPrNFv4@PlzX$*}MHm7c3W2LSo1r}3TN)o&G zb-I|#jNA+w?cj3j{fUk<(&a**2m5W^()M!O=_8sK6NucqBQCgsg*K10_wS|iQB3h; zNp&_(oPJ890==#JIX2uzJpJAmRFGkvmRP9o?3RYV%HtIWQ@RlcU?|C6Z@HaZ)AHvk z4|!F1!{|+z1t7$Y%m7Tt1y%+xG%-kM*4QB2y(ho!Xj!&AV}xXKKZ(pXP)BE5JZ;fa zQD>;Y2DrIWfce<^4P?>3<ecdj%Gkf<rzUIJpcgr1-ikvw1X9-8u)hxzH2Y+Qx$}=^ zM!j`m(2pSvn>=Cy9i5QkzcQoljYsi3=PI8R;(@p8?0olxk!HHWv>&JJ>e5EDnCm5R zX_}+$C&QZ@{M_GU$JnVK(c}Ft$@dTvK4dZBWFo$@Fr<lmPJ_o&YHVV9W_qq;w9?W% zXTfvAc3xxth3>yl_}j7AcwUMVQ(kM{=&!P7YMa;7!>Iq9IJy79g&+Cwu_c1~j=Qv3 zuNzmGePN$OU?$$Nds^9)_|_j!dRXD$y}v_|6nw1acIV61wnh64X^$P^7kelgy<P0R zuR=*Y$3Y=o+*6PEg7zK^Pr!qwE9(zP>;cQ0LSAZxNB5WqwYCe|xrBmWv7r|TqU)Yi zOH2{W_(JD&re^SVBWL4O`a}J482S5+TT)J2pk|mKVuMc5|A<&Wxcl&`^*u^Qwx83` zF*I*$#2R;2OUZbt*fo|bP8^2G94;kL(4gWGv*pmo)8~M9!~HlPgW)kpFaWuwgzW7j z!~6jMOYwZaiQ&kMrP}cR+C7dXYPA%|a9eqaE+@^we{#fU{~)6;j5fLTL;7Xe9jXc3 z()%k{aSID=nh$>dv|2gfeRaT706v=)MAQ3LM{T3=&7(|V1G74PS2PNk){9>b+6nuI z@~JZMu>8zJTX)id9Dl<Ek7NeZW*Hb<i@`K~CAx4#bYtUdXi$oM18ReR-Cj#nZ{M7z z`|wYMx~q4EB2p23P7^Blf)Vtr?V{`f?+{n=?mQKZHkJdFBV)2!Bg&G0#8ws)lf`Uf zQ(tp>#{8!0kQ6(1gj<7*@;vdm0M^0wTz<}aj9jcQ0R!ZZ>4GgDG5%_DM>Ob2lLzXZ zu3E3_y5_eXJ!I_j01Xd%Df2KD2;rpY07i3_KP+cRf3!kKZ?}n<zT>Q)Ekg3^Ka6_B z&Ag1%^0GY0<4ZL*3!@$(pgp7vBxT02_5JBFy@x;9C0K!;toSsFmkc^NA2g^tX^=*V znx9Et@<Hxdq@Qq!o>PvE598}6uVY&)R5&~ZUDw;hRt(LJm(1%cg4x)MVzn}LY#}N~ zFk3mUpBh1qOw=A2O%!zg#bNDL={YKH!2D96w_Sa>19Cns+Rw)YLYTPj%q=vO6c*-m zcZ)c4^MvUKf(u|`!?nYYy6<^9A=3q6v;Db=BNGP0G@U~|QwiIanFD7@Qp;PAr6<>1 zA_KGxrY_P&(LmG;Q*p12VO|}|+8%_y|Fi_pMK%YK;k#r@TAF?OcrFxeIGn`07LjCJ z4ag1ygq;PZDPiQ|vlCPl+*D_meJ8}Ajs^S7LTom>JbeY8YjERgC8q@Y!W{EfmLTp} zIu6}!GBUcmFn1F!dW@Gzf?^!g=Ch{B=Xos!Y^E4EXAuO+fmu7K@p>O_mK>LsH4JV- z(o$uIEP$gwjT_#ki=?FUWZMRRe@lE%we8kt(j>lJzR9XHnnd$lB2<NuZ3~(<3=ju# zR6hf)?R6%Swu^I*f+DBI;cUz#Ee9ikC`I;~BrH&1>F}>Cx0W{Ph>((oanDT<LL}dR zMnRB?Ra*Rqmz>%IDwTj&T_ljs8r%e~9M0ngqU<ybVm68ESdfj5q(A&Kco<EUoqs=e z3$mVLO3*D0;#`S6J^^o7&ER2#u7nS8qK)_WM6tahYz7$s%m#5BXsq-V>EoE+v5wMh zyDq(ucd^Db9xpGPZ;#toE;!4gfl^-$wk)^oJs3c)vZU+&9Cr2<V~0msa^R;a`vfo2 z-CUZ&qUAkx(;6*&@b|7}u!5a3vlXeqIXepAgOPbA)=+Im^gGYN=q_(4URH`z{sAPs z)mf+lqFGuH(K=DN#PiC^XB*J5G9d`&QF=Xls4RW%EDNu8G3~H^@D%WUvy|MZtP(Ns z%Bgi*yu2X%hJAh0CHZ<68K$3GoivlO%KW%umDz<=4m{Ay-~AC0K$>3=+^@@A?~w13 z-F%4lLD2b?7jX(Ki;e=nVp+d$K~>^+B7(twM&|U!>ZsnGZuRMmwqo;xomZ@P9{=x= z?epvBY<w<;|G&QY%!SWf_{@dRTmWw}KTE;?wiKA)?aOX027crL;I+C{(9u&zDi59i G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-02.html.8cd858df1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-02.html.8cd858df1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-03.html.73b28c271.png b/integration_tests/snapshots/css/css-variables/variable-declaration-03.html.73b28c271.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-04.html.6b76ecbc1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-04.html.6b76ecbc1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-05.html.92548ee01.png b/integration_tests/snapshots/css/css-variables/variable-declaration-05.html.92548ee01.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 zcmeHL`&*J}8n$t!nkjR4yQYJt>|X8U>{_}_spSD@vdP*;E$zcmLMkg$1f~!{5E*kO zr!jS!HmyvkoW}eRunasPlGCIeR4U^n3Z;37gQSQk2=4pM{uBE{To)I7FW=#L?&rDh z`*}ILFFw}mBcG2vJUqPi?B1E^;j!q1hsS&0E?o>(Zu<<?f{#U%#Mm7kg5Gs+z`^^J z9eWNe1+V<2M}P3}Se3bF=NAX)SIq;g`bw&+Ytp`dZ>|5%&%TPP*j!ZFcl7+%Nn1o0 zlbU}0l{B>GV%~+ku!n1xlkY6AxKVLw$S>tUP;tvm_bZ<|<C)PLd#<1U{?e!P;(;F? zmgX0*n<+!@e|pKy81XMJEu~b;?X75)O-kojTG~~WWy;h*kD+Y4p5x(B74X#K`ujeA zeS!Ia(Q_q)Y9FMjE+nAmD$-R#`~oWB$FQbN@+EI&yy0^8d^%Q?rC7CO9sOmYC|h%< zpDc43DzK!wzW4}YNqTKu6$bAzBpY89qXeGbk#7et1w*zMNeM)Sb<sRo`>K13ynZ`n zH2dVe81JY9DEHgqrmSPAxreca@tSgh+vSw;AJwOd;4|K+wAE?!!z?oa&%_$lKkHo+ zv=GMhc&kw$$x=5Qwv4yx_ax(4)VS(!(P&Q<$mFB9c=7!~njS|`3HUI4c4dIl+@wz{ zkDS_VZXPa!Y`b!}%!w9$s^)yGe@d)>-fJzVyOnCpa?j5pxim@3Hl?)Z=WY7B;aVEg zETZKPT7DVhvfD@H+yy;n@t+P!$_u7zLXJNC$>+19yIC(iQMX{YqiBgeNSj$(%~%qA zu(=_A+pF&k<Kwr=%F4orepsJ1G0-B_%j`NPPa=`XMb9EEZH@8hxz`4OEj1_S#r0zU zR?XS@(J)zE0?UvDLDadC%48ai#<DQ=JQ)5s5c8t><Z#x*pZ=zJ_G~|hKcjT*mR~W# zG#msa)yDHDCMK?g5HJ=;THeUT@ad;9#=Cv-yge5-C{i-2AqWcK(2r1QG?mM)mud3W zQMxyW<eJpsmNsjaJjz~Q;BqMbQF}Epxb5u@<i_%XZcdcMe$qPH!$WSpopPLOi@Un* zwj3?QNyLIy1MC<WLnr(B`6+~WnNJw$AO!i*22R>)P%Z#Rt3*No<+1&?EI2!t13|oG zp};DSmf4%v<*E<cp2m3F)><)Lq`d-}+Yvr~tq88EshI&)b98tY?91S?a;q7JI%ztC zV_{mPO)DZy4G5eF3`U_Wtx3lqc=?9b(@+TjXollWtuuSLV+pE$ec84f^38*Ew;b)v zM)H0A=z@N_t;k+4KEFA=^DdkPBGJ)LVw=A?%!4tI`R#pga`gNwZ|&^lFcuPduGn^s zHZc9B4}9-JvD-w-ev_sV9GqVtD~_0Gu8HHKrMQz1J|<E)^r5r4nq!qBI4c&#%Bk9# znJBTeJ5ve#QlAhQf_x)#vz>Etwl1jU_0JfHI6eN1FH$1q2|e-jT4>Jr)0uvPP^3&n zPUOL%%o$TCWwbjKbT^30)F>xi*btY+b{X+u5#<7b04)9#vm#TK#w13}sMA4Ay}i9k zg+j3*{;E1Y+BJ=V_&gquk8KiwsMnWAK8NAKys>JQlGeL@>@GN2C#?PIGFZidu!)`L z)|vrhGy7^2$b3<*wI{48X2tejAP7kWd1J-dnhg|F9y&ZCLJNqjBehL-=Gqj=_Ffj! z&gpuCn%GNK;Z!i}M}4v1e8!h_Lcid)Yws0$g6RIdepmKXzCAJni5S~N!bxmOELa~d zk8XFp4v5Z7`t_?!&m}8Nls13X^fR3`!|<Yx*nc<E+{bn2@L-T*xlJQ4l}k<zcM?zO zI+%*k4;)tRLO#=FQ!y+5ATitzL%dW%)nW(C6J$Y(iXdDobRiFr2RtcI*mj4eNlJ)P zz;LN=m_V*x7Tne#7o&h`0W>4@bMa1azX5;<<IPR!u%%^;0}7?APa%gF1H=(<eI1}c z;W;NjJio6@m<}?|EqZI0El5RTTOk^RsD1nDep9onB&hY4ODpe*n~p@?FZQ;0r4be! zg=$K@ct%Z}ShL(evY8KfL({BGh`jyabMDE`wJiT=&<E(@RDxM0b5p?sw0gG~lnxLU z1T38$Dv_qA(O-QJLIuC%a$WOw>x2=%;Hc_F=>o-X(Bl{4!|TM_3B0!k=r0V5XN^-o z{VfKDv$`*Bw%zmeegPywe2fA?qC*|0Vb}psPf3V&<@8+MQtJ;u&=K3gQy>VyTp$Kb za<vs48vm0NUw1vGcqT1ejVVSI&@Cae93m|@SLt?I^b*rb?(9xTI@viYUITFH0Wu+I z9{SPo1n*8XN6-G^s;lLs0=mX<w^PA2pxS8=mTd|ZXDQ&+yC?Sh0qL=|+oeL<9Jn8T z{jPIrQ)K&ODS6t>0=9!h8g6R1Z_s3L`G8k<3l^Mu)r~JpKvt7O4<IOnHZkCX&ZiUA z4cCF-CpRANfkBkbpi7OHGdOP=-NY!%OX-o~CjY5m--Y#>0oB69rS!5oq#FcnQ;!{! z<!m5UNLN;~-E&xJ&nJ5;4k&z8WnrP;yu9ZVN&kjRc)c2zvp??gXE%Z9<C_L^vD}C5 zfKXj$)h4aaa96qTVqmiJyAM}p{r265ph=)6eSuTUpip1lE3De6B?zUA`;tvvg088N zCqTUMvVy8X9U^~^rb^2x@(}Bz3jv5SF)I;RuYFO((2vnaFK1+Roc@3{bG@;#F;`1$ z>Zj>h&U!Xh9zhq<ljYN9u~yJN*F#qR{K;MeneD`uuG@YmsQKk-V(a(W<xw-Y;IN6? zX>IhAeBhj^1ls^bu4v1%I_5oPLq7(>hSIR@GUuR0$rH9adMBP@=)C0LT~52um(+Y+ zDZpX9qo((Ht@+orGN5x!B0lF53Ao|0kVE&rh5N<ns+xfAKuo>*H8WT`{T$m{m!Zw5 zreZdw-o1sX|9Zb~X!alK9POSgcl@@xeIG>r-TkfSid)|=TdjP4b1%W=u!)X5_z}@+ z0zeI$_x?+%d12({?%3E^M5qDxun;-_=@AN};O0b&SqL-maV|j5dT-WvjL6y>L7us7 z*R$D!^x;yf0!miM3uYQq8xDPe%b$EuRnHu3r7p~^Anua$9+u({lYskNko+^0BhNrv zFNcHSgD~>=qUhAfW3+YPO2Sbc5H41G8n`@aVcsgOtU!pGt-{gxg`P{4Ec>7;W5u%7 zK_*INcZI3V+8Y8|<4OdLWNwtB6&g(nFutmDPIVU=14g8yHls?It7qB!3!G;CsX_Ab zxv3HKgU8I?P~4CjV;`U-@bR+t86DHYm}nU@IE+Fe9G3Qq)Egu*C018ghopC&3!i;i zL_o?2#;ai)Fpl;h0Ca!D;h54jn{SA<BniWGyDJN%NqzR^YQ>Xa*}<A!r7)1$7jt7= zJVLorzX<V$1kORc<9VgwWs$c)I@8aCb@UKTD({P;#R|03WGyoj5|#(VT{+-1H71xr zX4Jm8FvZpF6d1G}ZJ;Mif6zH0AtQ~jYU9`Y03tXqzwo=cgH)jN{lJWQhEnao882VV z62F|MtL}#b&vHCYZchy0=AS||E=V>%1;aY*t24{jPE~Y~D7|3Vx$>{WJ6Mw?_<BVB zF@l3Bs)Ljs460;VQ1i9UaugoP1W_{`@DJ4%d1MlM_CCs>#FD`T2M!wx9DQ8n{#ZW< z09CihbMXtLrAE6ihZM}u>Wtl3u)LN!bT&Xa{;Y->e6xf3Tc>4By8^fF5AKeDr^Rt4 zTMIx0U<9y^$;o_|kyOZuMx0wRTo$35#y#;Rx80`AKRmx4OW-4I?Z=*<WGNxY{PW(R zOkk0nYcs&bAl)}O`QwX$*yQA7->oP1Sq7Kl-BXoJ6cYeR%ykwm_R0e37#dB}eF+vy zz%BwGV29(we|WN_K4XFDJ_jmVP1EJE{3G&b-|8&h>$Wu@Y?g<o0VhJIBZe3^s<!5T zRTavYtAQ7Vf(+6Bvs^F$=@8Ff?Cn1wK_M;*MD#@Qx>LI?%^b-#Ff#drA%~=QD4Rkx zqlj~74BXA^W03V)I%-wWL3Kv-f~&16GVc`EDi~W)&9Q!<3k*+X(iZN!IfN2GR3oLU zqbfy`OXMGHxr59&X!22{v=tju618L*%L{}n$1TPh70GzMf|sl!Nz7t}RkI4vK=`0! zwF;3C4WSK!gwmN*sl&h%Aw4ytT7bg}j_E;5+bnu^NjhU=T3fUIQuZS@!-OQ>pI#ni zIJc$uR$WppNs%6!t=_k{Vq+cEs8lS8p6%G=-q4Wq=6Na@&cXFEgmA<w$>qnMQ~)J8 z;*lsd7LKyP@GxDQ+IT$93<h8uAb}E0bA;Zj+d~M-dR;>PdlmuB=CgG{B4~FQ93#PO zjgty033e;8zhum^Uw^!{N_4#E3S&l-L9iJVBH$BJ^Iq?JT4Z~a|Mz8o{m<u~-~Hvp zxBK4n`qRI!E!y$n*3UmJT)gy?|81V${RnoD??8A5!aF*=(}Z`z_|6^PS>*rKoZyJ& ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-06.html.f4e5ebdd1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-06.html.f4e5ebdd1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-07.html.e54ecb071.png b/integration_tests/snapshots/css/css-variables/variable-declaration-07.html.e54ecb071.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-08.html.2ee5de2f1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-08.html.2ee5de2f1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-09.html.bb23c5ba1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-09.html.bb23c5ba1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-10.html.1bd4944c1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-10.html.1bd4944c1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-13.html.2e4a404b1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-13.html.2e4a404b1.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 zcmeHL`&W|L-nX&qX^b<cb?T@o^RC{Gr&Z=mMVc3!>E<-0W(FpTOi^AyBvV2~pr&)I zsnqFeVM&eU<>8hZctfZxu?#PxfOj-g6cjW9l!wcCY`uTP`$NxKdp+y<?!BMAzn9PF z``!DuBN1V4fARQ>i;Iif!JqaWb#Yme?Bep(`IXBdNtMU@a_F@r<7n6(7k>MODd=Nq z#-4-6Rzgqi%2StJT-FUA-1p<Ltot*A*>|y&2bwuDsjv3N&%I0c-$SRyy*}OZeMP?S zWbe1Wm+SVWkgE3YA6#<%Kx};H@^4;X|BCq`F7Al}T^{BYcF&wn&D^*4&B;CH{%Ymf z4qMd0P0Rj%W5j%&(NuCtGg@LD%3xRq;|zh8`?PN5&hTiLS1C?>q|5m?t9JhIuM5jp z3L`J1<>e8LMH1A;HbXBsa=lA^E@JvMBLx1O%{7zMU@XawQ>(|AybE$fL*iZ@qbplD z9;cQL+;$tDe}XwXdy@zjGUisW9LxyNn(M;s4^%bjt39j>H$v6~e7eH{jGe9)(rHUb zMPl%Hx&ld6hOiFToQ~rKm|r6dw>vny{pNMFX9XdBW_W0&P*C@HO@sah>!T*j5uvg- z_E6<=#F-(DZq{jh6KXzoKD4_1qJT+5>}^J&6yUHYVYbXqbwVu`^_OMly9G=?^uRWz zv(dZkcf*vQ@ZQ;lKHXmz_RQY+yFv>W%XGarmCq+U5<~!IAdjKZIj|mBbzJiyw5_Uq z$<ky)ux&&e_;L1gn=FY&p|G&v)ZEyCp3P$y!|n+9igIcFop+&Siw*rC6YXYyZ=Pe5 z7y5a^lcRsE(j4%okLrXIo7VTtgl)hbnt(@dVW&?#UfXAt8x-V%&{6=%QPWAh<8>7| zf;tHRU@jID$6mv^<At;Y-+Ro%z9HII{j+y0s2UOtg>s9xEMTh6M3KPZjuw@wUo1o; z$KGK;E%lh1My{X;q+9?{isx;g2Q9QveICfX%9|eTia&$mg<7s)a(aHFfZxbetR369 zU2b&YsIQ@THbCV3P&8)oOc0CFTjP(#Nlh15>2yu+t*Z*_-M#q|i~2aH#H7V+ciwHV zUmp9sKLAkp1=lV|n*C6yMO86#kGqMPd)1GPT!0`-6PPqj)Lbbyc=Wb#MhTDnM@=og z*2fOFr`jj#b`aj!@Pxi*Hp&jSX7XrbG=f|$CFQ<r*U;KjX}sG$;_5|%Lbl)>h(pXq zIx$;HGOhFYc)GQicxt?V)j+TH0bBvxY-c@<q_Ul@TGQelw2juXM*7f;CIy3jrPKl! zaM0>sOk(@A7D<aD+08JbylptpVMIHBaT^k4+AimUkxUdZ;p~s7m?XK>7ET#KkSpCI z>9)r@eX#8zi~cd10}%T0aCA*19TRT9>O6`<={oAgIcnNod9Y&@Wn_i((ZqenJ1zLU zoG|1$3`Ix{o1Tjp$RiB(8G21=BUm_xq_(HX?Tt5y+EVYM-R${E0AMXV`ws%_DMx88 zgxqY$Pda@oCB!uTOz6UCiZn4QoMvk#c1vwEU*@L_Lic(}!cdYQu={F6#p1ts37PHl z`@0g8>+;g#Fg(eHRXR^>(}|axA76+ZJnFek+)x(k_=eIcT(4TPUJ8^2&tHaCOdniJ zc)Qm-z<f7?j*%l5DiunFP^Q6;ZGr=j*nXASe#{83GhH0QsRaO&{eBB^QuJgDZx$K; z+3mdE(PPcnCMd|RAv#VAIQ4!ZPjdXElI2K8y@$g@ZjsI8N(@=Uo*eqbCvE))E`g2Y zEhjt9@`Atz3d*ZT!V$YhMQ>-ulE6Z?@~zWh83T!vMQsGd4jihq$(8av4i~kWA1nYA zz-=E%bskfgn(ErT5s^9YXa6kBpf)woOjsQCj6gA+Xpb9KZPmy2J`<7D^z<EKTR~!a z$IHa=&4LtGmva6iMH7$Q#b{HOrLYobyKyP&EL&6a7(#9et89wy%$L6}`Hwu=@I#H? z0dRTo>yXvO!yIHiabOGD|0-?WF3)Y&k$+d|{KPpgMYXx$rO(4#Eb#-W;2a5W$<~C) zWMqey%37SsQ-gg&?I)(19c8iBr_vP6z@?TVU*^lzE#4|Y%yhqFSxmlfquoEed#BvY z_Dx&d$}Wfz)+%fD;YI#Zi8FnyHYUX4CclEmiDfea<^W=GL28ifL5B>I4eo5-IM!Bc z|4_8ntdQs%-<V%i%aFAxZf=Eso>tS9DiYH4WUTERQcc8x{8bj5xgy%<8j)4jXS+@Q ztWTW70%Jj@5JKD#J6QA7y|2?BnRsfix;G=MPv#Tc9wgN(C8I;sAZA_d<RJTQN8p?v zr+=V@O!a1*fxr!cxnQbWzn$^+`lT3@!dfd9B(lw)SmwKz3fKd~G0kmgh<SU$f-4T7 z`8)_)`Q|mpYADmb*2z4FR&032a;UwdGH_?ll5)W?!fZ6Iax<)fNntl7OGU!&Pd*z$ zPLxE$k)j#?Mtvi(ub>o6Y!lfrmRl>lqUg9rVUC}3pM0JpQ}tZ(j#Vf{mY|4}%FQwq z<=o-B(ID3y@hoe4WhTxtq-XgK5Kbu?SYUNDheuSkmFg~o%AV3fR;G6b?jsj#fqU>r zPjd*vJ4%jk<fvje%xIx?1@V-kcPhUN;RyhQ$@Pbb+GYnANFij+ee{^|<U$GwJ6KU0 zsodiE0K=ySUEs7H3Pf@h?!vIW=rI8B?E2>IV*+p7iukNV6PAK^8~!v70E;kS(OPOO z**Amyj_@4=MxU*Q$tga6g*TeieeS_VHdSflI7``;qnox6UfkB`E%1vWefD76e055+ zp{t>xBjFe?9Lfey;Qag|MqME69&R;1(CLkhpP8JJ=&D^|6<uz2l#mIgm3LhThn#?< zwfyfuaZIVskQ0$>v5SowdW`xQ)OJe5bmcW|gZh6I40e@nLn)QZSv17d#mKwgmC@VZ zX@fIgymA{{poof8UW0MdxQCV)TAZBrI)gZTuUyedI>fPD(Gl&%0<OCZDQY;k0c!}C z(I?O-#jjgBIPQ_zsltwuC=xAzplz}>03MC4ePD@>pgbtkr%xLW)f6#l0A0^zG5d5J z&7U<y(85IPhBLEyjL~|^s~KFMfh0WlD)VKvBOEc_p^rg<wT2drKW(BnXL;=)WvI!O z8hb9uYp4by<o2l!u3jan=uhh4n(uWRK5l!2XHbRm`w1)5vjtV(*Ce4S^C!1V>tROE zE+snJJ9pnA{X#S6{W(5x(U>n~NAPw@s-~-_^({hCHG-sl{q7Dqw{|(-p6fNMjOn=m zuX~tR!~^ol&Q|^sY*56qYu66vIMvGb($+3YQ{y@jt3O9q_)oZ65!>x;nPt)6rYriN zJ;4;W{MxImA0tm7b2nxf*GqSgnrEv~hz1GOR;Y&XJ0$G5pWqcTBphcB2A){6u`MQY z{?@yaXW?u#Bt?9>`b3dEYa_4`_&=iqu&_FE{#Ge|Yr8SgsZ39EOEvqV^%RC6tTRqx zeLy&HfMa}E;(@KLvW8mwAd3HZ{!L-YK;mJJId%uZ=&P<sDa=l$3?`+8@769s*xwXZ z<{NCu2p&2-$XWrIMIP4iYGCv_YcUcP{>O<pMtXRNwSq-$so?|$5g^Z_)CzyayR{|p z-QLDdZTxrU%KQtFj7lH{FfuM3)4VV;lL{$GUxR${qd-^*j<lmp`P?V163Qp;S9{jD zs?<jxT-sJqkexxu>I1aS)=<^06+_JFf7JkiFpuISrwkbv?Xb17%&n#7e<*os>yJFT z;t4|c7|(I{s2rv;#Q>1VJ{ylhQM|I}6D&igCQ8zOCW${aQ)H{#EAMq=ofPgORXSjU z;tuenzO4l7>9=r%7h?Q_mHi%v9=OH#sK;YTpx((_W(X+<fC=&BJ;Z#apa+P?#TXMD z_X&m)EN}Qlzm;c*vGn<I+nZtini-NqMOJF1A<{*v!^}#I<^{&@Y0|HTl*oms_W6$m zUSX9%Y2+r8OhHxf$*<&DZ^yLHVNz2Xb74*0>^qFbN>xGi2@vZODqLfGzDzjsHiKxd zk2z*I4QGER0!`%*m*!nX9x2t)bia4Rf6wURg86>@a5iYcfFG}FpT;>W%Yr{FL4eQ7 zRU<yq;}_YslX9obUwMDO&){j0gvEmAy7UqF{<27pYiQ;P-tMuN82cYpKOJ6lA^2xB zVYbCDztFSJoH52`>mllyip<HRuuZl(lRR*$*Q0Lq4#qjLg#&1xlGWca-o9^N7oVMA zVt!!j3cNIyAc${PU}<ynVWyUrLA#{*u!XZ86-T4F4MK&!rJtH_9}UtiLu47H;dN&M z^bvxqizNi2Sagj(H`R^v!XfRe!U~<vXHM&SNPQ!rd4OMuNQw`Th>gE5DD15D5y#co z1Zq&4Azotic%e6lM}{Br`UVbmtCg+E!sY&j5AcICYgbUqp(<24a)-d)O@FzpAu+4& zfGU-%O%@cC=-=S0YANKQUFS^9b}qf7gW>MZyADw~iJ*GCeh$LK%w9!m-!8L~0b_oR zaV_RxcvS+qVI0;n$#NS5Pen2Y=>82=(mTjW0kM5`BzM7(-7FcZRP?m=yP=vlD!O)% z3k^##O|E#s_1!fs<~JdoT6Jcj^S32GnLyZ1<_!_usX^^+R_vnuOtQDbh)~JFTdb${ zI%h5-W};vV_(+%|dfv{saxpa4t{RLlMmj@fP<Gueg-$j{)AK^V4eLA%xO?!s6grNO zLZf(RJr6;eTn00~a{KFBe^-}1|93ju)amkQsmHgsesKNw-;&QCfqH*_xa3Rc7bAR$ xgfDRT!U<o9@k@62k|Lp7-2YcbsBpq2y}Em|oJU_nH{UJ?_ebpG@A>7|{{n0VdPo2O literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-14.html.d5341a8a1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-14.html.d5341a8a1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-15-ref.html.13de75671.png b/integration_tests/snapshots/css/css-variables/variable-declaration-15-ref.html.13de75671.png new file mode 100644 index 0000000000000000000000000000000000000000..251d4c98937ba2c66f2d3560c487223846d53699 GIT binary patch literal 5566 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 QuudnwjQoOq<ihv=2Ys7}>;M1& literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-15.html.771703731.png b/integration_tests/snapshots/css/css-variables/variable-declaration-15.html.771703731.png new file mode 100644 index 0000000000000000000000000000000000000000..251d4c98937ba2c66f2d3560c487223846d53699 GIT binary patch literal 5566 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 QuudnwjQoOq<ihv=2Ys7}>;M1& literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-16-ref.html.9e4852411.png b/integration_tests/snapshots/css/css-variables/variable-declaration-16-ref.html.9e4852411.png new file mode 100644 index 0000000000000000000000000000000000000000..251d4c98937ba2c66f2d3560c487223846d53699 GIT binary patch literal 5566 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 QuudnwjQoOq<ihv=2Ys7}>;M1& literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-16.html.947fc0031.png b/integration_tests/snapshots/css/css-variables/variable-declaration-16.html.947fc0031.png new file mode 100644 index 0000000000000000000000000000000000000000..251d4c98937ba2c66f2d3560c487223846d53699 GIT binary patch literal 5566 zcmeHL{a=#z-nY3sTixbbr`&wvba$HPwKhG?)D(#xwyc#}*(HV|owd{;VLspk2&~m| z-KBQUmge)yOuG~~KHw7|xh<_JVk)30pqY{&kPm>0!2M<a#r=B44_q%Uu8ZsQ`Mlqs z_xt_g=IO|A*UcWAot&IpPaOZ^tdrA*WGAP0zT31BoN4rUSqlyu($9t;abgd6&4CZ^ zrXM+RZWDOrZu;g&C#N0BC%!m*F1uVeLAxDKQS>g55udnUdY^w~$M_d#ijKQ}_{+m> zn{WKQga6&R#~Xg%zxj`v3;)=B{rKiD|91V||90PXHTKiP9|V5AJI(jdA=1<TdH>|y zIOnt-b?rk%ttRvOi=>T3q`4S(>&gmsPuYBb$P!Y$o@l951SDaRx|ZmAIM`ac@8U7u zY$*{7am&bE9jrcz3@3wO#kaq?YU9<Sh6F*Uf@Totp8Hm<=lKZzZteVxu*KcaOR_J# zFFFb(nH5*5Vp!O~`Nc19?c~A)OnmCoIx=nX)$^1#MW*N7#sQRhA_hvSt;oa@$ME(w zSq!8biH`EVo6^m^)#&1#aaz}*u9Q#)Le%H*c)WMovt0s9rZPwEaZ?nm;%AQjGf$qo zBnoe_d9va=nli^O6^GhZrnUV6;`O|q4+p055D6Uq;#XEvo6wsJs@hW=f3`VM1Q#bz zAGgX0^~||e^y0q?NA7`6FEyO>>v#ymJ3oy*?S@)DO<%4?c;8K(5{`|MnU?XR`UC3n zbi#N_8>?A-koL?fOQ7kfE|%YY51uirnD#pTU&$4He!MH^dU7VOx@fMt_|%>F>0`<I z>XNn=ZM`fi4J4lHxnWPVH{W-Q<!7gQgYpAO6YcWUUT-abX+ZlK&(LKd$XhN`RCF^0 zN&da(qz0U?S7fOKt)pMx^7!z>y45AM8&W?Ug~nO*JTe5~sB<-x!NV@bBI|q-y8i0b zs~*9mcwL){5lB|@l}%&KNwOzT4k<E*BLo(<z$9N=u0`YA@wutq8H0z7Je8I0=-1iV z*|<C$#o_8M^%Y4DTIQp{gf7z!G*$i0?Y>ey&yY}z%RG-Vjzk-4{OON-@;x!M`NnV@ z(`pp-dgr|Qm$ApaeJ{KH!N!dnHFq%y=M7%v6zO@&!c%t)Bx$)keB-wDnE0UOiZ}K3 zeeTnpaU+@IDHsRvEG(DsER-A+^;^i)_3fJ9w?{R=Flh77veU&V3IJJh{k`pkUhk}U z?GMxg=+);@FiaRI50T7e5cCrr5^Nk%;HVq$Fn+slWP=-X<u}Umi*VWEOdqWe!@@$f zRSc@GQ_Bwg=dafttVXR&UK7kH=zQCnR{OfTkp15Fk4He2ngXQ0Zk)tRdDiHkz|R&~ zTo;tt1J&>il>ZpY1qZP>91eZ853g5rY2f-B4e1Jsj4Q<=XYXAEOHvY$)XHI6wXET} z7MLJL+t;zv;B86w<L-iFNYbieSTcuB_&RQ|%s11<%R|x=wUq(F*AH%CiJXdRZ!{y< zGOn^oi#|O+w6?Z}tsTBBMw#Q0a}R%_wk3g%=UaK|<2*cm<yUvADl4=?I+r2uc0o*x z+X1AFT&dhNPF6iSRr2{a4S*v82%^o^6ibmVXxl;|t#^sbp|cb@>I$MR&b-oz&qFY> z%ffQf>fy}!#$J;&P8H^wH1zO93fNF0N@%rOb)y#xQ^v0}N7TbXfKQZy#<ZC~q%xW8 z<A~A`EK#6-!{+I+>UE#oH{FieKoAn_A5BZwr&kdniDN;7(r5dXq2`HnG1f8)?mW0u zB3`bn){8|OT)YNhI4k~5)s<_`0<ccpyL&OM(u7;R0LZX!Dzt1K+jZ;KR+wNh2$|&D zKWuSt?ds~voVknH$_wCht!eHsTf|{YrwoX3fOH&@^;ukNLqzHR$CS_tB_m^cxx$cJ z>mVl!misdnZYkTNAD#NhB&uhV4uw`k`cGnT6v)t}ZxtgAd>}p2IC2V$g&y2pat+8A z<@lhg4%zFNf<JThBrzr8FT4$m*^;DVVNvA$@xRobDj6#B!^yEk9ncvPB<Zp!4D570 zseS4O__6gRF(S><K3(9M$*7{6#-vzcU$H-Vy0_4fV%OAj^r*ag42Q(xwq`H91lK=t zcJpc~1ag`c*ZB39L>6~@<bnI<2XaY%MwZ_sTC%a~r9-9iy^*ajyk@b~!y1RY5e1^4 z@dnV1s4SS1C@O`_N*dW{Hp{^3V5;ov>hKm8Qr}(;!=|yMD(c!?t#+Xo!AYvjc?5WJ z_S&^;#=)a*KyCK*dPVbet#K8OwVnFGU3x#y2kYYX?UNfm*odlw0;NT1j**lG1IV_9 zyt*U*=BEf4)=VSp*^uNvwI^-_c%%<f0F9(b)#?BYdmuBf=!A9~#7)$PheqvA9XS&& zNi0i=frzue49|`1E(~iLFscVC;viz=i*wMQLO++G`S(e`^V+}k1NDrZ?#Zu*;fSEI zhYrbKvl^Iqh$HCTN70O6fqtR@)3{WlrZg;P5o=)P&g{iMZWHKIAfsG7=`LJ(s$9fn zj@7_$CDXRL4H5h|`Q#y?-D)t<(TV-{G#R0eo4L~Q<m!A4z=LhG*-BBgOb34)))1<1 zpC-%=)$Eo_s|yceA*i-I?W>e#QILS6&v{XdXLP{QceOu9N6w-2EyI!HgTKKr?3O#} zEHLo=k030gy+O&h0ijJ@i2RV(R4X4N$h;&1s06>v@wlnqa!v-O5j4LP2L&EDFiNXV z)w0^y`!4p3^mvlv0W9EbY}-Uy-yLIbAwnn}#VOoZqv)FGqXko7=iu({?p=*cl|?DD zbXmeW2-od}J)}3zJq7E#fQ`^T!?x7z^ox%TR^_IPDv)iAH`fp&kIG2cr1I1rDnd1v z0TV=b_W-(?v3=;}`wj+ZB=uTg0tN*WkX-5z(QSq#*)h+QmhQrwYSO1X+ZbqMnydpp z55s{|48RhYU|rh(f#>Hgir`&I{rAp{lZAAH%^^kuVN3m;FaLKhK{N7rw+GJ>57bY` zohgdjdOirue-}E-b1`6({kgKKY8h;{6sW;z_xNuQM^uDzKaMPq#alHPt+ye&_ec9H zC5fj4WV`8GSqJRMq^|-v;TWqk6tf~r1_aa82_#fg6i0d-9T`%i2d3Hi=GQGmV5LW2 zzKIzn)Uz~z^acgPD8Xohg{u(;Q*!@dS$?oGHXvzWUeTp%9S8I9V#PK-fTifDY8H(; zJ>M+$NpsC2viyYWi)$|m8T(&-QWZR`Fre+LVlFTVNK)#q6f;cC+=U)b8PJHnGfLXC zw(i;k$6$-E8azi-ip(Ner`(uz0QWYS@GS2_0>@JJ!tvlXC6%aa?DYw}JoGRWB#S?t z&k?f<LT2xQtPrCE+Toa&6s*eGa0ETWvDNSSmY;UVWt<^<D$qKWk)0*^{=c>ml>8j- zm9eHX$R1plf@=d7FIk=&*8Z`V({OZa$U`4s)vl%2NTaj;B}<Rgt!hyjrWe7?RZz7o zO3z10BmA5t4e+QsGhdC`U0VCX^y;T5scZ1WX&0aDNi5<&9|a_yGOVyWW=pr_X8}zI zWC`wC$nTu|ev3vXv{4*+gCYu)jXZhs1kL3HB=*;(PWLzhjO}siBwn9y@Q{YGU%qDC zwg8YmN@D_J8?KE)G)0=LmBKX%N4~N!ofrQ#7X%=a@+x8gAU(>v4RfWA(v_DkaS+o# zxF;mDxTfb(j5L-t6u4>}#H}s}1=q;MOxu#OCe{tT{sucfpOCVTW|~*h3lFZnzEi&) z$TKiS#A2h?w=(+yn4#H3<Ooaz0Q?n50-W~q2O+U7$!82{O(E&C4O}vFeHuY4o}v`q zw;BGp1NKZw)t1#x@ok?JP*6fToi0$Djh&bQzKu{K<AE3k3Yi<ObwdhSTxHEIT(BY| zn(Wt!SFK6g<SM((OwmtIbfkl;fQ<>WRAwMTF_6Ii`31YxOooh)(l^}fX$k80-F)l? z4ApBu*kbS48#4kj4;2&6FL6i>uv*o63?~6sh%&{ZKzImbtgoz1x}e*;EseRWi`3Pb zVj5=K?MIFtgFxETXtkc1t823tRu^ZCLnr+qJYH2+iN|pXkR$tWjnX(bnpgF{C_8Cy zOhX;W=aQkClSD_l%@SG^c0KNUW?5U<pws$AWm7shq`&L&*PjnLU;iNayVLKuzW>!P z8;-oU|IpvBZrrqI(CPl$FW(N~Efe0Z!&^;wD~xa5;jKmff656X_IKVnn{kN5Cu+fe QuudnwjQoOq<ihv=2Ys7}>;M1& literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-17-ref.html.6a121adf1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-17-ref.html.6a121adf1.png new file mode 100644 index 0000000000000000000000000000000000000000..b70bc0d6523c5adf1a973c2e9a5d095e4d6b0a8c GIT binary patch literal 5855 zcmeHLi&s+F{<rJgtm&$^TIHmvx7DwjJ3YMp8nqx~s+pR}(z<!@fy#6(O%cs8L=>^c zDW@r=>7k{embaV^A{iknf<2{|Xeyw{L-T=&fJTUbz;B=VFMeyqT4%A&;q1M?ug_y~ z{!qj|&(D27cXM;|{Nej=|Lx|s;DnpoUrsMt2%a?hzN-UY3$p*cZ?{{6Wc7P+^I7)p zAEFk44{g!0U)|hRp8w(7T~WC=^~(G=a<aoUvmv+QpO=gO*xdhl$>(?1-Ay`L>ebPG zD0KvGd2!_KwU#d#$G!@9dw_JKc;Ih0UoTsn8t}ccdriRYZ%JSMb}RhKv);e0@E+3H ziv?lWpdE`A%}($KNrI*>ZGlk3>C!n`)JhJ+Xt6(tCbCz!W#zf}8{8hj>?1YCc9*ka zC-tYz1QCs=+iC7QLRM3?(sq}_XxX_#t-8D}Jnh5iOYl6BbakdKf*IFa7peI&iSw#M z9XLnm$+vxcFJ(n@g6T|(nhHU|+2e0eqnGwfn&kw7{`8T$2zC+!)0F(?`b^dO9I-#8 z&!3{4sp^Qiwqhf$&ofkc$QwI9y)ZC;Puw7NOWeahympLU%A0=d5&CL%YisLdO_7(P zX2j4a4nR(UoXa%V?d~pOh>EIZ>g4TCa?0bNLhI|ew%n|P6<^=uruW}JO2iBuE%wI+ zy5_{XjRfnf{EGf8v41oK@ilaNzOR;<n6KyA6mk)6LpANEgEzNl6_pCttb3Dv<zUD? zs%c!hzB2v6;||)~a4}Kch9pH&JhVObrr&?uan7YlZ|#jhOC!+wzSw_(Y+=~uw6wb# z{P;3Bnv;krBMHqi9yp0@w&{p_N_ip<f6e&)Tb3kJ2fkcBuhfR|A9uiT>v#nbhSR!h zb_E}Qb(P}z@tm@{#Jug7lH<ZH%bfbmLRJuc=7Ll}F)Ba>Cv7^8k!EZV7~nN~&h-^o zRPxx66v<5mkt2jDMs@Iyk4~e;uP04ji%yIk>klv6bPst?*^*5(C|iP$y|{z|Jw$7& zcVs8~pM137&waDIF!?lF`&b!1?Zu@v<bmVm<}Wh&svH(lI&$y9P$@Vs4uX8*Pww(u z^}qkkXdXTXNk*CmGg!#x2SZg+1jl4F+MeffSmX&56_h{KxX)}*333KW{0dBdPKsdF zw&cD`Yxdw!#jRao_P$>hFGqD%=e|2XIXMZ73hc6w!So{u2?^$B%Oj&8cJEFB#-hwN zGmW%5+pHdsHZ&(OPd-`9OCNX;pxYF4zebD_2Nu$VAA7=yt0~InEVIy9z%jM4Sgg`8 z+eGW=<?zWX)$_-YAVl+HLGoHgSZhZXtp06y^yL-eRDUCRenu>BqndC66}KyFkXo3d z%n}E<zLS#<ik}Q>^B(`zr|<LvcQ{icGIOgP^<|s!IPvQ210-%|h?JkHLB0ERtt?mJ z<s>T57XvV@p1xmCFfZVVY-YA(2OEy74l7XnzAs>)6gByWtS+(|2kCDW2c)riAw*d{ zxRIcZ>1^I?_a1K^oc8pbC$T&+FBNT@sKrA(Bi$lk(Ms-wmp6gf1ZJI_gIvsl*#Uir zUnW==LNO?D5UD&NC$;Y8Py79X)1p{#&hS~ECRtp*sWWu&#qVpuMjNVfZ}VgdaIgY~ zHv?E<rXRVJ-aR|dh7~@d>9aYr11Wq2<gA-wfK<(Z{HYhZo(cK_GH^K^cqEJA;y`u8 zj<Kr}r%Q`<rGmr3pn?o$#DT`G>PKhX2X}e+_r(!O)(>M$hhX~2lEKtErVKDb??SKD z{kZy=#t2iJNrFb3l*h{x6fZYaEB7)2R50wHH*t$;F548_W3w1FHjrZ}=yXp1(PB`O zq>hWJ8^PkSesCgfZaU<r(H9catv$s}VFk8_PKTX;B?5KN6bGh;<zG7pS{VoNhz%`w zH^)BsF9e}=^<10)Tf2{uv3&i36Yd^f>5%m9fg3%sMpfxqeis=a4ygp`*{@5Pq=lZo zeE`Z2n~WlR6P#AZ%uxs=f$B`%iirzBqGs+CQg%qXrG5T`O!4ZBQ_7)BK}EqJayzBb z1eFBqnh%ahpPL>gfeTaS8L+b%xi%-XKIF~fE(uy&L3GiH)H|RZSeLacB6s}F4Q>yO zK(~&8T8>_cOdQcsJZy6#4C5xj#O({<^~KBApDIhBj$H%Y0tm(vi9QqghL$=AvUg_E zd#z5uwF41Y$H&<zZdU`qOi{>*?wZ-2@E~$oAa(S2unZvx(3$zU8F1t7y|X)3dpUed zDkM$rsuC(@qE}47X$;J4OiC<e3JE1xV(w33=x1K1kG}YQqd!q)H_Z-#+)QBk8IOSe z*rpKiZcDsRP@*!9sOQNOb@RIm#&4v=5!u3-4nKCG%Skc{Er}z_;r64#h%5vdrra)! zr5=V0=SKor@s^}Odkh3Qri-!lH@2n7dhUelDmC-TC4pEvGto3wPH6B5q}>mjIUm{g z<m?inbKXh@JId<9%=che0=X8rFYH?p9e)@I5C;IFZtuk}4)0r?^$cZ9;)8VgrrhbF z#}H(`6I5`IJJnn5JOx2QFqA~hID%(*vYsn}Sq-i5%Amw}06Z2lMooaI%@x|i`}`q1 zi3e$^mf&CP#`bm?&i-u_q{*_HwDNhY2`vvW2rX)I9{cFib7Xo0ByIV}{3Cbo`kczK z*X8o7|2&cgxJ3%^4)mZSRCJiJ^TYR@)!4#2X7R;Uz0@{+)1d7B(Why{PtHQTS@CWu z9?N3TM%O|R&pKWyPr=#;e8XtD`uk74z@ztfD`g1xf&wa0LXT;B|EYoyMTS6+VhCJ~ zJn8(dcOtt1pHf9lMr3bqi7x~}N#asASAt@RpTDG-UaVzAN%7j;Xz~&bQci@{VYtXU zta<&xiYO#HW>&{4slZ}$VI(qqri!X8*qVA_bJN5od^LJCg2jrhNqa$eL>(o5Ks=uo zdvlINV#q=xebx4pKQgqc1^UuBi#_zzMEA9(1UcRdJ3s7g$yFEZYy%mwRLPQ?J9x)l zUamlq(+V6q0tbeH-add~Cm4ZKk`{{2>jdAsU}BG%Q|Ms9GJVWMSeVA3$F{?Hkz$YZ z1AdMdQ$Y0(UjNsvD(d)+ift(oNH}+a30b?ZG`=n?%=VUngQVv-RK-LBH0&p2x}K4B zY<`Y&scT+~)tp3gz0}k*2By!*>|kbl-p9B5bf9c3pnLq4TjFE#jY0}$wm(T{&Qr}y z$_f}+fcjXI?9+e@3@n;a0;+G$>m<+EH7*ehgMoV+E5q;s&(|U3CQD+!Zgg|K4#}%A zHjk9rz{o{aW{Rl#7Ht*TY1PUZ^pCZ14^O-mRIQ8w21T3IPIthBcDflD7alTs5)+_$ zS!bvwK1ek89|%HNL*&qB$^nb(UTx&mBkGCJ%&I#3k!eH{md?(ysW`|n7F76t6Hr@m z+RMx9nA#;iTk3%mXiB8bJXZj&odjTJhX)BC!cI-<&t#?+jPl^f@+q=B0kjfrY|Dm( zD?0wVWM!05EkqktI6Mgo)zGpr&bbktKtLP=hq755QENx3Kc1V?)qr)*FqoW;4b8V< zcwE91$VS-u#_7cEW4nGX378NVMS@vPp$i3M>`%Xe7$%jG9=)g7i%kNf#~VaAgLmTX zZItQFHnjH3uI(ciD}sqQ|J<Q7h`mBQXvr5_Z___qsXi6)-A$nRX26ZI?|c%XG&UI@ zrL7TQ4`;NDmGSHbfgEgxFDKBcT3M6l*tTlx0SE#<u5pWK?p;L)DGqTs7#VfuxeucF zQmORM<T|S`0$`~KQIHhqnH9jkK9-vXJOmN|%JH#R2fD~wx_0j=LsHn}AGK-kd+sm^ z@d)1>uH7qb&Kj94303#k7$2h@)~><^&^ugow0yrtC#rhuSe#E{cQ)P^(ZL+ZywFqK z)=IZI4pj?1byzfoJWAG0K;f8%%M_s3PZn=@D2o#S<CUj;-Uz&nL~1u6hmbRh@J!H_ zQw>i7>2t%(F~oZLeSP9kN<9)+#*6^)2vCS^lI^uzCxYglgayEPB><N|02@o8X!ZxQ z!1%6NTa}M`odK*U5E(H1#!WiW+7ZM=ZV~|Fi5j7Fr3^hfqaP~rHq@I2HO?k6s>s9J zu+^ZDaP1AnueS*5J^e!Jx2Y%I)(-YYVpU11SZ=a823ds}7k&lkAhULF$>h#J)TtAG z+mhexAHhKa(3|KT9f)860u?_;m-q(5=J&mIh(<6SpnLeddalc9=c_0hq4+_t{}NWE z?$@Pjaji*7Z?118O?2O4YOkz6m<CCa*dd9JI!G9oU+ARNW{RxIh{6N+DNxW%o$@@v z@z-V2<Vq5*GDBg?8waS4#%Ht~FBozUj4@i<Y?CY=I56X3`~T$I-q-1h0_TVJ``l3* ztlDZ$C-9xue7Lfds-U*%-K56nD~U8hV&3~Zc@DEYKYq$Vm`!Ayg<?fC>wfX-HjSG9 zjr;u!CE>Hb@2^>~d-2!be0gT!qIKPFw?6az$JKA#e_3|o^r63ae){oK5k5`ACpdiK pgiplyNgX~(<o^SoV8aTB2F|R{Dq05qq;~t^yNGWacK`VE{{cBDNE`qF literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-17.html.1578222c1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-17.html.1578222c1.png new file mode 100644 index 0000000000000000000000000000000000000000..b70bc0d6523c5adf1a973c2e9a5d095e4d6b0a8c GIT binary patch literal 5855 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-18-ref.html.2b364bdd1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-18-ref.html.2b364bdd1.png new file mode 100644 index 0000000000000000000000000000000000000000..251d4c98937ba2c66f2d3560c487223846d53699 GIT binary patch literal 5566 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 QuudnwjQoOq<ihv=2Ys7}>;M1& literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-18.html.74b52e161.png b/integration_tests/snapshots/css/css-variables/variable-declaration-18.html.74b52e161.png new file mode 100644 index 0000000000000000000000000000000000000000..b70bc0d6523c5adf1a973c2e9a5d095e4d6b0a8c GIT binary patch literal 5855 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-19.html.66f8ee771.png b/integration_tests/snapshots/css/css-variables/variable-declaration-19.html.66f8ee771.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-20.html.2e850f5e1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-20.html.2e850f5e1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png b/integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-24.html.1d9a68901.png b/integration_tests/snapshots/css/css-variables/variable-declaration-24.html.1d9a68901.png new file mode 100644 index 0000000000000000000000000000000000000000..d3699942bc8b1c6fdd40ddbc98bd529defb48921 GIT binary patch literal 5175 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-25.html.499f04671.png b/integration_tests/snapshots/css/css-variables/variable-declaration-25.html.499f04671.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-26.html.a69cc9571.png b/integration_tests/snapshots/css/css-variables/variable-declaration-26.html.a69cc9571.png new file mode 100644 index 0000000000000000000000000000000000000000..d3699942bc8b1c6fdd40ddbc98bd529defb48921 GIT binary patch literal 5175 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-29.html.ba373c8d1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-29.html.ba373c8d1.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-30.html.8e9b99541.png b/integration_tests/snapshots/css/css-variables/variable-declaration-30.html.8e9b99541.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 zcmeHL`&&}k{<f)Xy6KsjGc~2@G}_hFQj-vs>8h6HG|gzHLi0eP;blhhhFofDvW}&R zC(S^uG(FuUQSk<%W2uNjV!2fmDiu^fL{LNozMH=1uQ*TqvY)-zdU)68vp(<pe%8AA z)2V><Yc{X3va(tq6nNxkE30KOR#snJ{?Z!!QnmSG1^8Ho`#Io{RVCkk5`0;KI~4TG zm*9`^CHjVy)kav*k%Paa(Uc<@cOsLEbZUWf-rm)%E57#l4psl%ituSh=h4v6pZ@W1 zL)gEv&i}OLcxeCb6aQGL>s=lG73%w)f8$FdXMKNobZzC$A2+Q2bLV@v&O`qUx)ZVY zj!Q#sA@^|!{O3N8y~|hMAYDMx$rOJLwU47u7BumpBSke{n{OYp+KFjF;=i!co_w(E zUth;ue&oITO%WF6sWipvM?Ol}ih=!dfHf7Q7J84XtxWh@K0YoqKh;&0+H!Ezri=3K zMA9soM%$$-q7=<bo=B?BsP5kn8CiNVZ0Z%^-qc@3Wa%kr@oc)?T$&Ym<Xi|#`l=fV z<w%x6iqlZ-iTg>VDc+=K>Om}(?UB^k8Rk{^?T8D*_*x{ZRxQQxecnG0tI08S6VTDw z#t&5ulVwzfWWZ=DGnmdS7DSl0Nfzjd=-$CBlkm(k)8FSXM099lf6BHXe;f~!)Hh5` z(mhG7cv0)hy*^?3?X{!#MN9yD?mzZ({iO&sx)DgmsHa|sGw@s&aR50##jS@?d0{eA zq~9UYl?a#aYO2%bK612Pm%p%9cqLUR#LXEgi4wmE+Yz52hI}H5D!Oj7dC1-uqo&#a zu3r{gI6%q2>6$KjoXBHqRPiL^(^d52hJVi^fMXn-Ds?thVr*J>-)pDqHxZ1{ZLoQ0 zuAGz_mC}fAn_LVdsqfZ=#0m;M1;LE1l@$o`#bYNO?t=ffY4-J2XzXH8cy08rl(9cc zkay;HDI4Z`BdCT5ajh>5!9ZoRW;+RsaCp%fI(&an(pz1Q1vc2M)cVV+bPyf?8g|Fn zg5#Ewtw?*y!945nkuVla6UG+=^@U8$;fD2NMLnvF^R3HcON7Oa@TF0N0fv=^vq{p* z<S3}FLKY41Qcm(-<%6sL546tMT#YOrfx89?{U7_G-0U?J4CGm3*!VT<shCRMDWqf< zgwL)5?iEBdnSvXMKQq~BqMG11?gr%056F7uTBvjil{8Uvn2N$XsGd2ncEgCo3d;l` z|8Y2QwqPwe{kQhW1Lb;82QaPa=t(E`^ym&8iBcD}$V6Zlm-O%|#Z61LWC_OM6aK3A zb5E1cSd%pzPGM1il9qiHHk<h^xwFn>h=9&^IcYLIA@;wyhqIm6hPuW$<eEQnkz+NE zCHaS$aOVQHnejZlPI{D6=9e;jv4i$fv|7LDhZH1~rt%)b?C%rX6?x83Q$Pns>g4J9 zTh8i1vci-OCaXaaR&W6<pa%6TJ6wA+=F{GMQwiOgL!MRF)H27vfslT{6?FG}8-UE+ z_N-v6b)~Jeya6&kqt3(_z7k=EXZ|4aQG5!tK{aXc0C@m4v{ZYJe%MqvH}L!x)yFOV zMZA8DRMOp+<;b7oH2G(mIc?Qro+mtFS{^({RQrJ8bpHM8+6<+NJ02pf!Z>2Z#RS<H zVN!<df~yPyz4l58|B-CNg+TcoXYRfx`@GlBHe#kJ4bb$QygX;>`r+r24wO)t_X%8n zxtejY=V>GXcn$rS5ilms8KJV7gV@skN_Ux2!6JNMx}$UE{gM{_D=^_QMsPDO$p=W8 zKUY`WVF35O6CqOE6>#L5P2Oo|oAE#xuD`pQVIDBz_y=WUgM19mv_*yyBq|0Y=}5Ry z&N6rDWVhSI_sB#YC%XCybsU>Yb`2n?ZY(Wokr)t3$gYH0V$`S7Bm}Wv?a~v1L7b?= zi(BQPqv`&>#<ts+%Br4bywgA+(mlm}fdQMFqL>k=k;UX5WruO5{+N&OY;WzO-WadV z1!7#R)0Ws_v(|X~SZVJ8u&TX<x$R*3?CemzxU9{Sfupntery7@37r5=q@@^R+3m#y zC6_&&(cb218P9_d8EVZAk{ATSbRQ^%nm~qMEOWXYp2Np;q3dAev#0@m+vaD$^MnLD z<K13xdHXV26;kGE;8YAaPqJs}WGT3s>EhH>5F?VL8fVX^Dr|G&Yl?v(mT^9@nZ0lR zQI%0_-USp10|OR~_)>L82TE}iWj7*kq>5C8k9-8Swh9XXn{O@6b{+$UDy7W9wb2kT znEEH9_eCJ5O6fk0fX>z|oI#9&GC$h2sxl5lw{vKIXi0@*{nT=BWom6#1r;Tw-;;ph zlq_&};EgnQf|xk7%8|g;x5-~`XWK58jf8Yv&WDpu2(mJbMQ$n|JmAa)<pkYM+eDvO zR2|N8I8ITRrAAGsFd#{AMrRoaXQw1n#tMHGI0t19sBhRCARW?=O>TTR!R+XP*Yp<7 zig!^7<RFWf-v{33p#|~t!Sc)f3dWpUPrNFv4@PlzX$*}MHm7c3W2LSo1r}3TN)o&G zb-I|#jNA+w?cj3j{fUk<(&a**2m5W^()M!O=_8sK6NucqBQCgsg*K10_wS|iQB3h; zNp&_(oPJ890==#JIX2uzJpJAmRFGkvmRP9o?3RYV%HtIWQ@RlcU?|C6Z@HaZ)AHvk z4|!F1!{|+z1t7$Y%m7Tt1y%+xG%-kM*4QB2y(ho!Xj!&AV}xXKKZ(pXP)BE5JZ;fa zQD>;Y2DrIWfce<^4P?>3<ecdj%Gkf<rzUIJpcgr1-ikvw1X9-8u)hxzH2Y+Qx$}=^ zM!j`m(2pSvn>=Cy9i5QkzcQoljYsi3=PI8R;(@p8?0olxk!HHWv>&JJ>e5EDnCm5R zX_}+$C&QZ@{M_GU$JnVK(c}Ft$@dTvK4dZBWFo$@Fr<lmPJ_o&YHVV9W_qq;w9?W% zXTfvAc3xxth3>yl_}j7AcwUMVQ(kM{=&!P7YMa;7!>Iq9IJy79g&+Cwu_c1~j=Qv3 zuNzmGePN$OU?$$Nds^9)_|_j!dRXD$y}v_|6nw1acIV61wnh64X^$P^7kelgy<P0R zuR=*Y$3Y=o+*6PEg7zK^Pr!qwE9(zP>;cQ0LSAZxNB5WqwYCe|xrBmWv7r|TqU)Yi zOH2{W_(JD&re^SVBWL4O`a}J482S5+TT)J2pk|mKVuMc5|A<&Wxcl&`^*u^Qwx83` zF*I*$#2R;2OUZbt*fo|bP8^2G94;kL(4gWGv*pmo)8~M9!~HlPgW)kpFaWuwgzW7j z!~6jMOYwZaiQ&kMrP}cR+C7dXYPA%|a9eqaE+@^we{#fU{~)6;j5fLTL;7Xe9jXc3 z()%k{aSID=nh$>dv|2gfeRaT706v=)MAQ3LM{T3=&7(|V1G74PS2PNk){9>b+6nuI z@~JZMu>8zJTX)id9Dl<Ek7NeZW*Hb<i@`K~CAx4#bYtUdXi$oM18ReR-Cj#nZ{M7z z`|wYMx~q4EB2p23P7^Blf)Vtr?V{`f?+{n=?mQKZHkJdFBV)2!Bg&G0#8ws)lf`Uf zQ(tp>#{8!0kQ6(1gj<7*@;vdm0M^0wTz<}aj9jcQ0R!ZZ>4GgDG5%_DM>Ob2lLzXZ zu3E3_y5_eXJ!I_j01Xd%Df2KD2;rpY07i3_KP+cRf3!kKZ?}n<zT>Q)Ekg3^Ka6_B z&Ag1%^0GY0<4ZL*3!@$(pgp7vBxT02_5JBFy@x;9C0K!;toSsFmkc^NA2g^tX^=*V znx9Et@<Hxdq@Qq!o>PvE598}6uVY&)R5&~ZUDw;hRt(LJm(1%cg4x)MVzn}LY#}N~ zFk3mUpBh1qOw=A2O%!zg#bNDL={YKH!2D96w_Sa>19Cns+Rw)YLYTPj%q=vO6c*-m zcZ)c4^MvUKf(u|`!?nYYy6<^9A=3q6v;Db=BNGP0G@U~|QwiIanFD7@Qp;PAr6<>1 zA_KGxrY_P&(LmG;Q*p12VO|}|+8%_y|Fi_pMK%YK;k#r@TAF?OcrFxeIGn`07LjCJ z4ag1ygq;PZDPiQ|vlCPl+*D_meJ8}Ajs^S7LTom>JbeY8YjERgC8q@Y!W{EfmLTp} zIu6}!GBUcmFn1F!dW@Gzf?^!g=Ch{B=Xos!Y^E4EXAuO+fmu7K@p>O_mK>LsH4JV- z(o$uIEP$gwjT_#ki=?FUWZMRRe@lE%we8kt(j>lJzR9XHnnd$lB2<NuZ3~(<3=ju# zR6hf)?R6%Swu^I*f+DBI;cUz#Ee9ikC`I;~BrH&1>F}>Cx0W{Ph>((oanDT<LL}dR zMnRB?Ra*Rqmz>%IDwTj&T_ljs8r%e~9M0ngqU<ybVm68ESdfj5q(A&Kco<EUoqs=e z3$mVLO3*D0;#`S6J^^o7&ER2#u7nS8qK)_WM6tahYz7$s%m#5BXsq-V>EoE+v5wMh zyDq(ucd^Db9xpGPZ;#toE;!4gfl^-$wk)^oJs3c)vZU+&9Cr2<V~0msa^R;a`vfo2 z-CUZ&qUAkx(;6*&@b|7}u!5a3vlXeqIXepAgOPbA)=+Im^gGYN=q_(4URH`z{sAPs z)mf+lqFGuH(K=DN#PiC^XB*J5G9d`&QF=Xls4RW%EDNu8G3~H^@D%WUvy|MZtP(Ns z%Bgi*yu2X%hJAh0CHZ<68K$3GoivlO%KW%umDz<=4m{Ay-~AC0K$>3=+^@@A?~w13 z-F%4lLD2b?7jX(Ki;e=nVp+d$K~>^+B7(twM&|U!>ZsnGZuRMmwqo;xomZ@P9{=x= z?epvBY<w<;|G&QY%!SWf_{@dRTmWw}KTE;?wiKA)?aOX027crL;I+C{(9u&zDi59i G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-31.html.d3c61d7c1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-31.html.d3c61d7c1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-32.html.195b62b21.png b/integration_tests/snapshots/css/css-variables/variable-declaration-32.html.195b62b21.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-33.html.cef687511.png b/integration_tests/snapshots/css/css-variables/variable-declaration-33.html.cef687511.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png b/integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png b/integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png b/integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png b/integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png new file mode 100644 index 0000000000000000000000000000000000000000..d3699942bc8b1c6fdd40ddbc98bd529defb48921 GIT binary patch literal 5175 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-38.html.549f86881.png b/integration_tests/snapshots/css/css-variables/variable-declaration-38.html.549f86881.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-39.html.237efc0f1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-39.html.237efc0f1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-40.html.1e4067991.png b/integration_tests/snapshots/css/css-variables/variable-declaration-40.html.1e4067991.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 G^Zx*_-wfXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-41.html.dc0de31e1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-41.html.dc0de31e1.png new file mode 100644 index 0000000000000000000000000000000000000000..d9e94fb5bf40f93a0f5d37965cb3c3c0adf7d001 GIT binary patch literal 3202 zcmeHKZBLq47=Ea&I#;HhjUTKpTb5;ON@N==Rwt!%I%k?{+n6wGna(ys>I><fR#Xs> zZHhB$_L6oKh1tfsMXNPd9D=BwuB{-b%nAxTY7vGCA|Rk3?D0?R6X(mFbLZUWoa@SU z?klJ9Vonw^A~pg50A%(T8J7V7K?Z<Btng4c(;vIq1qTFpIV&CLRm7R$$zd=(J1-m_ z^ziE(062=u&iFL1s?DOsbK-YmSu2!l=Z@XyJpO^)@%>w&yyFr5j<UDep%dvUO=t9J z)Dq`ca#S<+<b`V;O`#|hJ7)ItW3uJMi>D@!$FLhYm+lWmttB<q^jX)WJxWzO7n0Le zlA{eNB(q7t^j8DVHLgGcz<NEJ%zD2rl=CK;^}7XwNo4xA+fr}eND>)-(u~mD^+!mG zHm)0DGApgx6aw+~Jd?h<H5y>(t4-}(uE_k5XS`S1t9cwSy!hFnqe9c=Lpm3A3sH1_ z5sLv0&wtEE#eiPRkW#!Q6@NWW64YXwCW~7V@&3Yg&qm)Uf3o;PIFUfNjXPX;zvmUG z#JPRVvk0LOS$GqLMl0WGP5X$qYt-58cFFeNCmco|rD8D!;eQQ#c#lisbKQCx<tv(H z5RbF1-7VyIc6R1pxpGgIBrdZ`z1H33ZzB~2ZrfjxN@TM31Fw5a0Gbr#dIM0Ptv|P= z6H_ZhYj-rG%zzJG!>n3vPS&46OKeXi_Nh|?LLrtiy6@7$U`?5x)h09_22h#9PfJ0M zIOmKIVk~BGAHrX~HJb@~Y%BTs`P>IttqN(am*E~GiYupkiDHIV^5n_KrKP3d`age& ztiv1F9^C**xv3waCDb}+MezMibLY`I!xEsbQP8F<`oEMDsk+nM-QAAqWXNZ&6EOD7 zad-y3CH@oavzB-sTIwk-DN*ooH2oqBH-+x8(Rbzv2W(%6ODx)0WOOv=;`g1KS`o;n zSw$N7xcXW+q1uG;zFb%!m6X8yU>vfIDyHFO0i``Gp{nSOgtV&hp;SQqFB_jH+BNb> zkhIn@5uNLEUH5el>1#^iO4C?fh~rf&USU>snVe>o1LnkUo`h@990;Y0UvmR|7^Z&8 zaNqr|73WgR<voucm8<=`CV^5epPHH~gZ#1(j|emwcNPi*swN_He`jELI8})c_~@RM z24*#x#biTB=RBbhmwR^CKzGh&oe=i*^vLD%j?Ws56OGXWQt4SYtToBrdUs&<2!TK# z!EhWe-V^wm`w>EEIR>*NB>t(2#5reR1yR(Mmca!@T}aqLG59Q=D&KcQc!=S(#Nle3 zY?Ei_q?bUrdUG-^^%faZ5~iNJ<6lp732?MZsn0GG!AdaMWoT$q1o*f8-~5K~Kz8Z_ z;L;&v)Vnjm5Tqceg5X04_JUwL3_3#44gVK63>?z`;*UVh$k*V11dyGXlhK=A#QqP1 CRQ9a^ literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-variables/css-variables.ts b/integration_tests/specs/css/css-variables/css-variables.ts index 0121972b6d..7646317419 100644 --- a/integration_tests/specs/css/css-variables/css-variables.ts +++ b/integration_tests/specs/css/css-variables/css-variables.ts @@ -1,33 +1,40 @@ describe('CSS Variables', () => { // https://github.com/web-platform-tests/wpt/blob/master/css/css-variables/css-variable-change-style-001.html - it('change-style-001', async () => { - - document.body.appendChild(createStyle(` + it('change-style-001', async (done) => { + document.body.appendChild( + createStyle(` .outer { --x: red; --y: green; --z: 28px; } - `)); - document.head.appendChild(createStyle(` + `) + ); + document.head.appendChild( + createStyle(` .inner { font-size: var(--z); } - `)); + `) + ); document.body.appendChild( - <div class="outer"> - <div class="inbetween"> - <div class="inner">FontSize should be 28px.</div> + <div class='outer'> + <div class='inbetween'> + <div class='inner'>FontSize should be 28px.</div> </div> </div> ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - it('change-style-002', async () => { - document.head.appendChild(createStyle(` + it('change-style-002', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: red; @@ -35,43 +42,52 @@ describe('CSS Variables', () => { --z: 28px; font-size: var(--z); } - `)); + `) + ); document.body.appendChild( - <div class="outer"> - <div class="inbetween"> - <div class="inner">FontSize should be 28px.</div> + <div class='outer'> + <div class='inbetween'> + <div class='inner'>FontSize should be 28px.</div> </div> </div> ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - - it('variable resolve color', async () => { - document.head.appendChild(createStyle(` + it('variable resolve color', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: red; --y: green; --z: 28px; background-color: var(--x); } - `)); + `) + ); document.body.appendChild( - <div class="outer"> - <div class="inbetween"> - <div class="inner">Background should be red.</div> + <div class='outer'> + <div class='inbetween'> + <div class='inner'>Background should be red.</div> </div> </div> ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - it('nested variables', async () => { - document.head.appendChild(createStyle(` + it('nested variables', async (done) => { + document.head.appendChild( + createStyle(` .inner { color: var(--x); } @@ -79,43 +95,53 @@ describe('CSS Variables', () => { --y: red; --x: var(--y); } - `)); + `) + ); document.body.appendChild( - <div class="outer"> - <div class="inbetween"> - <div class="inner">Color should be red.</div> + <div class='outer'> + <div class='inbetween'> + <div class='inner'>Color should be red.</div> </div> </div> ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); describe('Shorthand CSS properties', () => { - it('background', async () => { - document.head.appendChild(createStyle(` + it('background', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: red; --y: green; --z: 28px; background: var(--y); } - `)); + `) + ); document.body.appendChild( - <div class="outer"> - <div class="inbetween"> - <div class="inner">Background should be green.</div> + <div class='outer'> + <div class='inbetween'> + <div class='inner'>Background should be green.</div> </div> </div> ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - it('margin', async () => { - document.head.appendChild(createStyle(` + it('margin', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: red; --y: green; @@ -123,21 +149,26 @@ describe('CSS Variables', () => { margin: var(--z); background: red; } - `)); + `) + ); document.body.appendChild( - <div class="outer"> - <div class="inbetween"> - <div class="inner">Background should be red with 28px margin.</div> + <div class='outer'> + <div class='inbetween'> + <div class='inner'>Background should be red with 28px margin.</div> </div> </div> ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - it('padding', async () => { - document.head.appendChild(createStyle(` + it('padding', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: red; --y: green; @@ -145,21 +176,26 @@ describe('CSS Variables', () => { padding: var(--z); background: red; } - `)); + `) + ); document.body.appendChild( - <div class="outer"> - <div class="inbetween"> - <div class="inner">Background should be red with 28px padding.</div> + <div class='outer'> + <div class='inbetween'> + <div class='inner'>Background should be red with 28px padding.</div> </div> </div> ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - it('border', async () => { - document.head.appendChild(createStyle(` + it('border', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: 4px; --y: solid; @@ -167,17 +203,23 @@ describe('CSS Variables', () => { border: var(--x) var(--y) var(--z); background: red; } - `)); + `) + ); document.body.appendChild( - <div class="outer"> - <div class="inbetween"> - <div class="inner">Background should be red with 4px green solid border.</div> + <div class='outer'> + <div class='inbetween'> + <div class='inner'> + Background should be red with 4px green solid border. + </div> </div> </div> ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); }); diff --git a/integration_tests/specs/css/css-variables/css-vars-custom-property-case-sensitive-001.html b/integration_tests/specs/css/css-variables/css-vars-custom-property-case-sensitive-001.html new file mode 100644 index 0000000000..df2b352702 --- /dev/null +++ b/integration_tests/specs/css/css-variables/css-vars-custom-property-case-sensitive-001.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<head> + <title>CSS Variables Test: custom property names are case-sensitive + + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+
+
+ + diff --git a/integration_tests/specs/css/css-variables/css-vars-custom-property-inheritance.html b/integration_tests/specs/css/css-variables/css-vars-custom-property-inheritance.html new file mode 100644 index 0000000000..bbda51d2fe --- /dev/null +++ b/integration_tests/specs/css/css-variables/css-vars-custom-property-inheritance.html @@ -0,0 +1,30 @@ + + + + + CSS Variables Test: custom properties use normal inheritance and cascade rules + + + + + + + +

Test passes if there is a filled green square and no red.

+
+
+ + diff --git a/integration_tests/specs/css/css-variables/environment.ts b/integration_tests/specs/css/css-variables/environment.ts index 8c239c479c..55e332a145 100644 --- a/integration_tests/specs/css/css-variables/environment.ts +++ b/integration_tests/specs/css/css-variables/environment.ts @@ -1,6 +1,6 @@ // https://drafts.csswg.org/css-env-1/#env-function describe('CSS Environment', () => { - it('work with safe-area-inset', async () => { + it('work with safe-area-inset', async (done) => { const container = document.createElement('div'); const paddings = [ 'env(safe-area-inset-top)', @@ -13,9 +13,10 @@ describe('CSS Environment', () => { document.body.appendChild(document.createTextNode('PASS if no red appears.')); document.body.appendChild(container); await snapshot(); + done(); }); - it('work with safe-area-inset fallback', async () => { + it('work with safe-area-inset fallback', async (done) => { const container = document.createElement('div'); // Env has value, so not fallback to other. const paddings = [ @@ -29,5 +30,6 @@ describe('CSS Environment', () => { document.body.appendChild(document.createTextNode('PASS if no red appears.')); document.body.appendChild(container); await snapshot(); + done(); }); }); diff --git a/integration_tests/specs/css/css-variables/variable-declaration-01.html b/integration_tests/specs/css/css-variables/variable-declaration-01.html new file mode 100644 index 0000000000..8c3ff421cf --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-01.html @@ -0,0 +1,17 @@ + + +CSS Test: Test declaring a variable consisting of a single token preceded by white space. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-02.html b/integration_tests/specs/css/css-variables/variable-declaration-02.html new file mode 100644 index 0000000000..4ee52b5860 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-02.html @@ -0,0 +1,17 @@ + + +CSS Test: Test declaring a variable consisting of a single token with no preceding white space. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-03.html b/integration_tests/specs/css/css-variables/variable-declaration-03.html new file mode 100644 index 0000000000..50aa2f3582 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-03.html @@ -0,0 +1,18 @@ + + +CSS Test: Test declaring a variable that references another variable. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-04.html b/integration_tests/specs/css/css-variables/variable-declaration-04.html new file mode 100644 index 0000000000..52d5d31489 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-04.html @@ -0,0 +1,18 @@ + + +CSS Test: Test declaring a variable consisting of a variable reference followed by white space. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-05.html b/integration_tests/specs/css/css-variables/variable-declaration-05.html new file mode 100644 index 0000000000..0403bcaec1 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-05.html @@ -0,0 +1,18 @@ + + +CSS Test: Test declaring a variable consisting of a variable reference that includes white space around the variable name. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-06.html b/integration_tests/specs/css/css-variables/variable-declaration-06.html new file mode 100644 index 0000000000..bccb52da55 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-06.html @@ -0,0 +1,18 @@ + + +CSS Test: Test overriding an existing variable declaration. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-07.html b/integration_tests/specs/css/css-variables/variable-declaration-07.html new file mode 100644 index 0000000000..0199c301d8 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-07.html @@ -0,0 +1,19 @@ + + +CSS Test: Test declaring a variable with valid syntax due to a variable reference having no tokens in its fallback. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-08.html b/integration_tests/specs/css/css-variables/variable-declaration-08.html new file mode 100644 index 0000000000..e3cfd9c089 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-08.html @@ -0,0 +1,19 @@ + + +CSS Test: Test declaring a variable that consists of a variable reference whose fallback is white space. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-09.html b/integration_tests/specs/css/css-variables/variable-declaration-09.html new file mode 100644 index 0000000000..c9d245888a --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-09.html @@ -0,0 +1,19 @@ + + +CSS Test: Test declaring a variable with a variable reference having only a comment in its fallback. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-10.html b/integration_tests/specs/css/css-variables/variable-declaration-10.html new file mode 100644 index 0000000000..cfcfd32eac --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-10.html @@ -0,0 +1,19 @@ + + +CSS Test: Test declaring a variable that consists of a variable reference with a fallback that includes a comment and an identifier. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-13.html b/integration_tests/specs/css/css-variables/variable-declaration-13.html new file mode 100644 index 0000000000..5c262b813f --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-13.html @@ -0,0 +1,19 @@ + + +CSS Test: Test declaring a variable with invalid syntax due to a variable reference having "!important" the top level of its fallback. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-14.html b/integration_tests/specs/css/css-variables/variable-declaration-14.html new file mode 100644 index 0000000000..153cfb9948 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-14.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable that consists of a variable reference and a following identifier with no intervening white space. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-15-ref.html b/integration_tests/specs/css/css-variables/variable-declaration-15-ref.html new file mode 100644 index 0000000000..6679d0de95 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-15-ref.html @@ -0,0 +1,15 @@ + + +CSS Reftest Reference + + + + +

This text must be in Ahem.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-15.html b/integration_tests/specs/css/css-variables/variable-declaration-15.html new file mode 100644 index 0000000000..9b0fcffc73 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-15.html @@ -0,0 +1,22 @@ + + +CSS Test: Test declaring a variable that consists of a comma-separated font family list. + + + + + + +

This text must be in Ahem.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-16-ref.html b/integration_tests/specs/css/css-variables/variable-declaration-16-ref.html new file mode 100644 index 0000000000..6679d0de95 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-16-ref.html @@ -0,0 +1,15 @@ + + +CSS Reftest Reference + + + + +

This text must be in Ahem.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-16.html b/integration_tests/specs/css/css-variables/variable-declaration-16.html new file mode 100644 index 0000000000..bb047bb53d --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-16.html @@ -0,0 +1,23 @@ + + +CSS Test: Test declaring a variable that consists of a comma-separated font family list with the first item being a variable reference. + + + + + + +

This text must be in Ahem.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-17-ref.html b/integration_tests/specs/css/css-variables/variable-declaration-17-ref.html new file mode 100644 index 0000000000..b0075fc38f --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-17-ref.html @@ -0,0 +1,15 @@ + + +CSS Reftest Reference + + + + +

This text must be in Ahem.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-17.html b/integration_tests/specs/css/css-variables/variable-declaration-17.html new file mode 100644 index 0000000000..f0e3c9c79b --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-17.html @@ -0,0 +1,23 @@ + + +CSS Test: Test declaring a variable that consists of a comma-separated font family list with the last item being a variable reference. + + + + + + +

This text must be in Ahem.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-18-ref.html b/integration_tests/specs/css/css-variables/variable-declaration-18-ref.html new file mode 100644 index 0000000000..6679d0de95 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-18-ref.html @@ -0,0 +1,15 @@ + + +CSS Reftest Reference + + + + +

This text must be in Ahem.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-18.html b/integration_tests/specs/css/css-variables/variable-declaration-18.html new file mode 100644 index 0000000000..7262e375e6 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-18.html @@ -0,0 +1,23 @@ + + +CSS Test: Test declaring a variable that consists of a comma-separated font family list with the comma coming from a variable reference. + + + + + + +

This text must be in Ahem.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-19.html b/integration_tests/specs/css/css-variables/variable-declaration-19.html new file mode 100644 index 0000000000..da0a825237 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-19.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable that consists of a function where one of the arguments is a variable reference. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-20.html b/integration_tests/specs/css/css-variables/variable-declaration-20.html new file mode 100644 index 0000000000..9bffae2d50 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-20.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable with "!important". + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-21.html b/integration_tests/specs/css/css-variables/variable-declaration-21.html new file mode 100644 index 0000000000..0a91196df0 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-21.html @@ -0,0 +1,23 @@ + + +CSS Test: Test declaring a variable that consists of a function where all of the arguments and commas are made up of variable references. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-22.html b/integration_tests/specs/css/css-variables/variable-declaration-22.html new file mode 100644 index 0000000000..a96d9cf189 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-22.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable that consists of a variable reference with a number of levels of variable reference fallbacks. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-23.html b/integration_tests/specs/css/css-variables/variable-declaration-23.html new file mode 100644 index 0000000000..b3b1d18234 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-23.html @@ -0,0 +1,22 @@ + + +CSS Test: Test declaring a variable with invalid syntax due to having two "!important" priorities. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-24.html b/integration_tests/specs/css/css-variables/variable-declaration-24.html new file mode 100644 index 0000000000..14fe47a1e5 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-24.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a variable that contains a CDO token. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-25.html b/integration_tests/specs/css/css-variables/variable-declaration-25.html new file mode 100644 index 0000000000..ad79f4ced1 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-25.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a variable that contains a CDC token. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-26.html b/integration_tests/specs/css/css-variables/variable-declaration-26.html new file mode 100644 index 0000000000..79ff10d2b6 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-26.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable that contains only a white space token. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-29.html b/integration_tests/specs/css/css-variables/variable-declaration-29.html new file mode 100644 index 0000000000..2dbe0183fe --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-29.html @@ -0,0 +1,17 @@ + + +CSS Test: Test declaring a variable with an invalid custom property name "--". + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-30.html b/integration_tests/specs/css/css-variables/variable-declaration-30.html new file mode 100644 index 0000000000..e354abb2d4 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-30.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable that contains a variable reference to itself. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-31.html b/integration_tests/specs/css/css-variables/variable-declaration-31.html new file mode 100644 index 0000000000..4a24bf2bd5 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-31.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with a digit. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-32.html b/integration_tests/specs/css/css-variables/variable-declaration-32.html new file mode 100644 index 0000000000..d11478e8f5 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-32.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with an escaped digit. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-33.html b/integration_tests/specs/css/css-variables/variable-declaration-33.html new file mode 100644 index 0000000000..521db857f5 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-33.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with an escaped letter. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-34.html b/integration_tests/specs/css/css-variables/variable-declaration-34.html new file mode 100644 index 0000000000..c6b4d42d34 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-34.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with a lone surrogate. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-35.html b/integration_tests/specs/css/css-variables/variable-declaration-35.html new file mode 100644 index 0000000000..f1289069f6 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-35.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with U+FFFD. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-36.html b/integration_tests/specs/css/css-variables/variable-declaration-36.html new file mode 100644 index 0000000000..1f984fe7a6 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-36.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with an out-of-range Unicode character escape. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-37.html b/integration_tests/specs/css/css-variables/variable-declaration-37.html new file mode 100644 index 0000000000..bd4b1c0f4b --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-37.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable consisting of a variable reference where white space surrounds the comma separating the variable name and fallback. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-38.html b/integration_tests/specs/css/css-variables/variable-declaration-38.html new file mode 100644 index 0000000000..ece8cf8ffa --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-38.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring two variables in the same declaration block that differ only in case, with lowercase first. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-39.html b/integration_tests/specs/css/css-variables/variable-declaration-39.html new file mode 100644 index 0000000000..d1caabbd6d --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-39.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring two variables in the same declaration block that differ only in case, with uppercase first. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-40.html b/integration_tests/specs/css/css-variables/variable-declaration-40.html new file mode 100644 index 0000000000..62a4e21700 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-40.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable with an invalid custom property name due to it beginning with "VAR-". + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-41.html b/integration_tests/specs/css/css-variables/variable-declaration-41.html new file mode 100644 index 0000000000..c1c585b0f9 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-41.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the second '-' in the "--" prefix of the custom property name is escaped. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-42.html b/integration_tests/specs/css/css-variables/variable-declaration-42.html new file mode 100644 index 0000000000..1a60ba2393 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-42.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable where the custom property name includes an unescaped Chinese character and an escape that is terminated by a space character. + + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-43.html b/integration_tests/specs/css/css-variables/variable-declaration-43.html new file mode 100644 index 0000000000..73aaef2b89 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-43.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable whose value is "initial". + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-44.html b/integration_tests/specs/css/css-variables/variable-declaration-44.html new file mode 100644 index 0000000000..f2a968d250 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-44.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable whose value is "inherit" where there is no variable to inherit from. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-45.html b/integration_tests/specs/css/css-variables/variable-declaration-45.html new file mode 100644 index 0000000000..a5003d9a69 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-45.html @@ -0,0 +1,24 @@ + + +CSS Test: Test declaring a variable whose value is "inherit" where there is a variable to inherit from. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-46.html b/integration_tests/specs/css/css-variables/variable-declaration-46.html new file mode 100644 index 0000000000..c846b1cf97 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-46.html @@ -0,0 +1,23 @@ + + +CSS Test: Test declaring a variable whose value is "initial" where there is a variable to inherit from. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-47.html b/integration_tests/specs/css/css-variables/variable-declaration-47.html new file mode 100644 index 0000000000..2f06d093f7 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-47.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a variable whose value consists of a reference to a variable whose value is "inherit". + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-48.html b/integration_tests/specs/css/css-variables/variable-declaration-48.html new file mode 100644 index 0000000000..9abf328977 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-48.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a number of variables in a cycle. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-49.html b/integration_tests/specs/css/css-variables/variable-declaration-49.html new file mode 100644 index 0000000000..9a4b984b80 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-49.html @@ -0,0 +1,26 @@ + + +CSS Test: Test declaring a variable that is a dependent of a variable involved in a cycle but which itself is not involved in a cycle. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-50.html b/integration_tests/specs/css/css-variables/variable-declaration-50.html new file mode 100644 index 0000000000..0545b003d9 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-50.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a number of variables in a chain, where the final element of the chain uses its fallback. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-51.html b/integration_tests/specs/css/css-variables/variable-declaration-51.html new file mode 100644 index 0000000000..eac02079bf --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-51.html @@ -0,0 +1,24 @@ + + +CSS Test: Test declaring a variable that consists of a reference to an invalid inherited variable. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-52.html b/integration_tests/specs/css/css-variables/variable-declaration-52.html new file mode 100644 index 0000000000..e913e2dcd9 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-52.html @@ -0,0 +1,24 @@ + + +CSS Test: Test declaring a variable that consists of a reference to an inherited variable whose value was a variable reference that used its fallback. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-53.html b/integration_tests/specs/css/css-variables/variable-declaration-53.html new file mode 100644 index 0000000000..b8b6a8cf5c --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-53.html @@ -0,0 +1,22 @@ + + +CSS Test: Test declaring a variable that consists of two variable references without fallback and with no intervening white space. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-54.html b/integration_tests/specs/css/css-variables/variable-declaration-54.html new file mode 100644 index 0000000000..8e0b39c4c4 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-54.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable that consists of two variable references with the first variable reference using fallback and with no intervening white space. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-55.html b/integration_tests/specs/css/css-variables/variable-declaration-55.html new file mode 100644 index 0000000000..2fb68d516d --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-55.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable that consists of two variable references with the second variable reference using fallback and with no intervening white space. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-56.html b/integration_tests/specs/css/css-variables/variable-declaration-56.html new file mode 100644 index 0000000000..21378f892d --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-56.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable whose value is "unset" where there is no variable to inherit from. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-57.html b/integration_tests/specs/css/css-variables/variable-declaration-57.html new file mode 100644 index 0000000000..aa8d84c6ac --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-57.html @@ -0,0 +1,24 @@ + + +CSS Test: Test declaring a variable whose value is "unset" where there is a variable to inherit from. + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-58.html b/integration_tests/specs/css/css-variables/variable-declaration-58.html new file mode 100644 index 0000000000..b05f967831 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-58.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a variable whose value consists of a reference to a variable whose value is "unset". + + + + +

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-59.html b/integration_tests/specs/css/css-variables/variable-declaration-59.html new file mode 100644 index 0000000000..2032a342d6 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-59.html @@ -0,0 +1,17 @@ + + +CSS Test: Test declaring a variable with a trailing invalid token. + + + + +

This text must be green.

diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index ee9dddb129..3914db8790 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -142,5 +142,17 @@ void main() { CSSStyleRule styleRule = rule as CSSStyleRule; expect(styleRule.lastSimpleSelector?.name, 'foo'); }); + + test('17', () { + CSSRule? rule = parseSingleRule(' .foo { width: calc(100% - 100px); }'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + }); + + test('18', () { + CSSRule? rule = parseSingleRule(' .foo { --x: red; }'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + }); }); } From ce6d4ca6aeedee437a2ede8bb8efc31ef8c47688 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Thu, 25 Aug 2022 23:42:17 +0800 Subject: [PATCH 206/498] fix: IMPORTANT --- ...om-property-inheritance.html.63fae6de1.png | Bin 8642 -> 8633 bytes webf/lib/src/css/parser/parser.dart | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/integration_tests/snapshots/css/css-variables/css-vars-custom-property-inheritance.html.63fae6de1.png b/integration_tests/snapshots/css/css-variables/css-vars-custom-property-inheritance.html.63fae6de1.png index 77ddc92a362bdc4dc6e8c0b5a7f40a5e822e63ac..d09311f3a55d103e369fee3d5e94e2adb390ead3 100644 GIT binary patch literal 8633 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 Uy4G*P=P(c$4V}v*wc8K>1vE4Y8UO$Q diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index dd012b38ad..877595fcdc 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -929,7 +929,6 @@ class CSSParser { } var expr = processExpr(); - // Handle !important (prio) var importantPriority = _maybeEat(TokenKind.IMPORTANT); style.setProperty(propertyIdent, expr, importantPriority); @@ -962,6 +961,9 @@ class CSSParser { if (parenCount == 0 && (_peek() == TokenKind.SEMICOLON || _peek() == TokenKind.RBRACE)) { break; } + if (_peek() == TokenKind.IMPORTANT) { + break; + } end = _next().span; } if (end != null) { From 9fcb887e2e3a780acce22b986a10e7549dd7c8b6 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Fri, 26 Aug 2022 00:39:29 +0800 Subject: [PATCH 207/498] fix: variable --- .../css-variables.ts.8842e44c1.png | Bin 5189 -> 5170 bytes .../css-variables.ts.f52dfa971.png | Bin 6585 -> 6580 bytes ...variable-declaration-05.html.92548ee01.png | Bin 5823 -> 6033 bytes ...variable-declaration-13.html.2e4a404b1.png | Bin 5940 -> 4993 bytes ...variable-declaration-20.html.2e850f5e1.png | Bin 5823 -> 6033 bytes ...variable-declaration-23.html.424206ac1.png | Bin 5823 -> 5175 bytes ...variable-declaration-30.html.8e9b99541.png | Bin 6033 -> 5823 bytes webf/lib/src/css/style_declaration.dart | 10 +++++++++- webf/lib/src/css/variable.dart | 14 +++++++++++--- 9 files changed, 20 insertions(+), 4 deletions(-) diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.8842e44c1.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.8842e44c1.png index e63609372a2f731ca259bcbd2fbaf998bc559354..d9d3c493a9f88cfee7e6f003bfcb591a0d159ef7 100644 GIT binary patch delta 2909 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 delta 2929 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 HV`u&cTm)r~ diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.f52dfa971.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.f52dfa971.png index a55c5eb4eb4b88f47ec80f8c89ee40e8808b9310..77f5425304086a38945e6e1a7c7362a4d51b2f1c 100644 GIT binary patch delta 4425 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 Doq^fY delta 4424 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-13.html.2e4a404b1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-13.html.2e4a404b1.png index d45b3fe6205321fcbafb594635db41dc395f35f0..11a245e86273398ca6fd3d7ca1f7cf4e7bd34833 100644 GIT binary patch delta 2730 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

&Cz~ zH$=dxn~6jNOca_ zs7MZ-)%R3leS+40dAH)RBuMY%ZZbpwH6S=6ntgy+t>X`X@wfzI zisK={P>JP^zZkZ14GET^AWm;5Y*@2Ea;V5ktt3LSPI{O{vC+K7_%&1Vr!ggREv|QU zxx_cBE-aJWVUjAS3Lg2DEcfk{_Bl*q$^_Q7wJg5FSgcf)t6>2X10n_6Y|l3dCf;TP zM0;z(DZ_a<>l-0ts&Ub3(GBE}RXUpPS9$Wcj6M!j9K?%eK^6?Od_(&z$yrw&{&6D$ zYN}C91V~O_VcE{goYG+B!y^Hs=OGdn3tjHh$KZ#nW7%$zIcK;Br(R<0zt{ik#CjB= zKjI0CT|vdAJ}u_#DHcmVI|uOQV)Jt-ER$`?BnzDz^lq8FhjGsAVuRXeWc4?Uw;y_U zB?rsA~YQ3{#8klVQ3|h+Ly2 zy5(YsK8Al|y`?~u3vbew=K68IIHY|`RH@V1bB?hGenP`-R>hnizk?9U3{-uU2+n5Nr-Et-y~iYnq_)SLFZx_BlO_5ddWTHET7oBHI}nx$mJKX)TuZ?`GI8bg%Mpn z2zR~1I^?-yt>I%~j`;9ZJLCG5$V9tpG`Sq6iZpI!b2e diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-20.html.2e850f5e1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-20.html.2e850f5e1.png index 7d88634a242de36024c2a9b7d75f53684677cf7e..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 100644 GIT binary patch literal 6033 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png index 7d88634a242de36024c2a9b7d75f53684677cf7e..d3699942bc8b1c6fdd40ddbc98bd529defb48921 100644 GIT binary patch literal 5175 zcmeI0=~vTN8png$!PbSLj>QE?TY*d~f`Aa##C0gLscl8dl4>HLY$kz(B_s+eZ4E>d zBqB?+RXL8h5RkAX5tpPt5Tc-9An*ech(Jgn7_ujmInzI4UU=W!``mlZbME<^@AKT} zxxXHY319cwmd_v%$hwH{_8);j?9w2R6=zrf&9>9HWwPEj+2N0b?}a?*bDpsotMGdx zepqcAq}7;TAdn3c5&QT2kXJK5n%_E=yGOVv;^H@7Jc$7@WzmjTcVF4R3LdrN<)QA! zz104K;pP`D_2$#mne+?w}4u5bBB(cWS&FOJr&kvlq)H0=k@UnVkckqn2L=p0ZohbI`SY@Jx0Z7RKVrjLUwcHK& zh%*z^R0nq&x$7Cfd2P?b(e|O0y7mADh4Lf2KyUg!bUvFt>dMK*7ryZUyh=STEsyeU z$LA8&T6>RhzE_Z?cLYw;OBQ%CkPi!*@5ET;p`AUpz~BS@Q1iaf8*p&?Hv zH=k)&T|&D+z=f26;r(M+6}D~YdwNw8yw=deitGysj<@KsMXxQuHXdwjj+);N_%D%{o|VIfYZB-0Zn&FVSzU9`Y~(^*u4fqs@{noOfbfjq;AF z`!Fc`B`&+&S?*fti3pD81h<(d-O`1XU4DoPlG>gzf(XcWh1m#==S#J`4}h##?gry! zN*(S-CSR4PMGSf8G*8BcM)PzNj?%j`jUn%wm*`972bKD#*OWCcfHyy=i>=P0<6aID zM&d$)tS`1)!%hQb5qy@jp&!_l@Dp*VO(CSuS-y?W9Xf_8rBK)kKK(5!CV8Q%45gBS z6(;#ZkL}q|5q`oIxXKSyqPV0`VG`}T7_$+>LBnX+Q51}6&B0mc8My`-@|verErMBd z_Tt_XVH^s{!Vk$iKitw3m=#RyoG3S3kv(y;`HS{4Ed%E5D(BbNT&{rXE3yG3GfH(i zbP-O6nNH)L%-}_ZS|QB-0hrBQ>H@k_W5&)x3$t)bdVp8&7HiTtDif^k`uC@#7NHf# zR-SWwCVnlaGs^;9wAFx}sP_DE6+_VJ1P#*SB*aMETsWiS;TAhke;FhG5wLfFUI|oe z1d?H8`6!(WqvUr7Nl2ZsI{{t?{?CaLaRC>DAQ!JOs&V$kA`TN-NU!}Yfo?m?H zkWN4DC)9ir(rYasS!j%ttmyLKkRY``+UYf&=}t@xtxAcU%kpBH({R1ea)%kAX+Q2= z4C#+_(^FzGt-OgclzBkw;)dC{s6ORrQ zr`ip@z^VF-(e{yv!_|Gn8CJQREbs@44@g`7Kru-cT@W93h@TgXyqz5a>jv^;FouHDm!B_>oRXXW7H5Dl|HwpbJ zyqx0XK+JPfYTTwQQeIlxsnQt%{LAr zr@2G4p6z2hEt##JYX~tZgeu8$1qUWaX6>JR#{vV{4lh2GSC0iW)h>}|I~bSxq8?CG zIyiS{&`j+J@k}m3)^>B|fOO@T;+9`@FlYG`!dLd3Hg2TC-n->bp4~^-6E#q%2(58Cyvq?^65TVs=a7vnD`>6f~1{@IV`F(3&gx60Dg|)%Q*V=zlx=#NF2~ABV&6Wxg{Tsl5 z6Qn12Vajru3TnTG7>IoPu-0~{0Nae@(%|cFVq_~HS zmQLj~?$3C1o9#6XlDPx_J_%Seo>@%$P}dW+OaSQ$N9=S`R7midxVcY&B`=o&XQMzi zsXBG(P$mbFW2vAoVM2i=sZBi`i}r>jKv`0k`XsCnl1c!_ z6*+Jga=q#0@YE|=D>!VbR{0d}8v4Ngo`^ zVDGVQs-6qiKe0n2y!9|X zS&YD5Ko&)1@z3~<_7h{4r7^wKN64A`dh6k7pX^QYbKXTl9ntUtK*UxTZZ0=pzy?d| z`&@_Woyx!0E*B1BuJ5?t(N<$}0zO3Ho*5Y;i!#sEMo{;vCjFLiT%)N#y4uHPBanI; zlqKP%SpiXLjen(e7VaKrc`93;xgf?o=K^z$7K0*vXJP2iqbrw?FkSF2%|@%k^24O7 z4V`EOTCzYb>5a#O?`o1;jRMp)+fw(n@J+E#zhuF)uccyFdC?7`Us+mWTG#sut;`JmpbIh3Q#Wb=?t8(A*$Z znAqeip1Q23**CQ2*TnJC_&ZqTL9DBsGO#rK@|X8U>{_}_spSD@vdP*;E$zcmLMkg$1f~!{5E*kO zr!jS!HmyvkoW}eRunasPlGCIeR4U^n3Z;37gQSQk2=4pM{uBE{To)I7FW=#L?&rDh z`*}ILFFw}mBcG2vJUqPi?B1E^;j!q1hsS&0E?o>(Zu<|5%&%TPP*j!ZFcl7+%Nn1o0 zlbU}0l{B>GV%~+ku!n1xlkY6AxKVLw$S>tUP;tvm_bZ<|K13ynZ`n zH2dVe81JY9DEHgqrmSPAxreca@tSgh+vSw;AJwOd;4|K+wAE?!!z?oa&%_$lKkHo+ zv=GMhc&kw$$x=5Qwv4yx_ax(4)VS(!(P&Q<$mFB9c=7!~njS|`3HUI4c4dIl+@wz{ zkDS_VZXPa!Y`b!}%!w9$s^)yGe@d)>-fJzVyOnCpa?j5pxim@3Hl?)Z=WY7B;aVEg zETZKPT7DVhvfD@H+yy;n@t+P!$_u7zLXJNC$>+19yIC(iQMX{YqiBgeNSj$(%~%qA zu(=_A+pF&k)P%Z#Rt3*No<+1&?EI2!t13|oG zp};DSmf4%v<*E<)Lq`d-}+Yvr~tq88EshI&)b98tY?91S?a;q7JI%ztC zV_{mPO)DZy4G5eF3`U_Wtx3lqc=?9b(@+TjXollWtuuSLV+pE$ec84f^38*Ew;b)v zM)H0A=z@N_t;k+4KEFA=^DdkPBGJ)LVw=A?%!4tI`R#pga`gNwZ|&^lFcuPduGn^s zHZc9B4}9-JvD-w-ev_sV9GqVtD~_0Gu8HHKrMQz1J|Etwl1jU_0JfHI6eN1FH$1q2|e-jT4>Jr)0uvPP^3&n zPUOL%%o$TCWwbjKbT^30)F>xi*btY+b{X+u5#<7b04)9#vm#TK#w13}sMA4Ay}i9k zg+j3*{;E1Y+BJ=V_&gquk8KiwsMnWAK8NAKys>JQlGeL@>@GN2C#?PIGFZidu!)`L z)|vrhGy7^2$b3<*wI{48X2tejAP7kWd1J-dnhg|F9y&ZCLJNqjBehL-=Gqj=_Ffj! z&gpuCn%GNK;Z!i}M}4v1e8!h_Lcid)Yws0$g6RIdepmKXzCAJni5S~N!bxmOELa~d zk8XFp4v5Z7`t_?!&m}8Nls13X^fR3`!|4@bMa1azX5;<x2=%;Hc_F=>o-X(Bl{4!|TM_3B0!k=r0V5XN^-o z{VfKDv$`*Bw%zmeegPywe2fA?qC*|0Vb}psPf3V&<@8+MQtJ;u&=K3gQy>VyTp$Kb za0=9!h8g6R1Z_s3L`G8k<3l^Mu)r~JpKvt7O40oB69rS!5oq#FcnQ;!{! zZj>h&U!Xh9zhqIhAeBhj^1ls^bu4v1%I_5oPLq7(>hSIR@GUuR0$rH9adMBP@=)C0LT~52um(+Y+ zDZpX9qo((Ht@+orGN5x!B0lF53Ao|0kVE&rh5N*H8WT`{T$m{m!Zw5 zreZdw-o1sX|9Zb~X!alK9POSgcl@@xeIG>r-TkfSid)|=TdjP4b1%W=u!)X5_z}@+ z0zeI$_x?+%d12({?%3E^M5qDxun;-_=@AN};O0b&SqL-maV|j5dT-WvjL6y>L7us7 z*R$D!^x;yf0!miM3uYQq8xDPe%b$EuRnHu3r7p~^Anua$9+u({lYskNko+^0BhNrv zFNcHSgD~>=qUhAfW3+YPO2Sbc5H41G8n`@aVcsgOtU!pGt-{gxg`P{4Ec>7;W5u%7 zK_*INcZI3V+8Y8|<4OdLWNwtB6&g(nFutmDPIVU=14g8yHls?It7qB!3!G;CsX_Ab zxv3HKgU8I?P~4CjV;`U-@bR+t86DHYm}nU@IE+Fe9G3Qq)Egu*C018ghopC&3!i;i zL_o?2#;ai)Fpl;h0Ca!D;h54jn{SAXa*}%1;aY*t24{jPE~Y~D7|3Vx$>{WJ6Mw?_#FD`T2M!wx9DQ8n{#ZW< z09CihbMXtLrAE6ihZM}u>Wtl3u)LN!bT&Xa{;Y->e6xf3Tc>4By8^fF5AKeDr^Rt4 zTMIx0U<9y^$;o_|kyOZuMx0wRTo$35#y#;Rx80`AKRmx4OW-4I?Z=*oP1Sq7Kl-BXoJ6cYeR%ykwm_R0e37#dB}eF+vy zz%BwGV29(we|WN_K4XFDJ_jmVP1EJE{3G&b-|8&h>$Wu@Y?gLI?%^b-#Ff#drA%~=QD4Rkx zqlj~74BXA^W03V)I%-wWL3Kv-f~&16GVc`EDi~W)&9Q!<3k*+X(iZN!IfN2GR3oLU zqbfy`OXMGHxr59&X!22{v=tju618L*%L{}n$1TPh70GzMf|sl!Nz7t}RkI4vK=`0! zwF;3C4WSK!gwmN*sl&h%Aw4ytT7bg}j_E;5+bnu^NjhU=T3fUIQuZS@!-OQ>pI#ni zIJc$uR$WppNs%6!t=_k{Vq+cEs8lS8p6%G=-q4Wq=6Na@&cXFEgmAqnMQ~)J8 z;*lsd7LKyP@GxDQ+IT$93PdlmuB=CgG{B4~FQ93#PO zjgty033e;8zhum^Uw^!{N_4#E3S&l-L9iJVBH$BJ^Iq?JT4Z~a|Mz8o{m*rKoZyJ& ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-30.html.8e9b99541.png b/integration_tests/snapshots/css/css-variables/variable-declaration-30.html.8e9b99541.png index a814636ca1b3a7f79a58e5babbc3f1e50daae2f0..7d88634a242de36024c2a9b7d75f53684677cf7e 100644 GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 6033 zcmeHL`&&}k{8h6HG|gzHLi0eP;blhhhFofDvW}&R zC(S^uG(FuUQSk<%W2uNjV!2fmDiu^fL{LNozMH=1uQ*TqvY)-zdU)68vp(s=lG73%w)f8$FdXMKNobZzC$A2+Q2bLV@v&O`qUx)ZVY zj!Q#sA@^|!{O3N8y~|hMAYDMx$rOJLwU47u7BumpBSke{n{OYp+KFjF;=i!co_w(E zUth;ue&oITO%WF6sWipvM?Ol}ih=!dfHf7Q7J84XtxWh@K0YoqKh;&0+H!Ezri=3K zMA9soM%$$-q7=VDc+=K>Om}(?UB^k8Rk{^?T8D*_*x{ZRxQQxecnG0tI08S6VTDw z#t&5ulVwzfWWZ=DGnmdS7DSl0Nfzjd=-$CBlkm(k)8FSXM099lf6BHXe;f~!)Hh5` z(mhG7cv0)hy*^?3?X{!#MN9yD?mzZ({iO&sx)DgmsHa|sGw@s&aR50##jS@?d0{eA zq~9UYl?a#aYO2%bK612Pm%p%9cqLUR#LXEgi4wmE+Yz52hI}H5D!Oj7dC1-uqo&#a zu3r{gI6%q2>6$KjoXBHqRPiL^(^d52hJVi^fMXn-Ds?thVr*J>-)pDqHxZ1{ZLoQ0 zuAGz_mC}fAn_LVdsqfZ=#0m;M1;LE1l@$o`#bYNO?t=ffY4-J2XzXH8cy08rl(9cc zkay;HDI4Z`BdCT5ajh>5!9ZoRW;+RsaCp%fI(&an(pz1Q1vc2M)cVV+bPyf?8g|Fn zg5#Ewtw?*y!945nkuVla6UG+=^@U8$;fD2NMLnvF^R3HcON7Oa@TF0N0fv=^vq{p* zh=9&^IcYLIA@;wyhqIm6hPuW$g4J9 zTh8i1vci-OCaXaaR&W62Z#RS`r+r24wO)t_X%8n zxtejY=V>GXcn$rS5ilms8KJV7gV@skN_Ux2!6JNMx}$UE{gM{_D=^_QMsPDO$p=W8 zKUY`WVF35O6CqOE6>#L5P2Oo|oAE#xuD`pQVIDBz_y=WUgM19mv_*yyBq|0Y=}5Ry z&N6rDWVhSI_sB#YC%XCybsU>Yb`2n?ZY(Wokr)t3$gYH0V$`S7Bm}Wv?a~v1L7b?= zi(BQPqv`&>#k=k;UX5WruO5{+N&OY;WzO-WadV z1!7#R)0Ws_v(|X~SZVJ8u&TXEhH>5F?VL8fVX^Dr|G&Yl?v(mT^9@nZ0lR zQI%0_-USp10|OR~_)>L82TE}iWj7*kq>5C8k9-8Swh9XXn{O@6b{+$UDy7W9wb2kT znEEH9_eCJ5O6fk0fX>z|oI#9&GC$h2sxl5lw{vKIXi0@*{nT=BWom6#1r;Tw-;;ph zlq_&};EgnQf|xk7%8|g;x5-~`XWK58jf8Yv&WDpu2(mJbMQ$n|JmAa)TTR!R+XP*Yp<7 zig!^7;Y2DrIWfce<^4P?>3=Cy9i5QkzcQoljYsi3=PI8R;(@p8?0olxk!HHWv>&JJ>e5EDnCm5R zX_}+$C&QZ@{M_GU$JnVK(c}Ft$@dTvK4dZBWFo$@Fryl_}j7AcwUMVQ(kM{=&!P7YMa;7!>Iq9IJy79g&+Cwu_c1~j=Qv3 zuNzmGePN$OU?$$Nds^9)_|_j!dRXD$y}v_|6nw1acIV61wnh64X^$P^7kelgy;cQ0LSAZxNB5WqwYCe|xrBmWv7r|TqU)Yi zOH2{W_(JD&re^SVBWL4O`a}J482S5+TT)J2pk|mKVuMc5|A<&Wxcl&`^*u^Qwx83` zF*I*$#2R;2OUZbt*fo|bP8^2G94;kL(4gWGv*pmo)8~M9!~HlPgW)kpFaWuwgzW7j z!~6jMOYwZaiQ&kMrP}cR+C7dXYPA%|a9eqaE+@^we{#fU{~)6;j5fLTL;7Xe9jXc3 z()%k{aSID=nh$>dv|2gfeRaT706v=)MAQ3LM{T3=&7(|V1G74PS2PNk){9>b+6nuI z@~JZMu>8zJTX)id9Dl#{8!0kQ6(1gj<7*@;vdm0M^0wTz<}aj9jcQ0R!ZZ>4GgDG5%_DM>Ob2lLzXZ zu3E3_y5_eXJ!I_j01Xd%Df2KD2;rpY07i3_KP+cRf3!kKZ?}nQ)Ekg3^Ka6_B z&Ag1%^0GY0<4ZL*3!@$(pgp7vBxT02_5JBFy@x;9C0K!;toSsFmkc^NA2g^tX^=*V znx9Et@PvE598}6uVY&)R5&~ZUDw;hRt(LJm(1%cg4x)MVzn}LY#}N~ zFk3mUpBh1qOw=A2O%!zg#bNDL={YKH!2D96w_Sa>19Cns+Rw)YLYTPj%q=vO6c*-m zcZ)c4^MvUKf(u|`!?nYYy6<^9A=3q6v;Db=BNGP0G@U~|QwiIanFD7@Qp;PAr6<>1 zA_KGxrY_P&(LmG;Q*p12VO|}|+8%_y|Fi_pMK%YK;k#r@TAF?OcrFxeIGn`07LjCJ z4ag1ygq;PZDPiQ|vlCPl+*D_meJ8}Ajs^S7LTom>JbeY8YjERgC8q@Y!W{EfmLTp} zIu6}!GBUcmFn1F!dW@Gzf?^!g=Ch{B=Xos!Y^E4EXAuO+fmu7K@p>O_mK>LsH4JV- z(o$uIEP$gwjT_#ki=?FUWZMRRe@lE%we8kt(j>lJzR9XHnnd$lB2F}>Cx0W{Ph>((oanDT%IDwTj&T_ljs8r%e~9M0ngqUEoE+v5wMh zyDq(ucd^Db9xpGPZ;#toE;!4gfl^-$wk)^oJs3c)vZU+&9Cr23=+^@@A?~w13 z-F%4lLD2b?7jX(Ki;e=nVp+d$K~>^+B7(twM&|U!>ZsnGZuRMmwqo;xomZ@P9{=x= z?epvBY? _storage; @@ -24,8 +24,16 @@ mixin CSSVariableMixin on RenderStyle { Map? storage = _storage; _addDependency(identifier, propertyName); - if (storage != null && storage.containsKey(identifier)) { - return storage[identifier]; + if (storage != null && storage[identifier] != null) { + final variable = CSSVariable.tryParse(this, propertyName, storage[identifier]!); + if (variable != null) { + final id = variable.identifier.trim(); + if (storage[id] != null) { + return getCSSVariable(id, propertyName); + } + } else { + return storage[identifier]; + } } else { // Inherits from renderStyle tree. return parent?.getCSSVariable(identifier, propertyName); From d45cd23bc391fdfb3bc2e78a92eaeaf54ccff73d Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 26 Aug 2022 21:57:20 +0800 Subject: [PATCH 208/498] feat: add canvas, input, button, and textarea element --- bridge/CMakeLists.txt | 21 ++++-- bridge/bindings/qjs/atomic_string.cc | 2 + bridge/bindings/qjs/binding_initializer.cc | 4 +- bridge/bindings/qjs/converter_impl.h | 7 ++ bridge/bindings/qjs/idl_type.h | 3 + bridge/bindings/qjs/wrapper_type_info.h | 6 ++ .../core/{dom => }/binding_call_methods.json5 | 74 ++++++++++++++++++- bridge/core/{dom => }/binding_object.cc | 2 +- bridge/core/{dom => }/binding_object.h | 4 +- bridge/core/dom/element.cc | 2 +- bridge/core/dom/element_test.cc | 35 +-------- bridge/core/dom/events/event_target.cc | 3 + bridge/core/dom/events/event_target.h | 4 +- .../core/dom/legacy/bounding_client_rect.cc | 27 +++---- .../core/dom/legacy/bounding_client_rect.d.ts | 16 ++-- bridge/core/dom/legacy/bounding_client_rect.h | 21 ++---- bridge/core/executing_context.cc | 3 +- bridge/core/fileapi/blob.h | 1 + bridge/core/frame/screen.cc | 3 +- .../html/canvas/canvas_rendering_context.cc | 15 ++++ .../html/canvas/canvas_rendering_context.d.ts | 5 ++ .../html/canvas/canvas_rendering_context.h | 26 +++++++ .../canvas/canvas_rendering_context_2d.cc | 23 ++++++ .../canvas/canvas_rendering_context_2d.d.ts | 48 ++++++++++++ .../html/canvas/canvas_rendering_context_2d.h | 27 +++++++ .../core/html/canvas/html_canvas_element.cc | 17 +++++ .../core/html/canvas/html_canvas_element.d.ts | 53 +------------ bridge/core/html/canvas/html_canvas_element.h | 5 ++ bridge/core/html/canvas_types.json5 | 13 ++++ bridge/core/html/forms/html_button_element.cc | 7 +- .../core/html/forms/html_button_element.d.ts | 9 +++ bridge/core/html/forms/html_button_element.h | 21 +++++- .../core/html/forms/html_input_element.d.ts | 50 ++++++------- bridge/core/html/forms/html_input_element.h | 1 + .../html/forms/html_textarea_element.d.ts | 34 ++++----- .../core/html/forms/html_textarea_element.h | 1 + bridge/core/html/html_image_element.h | 1 + bridge/core/html/html_tag_names.json5 | 26 +++---- bridge/core/timing/performance.h | 2 +- bridge/foundation/native_value.h | 2 - bridge/foundation/native_value_converter.h | 24 +----- .../code_generator/src/generate_header.ts | 0 .../code_generator/src/genereate_source.ts | 0 .../code_generator/src/idl/analyzer.ts | 9 ++- .../code_generator/src/idl/declaration.ts | 2 + .../code_generator/src/idl/generateSource.ts | 59 ++++++++------- webf/lib/src/bridge/native_types.dart | 4 - webf/lib/src/dom/bounding_client_rect.dart | 48 ++++++++---- webf/lib/src/dom/element.dart | 2 +- webf/lib/src/dom/elements/canvas/canvas.dart | 2 +- .../elements/canvas/canvas_context_2d.dart | 8 +- 51 files changed, 497 insertions(+), 285 deletions(-) rename bridge/core/{dom => }/binding_call_methods.json5 (51%) rename bridge/core/{dom => }/binding_object.cc (96%) rename bridge/core/{dom => }/binding_object.h (94%) create mode 100644 bridge/core/html/canvas/canvas_rendering_context.cc create mode 100644 bridge/core/html/canvas/canvas_rendering_context.d.ts create mode 100644 bridge/core/html/canvas/canvas_rendering_context.h create mode 100644 bridge/core/html/canvas/canvas_rendering_context_2d.cc create mode 100644 bridge/core/html/canvas/canvas_rendering_context_2d.d.ts create mode 100644 bridge/core/html/canvas/canvas_rendering_context_2d.h create mode 100644 bridge/core/html/canvas_types.json5 create mode 100644 bridge/core/html/forms/html_button_element.d.ts delete mode 100644 bridge/scripts/code_generator/src/generate_header.ts delete mode 100644 bridge/scripts/code_generator/src/genereate_source.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 612724c72f..a32c6d95e4 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -186,7 +186,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/dom/events/event_target.cc core/dom/events/event_listener_map.cc core/dom/events/event_target_impl.cc - core/dom/binding_object.cc + core/binding_object.cc core/dom/node.cc core/dom/node_traversal.cc core/dom/live_node_list_base.cc @@ -225,13 +225,13 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/html/html_anchor_element.cc core/html/html_image_element.cc core/html/html_script_element.cc -# core/html/html_anchor_element.cc -# core/html/html_template_element.cc -# core/html/forms/html_input_element.cc -# core/html/forms/html_textarea_element.cc -# core/html/html_image_element.cc -# core/html/html_script_element.cc core/html/html_unknown_element.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 @@ -301,6 +301,13 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") 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_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 diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index f7aef069af..56ee8f266b 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -102,6 +102,8 @@ bool AtomicString::IsEmpty() const { } std::string AtomicString::ToStdString() const { + if (IsNull()) return ""; + const char* buf = JS_AtomToCString(ctx_, atom_); std::string result = std::string(buf); JS_FreeCString(ctx_, buf); diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 7c8a587955..f556281ca4 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -22,7 +22,8 @@ #include "qjs_event_target.h" #include "qjs_focus_event.h" #include "qjs_gesture_event.h" -#include "qjs_html_collection.h" +#include "qjs_html_all_collection.h" +#include "qjs_html_canvas_element.h" #include "qjs_html_body_element.h" #include "qjs_html_div_element.h" #include "qjs_html_element.h" @@ -88,6 +89,7 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLScriptElement::Install(context); QJSHTMLUnknownElement::Install(context); QJSHTMLTemplateElement::Install(context); + QJSHTMLCanvasElement::Install(context); QJSCSSStyleDeclaration::Install(context); QJSBoundingClientRect::Install(context); QJSScreen::Install(context); diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 36d4ee3b5c..1042f790d2 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -339,6 +339,13 @@ struct Converter : public ConverterBase { } }; +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) { diff --git a/bridge/bindings/qjs/idl_type.h b/bridge/bindings/qjs/idl_type.h index 8f0a33fe62..6eb77b9c45 100644 --- a/bridge/bindings/qjs/idl_type.h +++ b/bridge/bindings/qjs/idl_type.h @@ -55,6 +55,9 @@ struct IDLUSVString final : public IDLTypeBaseHelper {}; // Object struct IDLObject : public IDLTypeBaseHelper {}; +// Promise +struct IDLPromise : public IDLTypeBaseHelper {}; + class JSEventHandler; // EventHandler struct IDLEventHandler : public IDLTypeBaseHelper> {}; diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index d425c90e01..faeed10ea2 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -54,8 +54,14 @@ enum { JS_CLASS_HTML_IMAGE_ELEMENT, JS_CLASS_HTML_SCRIPT_ELEMENT, JS_CLASS_HTML_ANCHOR_ELEMENT, + JS_CLASS_HTML_CANVAS_ELEMENT, + 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 */ diff --git a/bridge/core/dom/binding_call_methods.json5 b/bridge/core/binding_call_methods.json5 similarity index 51% rename from bridge/core/dom/binding_call_methods.json5 rename to bridge/core/binding_call_methods.json5 index b749a876f4..4448d194a0 100644 --- a/bridge/core/dom/binding_call_methods.json5 +++ b/bridge/core/binding_call_methods.json5 @@ -37,6 +37,13 @@ "availHeight", "width", "height", + "top", + "bottom", + "left", + "right", + "x", + "y", + "z", "screen", "target", "accessKey", @@ -68,6 +75,71 @@ "fetchPriority", "loading", "noModule", - "async" + "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", ] } diff --git a/bridge/core/dom/binding_object.cc b/bridge/core/binding_object.cc similarity index 96% rename from bridge/core/dom/binding_object.cc rename to bridge/core/binding_object.cc index 039d71f2b5..63282172ce 100644 --- a/bridge/core/dom/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -26,7 +26,7 @@ BindingObject::~BindingObject() { delete binding_object_; } -void BindingObject::BindDartObject(NativeBindingObject* native_binding_object) { +BindingObject::BindingObject(ExecutingContext* context, NativeBindingObject* native_binding_object) { native_binding_object->binding_target_ = this; native_binding_object->invoke_binding_methods_from_dart = NativeBindingObject::HandleCallFromDartSide; binding_object_ = native_binding_object; diff --git a/bridge/core/dom/binding_object.h b/bridge/core/binding_object.h similarity index 94% rename from bridge/core/dom/binding_object.h rename to bridge/core/binding_object.h index 3b2fbbc049..39a7f6416a 100644 --- a/bridge/core/dom/binding_object.h +++ b/bridge/core/binding_object.h @@ -61,11 +61,11 @@ class BindingObject { NativeValue GetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const; NativeValue SetBindingProperty(const AtomicString& prop, NativeValue value, ExceptionState& exception_state) const; - const NativeBindingObject* bindingObject() const { return binding_object_; } + NativeBindingObject* bindingObject() const { return binding_object_; } protected: // NativeBindingObject may allocated at Dart side. Binding this with Dart allocated NativeBindingObject. - void BindDartObject(NativeBindingObject* native_binding_object); + explicit BindingObject(ExecutingContext* context, NativeBindingObject* native_binding_object); private: ExecutingContext* context_{nullptr}; diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index ca170892dd..14c4e9e6ca 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -75,7 +75,7 @@ BoundingClientRect* Element::getBoundingClientRect(ExceptionState& exception_sta NativeValue result = InvokeBindingMethod(binding_call_methods::kgetBoundingClientRect, 0, nullptr, exception_state); return BoundingClientRect::Create( GetExecutingContext(), - NativeValueConverter>::FromNativeValue(result)); + NativeValueConverter>::FromNativeValue(result)); } void Element::click(ExceptionState& exception_state) { diff --git a/bridge/core/dom/element_test.cc b/bridge/core/dom/element_test.cc index 76957cfa61..f82058d281 100644 --- a/bridge/core/dom/element_test.cc +++ b/bridge/core/dom/element_test.cc @@ -135,37 +135,4 @@ TEST(Element, instanceofEventTarget) { EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); -} - -TEST(Element, stringifyBoundingClientRect) { - using namespace webf; - - 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}"); - }; - auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { - WEBF_LOG(VERBOSE) << errmsg; - errorCalled = true; - }); - auto context = bridge->GetExecutingContext(); - - NativeBoundingClientRect nativeRect{ - 10.0, 20.0, 30.0, 40.0, 10.0, 20.0, 30.0, 40.0, - }; - - { - MemberMutationScope scope{context}; - auto* clientRect = BoundingClientRect::Create(context, &nativeRect); - context->DefineGlobalProperty("boundingClient", clientRect->ToQuickJS()); - } - - const char* code = "console.log(JSON.stringify(boundingClient))"; - bridge->evaluateScript(code, strlen(code), "vm://", 0); - - EXPECT_EQ(errorCalled, false); - EXPECT_EQ(logCalled, true); -} +} \ No newline at end of file diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index c7eaedb112..a92184787e 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -45,6 +45,9 @@ EventTarget::~EventTarget() { 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; } diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 5ddd680781..b44b6022bb 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -9,7 +9,7 @@ #include "bindings/qjs/js_event_listener.h" #include "bindings/qjs/qjs_function.h" #include "bindings/qjs/script_wrappable.h" -#include "core/dom/binding_object.h" +#include "core/binding_object.h" #include "event_listener_map.h" #include "foundation/logging.h" #include "foundation/native_string.h" @@ -90,6 +90,7 @@ class EventTarget : public ScriptWrappable, public BindingObject { EventTarget() = delete; ~EventTarget(); explicit EventTarget(ExecutingContext* context); + explicit EventTarget(ExecutingContext* context, NativeBindingObject* native_binding_object); virtual Node* ToNode(); @@ -158,6 +159,7 @@ 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; diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc index a6c1c8d54b..6eda612ced 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.cc +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -8,26 +8,17 @@ namespace webf { -BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, - NativeBoundingClientRect* native_bounding_client_rect) { - return MakeGarbageCollected(context, native_bounding_client_rect); +BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, NativeBindingObject* native_binding_object) { + return MakeGarbageCollected(context, native_binding_object); } -BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, ExceptionState& exceptionState) { - return nullptr; -} - -BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBoundingClientRect* nativeBoundingClientRect) - : ScriptWrappable(context->ctx()), - x_(nativeBoundingClientRect->x), - y_(nativeBoundingClientRect->y), - width_(nativeBoundingClientRect->width), - height_(nativeBoundingClientRect->height), - top_(nativeBoundingClientRect->top), - right_(nativeBoundingClientRect->right), - left_(nativeBoundingClientRect->left), - bottom_(nativeBoundingClientRect->bottom) {} +BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBindingObject* native_binding_object) + : ScriptWrappable(context->ctx()), BindingObject(context, native_binding_object) {} -void BoundingClientRect::Trace(GCVisitor* visitor) const {} +NativeValue BoundingClientRect::HandleCallFromDartSide(NativeString* method, + int32_t argc, + const NativeValue* argv) const { + 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 index 26fe6df07a..49b457e4e6 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.d.ts +++ b/bridge/core/dom/legacy/bounding_client_rect.d.ts @@ -1,12 +1,12 @@ interface BoundingClientRect { - readonly x: double; - readonly y: double; - readonly width: double; - readonly height: double; - readonly top: double; - readonly right: double; - readonly bottom: double; - readonly left: double; + readonly x: DartImpl; + readonly y: DartImpl; + readonly width: DartImpl; + readonly height: DartImpl; + readonly top: DartImpl; + readonly right: DartImpl; + readonly bottom: DartImpl; + readonly left: DartImpl; new(): void; } diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index 77235563d7..22f7e427d3 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -8,32 +8,21 @@ #include "bindings/qjs/exception_state.h" #include "bindings/qjs/script_wrappable.h" +#include "core/binding_object.h" namespace webf { class ExecutingContext; -struct NativeBoundingClientRect { - double x; - double y; - double width; - double height; - double top; - double right; - double bottom; - double left; -}; - -class BoundingClientRect : public ScriptWrappable { +class BoundingClientRect : public ScriptWrappable, public BindingObject { DEFINE_WRAPPERTYPEINFO(); public: BoundingClientRect() = delete; - static BoundingClientRect* Create(ExecutingContext* context, NativeBoundingClientRect* native_bounding_client_rect); - static BoundingClientRect* Create(ExecutingContext* context, ExceptionState& exceptionState); - explicit BoundingClientRect(ExecutingContext* context, NativeBoundingClientRect* nativeBoundingClientRect); + static BoundingClientRect* Create(ExecutingContext* context, NativeBindingObject* native_binding_object); + explicit BoundingClientRect(ExecutingContext* context, NativeBindingObject* native_binding_object); - void Trace(GCVisitor* visitor) const override; + NativeValue HandleCallFromDartSide(NativeString *method, int32_t argc, const NativeValue *argv) const override; double x() const { return x_; } double y() const { return y_; } diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 49d4bd2aac..40395e6f03 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -77,7 +77,6 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& ExecutingContext::~ExecutingContext() { valid_contexts[context_id_] = false; - ctx_invalid_ = true; // Check if current context have unhandled exceptions. JSValue exception = JS_GetException(script_state_.ctx()); @@ -93,6 +92,8 @@ ExecutingContext::~ExecutingContext() { for (auto& active_wrapper : active_wrappers_) { JS_FreeValue(ctx(), active_wrapper->ToQuickJSUnsafe()); } + + ctx_invalid_ = true; } ExecutingContext* ExecutingContext::From(JSContext* ctx) { diff --git a/bridge/core/fileapi/blob.h b/bridge/core/fileapi/blob.h index c6f0df7724..b81f3427dd 100644 --- a/bridge/core/fileapi/blob.h +++ b/bridge/core/fileapi/blob.h @@ -20,6 +20,7 @@ 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, diff --git a/bridge/core/frame/screen.cc b/bridge/core/frame/screen.cc index 74b669a9e0..bccfbd4044 100644 --- a/bridge/core/frame/screen.cc +++ b/bridge/core/frame/screen.cc @@ -10,8 +10,7 @@ namespace webf { Screen::Screen(Window* window, NativeBindingObject* native_binding_object) - : EventTargetWithInlineData(window->GetExecutingContext()) { - BindDartObject(native_binding_object); + : EventTargetWithInlineData(window->GetExecutingContext(), native_binding_object) { } } // namespace webf 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..0c97f3488b --- /dev/null +++ b/bridge/core/html/canvas/canvas_rendering_context.cc @@ -0,0 +1,15 @@ +/* +* 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; } + +} \ 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..370da73627 --- /dev/null +++ b/bridge/core/html/canvas/canvas_rendering_context.h @@ -0,0 +1,26 @@ +/* +* 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: + +}; + +} + +#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..acb64474dd --- /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(NativeString* method, + int32_t argc, + const NativeValue* argv) const { + 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; + direction: DartImpl; + font: DartImpl; + strokeStyle: DartImpl; + lineCap: DartImpl; + lineDashOffset: DartImpl; + lineJoin: DartImpl; + lineWidth: DartImpl; + miterLimit: DartImpl; + textAlign: DartImpl; + textBaseline: DartImpl; + // @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; + arcTo(x1: number, y1: number, x2: number, y2: number, radius: number): DartImpl; + beginPath(): DartImpl; + bezierCurveTo(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number): DartImpl; + clearRect(x: number, y: number, w: number, h: number): DartImpl; + closePath(): DartImpl; + clip(path?: string): DartImpl; + drawImage(image: HTMLImageElement, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): DartImpl; + drawImage(image: HTMLImageElement, dx: number, dy: number, dw: number, dh: number): DartImpl; + drawImage(image: HTMLImageElement, dx: number, dy: number): DartImpl; + ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean): DartImpl; + fill(path?: string): DartImpl; + fillRect(x: number, y: number, w: number, h: number): DartImpl; + fillText(text: string, x: number, y: number, maxWidth?: number): DartImpl; + lineTo(x: number, y: number): DartImpl; + moveTo(x: number, y: number): DartImpl; + rect(x: number, y: number, w: number, h: number): DartImpl; + restore(): DartImpl; + resetTransform(): DartImpl; + rotate(angle: number): DartImpl; + quadraticCurveTo(cpx: number, cpy: number, x: number, y: number): DartImpl; + stroke(): DartImpl; + strokeRect(x: number, y: number, w: number, h: number): DartImpl; + save(): DartImpl; + scale(x: number, y: number): DartImpl; + strokeText(text: string, x: number, y: number, maxWidth?: number): DartImpl; + setTransform(a: number, b: number, c: number, d: number, e: number, f: number): DartImpl; + transform(a: number, b: number, c: number, d: number, e: number, f: number): DartImpl; + translate(x: number, y: number): DartImpl; + reset(): DartImpl; + 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..4a236eb54f --- /dev/null +++ b/bridge/core/html/canvas/canvas_rendering_context_2d.h @@ -0,0 +1,27 @@ +/* +* 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(NativeString *method, int32_t argc, const NativeValue *argv) const override; + + bool IsCanvas2d() const override; +}; + +} + +#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 index 47ceb5cd10..3c811468b0 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -3,9 +3,26 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "html_canvas_element.h" +#include "binding_call_methods.h" +#include "foundation/native_value_converter.h" #include "html_names.h" +#include "canvas_types.h" +#include "canvas_rendering_context_2d.h" namespace webf { HTMLCanvasElement::HTMLCanvasElement(Document& document) : HTMLElement(html_names::kcanvas, &document) {} + +CanvasRenderingContext* HTMLCanvasElement::getContext(const AtomicString& type, ExceptionState& exception_state) const { + NativeValue value = InvokeBindingMethod(binding_call_methods::kgetContext, 0, nullptr, exception_state); + NativeBindingObject* native_binding_object = + NativeValueConverter>::FromNativeValue(value); + + if (type == canvas_types::k2d) { + return MakeGarbageCollected(GetExecutingContext(), native_binding_object); + } + + return nullptr; +} + } // namespace webf diff --git a/bridge/core/html/canvas/html_canvas_element.d.ts b/bridge/core/html/canvas/html_canvas_element.d.ts index ab34cf216c..33cc2adf7c 100644 --- a/bridge/core/html/canvas/html_canvas_element.d.ts +++ b/bridge/core/html/canvas/html_canvas_element.d.ts @@ -1,55 +1,8 @@ import {HTMLElement} from "../html_element"; -interface CanvasRenderingContext2D { - 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: HTMLImageElement, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void; - drawImage(image: HTMLImageElement, dx: number, dy: number, dw: number, dh: number): void; - drawImage(image: HTMLImageElement, dx: number, dy: number): void; - ellipse(x: number, y: number, radiusX: number, radiusY: number, rotation: number, startAngle: number, endAngle: number, anticlockwise?: boolean): void; - fill(path?: string): void; - fillRect(x: number, y: number, w: number, h: number): void; - 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; - new(): void; -} - interface HTMLCanvasElement extends HTMLElement { - width: int64; - height: int64; - getContext: (contextType: string) => CanvasRenderingContext2D; + width: DartImpl; + height: DartImpl; + 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 index 1442625112..0691d08ccc 100644 --- a/bridge/core/html/canvas/html_canvas_element.h +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -6,12 +6,17 @@ #define BRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ #include "core/html/html_element.h" +#include "canvas_rendering_context.h" namespace webf { class HTMLCanvasElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); public: explicit HTMLCanvasElement(Document&); + + CanvasRenderingContext* getContext(const AtomicString& type, ExceptionState& exception_state) const; + }; } // namespace webf 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/forms/html_button_element.cc b/bridge/core/html/forms/html_button_element.cc index e19c6343eb..41ee1960bb 100644 --- a/bridge/core/html/forms/html_button_element.cc +++ b/bridge/core/html/forms/html_button_element.cc @@ -1,5 +1,6 @@ -// -// Created by yhtree on 2022/4/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" 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; + type: DartImpl; + name: DartImpl; + value: DartImpl; + new(): void; +} diff --git a/bridge/core/html/forms/html_button_element.h b/bridge/core/html/forms/html_button_element.h index 42efce290f..4240b0cd04 100644 --- a/bridge/core/html/forms/html_button_element.h +++ b/bridge/core/html/forms/html_button_element.h @@ -1,10 +1,23 @@ -// -// Created by yhtree on 2022/4/15. -// +/* +* 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_ -class html_button_element {}; +#include "core/html/html_element.h" + +namespace webf { + +class HTMLButtonElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); + public: + + private: + +}; + +} #endif // BRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_input_element.d.ts b/bridge/core/html/forms/html_input_element.d.ts index ffd542b5d9..a082ffff92 100644 --- a/bridge/core/html/forms/html_input_element.d.ts +++ b/bridge/core/html/forms/html_input_element.d.ts @@ -1,30 +1,30 @@ import {HTMLElement} from "../html_element"; interface HTMLInputElement extends HTMLElement { - width: number; - height: number; - defaultValue: string; - value: string; - accept: string; - autocomplete: string; - autofocus: boolean; - checked: boolean; - disabled: boolean; - min: string; - max: string; - minLength: double; - maxLength: double; - size: double; - multiple: boolean; - name: string; - step: string; - pattern: string; - required: boolean; - readonly: boolean; - placeholder: string - type: string; - inputMode: string; - focus(): void; - blur(): void; + width: DartImpl; + height: DartImpl; + defaultValue: DartImpl; + value: DartImpl; + accept: DartImpl; + autocomplete: DartImpl; + autofocus: DartImpl; + checked: DartImpl; + disabled: DartImpl; + min: DartImpl; + max: DartImpl; + minLength: DartImpl; + maxLength: DartImpl; + size: DartImpl; + multiple: DartImpl; + name: DartImpl; + step: DartImpl; + pattern: DartImpl; + required: DartImpl; + readonly: DartImpl; + placeholder: DartImpl + type: DartImpl; + inputMode: DartImpl; + focus(): DartImpl; + blur(): DartImpl; new(): void; } diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h index ec4547a618..a5d87a47de 100644 --- a/bridge/core/html/forms/html_input_element.h +++ b/bridge/core/html/forms/html_input_element.h @@ -10,6 +10,7 @@ namespace webf { class HTMLInputElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); public: explicit HTMLInputElement(Document&); }; diff --git a/bridge/core/html/forms/html_textarea_element.d.ts b/bridge/core/html/forms/html_textarea_element.d.ts index 5f74c982ab..386344c7cf 100644 --- a/bridge/core/html/forms/html_textarea_element.d.ts +++ b/bridge/core/html/forms/html_textarea_element.d.ts @@ -2,22 +2,22 @@ import {HTMLElement} from "../html_element"; interface HTMLTextAreaElement extends HTMLElement { - defaultValue: string; - value: string; - cols: double; - rows: double; - wrap: string; - autofocus: boolean; - autocomplete: string; - disabled: boolean; - minLength: double; - maxLength: double; - name: string; - placeholder: string; - readonly: boolean; - required: boolean; - inputMode: string; - focus(): void; - blur(): void; + defaultValue: DartImpl; + value: DartImpl; + cols: DartImpl; + rows: DartImpl; + wrap: DartImpl; + autofocus: DartImpl; + autocomplete: DartImpl; + disabled: DartImpl; + minLength: DartImpl; + maxLength: DartImpl; + name: DartImpl; + placeholder: DartImpl; + readonly: DartImpl; + required: DartImpl; + inputMode: DartImpl; + focus(): DartImpl; + blur(): DartImpl; new(): void; } diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h index f7e77681f4..4ec9dd9cd8 100644 --- a/bridge/core/html/forms/html_textarea_element.h +++ b/bridge/core/html/forms/html_textarea_element.h @@ -10,6 +10,7 @@ namespace webf { class HTMLTextareaElement : public HTMLElement { + DEFINE_WRAPPERTYPEINFO(); public: explicit HTMLTextareaElement(Document&); }; diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index 6ac0a2d307..0fd54d5a0c 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -12,6 +12,7 @@ namespace webf { class HTMLImageElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: + using ImplType = HTMLImageElement*; explicit HTMLImageElement(Document& document); bool KeepAlive() const override; diff --git a/bridge/core/html/html_tag_names.json5 b/bridge/core/html/html_tag_names.json5 index a4cf2a2cab..a6710aa070 100644 --- a/bridge/core/html/html_tag_names.json5 +++ b/bridge/core/html/html_tag_names.json5 @@ -19,10 +19,10 @@ ] }, "data": [ -// { -// "name": "canvas", -// "interfaceHeaderDir": "core/html/canvas" -// }, + { + "name": "canvas", + "interfaceHeaderDir": "core/html/canvas" + }, { "name": "a", "interfaceName": "HTMLAnchorElement", @@ -32,15 +32,15 @@ "body", "head", "div", -// { -// "name": "input", -// "interfaceHeaderDir": "core/html/forms" -// }, -// { -// "name": "textarea", -// "interfaceName": "HTMLTextareaElement", -// "interfaceHeaderDir": "core/html/forms" -// }, + { + "name": "input", + "interfaceHeaderDir": "core/html/forms" + }, + { + "name": "textarea", + "interfaceName": "HTMLTextareaElement", + "interfaceHeaderDir": "core/html/forms" + }, "template", { "name": "img", diff --git a/bridge/core/timing/performance.h b/bridge/core/timing/performance.h index a6180a543e..7e313f0259 100644 --- a/bridge/core/timing/performance.h +++ b/bridge/core/timing/performance.h @@ -7,7 +7,7 @@ #define BRIDGE_PERFORMANCE_H #include "bindings/qjs/script_wrappable.h" -#include "core/dom/binding_object.h" +#include "core/binding_object.h" #if ENABLE_PROFILE #define PERF_WIDGET_CREATION_COST "widget_creation_cost" diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 72b28a88c6..7785ae27d2 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -29,8 +29,6 @@ enum NativeTag { enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, - NativeBoundingClientRect = 2, - NativeCanvasRenderingContext2D = 3, BindingObject = 4 }; diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 04022db9b4..a8f0cf3227 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -5,7 +5,7 @@ #ifndef BRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ #define BRIDGE_FOUNDATION_NATIVE_VALUE_CONVERTER_H_ -#include "core/dom/binding_object.h" +#include "core/binding_object.h" #include "native_type.h" #include "native_value.h" @@ -71,34 +71,14 @@ struct NativeValueConverter : public NativeValueConverterBase -struct NativeValueConverter> - : public NativeValueConverterBase> { - static NativeValue ToNativeValue(ImplType value) { - return Native_NewPtr(JSPointerType::NativeBoundingClientRect, value); - } - static ImplType FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } -}; template <> struct NativeValueConverter> : public NativeValueConverterBase> { static NativeValue ToNativeValue(ImplType value) { return Native_NewPtr(JSPointerType::BindingObject, value); } - static ImplType FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } -}; - -template <> -struct NativeValueConverter> - : public NativeValueConverterBase> { - static NativeValue ToNativeValue(ImplType value) { - return Native_NewPtr(JSPointerType::NativeCanvasRenderingContext2D, value); - } + static NativeValue ToNativeValue(BindingObject* value) { return Native_NewPtr(JSPointerType::BindingObject, value->bindingObject()); } static ImplType FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } }; 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 e69de29bb2..0000000000 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 e69de29bb2..0000000000 diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 5c89c699e3..9952fec6e8 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -90,7 +90,9 @@ function getParameterBaseType(type: ts.TypeNode, mode?: ParameterMode): Paramete let identifier = (typeReference.typeName as ts.Identifier).text; if (identifier === 'Function') { return FunctionArgumentType.function; - } else if (identifier === 'int32') { + } else if (identifier === 'Promise') { + return FunctionArgumentType.promise; + }else if (identifier === 'int32') { return FunctionArgumentType.int32; } else if (identifier === 'int64') { return FunctionArgumentType.int64; @@ -138,7 +140,9 @@ function getParameterType(type: ts.TypeNode, mode?: ParameterMode): ParameterTyp function paramsNodeToArguments(parameter: ts.ParameterDeclaration): FunctionArguments { let args = new FunctionArguments(); args.name = getParameterName(parameter.name); - args.type = getParameterType(parameter.type!); + let typeMode = new ParameterMode(); + args.type = getParameterType(parameter.type!, typeMode); + args.typeMode = typeMode; args.required = !parameter.questionToken; return args; } @@ -214,6 +218,7 @@ function walkProgram(statement: ts.Statement) { f.args = []; m.parameters.forEach(params => { let p = paramsNodeToArguments(params); + f.args.push(p); }); obj.methods.push(f); diff --git a/bridge/scripts/code_generator/src/idl/declaration.ts b/bridge/scripts/code_generator/src/idl/declaration.ts index fdd682a0ce..6173af9b27 100644 --- a/bridge/scripts/code_generator/src/idl/declaration.ts +++ b/bridge/scripts/code_generator/src/idl/declaration.ts @@ -4,6 +4,7 @@ export enum FunctionArgumentType { // Basic types dom_string, object, + promise, int32, int64, double, @@ -19,6 +20,7 @@ export enum FunctionArgumentType { export class FunctionArguments { name: string; type: ParameterType[] = []; + typeMode: ParameterMode; required: boolean; } diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 25a98ccf1c..9d38944b93 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -16,12 +16,6 @@ import path from 'path'; import {getTemplateKind, TemplateKind} from "./generateHeader"; import {GenerateOptions} from "./generator"; -enum PropType { - hostObject, - Element, - Event -} - function generateMethodArgumentsCheck(m: FunctionDeclaration) { if (m.args.length == 0) return ''; @@ -113,6 +107,9 @@ export function generateIDLTypeConverter(type: ParameterType[], isOptional?: boo case FunctionArgumentType.object: returnValue = `IDLObject`; break; + case FunctionArgumentType.promise: + returnValue = 'IDLPromise'; + break; default: case FunctionArgumentType.any: returnValue = `IDLAny`; @@ -132,6 +129,10 @@ export function generateIDLTypeConverter(type: ParameterType[], isOptional?: boo function generateNativeValueTypeConverter(type: ParameterType[]): string { let returnValue = ''; + if (typeof type[0] === 'string') { + return `NativeTypePointer`; + } + switch (type[0]) { case FunctionArgumentType.int32: returnValue = `NativeTypeInt64`; @@ -176,13 +177,36 @@ function generateCallMethodName(name: string) { 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}, ${declare.args.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 (options.isInstanceMethod) { + 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 { @@ -238,14 +262,15 @@ function generateFunctionCallBody(blob: IDLBlob, declaration: FunctionDeclaratio if (declaration.returnType[0] != FunctionArgumentType.void) { returnValueAssignment = 'return_value ='; } - if (options.isInstanceMethod) { + 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(',')});`; } - return `${requiredArgumentsInit.join('\n')} if (argc <= ${minimalRequiredArgc}) { ${call} @@ -256,10 +281,6 @@ ${optionalArgumentsInit.join('\n')} `; } -type OverLoadMethods = { - [name: string]: FunctionDeclaration[]; -}; - function generateOverLoadSwitchBody(overloadMethods: FunctionDeclaration[]) { let callBodyList = overloadMethods.map((overload, index) => { return `if (${overload.args.length} == argc) { @@ -323,23 +344,11 @@ function generateReturnValueResult(blob: IDLBlob, type: ParameterType[], mode?: return `return_value->${method}()`; } - if (typeof type[0] === 'string') { - if (type[0] === 'Promise') { - return `return_value.${method}()`; - } else { - return `return_value->${method}()`; - } - } - return `Converter<${generateIDLTypeConverter(type)}>::ToValue(ctx, std::move(return_value))`; } type GenFunctionBodyOptions = { isConstructor?: boolean, isInstanceMethod?: boolean }; -function generateIndexedPropertyBody() { - -} - function generateFunctionBody(blob: IDLBlob, declare: FunctionDeclaration, options: GenFunctionBodyOptions = { isConstructor: false, isInstanceMethod: false diff --git a/webf/lib/src/bridge/native_types.dart b/webf/lib/src/bridge/native_types.dart index 41e2483e43..06df303801 100644 --- a/webf/lib/src/bridge/native_types.dart +++ b/webf/lib/src/bridge/native_types.dart @@ -286,10 +286,6 @@ class NativeBindingObject extends Struct { external Pointer> invokeBindingMethodFromNative; } -class NativeCanvasRenderingContext2D extends Struct { - external Pointer invokeBindingMethod; -} - class NativePerformanceEntry extends Struct { external Pointer name; external Pointer entryType; diff --git a/webf/lib/src/dom/bounding_client_rect.dart b/webf/lib/src/dom/bounding_client_rect.dart index cb7a58446c..ac9406dce7 100644 --- a/webf/lib/src/dom/bounding_client_rect.dart +++ b/webf/lib/src/dom/bounding_client_rect.dart @@ -6,9 +6,10 @@ 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 +20,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 = malloc.allocate(sizeOf()), + 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/element.dart b/webf/lib/src/dom/element.dart index b2aab26aaf..91fdf7b92a 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -259,7 +259,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': 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..e598607495 100644 --- a/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart +++ b/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart @@ -45,10 +45,10 @@ typedef CanvasAction = void Function(Canvas, Size); class CanvasRenderingContext2D extends BindingObject { CanvasRenderingContext2D(this.canvas) - : _pointer = malloc.allocate(ffi.sizeOf()), + : _pointer = malloc.allocate(ffi.sizeOf()), super(); - final ffi.Pointer _pointer; + final ffi.Pointer _pointer; @override get pointer => _pointer; @@ -56,10 +56,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. From e87bd06d1c3f0ecd3abbcf1313681239fd59609c Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Fri, 26 Aug 2022 13:58:36 +0000 Subject: [PATCH 209/498] Committing clang-format changes --- bridge/bindings/qjs/atomic_string.cc | 5 +- bridge/bindings/qjs/binding_initializer.cc | 4 +- bridge/bindings/qjs/converter_impl.h | 6 +- bridge/bindings/qjs/heap_vector.h | 8 +- bridge/bindings/qjs/script_wrappable.cc | 7 +- .../core/css/legacy/css_style_declaration.cc | 2 +- .../css/legacy/css_style_declaration_test.cc | 76 ++++++++++++++++++- bridge/core/dom/child_node_list.cc | 2 +- bridge/core/dom/child_node_list.h | 4 +- bridge/core/dom/collection_index_cache.h | 58 +++++++------- bridge/core/dom/element.cc | 3 +- bridge/core/dom/events/event_target.cc | 4 +- bridge/core/dom/events/event_target.h | 3 +- bridge/core/dom/legacy/bounding_client_rect.h | 2 +- bridge/core/frame/screen.cc | 3 +- .../html/canvas/canvas_rendering_context.cc | 13 ++-- .../html/canvas/canvas_rendering_context.h | 9 ++- .../html/canvas/canvas_rendering_context_2d.h | 7 +- .../core/html/canvas/html_canvas_element.cc | 4 +- bridge/core/html/canvas/html_canvas_element.h | 4 +- bridge/core/html/forms/html_button_element.cc | 6 +- bridge/core/html/forms/html_button_element.h | 11 ++- bridge/core/html/forms/html_input_element.h | 1 + .../core/html/forms/html_textarea_element.h | 1 + bridge/core/html/html_all_collection.h | 3 +- bridge/core/html/html_anchor_element.cc | 2 +- bridge/core/html/html_anchor_element.h | 2 + bridge/core/html/html_image_element.cc | 1 - bridge/core/html/html_image_element.h | 3 +- bridge/core/input/touch_list.cc | 2 +- bridge/foundation/native_value.h | 6 +- bridge/foundation/native_value_converter.h | 9 ++- 32 files changed, 174 insertions(+), 97 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index 56ee8f266b..783d4470be 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -49,7 +49,7 @@ AtomicString::StringKind GetStringKind(const NativeString* native_string) { AtomicString::StringKind predictKind = std::islower(native_string->string()[0]) ? AtomicString::StringKind::kIsLowerCase : AtomicString::StringKind::kIsUpperCase; - for(int i = 0; i < native_string->length(); i ++) { + 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; @@ -102,7 +102,8 @@ bool AtomicString::IsEmpty() const { } std::string AtomicString::ToStdString() const { - if (IsNull()) return ""; + if (IsNull()) + return ""; const char* buf = JS_AtomToCString(ctx_, atom_); std::string result = std::string(buf); diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index f556281ca4..24c55a030c 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -23,14 +23,14 @@ #include "qjs_focus_event.h" #include "qjs_gesture_event.h" #include "qjs_html_all_collection.h" -#include "qjs_html_canvas_element.h" #include "qjs_html_body_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_script_element.h" #include "qjs_html_image_element.h" +#include "qjs_html_script_element.h" #include "qjs_html_template_element.h" #include "qjs_html_unknown_element.h" #include "qjs_input_event.h" diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 1042f790d2..5f7a447fd7 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -339,11 +339,9 @@ struct Converter : public ConverterBase { } }; -template<> +template <> struct Converter : public ConverterBase { - static JSValue ToValue(JSContext* ctx, ImplType value) { - return value.ToQuickJS(); - } + static JSValue ToValue(JSContext* ctx, ImplType value) { return value.ToQuickJS(); } }; template <> diff --git a/bridge/bindings/qjs/heap_vector.h b/bridge/bindings/qjs/heap_vector.h index 8ad3c07b0d..dda66171f7 100644 --- a/bridge/bindings/qjs/heap_vector.h +++ b/bridge/bindings/qjs/heap_vector.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #ifndef BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_ #define BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_ @@ -20,11 +20,11 @@ class HeapVector final { template void HeapVector::Trace(GCVisitor* visitor) const { - for(auto& item : entries_) { + for (auto& item : entries_) { visitor->Trace(item); } } -} +} // namespace webf #endif // BRIDGE_BINDINGS_QJS_HEAP_VECTOR_H_ diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 5844976699..057b0e6ba4 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -10,8 +10,7 @@ namespace webf { ScriptWrappable::ScriptWrappable(JSContext* ctx) - : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), context_(ExecutingContext::From(ctx)) { -} + : ctx_(ctx), runtime_(JS_GetRuntime(ctx)), context_(ExecutingContext::From(ctx)) {} JSValue ScriptWrappable::ToQuickJS() const { return JS_DupValue(ctx_, jsObject_); @@ -156,8 +155,8 @@ void ScriptWrappable::InitializeQuickJSObject() { 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 { + 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(); diff --git a/bridge/core/css/legacy/css_style_declaration.cc b/bridge/core/css/legacy/css_style_declaration.cc index 6782a5d2fe..5a97c19d52 100644 --- a/bridge/core/css/legacy/css_style_declaration.cc +++ b/bridge/core/css/legacy/css_style_declaration.cc @@ -114,7 +114,7 @@ bool CSSStyleDeclaration::NamedPropertyQuery(const AtomicString& key, ExceptionS } void CSSStyleDeclaration::NamedPropertyEnumerator(std::vector& names, ExceptionState&) { - for(auto& entry : cssPropertyList) { + for (auto& entry : cssPropertyList) { names.emplace_back(AtomicString(ctx(), entry.first)); } } diff --git a/bridge/core/css/legacy/css_style_declaration_test.cc b/bridge/core/css/legacy/css_style_declaration_test.cc index 8c29a118ff..42f28251b4 100644 --- a/bridge/core/css/legacy/css_style_declaration_test.cc +++ b/bridge/core/css/legacy/css_style_declaration_test.cc @@ -29,15 +29,85 @@ TEST(CSSStyleDeclaration, enumerateStyles) { bool static logCalled = false; webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { logCalled = true; - EXPECT_STREQ(message.c_str(), "['zoom', 'writingMode', 'wordSpacing', 'wordBreak', 'willChange', 'width', 'widows', 'visibility', 'vectorEffect', 'userZoom', 'userSelect', 'unicodeRange', 'unicodeBidi', 'transitionProperty', 'transition', 'touchAction', 'textRendering', 'range', 'textOverflow', 'breakAfter', 'order', 'textIndent', 'textUnderlineOffset', 'textEmphasisColor', 'textDecorationThickness', 'textDecorationSkipInk', 'markerEnd', 'textDecorationLine', 'textAnchor', 'tableLayout', 'listStyleType', 'tabSize', 'system', 'scrollMarginBlock', 'syntax', 'textEmphasisStyle', 'offsetDistance', 'isolation', 'symbols', 'scrollPaddingLeft', 'strokeWidth', 'strokeOpacity', 'strokeLinejoin', 'stopOpacity', 'fillOpacity', 'strokeMiterlimit', 'fontSynthesisWeight', 'sizeAdjust', 'direction', 'pageOrientation', 'size', 'ascentOverride', 'shapeOutside', 'shapeImageThreshold', 'scrollSnapType', 'borderTopStyle', 'scrollSnapAlign', 'borderBottomRightRadius', 'textTransform', 'textAlign', 'columnFill', 'scrollSnapStop', 'wordWrap', 'scrollPaddingTop', 'scrollPaddingRight', 'scrollPaddingInlineStart', 'gridArea', 'textEmphasisPosition', 'animationDuration', 'scrollPaddingBottom', 'scrollPaddingBlockEnd', 'stroke', 'scrollMarginLeft', 'scrollPadding', 'float', 'scrollMargin', 'backgroundClip', 'shapeRendering', 'borderStartEndRadius', 'rx', 'transitionDelay', 'flexShrink', 'rowGap', 'colorScheme', 'prefix', 'position', 'pageBreakAfter', 'pointerEvents', 'placeSelf', 'placeItems', 'scrollBehavior', 'scrollMarginBottom', 'perspective', 'borderInlineStartStyle', 'strokeDasharray', 'borderBottomStyle', 'gridTemplateAreas', 'pageBreakBefore', 'transitionTimingFunction', 'scrollPaddingInlineEnd', 'paddingLeft', 'paddingInlineStart', 'paddingInlineEnd', 'paddingInline', 'aspectRatio', 'paddingBlockStart', 'top', 'scrollPaddingBlock', 'paddingBlock', 'padding', 'overscrollBehaviorX', 'overscrollBehaviorInline', 'overscrollBehaviorBlock', 'overscrollBehavior', 'overflowWrap', 'listStylePosition', 'right', 'overflow', 'quotes', 'objectPosition', 'outlineWidth', 'outlineOffset', 'outline', 'orphans', 'borderBlockEndWidth', 'orientation', 'opacity', 'overflowY', 'maxZoom', 'objectFit', 'minWidth', 'textUnderlinePosition', 'minInlineSize', 'minHeight', 'y', 'paintOrder', 'columnGap', 'transformOrigin', 'borderLeft', 'minBlockSize', 'maxWidth', 'maxInlineSize', 'alignmentBaseline', 'color', 'maxHeight', 'maskType', 'markerMid', 'marker', 'marginInline', 'marginBottom', 'marginBlockStart', 'left', 'marginBlockEnd', 'textDecorationColor', 'marginBlock', 'textSizeAdjust', 'marginRight', 'margin', 'perspectiveOrigin', 'offsetPath', 'listStyle', 'lineGapOverride', 'overflowClipMargin', 'letterSpacing', 'justifySelf', 'markerStart', 'insetInlineStart', 'placeContent', 'insetInline', 'backgroundRepeatY', 'fontWeight', 'r', 'x', 'insetBlockEnd', 'borderSpacing', 'insetBlock', 'height', 'inset', 'offset', 'inlineSize', 'suffix', 'borderBlockColor', 'clip', 'initialValue', 'inherits', 'paddingBlockEnd', 'backgroundImage', 'imageRendering', 'mask', 'textEmphasis', 'hyphens', 'outlineStyle', 'textCombineUpright', 'borderRight', 'marginLeft', 'gridTemplateRows', 'marginInlineEnd', 'transformBox', 'resize', 'gridRowEnd', 'borderBlockEndColor', 'shapeMargin', 'gridColumnEnd', 'gridColumn', 'borderImageOutset', 'flexDirection', 'fallback', 'lightingColor', 'gridAutoFlow', 'borderRightWidth', 'gap', 'scrollMarginInline', 'fontVariantCaps', 'fontVariantEastAsian', 'textDecoration', 'insetBlockStart', 'fontSynthesisSmallCaps', 'fontStyle', 'appearance', 'overscrollBehaviorY', 'borderInlineWidth', 'filter', 'verticalAlign', 'backgroundAttachment', 'fontSize', 'gridColumnGap', 'flex', 'fontOpticalSizing', 'gridRowGap', 'fontFamily', 'font', 'colorInterpolationFilters', 'flexFlow', 'backgroundRepeatX', 'columnRuleColor', 'fillRule', 'emptyCells', 'display', 'textShadow', 'animationFillMode', 'floodColor', 'descentOverride', 'gridAutoRows', 'fontVariationSettings', 'stopColor', 'fontFeatureSettings', 'cursor', 'paddingRight', 'accentColor', 'borderColor', 'backdropFilter', 'counterReset', 'content', 'columns', 'cx', 'mixBlendMode', 'fontKerning', 'columnWidth', 'overflowAnchor', 'alignContent', 'columnSpan', 'zIndex', 'columnRule', 'backgroundRepeat', 'fontVariantNumeric', 'borderBlockStartStyle', 'columnCount', 'textAlignLast', 'fontVariant', 'colorRendering', 'lineHeight', 'borderBlockEndStyle', 'borderInlineEndColor', 'colorInterpolation', 'src', 'lineBreak', 'clipRule', 'clipPath', 'clear', 'floodOpacity', 'alignSelf', 'gridAutoColumns', 'caretColor', 'justifyItems', 'captionSide', 'backgroundBlendMode', 'bufferedRendering', 'listStyleImage', 'forcedColorAdjust', 'animationName', 'counterSet', 'breakInside', 'boxSizing', 'columnRuleStyle', 'justifyContent', 'textOrientation', 'breakBefore', 'outlineColor', 'borderTopWidth', 'all', 'gridColumnStart', 'minZoom', 'borderTopLeftRadius', 'marginTop', 'borderBlockStyle', 'backgroundOrigin', 'borderTop', 'cy', 'speakAs', 'negative', 'borderStartStartRadius', 'backgroundPositionY', 'borderLeftStyle', 'boxShadow', 'blockSize', 'borderInlineStartWidth', 'borderInlineEnd', 'borderInline', 'gridRowStart', 'fill', 'borderImageWidth', 'additiveSymbols', 'scrollMarginBlockEnd', 'borderImageSlice', 'borderImage', 'borderBottomLeftRadius', 'borderBottomWidth', 'borderImageRepeat', 'textDecorationStyle', 'borderRightStyle', 'page', 'imageOrientation', 'borderEndEndRadius', 'gridGap', 'scrollMarginInlineEnd', 'gridTemplateColumns', 'flexWrap', 'borderInlineColor', 'borderBottomColor', 'scrollMarginInlineStart', 'fontDisplay', 'dominantBaseline', 'borderRadius', 'borderBottom', 'borderBlockWidth', 'baselineShift', 'gridTemplate', 'borderBlockStartWidth', 'whiteSpace', 'fontSynthesis', 'fontSynthesisStyle', 'borderBlockStart', 'borderTopRightRadius', 'transformStyle', 'animation', 'marginInlineStart', 'borderInlineStyle', 'fontVariantLigatures', 'borderInlineStartColor', 'borderInlineStart', 'backgroundSize', 'scrollMarginBlockStart', 'borderEndStartRadius', 'backgroundPosition', 'scrollPaddingBlockStart', 'insetInlineEnd', 'borderLeftColor', 'border', 'flexBasis', 'borderInlineEndStyle', 'borderWidth', 'counterIncrement', 'ry', 'contentVisibility', 'background', 'borderCollapse', 'borderBlock', 'offsetRotate', 'animationTimingFunction', 'pad', 'maxBlockSize', 'fontStretch', 'animationDelay', 'speak', 'paddingBottom', 'borderLeftWidth', 'borderImageSource', 'gridRow', 'columnRuleWidth', 'backfaceVisibility', 'flexGrow', 'strokeDashoffset', 'grid', 'scrollbarGutter', 'scrollPaddingInline', 'borderStyle', 'animationIterationCount', 'animationPlayState', 'rubyPosition', 'animationDirection', 'paddingTop', 'pageBreakInside', 'd', 'transform', 'scrollMarginRight', 'bottom', 'overflowX', 'borderTopColor', 'appRegion', 'backgroundColor', 'transitionDuration', 'alignItems', 'borderBlockStartColor', 'borderBlockEnd', 'strokeLinecap', 'borderRightColor', 'scrollMarginTop', 'borderInlineEndWidth', 'backgroundPositionX']"); + EXPECT_STREQ( + message.c_str(), + "['zoom', 'writingMode', 'wordSpacing', 'wordBreak', 'willChange', 'width', 'widows', 'visibility', " + "'vectorEffect', 'userZoom', 'userSelect', 'unicodeRange', 'unicodeBidi', 'transitionProperty', 'transition', " + "'touchAction', 'textRendering', 'range', 'textOverflow', 'breakAfter', 'order', 'textIndent', " + "'textUnderlineOffset', 'textEmphasisColor', 'textDecorationThickness', 'textDecorationSkipInk', 'markerEnd', " + "'textDecorationLine', 'textAnchor', 'tableLayout', 'listStyleType', 'tabSize', 'system', 'scrollMarginBlock', " + "'syntax', 'textEmphasisStyle', 'offsetDistance', 'isolation', 'symbols', 'scrollPaddingLeft', 'strokeWidth', " + "'strokeOpacity', 'strokeLinejoin', 'stopOpacity', 'fillOpacity', 'strokeMiterlimit', 'fontSynthesisWeight', " + "'sizeAdjust', 'direction', 'pageOrientation', 'size', 'ascentOverride', 'shapeOutside', " + "'shapeImageThreshold', 'scrollSnapType', 'borderTopStyle', 'scrollSnapAlign', 'borderBottomRightRadius', " + "'textTransform', 'textAlign', 'columnFill', 'scrollSnapStop', 'wordWrap', 'scrollPaddingTop', " + "'scrollPaddingRight', 'scrollPaddingInlineStart', 'gridArea', 'textEmphasisPosition', 'animationDuration', " + "'scrollPaddingBottom', 'scrollPaddingBlockEnd', 'stroke', 'scrollMarginLeft', 'scrollPadding', 'float', " + "'scrollMargin', 'backgroundClip', 'shapeRendering', 'borderStartEndRadius', 'rx', 'transitionDelay', " + "'flexShrink', 'rowGap', 'colorScheme', 'prefix', 'position', 'pageBreakAfter', 'pointerEvents', 'placeSelf', " + "'placeItems', 'scrollBehavior', 'scrollMarginBottom', 'perspective', 'borderInlineStartStyle', " + "'strokeDasharray', 'borderBottomStyle', 'gridTemplateAreas', 'pageBreakBefore', 'transitionTimingFunction', " + "'scrollPaddingInlineEnd', 'paddingLeft', 'paddingInlineStart', 'paddingInlineEnd', 'paddingInline', " + "'aspectRatio', 'paddingBlockStart', 'top', 'scrollPaddingBlock', 'paddingBlock', 'padding', " + "'overscrollBehaviorX', 'overscrollBehaviorInline', 'overscrollBehaviorBlock', 'overscrollBehavior', " + "'overflowWrap', 'listStylePosition', 'right', 'overflow', 'quotes', 'objectPosition', 'outlineWidth', " + "'outlineOffset', 'outline', 'orphans', 'borderBlockEndWidth', 'orientation', 'opacity', 'overflowY', " + "'maxZoom', 'objectFit', 'minWidth', 'textUnderlinePosition', 'minInlineSize', 'minHeight', 'y', 'paintOrder', " + "'columnGap', 'transformOrigin', 'borderLeft', 'minBlockSize', 'maxWidth', 'maxInlineSize', " + "'alignmentBaseline', 'color', 'maxHeight', 'maskType', 'markerMid', 'marker', 'marginInline', 'marginBottom', " + "'marginBlockStart', 'left', 'marginBlockEnd', 'textDecorationColor', 'marginBlock', 'textSizeAdjust', " + "'marginRight', 'margin', 'perspectiveOrigin', 'offsetPath', 'listStyle', 'lineGapOverride', " + "'overflowClipMargin', 'letterSpacing', 'justifySelf', 'markerStart', 'insetInlineStart', 'placeContent', " + "'insetInline', 'backgroundRepeatY', 'fontWeight', 'r', 'x', 'insetBlockEnd', 'borderSpacing', 'insetBlock', " + "'height', 'inset', 'offset', 'inlineSize', 'suffix', 'borderBlockColor', 'clip', 'initialValue', 'inherits', " + "'paddingBlockEnd', 'backgroundImage', 'imageRendering', 'mask', 'textEmphasis', 'hyphens', 'outlineStyle', " + "'textCombineUpright', 'borderRight', 'marginLeft', 'gridTemplateRows', 'marginInlineEnd', 'transformBox', " + "'resize', 'gridRowEnd', 'borderBlockEndColor', 'shapeMargin', 'gridColumnEnd', 'gridColumn', " + "'borderImageOutset', 'flexDirection', 'fallback', 'lightingColor', 'gridAutoFlow', 'borderRightWidth', 'gap', " + "'scrollMarginInline', 'fontVariantCaps', 'fontVariantEastAsian', 'textDecoration', 'insetBlockStart', " + "'fontSynthesisSmallCaps', 'fontStyle', 'appearance', 'overscrollBehaviorY', 'borderInlineWidth', 'filter', " + "'verticalAlign', 'backgroundAttachment', 'fontSize', 'gridColumnGap', 'flex', 'fontOpticalSizing', " + "'gridRowGap', 'fontFamily', 'font', 'colorInterpolationFilters', 'flexFlow', 'backgroundRepeatX', " + "'columnRuleColor', 'fillRule', 'emptyCells', 'display', 'textShadow', 'animationFillMode', 'floodColor', " + "'descentOverride', 'gridAutoRows', 'fontVariationSettings', 'stopColor', 'fontFeatureSettings', 'cursor', " + "'paddingRight', 'accentColor', 'borderColor', 'backdropFilter', 'counterReset', 'content', 'columns', 'cx', " + "'mixBlendMode', 'fontKerning', 'columnWidth', 'overflowAnchor', 'alignContent', 'columnSpan', 'zIndex', " + "'columnRule', 'backgroundRepeat', 'fontVariantNumeric', 'borderBlockStartStyle', 'columnCount', " + "'textAlignLast', 'fontVariant', 'colorRendering', 'lineHeight', 'borderBlockEndStyle', " + "'borderInlineEndColor', 'colorInterpolation', 'src', 'lineBreak', 'clipRule', 'clipPath', 'clear', " + "'floodOpacity', 'alignSelf', 'gridAutoColumns', 'caretColor', 'justifyItems', 'captionSide', " + "'backgroundBlendMode', 'bufferedRendering', 'listStyleImage', 'forcedColorAdjust', 'animationName', " + "'counterSet', 'breakInside', 'boxSizing', 'columnRuleStyle', 'justifyContent', 'textOrientation', " + "'breakBefore', 'outlineColor', 'borderTopWidth', 'all', 'gridColumnStart', 'minZoom', 'borderTopLeftRadius', " + "'marginTop', 'borderBlockStyle', 'backgroundOrigin', 'borderTop', 'cy', 'speakAs', 'negative', " + "'borderStartStartRadius', 'backgroundPositionY', 'borderLeftStyle', 'boxShadow', 'blockSize', " + "'borderInlineStartWidth', 'borderInlineEnd', 'borderInline', 'gridRowStart', 'fill', 'borderImageWidth', " + "'additiveSymbols', 'scrollMarginBlockEnd', 'borderImageSlice', 'borderImage', 'borderBottomLeftRadius', " + "'borderBottomWidth', 'borderImageRepeat', 'textDecorationStyle', 'borderRightStyle', 'page', " + "'imageOrientation', 'borderEndEndRadius', 'gridGap', 'scrollMarginInlineEnd', 'gridTemplateColumns', " + "'flexWrap', 'borderInlineColor', 'borderBottomColor', 'scrollMarginInlineStart', 'fontDisplay', " + "'dominantBaseline', 'borderRadius', 'borderBottom', 'borderBlockWidth', 'baselineShift', 'gridTemplate', " + "'borderBlockStartWidth', 'whiteSpace', 'fontSynthesis', 'fontSynthesisStyle', 'borderBlockStart', " + "'borderTopRightRadius', 'transformStyle', 'animation', 'marginInlineStart', 'borderInlineStyle', " + "'fontVariantLigatures', 'borderInlineStartColor', 'borderInlineStart', 'backgroundSize', " + "'scrollMarginBlockStart', 'borderEndStartRadius', 'backgroundPosition', 'scrollPaddingBlockStart', " + "'insetInlineEnd', 'borderLeftColor', 'border', 'flexBasis', 'borderInlineEndStyle', 'borderWidth', " + "'counterIncrement', 'ry', 'contentVisibility', 'background', 'borderCollapse', 'borderBlock', 'offsetRotate', " + "'animationTimingFunction', 'pad', 'maxBlockSize', 'fontStretch', 'animationDelay', 'speak', 'paddingBottom', " + "'borderLeftWidth', 'borderImageSource', 'gridRow', 'columnRuleWidth', 'backfaceVisibility', 'flexGrow', " + "'strokeDashoffset', 'grid', 'scrollbarGutter', 'scrollPaddingInline', 'borderStyle', " + "'animationIterationCount', 'animationPlayState', 'rubyPosition', 'animationDirection', 'paddingTop', " + "'pageBreakInside', 'd', 'transform', 'scrollMarginRight', 'bottom', 'overflowX', 'borderTopColor', " + "'appRegion', 'backgroundColor', 'transitionDuration', 'alignItems', 'borderBlockStartColor', " + "'borderBlockEnd', 'strokeLinecap', 'borderRightColor', 'scrollMarginTop', 'borderInlineEndWidth', " + "'backgroundPositionX']"); }; 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(Object.keys(document.body.style))"; + const char* code = "console.log(Object.keys(document.body.style))"; bridge->evaluateScript(code, strlen(code), "vm://", 0); EXPECT_EQ(errorCalled, false); EXPECT_EQ(logCalled, true); diff --git a/bridge/core/dom/child_node_list.cc b/bridge/core/dom/child_node_list.cc index 63ad81f5b3..85a83cfd54 100644 --- a/bridge/core/dom/child_node_list.cc +++ b/bridge/core/dom/child_node_list.cc @@ -26,7 +26,7 @@ bool ChildNodeList::NamedPropertyQuery(const AtomicString& key, ExceptionState& void ChildNodeList::NamedPropertyEnumerator(std::vector& names, ExceptionState& exception_state) { uint32_t size = collection_index_cache_.NodeCount(*this); - for (int i = 0; i < size; i ++) { + for (int i = 0; i < size; i++) { names.emplace_back(AtomicString(ctx(), std::to_string(i))); } } diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h index ca709740cb..163e0bf867 100644 --- a/bridge/core/dom/child_node_list.h +++ b/bridge/core/dom/child_node_list.h @@ -25,8 +25,8 @@ class ChildNodeList : public NodeList { 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; + 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(); } diff --git a/bridge/core/dom/collection_index_cache.h b/bridge/core/dom/collection_index_cache.h index 953f9db3da..215a003473 100644 --- a/bridge/core/dom/collection_index_cache.h +++ b/bridge/core/dom/collection_index_cache.h @@ -1,33 +1,33 @@ /* -* 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. -*/ + * 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_ diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 14c4e9e6ca..188d905601 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -74,8 +74,7 @@ BoundingClientRect* Element::getBoundingClientRect(ExceptionState& exception_sta GetExecutingContext()->FlushUICommand(); NativeValue result = InvokeBindingMethod(binding_call_methods::kgetBoundingClientRect, 0, nullptr, exception_state); return BoundingClientRect::Create( - GetExecutingContext(), - NativeValueConverter>::FromNativeValue(result)); + GetExecutingContext(), NativeValueConverter>::FromNativeValue(result)); } void Element::click(ExceptionState& exception_state) { diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index a92184787e..b293c7aaa0 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -46,7 +46,9 @@ 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++) {} + : BindingObject(context, native_binding_object), + ScriptWrappable(context->ctx()), + event_target_id_(global_event_target_id++) {} Node* EventTarget::ToNode() { return nullptr; diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index b44b6022bb..a48ac0fe01 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -159,7 +159,8 @@ 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){}; + explicit EventTargetWithInlineData(ExecutingContext* context, NativeBindingObject* native_binding_object) + : EventTarget(context, native_binding_object){}; void Trace(GCVisitor* visitor) const override; diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index 22f7e427d3..ae84fc038a 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -22,7 +22,7 @@ class BoundingClientRect : public ScriptWrappable, public BindingObject { static BoundingClientRect* Create(ExecutingContext* context, NativeBindingObject* native_binding_object); explicit BoundingClientRect(ExecutingContext* context, NativeBindingObject* native_binding_object); - NativeValue HandleCallFromDartSide(NativeString *method, int32_t argc, const NativeValue *argv) const override; + NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const override; double x() const { return x_; } double y() const { return y_; } diff --git a/bridge/core/frame/screen.cc b/bridge/core/frame/screen.cc index bccfbd4044..e02ee7f93e 100644 --- a/bridge/core/frame/screen.cc +++ b/bridge/core/frame/screen.cc @@ -10,7 +10,6 @@ namespace webf { Screen::Screen(Window* window, NativeBindingObject* native_binding_object) - : EventTargetWithInlineData(window->GetExecutingContext(), native_binding_object) { -} + : EventTargetWithInlineData(window->GetExecutingContext(), native_binding_object) {} } // namespace webf diff --git a/bridge/core/html/canvas/canvas_rendering_context.cc b/bridge/core/html/canvas/canvas_rendering_context.cc index 0c97f3488b..87627687bc 100644 --- a/bridge/core/html/canvas/canvas_rendering_context.cc +++ b/bridge/core/html/canvas/canvas_rendering_context.cc @@ -1,15 +1,16 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * 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()) { -} +CanvasRenderingContext::CanvasRenderingContext(ExecutingContext* context) : ScriptWrappable(context->ctx()) {} -bool CanvasRenderingContext::IsCanvas2d() const { return false; } +bool CanvasRenderingContext::IsCanvas2d() const { + return false; +} -} \ No newline at end of file +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/canvas/canvas_rendering_context.h b/bridge/core/html/canvas/canvas_rendering_context.h index 370da73627..44340fd2da 100644 --- a/bridge/core/html/canvas/canvas_rendering_context.h +++ b/bridge/core/html/canvas/canvas_rendering_context.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * 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_ @@ -12,15 +12,16 @@ namespace webf { class CanvasRenderingContext : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = CanvasRenderingContext*; explicit CanvasRenderingContext(ExecutingContext* context); virtual bool IsCanvas2d() const; - private: + private: }; -} +} // namespace webf #endif // BRIDGE_CORE_HTML_CANVAS_CANVAS_RENDERING_CONTEXT_H_ diff --git a/bridge/core/html/canvas/canvas_rendering_context_2d.h b/bridge/core/html/canvas/canvas_rendering_context_2d.h index 4a236eb54f..7104912c7e 100644 --- a/bridge/core/html/canvas/canvas_rendering_context_2d.h +++ b/bridge/core/html/canvas/canvas_rendering_context_2d.h @@ -1,5 +1,5 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #ifndef BRIDGE_CORE_HTML_CANVAS_CANVAS_RENDERING_CONTEXT_2D_H_ @@ -12,16 +12,17 @@ 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(NativeString *method, int32_t argc, const NativeValue *argv) const override; + NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const 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 index 3c811468b0..ea2f83566b 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -4,10 +4,10 @@ */ #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 "canvas_types.h" -#include "canvas_rendering_context_2d.h" namespace webf { diff --git a/bridge/core/html/canvas/html_canvas_element.h b/bridge/core/html/canvas/html_canvas_element.h index 0691d08ccc..3ed579b08d 100644 --- a/bridge/core/html/canvas/html_canvas_element.h +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -5,18 +5,18 @@ #ifndef BRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ #define BRIDGE_CORE_HTML_CANVAS_HTML_CANVAS_ELEMENT_H_ -#include "core/html/html_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; - }; } // namespace webf diff --git a/bridge/core/html/forms/html_button_element.cc b/bridge/core/html/forms/html_button_element.cc index 41ee1960bb..3d44078f1a 100644 --- a/bridge/core/html/forms/html_button_element.cc +++ b/bridge/core/html/forms/html_button_element.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_button_element.h" diff --git a/bridge/core/html/forms/html_button_element.h b/bridge/core/html/forms/html_button_element.h index 4240b0cd04..1d454a5be7 100644 --- a/bridge/core/html/forms/html_button_element.h +++ b/bridge/core/html/forms/html_button_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ #define BRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ @@ -12,12 +12,11 @@ namespace webf { class HTMLButtonElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); - public: + public: private: - }; -} +} // namespace webf #endif // BRIDGE_CORE_HTML_FORMS_HTML_BUTTON_ELEMENT_H_ diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h index a5d87a47de..d1937c79fa 100644 --- a/bridge/core/html/forms/html_input_element.h +++ b/bridge/core/html/forms/html_input_element.h @@ -11,6 +11,7 @@ namespace webf { class HTMLInputElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); + public: explicit HTMLInputElement(Document&); }; diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h index 4ec9dd9cd8..41ea84f071 100644 --- a/bridge/core/html/forms/html_textarea_element.h +++ b/bridge/core/html/forms/html_textarea_element.h @@ -11,6 +11,7 @@ namespace webf { class HTMLTextareaElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); + public: explicit HTMLTextareaElement(Document&); }; diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h index 2d1bbd9d49..cfbb212ca5 100644 --- a/bridge/core/html/html_all_collection.h +++ b/bridge/core/html/html_all_collection.h @@ -12,10 +12,11 @@ namespace webf { class HTMLAllCollection : public HTMLCollection { DEFINE_WRAPPERTYPEINFO(); + public: 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 index 35157cde9c..1615384da8 100644 --- a/bridge/core/html/html_anchor_element.cc +++ b/bridge/core/html/html_anchor_element.cc @@ -7,6 +7,6 @@ namespace webf { -HTMLAnchorElement::HTMLAnchorElement(Document& document): HTMLElement(html_names::ka, &document) {} +HTMLAnchorElement::HTMLAnchorElement(Document& document) : HTMLElement(html_names::ka, &document) {} } // namespace webf diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h index 4b862f7b38..51a96c7858 100644 --- a/bridge/core/html/html_anchor_element.h +++ b/bridge/core/html/html_anchor_element.h @@ -11,8 +11,10 @@ namespace webf { class HTMLAnchorElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); + public: explicit HTMLAnchorElement(Document& document); + private: }; diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index e384d0ac0b..912b5c0f4d 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -19,5 +19,4 @@ bool HTMLImageElement::KeepAlive() const { return true; } - } // namespace webf diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index 0fd54d5a0c..d61889478c 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -11,6 +11,7 @@ namespace webf { class HTMLImageElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = HTMLImageElement*; explicit HTMLImageElement(Document& document); @@ -18,8 +19,8 @@ class HTMLImageElement : public HTMLElement { bool KeepAlive() const override; ScriptPromise decode(ExceptionState& exception_state) const; - private: + private: }; } // namespace webf diff --git a/bridge/core/input/touch_list.cc b/bridge/core/input/touch_list.cc index e45a5b5d1c..047a477c60 100644 --- a/bridge/core/input/touch_list.cc +++ b/bridge/core/input/touch_list.cc @@ -29,7 +29,7 @@ bool TouchList::NamedPropertyQuery(const AtomicString& key, ExceptionState& exce } void TouchList::NamedPropertyEnumerator(std::vector& props, ExceptionState& exception_state) { - for(int i = 0; i < values_.size(); i ++) { + for (int i = 0; i < values_.size(); i++) { props.emplace_back(AtomicString(ctx(), std::to_string(i))); } } diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 7785ae27d2..1ec2448197 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -26,11 +26,7 @@ enum NativeTag { TAG_ASYNC_FUNCTION = 8, }; -enum class JSPointerType { - AsyncContextContext = 0, - NativeFunctionContext = 1, - BindingObject = 4 -}; +enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, BindingObject = 4 }; class ExecutingContext; class ScriptValue; diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index a8f0cf3227..7ccb763847 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -34,7 +34,10 @@ template <> struct NativeValueConverter : public NativeValueConverterBase { static NativeValue ToNativeValue(const ImplType& value) { return Native_NewString(value.ToNativeString().release()); } - static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { return AtomicString(ctx, static_cast(value.u.ptr));; } + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + return AtomicString(ctx, static_cast(value.u.ptr)); + ; + } }; template <> @@ -78,7 +81,9 @@ template <> struct NativeValueConverter> : public NativeValueConverterBase> { static NativeValue ToNativeValue(ImplType value) { return Native_NewPtr(JSPointerType::BindingObject, value); } - static NativeValue ToNativeValue(BindingObject* value) { return Native_NewPtr(JSPointerType::BindingObject, value->bindingObject()); } + static NativeValue ToNativeValue(BindingObject* value) { + return Native_NewPtr(JSPointerType::BindingObject, value->bindingObject()); + } static ImplType FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } }; From 17faabfeec01391825b142a18a98cf2b52a83a07 Mon Sep 17 00:00:00 2001 From: andycall Date: Sat, 27 Aug 2022 11:01:45 +0800 Subject: [PATCH 210/498] fix: fix ts failed. --- bridge/core/binding_object.cc | 1 - bridge/core/dom/events/event_target.cc | 2 +- bridge/scripts/code_generator/package.json | 2 +- .../code_generator/src/idl/analyzer.ts | 1 + integration_tests/lib/bridge/from_native.dart | 60 ++++++++++++------- integration_tests/lib/plugin.dart | 9 ++- webf/lib/src/module/timer.dart | 3 - 7 files changed, 49 insertions(+), 29 deletions(-) diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 63282172ce..cb73c3f1b7 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -42,7 +42,6 @@ NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, return Native_NewNull(); } - WEBF_LOG(VERBOSE) << " binding object_ " << &binding_object_; NativeValue return_value = Native_NewNull(); binding_object_->invoke_bindings_methods_from_native(binding_object_, &return_value, method.ToNativeString().release(), argc, argv); diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index b293c7aaa0..66bec29f56 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -39,7 +39,7 @@ EventTarget* EventTarget::Create(ExecutingContext* context, ExceptionState& exce } EventTarget::~EventTarget() { - // GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, nullptr); } EventTarget::EventTarget(ExecutingContext* context) diff --git a/bridge/scripts/code_generator/package.json b/bridge/scripts/code_generator/package.json index 9595178807..29cf3bce23 100644 --- a/bridge/scripts/code_generator/package.json +++ b/bridge/scripts/code_generator/package.json @@ -15,6 +15,6 @@ "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/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 9952fec6e8..0b64f72b7f 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -262,6 +262,7 @@ function walkProgram(statement: ts.Statement) { }); if (!constructorDefined && obj.kind === ClassObjectKind.interface) { + console.log(obj.kind); throw new Error(`Interface: ${interfaceName} didn't have constructor defined.`); } diff --git a/integration_tests/lib/bridge/from_native.dart b/integration_tests/lib/bridge/from_native.dart index f674adda4c..11f65cdf6c 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 charStr) { _listenerList[contextId](msg); } -final Pointer> _nativeOnJsError = Pointer.fromFunction(_onJSError); +final Pointer> _nativeOnJsError = + Pointer.fromFunction(_onJSError); typedef NativeMatchImageSnapshotCallback = Void Function( Pointer callbackContext, Int32 contextId, Int8, Pointer); typedef DartMatchImageSnapshotCallback = void Function( Pointer callbackContext, int contextId, int, Pointer); -typedef NativeMatchImageSnapshot = Void Function(Pointer callbackContext, Int32 contextId, Pointer, Int32, - Pointer, Pointer>); - -void _matchImageSnapshot(Pointer callbackContext, int contextId, Pointer bytes, int size, - Pointer snapshotNamePtr, Pointer> pointer) { +typedef NativeMatchImageSnapshot = Void Function( + Pointer callbackContext, + Int32 contextId, + Pointer, + Int32, + Pointer, + Pointer>); + +void _matchImageSnapshot( + Pointer callbackContext, + int contextId, + Pointer bytes, + int size, + Pointer snapshotNamePtr, + Pointer> pointer) { DartMatchImageSnapshotCallback callback = pointer.asFunction(); String filename = nativeStringToString(snapshotNamePtr); matchImageSnapshot(bytes.asTypedList(size), filename).then((value) { @@ -67,8 +78,8 @@ void _matchImageSnapshot(Pointer callbackContext, int contextId, Pointer> _nativeMatchImageSnapshot = - Pointer.fromFunction(_matchImageSnapshot); +final Pointer> + _nativeMatchImageSnapshot = Pointer.fromFunction(_matchImageSnapshot); typedef NativeEnvironment = Pointer Function(); typedef DartEnvironment = Pointer Function(); @@ -77,9 +88,11 @@ Pointer _environment() { return (jsonEncode(Platform.environment)).toNativeUtf8(); } -final Pointer> _nativeEnvironment = Pointer.fromFunction(_environment); +final Pointer> _nativeEnvironment = + Pointer.fromFunction(_environment); -typedef NativeSimulatePointer = Void Function(Pointer>, Int32 length, Int32 pointer); +typedef NativeSimulatePointer = Void Function( + Pointer>, Int32 length, Int32 pointer); typedef NativeSimulateInputText = Void Function(Pointer); PointerChange _getPointerChange(double change) { @@ -100,7 +113,8 @@ class MousePointer extends Struct { external double change; } -void _simulatePointer(Pointer> mousePointerList, int length, int pointer) { +void _simulatePointer( + Pointer> mousePointerList, int length, int pointer) { List data = []; for (int i = 0; i < length; i++) { @@ -125,7 +139,8 @@ void _simulatePointer(Pointer> mousePointerList, int lengt window.onPointerDataPacket!(dataPacket); } -final Pointer> _nativeSimulatePointer = Pointer.fromFunction(_simulatePointer); +final Pointer> _nativeSimulatePointer = + Pointer.fromFunction(_simulatePointer); late TestTextInput testTextInput; void _simulateInputText(Pointer nativeChars) { @@ -133,8 +148,8 @@ void _simulateInputText(Pointer nativeChars) { testTextInput.enterText(text); } -final Pointer> _nativeSimulateInputText = - Pointer.fromFunction(_simulateInputText); +final Pointer> + _nativeSimulateInputText = Pointer.fromFunction(_simulateInputText); final List _dartNativeMethods = [ _nativeOnJsError.address, @@ -144,15 +159,20 @@ final List _dartNativeMethods = [ _nativeSimulateInputText.address ]; -typedef Native_RegisterTestEnvDartMethods = Void Function(Int32 contextId, Pointer methodBytes, Int32 length); -typedef Dart_RegisterTestEnvDartMethods = void Function(int contextId, Pointer methodBytes, int length); +typedef Native_RegisterTestEnvDartMethods = Void Function( + Int32 contextId, Pointer methodBytes, Int32 length); +typedef Dart_RegisterTestEnvDartMethods = void Function( + int contextId, Pointer methodBytes, int length); -final DartRegisterTestEnvDartMethods _registerTestEnvDartMethods = WebFDynamicLibrary.ref - .lookup>('registerTestEnvDartMethods') - .asFunction(); +final Dart_RegisterTestEnvDartMethods _registerTestEnvDartMethods = + WebFDynamicLibrary.ref + .lookup>( + 'registerTestEnvDartMethods') + .asFunction(); void registerDartTestMethodsToCpp(int contextId) { - Pointer bytes = malloc.allocate(sizeOf() * _dartNativeMethods.length); + Pointer bytes = + malloc.allocate(sizeOf() * _dartNativeMethods.length); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); _registerTestEnvDartMethods(contextId, bytes, _dartNativeMethods.length); diff --git a/integration_tests/lib/plugin.dart b/integration_tests/lib/plugin.dart index be3df35167..ad16e7a521 100644 --- a/integration_tests/lib/plugin.dart +++ b/integration_tests/lib/plugin.dart @@ -56,7 +56,11 @@ void main() async { File specs = File(path.join(testDirectory, '.specs/plugin.build.js')); List> allSpecsPayload = [ - {'filename': path.basename(specs.path), 'filepath': specs.path, 'code': specs.readAsStringSync()} + { + 'filename': path.basename(specs.path), + 'filepath': specs.path, + 'code': specs.readAsStringSync() + } ]; List widgets = []; @@ -84,12 +88,11 @@ void main() async { )); WidgetsBinding.instance.addPostFrameCallback((_) async { - registerDartTestMethodsToCpp(); - List> testResults = []; for (int i = 0; i < widgets.length; i++) { int contextId = i; + registerDartTestMethodsToCpp(contextId); initTestFramework(contextId); addJSErrorListener(contextId, (String err) { print(err); diff --git a/webf/lib/src/module/timer.dart b/webf/lib/src/module/timer.dart index 62b525a971..62361e807e 100644 --- a/webf/lib/src/module/timer.dart +++ b/webf/lib/src/module/timer.dart @@ -63,9 +63,7 @@ mixin TimerMixin { int setTimeout(int timeout, void Function() callback) { Duration timeoutDurationMS = Duration(milliseconds: timeout); int id = _timerId++; - print('set timer: $id'); _timerMap[id] = Timer(timeoutDurationMS, () { - print('trigger timer: $id'); callback(); _timerMap.remove(id); }); @@ -75,7 +73,6 @@ mixin TimerMixin { void clearTimeout(int timerId) { // If timer already executed, which will be removed. if (_timerMap[timerId] != null) { - print('clear timer $timerId'); _timerMap[timerId]!.cancel(); _timerMap.remove(timerId); } From ff8f8ddebc66a5b3a5ed992a8da3c5e56c6decdf Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Sat, 27 Aug 2022 03:06:52 +0000 Subject: [PATCH 211/498] Committing clang-format changes --- bridge/core/dom/events/event_target.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 66bec29f56..a191e69ba2 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -39,7 +39,7 @@ EventTarget* EventTarget::Create(ExecutingContext* context, ExceptionState& exce } EventTarget::~EventTarget() { - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, nullptr); } EventTarget::EventTarget(ExecutingContext* context) From dbe9e591434693915d85c48a3b690dbe44eb9332 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 28 Aug 2022 01:19:59 +0800 Subject: [PATCH 212/498] feat: var test fix: var reference cycle --- .../css/css-box/overflow.ts.7af966671.png | Bin 3754 -> 3761 bytes .../css/css-box/overflow.ts.ea32ff0d1.png | Bin 2386 -> 2396 bytes .../css/css-box/overflow.ts.ea32ff0d2.png | Bin 2807 -> 2825 bytes ...variable-declaration-13.html.2e4a404b1.png | Bin 4993 -> 6033 bytes ...variable-declaration-19.html.66f8ee771.png | Bin 5823 -> 6033 bytes ...variable-declaration-21.html.73214c1b1.png | Bin 5823 -> 0 bytes ...variable-declaration-22.html.823bc2641.png | Bin 5823 -> 0 bytes ...variable-declaration-23.html.424206ac1.png | Bin 5175 -> 5940 bytes ...variable-declaration-24.html.1d9a68901.png | Bin 5175 -> 0 bytes ...variable-declaration-26.html.a69cc9571.png | Bin 5175 -> 0 bytes ...variable-declaration-30.html.8e9b99541.png | Bin 5823 -> 6033 bytes ...variable-declaration-31.html.d3c61d7c1.png | Bin 5823 -> 0 bytes ...variable-declaration-33.html.cef687511.png | Bin 5823 -> 0 bytes ...variable-declaration-34.html.f3e1cff11.png | Bin 5823 -> 0 bytes ...variable-declaration-35.html.e45d7f911.png | Bin 5823 -> 0 bytes ...variable-declaration-36.html.61d31a411.png | Bin 5823 -> 0 bytes ...variable-declaration-37.html.ba254d591.png | Bin 5175 -> 0 bytes ...variable-declaration-41.html.dc0de31e1.png | Bin 3202 -> 0 bytes ...variable-declaration-45.html.6172c1901.png | Bin 0 -> 5940 bytes ...variable-declaration-47.html.feb731161.png | Bin 0 -> 5940 bytes ...ariable-declaration-48.html.bbb585ba1.png} | Bin ...variable-declaration-49.html.a59c4b611.png | Bin 0 -> 5940 bytes ...variable-declaration-50.html.4c6573d81.png | Bin 0 -> 6033 bytes ...variable-declaration-51.html.1b411d261.png | Bin 0 -> 6033 bytes ...variable-declaration-52.html.53af0e591.png | Bin 0 -> 5940 bytes ...variable-declaration-57.html.889f7f911.png | Bin 0 -> 5940 bytes ...variable-declaration-58.html.e5a37f891.png | Bin 0 -> 5940 bytes ...variable-declaration-59.html.26af32b61.png | Bin 0 -> 6033 bytes .../specs/css/css-box/overflow.ts | 18 +++++-- .../variable-declaration-21.html | 23 -------- .../variable-declaration-22.html | 20 ------- .../variable-declaration-24.html | 25 --------- .../variable-declaration-26.html | 20 ------- .../variable-declaration-31.html | 20 ------- .../variable-declaration-32.html | 20 ------- .../variable-declaration-33.html | 20 ------- .../variable-declaration-34.html | 20 ------- .../variable-declaration-35.html | 20 ------- .../variable-declaration-36.html | 20 ------- .../variable-declaration-37.html | 20 ------- .../variable-declaration-41.html | 20 ------- .../variable-declaration-42.html | 21 -------- .../variable-declaration-43.html | 20 ------- .../variable-declaration-44.html | 20 ------- .../variable-declaration-46.html | 23 -------- .../variable-declaration-49.html | 6 +-- .../variable-declaration-53.html | 22 -------- .../variable-declaration-54.html | 21 -------- .../variable-declaration-55.html | 21 -------- .../variable-declaration-56.html | 20 ------- webf/lib/src/css/parser/parser.dart | 30 ++++++++--- webf/lib/src/css/render_style.dart | 4 +- webf/lib/src/css/values/color.dart | 29 +++++----- webf/lib/src/css/values/variable.dart | 24 +++++---- webf/lib/src/css/variable.dart | 50 +++++++++++++++--- webf/lib/src/dom/document.dart | 11 +++- webf/lib/src/dom/style_node_manager.dart | 7 +-- 57 files changed, 127 insertions(+), 468 deletions(-) delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-24.html.1d9a68901.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-26.html.a69cc9571.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-31.html.d3c61d7c1.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-33.html.cef687511.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-41.html.dc0de31e1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-45.html.6172c1901.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-47.html.feb731161.png rename integration_tests/snapshots/css/css-variables/{variable-declaration-32.html.195b62b21.png => variable-declaration-48.html.bbb585ba1.png} (100%) create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-49.html.a59c4b611.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-50.html.4c6573d81.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-51.html.1b411d261.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-52.html.53af0e591.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-57.html.889f7f911.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-58.html.e5a37f891.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-59.html.26af32b61.png delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-21.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-22.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-24.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-26.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-31.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-32.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-33.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-34.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-35.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-36.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-37.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-41.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-42.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-43.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-44.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-46.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-53.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-54.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-55.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-56.html diff --git a/integration_tests/snapshots/css/css-box/overflow.ts.7af966671.png b/integration_tests/snapshots/css/css-box/overflow.ts.7af966671.png index d8aae7aec390fefdc5b0c3b2c8588e48cd8cdc98..db846c8b6110ed8a070d96265fb8e1789afbe2ed 100644 GIT binary patch literal 3761 zcmeHK`CC(08cxD0h$wv+S{A`Nh%HO%MA?@>nW2~pGu0XymROer%93c&6|UV?xrIU@Ru zCG=dgyi^8*?Te3&3_p`ww=#=c#AUf~H`HrbVHN6!+hqf!fM?T3zO(a=^8EZmd+(En zCy#bndYj&Iv-=ofa){rszXWv7*pjsMy zeMSt&`MxkmW^XKzICOGhD!0NV$d@O*8-=fM20Dxez~bNojed<_*y2j{1gDonNxrO< zG0+nip!g?tIDym$R=A*;loimu8muc=D-EM}=vINX!W@Z1kN<7fg;pXx zPwYexD*p!f_2l6v7>^v_1S-K+`9UoB`U-cM>|3gtN)rYvRBb%DC``e~dBW(1$5098 z+E-D8P@@KjCnhHI&0B~fut11XaMZ(TQ!PrzxxES{I!wC=Him^Ls+eB>DJIixM9Ewq zU|UGSr>8HS=>6!;KhR+t&q0sS`InkV=fLBNd0U=*wo7R@w|D(a1F?r9(aIcXWb^i| zH^803%+2dd-#YrkWhhqQlPg>o>)3$LgO#HoH`JFbzTG4)gCY5!j^>C~p1l}U4ImjA zAL*5i7$n^H@0)oGcgF(=F`WKVLPlqbgrmEkgh)fn$O~mn($h<{g0mhMBOU!?WMrcr zL-beZ7o$*qB6+q2DM28mTHF_svfHIJAUpG@Z9^fcePXg}2_Kw7s$+>$c3<2jH#cO4p9GQ3mF zrJ?)324u;00;XEicM47uao$(wDG=a&I+LM)FGFMH!VuO)F5eR(XH2pSpvdph*o6Eg zDEC7uqg~ztu^mGsHWPT@+iD~mQFdVQh9~1e-XP|F68yPkW?#Ip<^7aaw?aK!h4o=0 zEbSH_C1zF4IQ6pZ7Twcl16kLN&;UoNN&GHR2#YM;0Wb5U!_NNAN-MW78>+8vjyEAK z;Rai3(!VrPUIKX-3i5|QUonbEk-Z0@v)L;(0hv(Yz2?oK%-jLK(}<6&b`(;n7aK7N z6G*oe)k2c0T~Y(2GNCqU4u8xubMRu%%SjspC|AudV29L)Z93^Dke*=1<@Zm`Yh^A? zCKW^4Zo}rg)}1BwgIN>a5ZQTw)P{I2zaLoXr2XZu%jIK=Y|`u( z*#%f8XW@Bb?mS2cMJWtwB*ZaC%m25Iy;h&68FHw*YChRPmaypcnqCV?X?1X#l`=^5 ziI>L(9u448MygOGez5*>w1w?R_iC2)sj1$5&JQ}?{zhF#tz2HB3Rzqed#>y|Cp>4} ztnoxqWyZ?n(~Df1$|LFyV`NpBJrhysTUL%t3gJ*@vkpeUV{TQ4iem*6*jSGKEfOW# zdV#3)ElOp?vA2HdI#G0b5unn5yv?Tf;>Q_lI%JHo;7`&f7~{G+CR@;PW$s>q#Jj?{ zIamBn_b0X63M{VSVBo@KU2lrwHI%IBcg1J0H|5 z#el1Uw5jX-`Db9Z3roi!>pSEvz!1{mvfI*gg#M?8QG`MjYh2oAK`7MC0Hv-pjSNRH zsM;z&Q<$BLuZ8pnoT@!bAFd^|rp3+13R~0hIu%gvQ#18ctdzt3!#zAkjl5S6n{X2o zblqD0UZX(`V(9vFe!YLl{MN*#w>;a?Cmo)9%+%5CWCQ%KyJmL^cDrC#2)ld1?sm9) gBK-fS;c{4``=wKvr44VPYbPxF_c4)-i1Ro83(FU(pa1{> literal 3754 zcmeHK`BPJQ5`QFt1QbCn1vx}+bKT z>s$V1_*rW!2P*&otV6#Dxc~rW2><{qL|H(Z=MEnmAz?h3Drqu*aW928%BU#gxomX~|gZcw?* z#g!Y?-<4Vi`XkBcmJNMdhF1C8Bk)39))l$mM2$M{m0I>Rssp+{o>PA#V31l44~e!ku`b{q&D2dI@lzJw<#jGfH)f6Qy@JRyu9&+e<8s%8?MrVY+}qC@Ch(dA^c~b zYIV4^rRBbb_pOx%WX*lv>d9CK7$&ryzC8B#CPl6Hu$faa4l^suCGj-hHQ<52!MHz@ z3#jrZja4ybA(q<8gLxFW-$Z1Nx$RML7+cL_&`6CHAL7#98M z4(*RnKuvg$13A*n$UKrzc&x~xY|sBD(2-G5IcB!5;-iY1wyg+W$(Oe@FzZdbBD92) zy_FS#(mk}J&=%6XxVoiD`oP+Pv?p&l*_W7zwTtCwt)NWZGZuf8@baNjmErIUlgE&kI z&F_zikO{(4JJiA*QCUx(hC0)p#E^85p#b-A7*)Qyx^t!y>cv+u?mbXgE$N~`UmifM zWz9##+QK$%VBDLaDAwFz(jJjxdFuUf>dG(_=p#a(Cherk`9%=ck+0`0r9)%I&eJ}E zjwefN6$k&C3BhuDCDZzOQVJ(kwU2M{JkT!Y7wxUK-U?@|i)mfZsX%iPV~0i*=g5~K z%PDzvbOh3gzLGs3MVC?J?Aa)ECHwS~w4edXTF-%ewhrcFU<;7jeExZ>QU2(?XKocvX5H0AAq$;vPyZ@GkF zL(5Vfr#C0hGdak?vC+Stpr{48J9c@PPLdh%($; z)BfD2VL`uwp-F}v_@N#DsWGoQ^M7lSQ z(WiFRT*vtLX(kTlr^#2P&Y?`2aqi?L#7vxjs7I z0`3Z$e3`4liJE!doQFYB zrO%=1`{w#cn*dL6+C{WE;@}~cp3sTd7TYD* qo`UUzu+0VAbhy0{{_kyQaA^#PKlc-@`vG*{1VT@ThqMI8-S{`_c61>C diff --git a/integration_tests/snapshots/css/css-box/overflow.ts.ea32ff0d1.png b/integration_tests/snapshots/css/css-box/overflow.ts.ea32ff0d1.png index 3c5c9bd536deafc0b8887e6764d6534814f2aed3..ad95b6996fd1cfb094fe8d467bd0b0e3fb9dfb2a 100644 GIT binary patch literal 2396 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_n+xPZ!6KiaBrZ8g?-o3NSdH-T43iCYhMDDK{fzLt2>*9&j?=(3rM% ztGHzKZi&$Gx0M+Z%`zG^8reF8Myb&tpd=Nn zc)s|yal90BLpjw_)QZQe?>*j{*YG+Ak#vD6`0Ja!`R5r9H1bL*cr9RD#4<{a2Ek}5 k7|jTyWx>cQ4qvgd+3>SVJUX< z4B-HR8jh3>1_q8lo-U3d6?5L+G~{A7VJUX< z4B-HR8jh3>1_rLpo-U3d6?5L+@jbCLS>nLQ*E0^?i zOWC3|IXTD9Ma<}(o0hC7c=3qNl>RjtVk(}(ll#>kZ>^ZUY^J60IqRf~^tvxvx9k1?B-WW0Gojb#! zHwzdSu}CR+jZ&jQKv60%(~pj^+5ThueCCGa8;;B_oF)!a9Kh6-OU{S1`Od_A|9fU1 zcly3PKVDRe|FtThl>{*KKbb8l`j+x-0L%-Q>A?)-Cdru_MNKi@2!dEe~(ogXKDz3!cP^?Agjn~$eoyVm#L zs^R}~VL2hWs53S-=YM_uoPXZt>%m`sKM(%y?|ZkZm?7s3(DjGy_SHN)dOJ<-{^ox_ zTW_a-i>rK9%&_L2etp{ach{7!&yV@`{W(L#S51YrKDs<>%j$#?${8 z6~Fy9^Y3#R+kLNOj(^~jvgn-rZo&NO^k=K)r^)TgfA+9CUGHz@U)#?s&oOS;CCv6~ z+WPc)H;gxbUN-alwsU*xzn)nqown`m+?`)v_X2~#(3^Sh(+g*`cc=5^%&h!%PWg5F z`to=4EI;>Oea>)W$I`~Sn)7GA-antSJN;REasQh7>Vs#J%h%t({qG^r%{g~G`8j{K zTdJ`59om1B5jmDVIXn4Dx3?TfEW5xE$iyq43Zb^%Tqxo&Sr#6f&`2GnV99$lBt}DG mGzSudXZG#ekvV^7(7;yg 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 BUAX`N diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-19.html.66f8ee771.png b/integration_tests/snapshots/css/css-variables/variable-declaration-19.html.66f8ee771.png index 7d88634a242de36024c2a9b7d75f53684677cf7e..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 100644 GIT binary patch literal 6033 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png b/integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png index d3699942bc8b1c6fdd40ddbc98bd529defb48921..d45b3fe6205321fcbafb594635db41dc395f35f0 100644 GIT binary patch literal 5940 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 literal 5175 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-31.html.d3c61d7c1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-31.html.d3c61d7c1.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-33.html.cef687511.png b/integration_tests/snapshots/css/css-variables/variable-declaration-33.html.cef687511.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png b/integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png b/integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png b/integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png b/integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png deleted file mode 100644 index d3699942bc8b1c6fdd40ddbc98bd529defb48921..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5175 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 CRQ9a^ diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-45.html.6172c1901.png b/integration_tests/snapshots/css/css-variables/variable-declaration-45.html.6172c1901.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-47.html.feb731161.png b/integration_tests/snapshots/css/css-variables/variable-declaration-47.html.feb731161.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-32.html.195b62b21.png b/integration_tests/snapshots/css/css-variables/variable-declaration-48.html.bbb585ba1.png similarity index 100% rename from integration_tests/snapshots/css/css-variables/variable-declaration-32.html.195b62b21.png rename to integration_tests/snapshots/css/css-variables/variable-declaration-48.html.bbb585ba1.png diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-49.html.a59c4b611.png b/integration_tests/snapshots/css/css-variables/variable-declaration-49.html.a59c4b611.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-50.html.4c6573d81.png b/integration_tests/snapshots/css/css-variables/variable-declaration-50.html.4c6573d81.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-57.html.889f7f911.png b/integration_tests/snapshots/css/css-variables/variable-declaration-57.html.889f7f911.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-58.html.e5a37f891.png b/integration_tests/snapshots/css/css-variables/variable-declaration-58.html.e5a37f891.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-59.html.26af32b61.png b/integration_tests/snapshots/css/css-variables/variable-declaration-59.html.26af32b61.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 { it('overflow with absolute positioned elements', async (done) => { let scroller; + let div1; let container = createElement('div', { style: { display: 'inline-block', @@ -210,27 +211,34 @@ describe('Overflow', () => { border: '5px solid #000', padding: '5px' } - }, [ - createElement('div', { + }, + [ + div1 = createElement('div', { style: { position: 'absolute', right: '-20px', + width: '100px', color: 'red', display: 'inline', - bottom: '-550px' + bottom: '0px', + backgroundColor: 'blue' } - }, [ + }, + [ createText('XXX') ]) ]) ]); document.body.appendChild(container); + div1.style.backgroundColor = 'red'; + await snapshot(); requestAnimationFrame(async () => { + div1.style.backgroundColor = 'green'; scroller.scroll(20, 550); - await snapshot(0.2); + await snapshot(); done(); }); }); diff --git a/integration_tests/specs/css/css-variables/variable-declaration-21.html b/integration_tests/specs/css/css-variables/variable-declaration-21.html deleted file mode 100644 index 0a91196df0..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-21.html +++ /dev/null @@ -1,23 +0,0 @@ - - -CSS Test: Test declaring a variable that consists of a function where all of the arguments and commas are made up of variable references. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-22.html b/integration_tests/specs/css/css-variables/variable-declaration-22.html deleted file mode 100644 index a96d9cf189..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-22.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable that consists of a variable reference with a number of levels of variable reference fallbacks. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-24.html b/integration_tests/specs/css/css-variables/variable-declaration-24.html deleted file mode 100644 index 14fe47a1e5..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-24.html +++ /dev/null @@ -1,25 +0,0 @@ - - -CSS Test: Test declaring a variable that contains a CDO token. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-26.html b/integration_tests/specs/css/css-variables/variable-declaration-26.html deleted file mode 100644 index 79ff10d2b6..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-26.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable that contains only a white space token. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-31.html b/integration_tests/specs/css/css-variables/variable-declaration-31.html deleted file mode 100644 index 4a24bf2bd5..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-31.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with a digit. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-32.html b/integration_tests/specs/css/css-variables/variable-declaration-32.html deleted file mode 100644 index d11478e8f5..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-32.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with an escaped digit. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-33.html b/integration_tests/specs/css/css-variables/variable-declaration-33.html deleted file mode 100644 index 521db857f5..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-33.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with an escaped letter. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-34.html b/integration_tests/specs/css/css-variables/variable-declaration-34.html deleted file mode 100644 index c6b4d42d34..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-34.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with a lone surrogate. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-35.html b/integration_tests/specs/css/css-variables/variable-declaration-35.html deleted file mode 100644 index f1289069f6..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-35.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with U+FFFD. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-36.html b/integration_tests/specs/css/css-variables/variable-declaration-36.html deleted file mode 100644 index 1f984fe7a6..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-36.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with an out-of-range Unicode character escape. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-37.html b/integration_tests/specs/css/css-variables/variable-declaration-37.html deleted file mode 100644 index bd4b1c0f4b..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-37.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable consisting of a variable reference where white space surrounds the comma separating the variable name and fallback. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-41.html b/integration_tests/specs/css/css-variables/variable-declaration-41.html deleted file mode 100644 index c1c585b0f9..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-41.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the second '-' in the "--" prefix of the custom property name is escaped. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-42.html b/integration_tests/specs/css/css-variables/variable-declaration-42.html deleted file mode 100644 index 1a60ba2393..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-42.html +++ /dev/null @@ -1,21 +0,0 @@ - - -CSS Test: Test declaring a variable where the custom property name includes an unescaped Chinese character and an escape that is terminated by a space character. - - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-43.html b/integration_tests/specs/css/css-variables/variable-declaration-43.html deleted file mode 100644 index 73aaef2b89..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-43.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable whose value is "initial". - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-44.html b/integration_tests/specs/css/css-variables/variable-declaration-44.html deleted file mode 100644 index f2a968d250..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-44.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable whose value is "inherit" where there is no variable to inherit from. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-46.html b/integration_tests/specs/css/css-variables/variable-declaration-46.html deleted file mode 100644 index c846b1cf97..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-46.html +++ /dev/null @@ -1,23 +0,0 @@ - - -CSS Test: Test declaring a variable whose value is "initial" where there is a variable to inherit from. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-49.html b/integration_tests/specs/css/css-variables/variable-declaration-49.html index 9a4b984b80..01a84937b4 100644 --- a/integration_tests/specs/css/css-variables/variable-declaration-49.html +++ b/integration_tests/specs/css/css-variables/variable-declaration-49.html @@ -8,9 +8,7 @@

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-53.html b/integration_tests/specs/css/css-variables/variable-declaration-53.html deleted file mode 100644 index b8b6a8cf5c..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-53.html +++ /dev/null @@ -1,22 +0,0 @@ - - -CSS Test: Test declaring a variable that consists of two variable references without fallback and with no intervening white space. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-54.html b/integration_tests/specs/css/css-variables/variable-declaration-54.html deleted file mode 100644 index 8e0b39c4c4..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-54.html +++ /dev/null @@ -1,21 +0,0 @@ - - -CSS Test: Test declaring a variable that consists of two variable references with the first variable reference using fallback and with no intervening white space. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-55.html b/integration_tests/specs/css/css-variables/variable-declaration-55.html deleted file mode 100644 index 2fb68d516d..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-55.html +++ /dev/null @@ -1,21 +0,0 @@ - - -CSS Test: Test declaring a variable that consists of two variable references with the second variable reference using fallback and with no intervening white space. - - - - -

This text must be green.

diff --git a/integration_tests/specs/css/css-variables/variable-declaration-56.html b/integration_tests/specs/css/css-variables/variable-declaration-56.html deleted file mode 100644 index 21378f892d..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-56.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable whose value is "unset" where there is no variable to inherit from. - - - - -

This text must be green.

diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 877595fcdc..d453048706 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -733,7 +733,7 @@ class CSSParser { // : or :: and making this a normal selector. For now, // create an empty pseudoName. Identifier pseudoName; - if (_peekIdentifier()) { + if (_peekIdentifier() && _peekToken.text != 'var' && _peekToken.text != 'rgb' && _peekToken.text != 'rgba') { pseudoName = identifier(); } else { return null; @@ -929,9 +929,15 @@ class CSSParser { } var expr = processExpr(); - // Handle !important (prio) - var importantPriority = _maybeEat(TokenKind.IMPORTANT); - style.setProperty(propertyIdent, expr, importantPriority); + if (expr != null) { + // Handle !important (prio) + var importantPriority = false; + // handle multi-important + while (_maybeEat(TokenKind.IMPORTANT)) { + importantPriority = true; + } + style.setProperty(propertyIdent, expr, importantPriority); + } } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) { _next(); } else if (_peekToken.kind == TokenKind.DIRECTIVE_INCLUDE) { @@ -946,10 +952,12 @@ class CSSParser { // expression: term [ operator? term]* // // operator: '/' | ',' - String processExpr([bool ieFilter = false]) { + String? processExpr([bool ieFilter = false]) { var start = _peekToken.span; FileSpan? end; + bool hasSynaxError = false; + var parenCount = 0; while (!_maybeEat(TokenKind.END_OF_FILE)) { if (_peek() == TokenKind.LPAREN) { @@ -958,14 +966,22 @@ class CSSParser { if (_peek() == TokenKind.RPAREN) { parenCount--; } - if (parenCount == 0 && (_peek() == TokenKind.SEMICOLON || _peek() == TokenKind.RBRACE)) { + if (parenCount <= 0 && (_peek() == TokenKind.SEMICOLON || _peek() == TokenKind.RBRACE)) { break; } if (_peek() == TokenKind.IMPORTANT) { - break; + if (parenCount == 0) { + break; + } else { + // synax error + hasSynaxError = true; + } } end = _next().span; } + if (hasSynaxError || parenCount < 0) { + return null; + } if (end != null) { return start.expand(end).text; } diff --git a/webf/lib/src/css/render_style.dart b/webf/lib/src/css/render_style.dart index e56174da56..363b6b1d50 100644 --- a/webf/lib/src/css/render_style.dart +++ b/webf/lib/src/css/render_style.dart @@ -25,7 +25,7 @@ abstract class RenderStyle { dynamic resolveValue(String property, String present); // CSSVariable - String? getCSSVariable(String identifier, String propertyName); + dynamic getCSSVariable(String identifier, String propertyName); void setCSSVariable(String identifier, String value); // Geometry @@ -400,7 +400,7 @@ class CSSRenderStyle extends RenderStyle RenderStyle renderStyle = this; // Process CSSVariable. - dynamic value = CSSVariable.tryParse(renderStyle, propertyName, propertyValue); + dynamic value = CSSVariable.tryParse(renderStyle, propertyValue); if (value != null) { return value; } diff --git a/webf/lib/src/css/values/color.dart b/webf/lib/src/css/values/color.dart index 320a2240d3..6da1a4ace1 100644 --- a/webf/lib/src/css/values/color.dart +++ b/webf/lib/src/css/values/color.dart @@ -170,8 +170,8 @@ const Map _namedColors = { final _colorHexRegExp = RegExp(r'^#([a-f0-9]{3,8})$', caseSensitive: false); final _colorHslRegExp = RegExp(r'^(hsla?)\(([0-9.-]+)(deg|rad|grad|turn)?[,\s]+([0-9.]+%)[,\s]+([0-9.]+%)([,\s/]+([0-9.]+%?))?\s*\)$'); -final _colorRgbRegExp = - RegExp(r'^(rgba?)\(([+-]?[0-9.]+%?)[,\s]+([+-]?[0-9.]+%?)[,\s]+([+-]?[0-9.]+%?)([,\s/]+([+-]?[0-9.]+%?))?\s*\)$'); +final _colorRgbRegExp = RegExp( + r'^(rgba?)\(([+-]?[^\s,.]+%?)[,\s]+([+-]?[^\s,.]+%?)[,\s]+([+-]?[^\s,.]+%?)([,\s/]+([+-]?[^\s,.]+%?))?\s*\)$'); final LinkedLruHashMap _cachedParsedColor = LinkedLruHashMap(maximumSize: 100); @@ -237,10 +237,10 @@ class CSSColor { renderStyle.addColorRelativeProperty(propertyName); return renderStyle.color; } - return parseColor(color); + return parseColor(color, renderStyle: renderStyle); } - static Color? parseColor(String color) { + static Color? parseColor(String color, {RenderStyle? renderStyle}) { color = color.trim().toLowerCase(); if (color == TRANSPARENT) { @@ -277,10 +277,10 @@ class CSSColor { } else if (color.startsWith(RGB)) { final rgbMatch = _colorRgbRegExp.firstMatch(color); if (rgbMatch != null) { - final double? rgbR = _parseColorPart(rgbMatch[2]!, 0, 255); - final double? rgbG = _parseColorPart(rgbMatch[3]!, 0, 255); - final double? rgbB = _parseColorPart(rgbMatch[4]!, 0, 255); - final double? rgbO = rgbMatch[6] != null ? _parseColorPart(rgbMatch[6]!, 0, 1) : 1; + final double? rgbR = _parseColorPart(rgbMatch[2]!, 0, 255, renderStyle); + final double? rgbG = _parseColorPart(rgbMatch[3]!, 0, 255, renderStyle); + final double? rgbB = _parseColorPart(rgbMatch[4]!, 0, 255, renderStyle); + final double? rgbO = rgbMatch[6] != null ? _parseColorPart(rgbMatch[6]!, 0, 1, renderStyle) : 1; if (rgbR != null && rgbG != null && rgbB != null && rgbO != null) { parsed = Color.fromRGBO(rgbR.round(), rgbG.round(), rgbB.round(), rgbO); } @@ -289,9 +289,9 @@ class CSSColor { final hslMatch = _colorHslRegExp.firstMatch(color); if (hslMatch != null) { final double? hslH = _parseColorHue(hslMatch[2]!, hslMatch[3]); - final double? hslS = _parseColorPart(hslMatch[4]!, 0, 1); - final double? hslL = _parseColorPart(hslMatch[5]!, 0, 1); - final double? hslA = hslMatch[7] != null ? _parseColorPart(hslMatch[7]!, 0, 1) : 1; + final double? hslS = _parseColorPart(hslMatch[4]!, 0, 1, renderStyle); + final double? hslL = _parseColorPart(hslMatch[5]!, 0, 1, renderStyle); + final double? hslA = hslMatch[7] != null ? _parseColorPart(hslMatch[7]!, 0, 1, renderStyle) : 1; if (hslH != null && hslS != null && hslL != null && hslA != null) { parsed = HSLColor.fromAHSL(hslA, hslH, hslS, hslL).toColor(); } @@ -439,9 +439,14 @@ String _x2(String value) { return sb.toString(); } -double? _parseColorPart(String value, double min, double max) { +double? _parseColorPart(String value, double min, double max, RenderStyle? renderStyle) { double? v; + if (value.startsWith('var') && renderStyle != null) { + final variable = CSSVariable.tryParse(renderStyle, value); + final computedValue = variable?.computedValue(''); + v = double.tryParse(computedValue); + } if (value.endsWith('%')) { final p = double.tryParse(value.substring(0, value.length - 1)); if (p == null) return null; diff --git a/webf/lib/src/css/values/variable.dart b/webf/lib/src/css/values/variable.dart index 62f37073fd..286b44700a 100644 --- a/webf/lib/src/css/values/variable.dart +++ b/webf/lib/src/css/values/variable.dart @@ -17,7 +17,7 @@ class CSSVariable { } // Try to parse CSSVariable. - static CSSVariable? tryParse(RenderStyle renderStyle, String propertyName, String propertyValue) { + static CSSVariable? tryParse(RenderStyle renderStyle, String propertyValue) { // font-size: var(--x); // font-size: var(--x, 28px); if (CSSFunction.isFunction(propertyValue, functionName: VAR)) { @@ -25,7 +25,12 @@ class CSSVariable { if (fns.first.args.isNotEmpty) { if (fns.first.args.length > 1) { // Has default value for CSS Variable. - return CSSVariable(fns.first.args.first, renderStyle, defaultValue: fns.first.args.last); + dynamic defaultValue = fns.first.args.last.trim(); + CSSVariable? defaultVar = CSSVariable.tryParse(renderStyle, defaultValue); + if (defaultVar != null) { + defaultValue = defaultVar; + } + return CSSVariable(fns.first.args.first, renderStyle, defaultValue: defaultValue); } else { return CSSVariable(fns.first.args.first, renderStyle); } @@ -35,23 +40,24 @@ class CSSVariable { } final String identifier; - final String? defaultValue; + final dynamic defaultValue; final RenderStyle _renderStyle; CSSVariable(this.identifier, this._renderStyle, {this.defaultValue}); // Get the lazy calculated CSS resolved value. dynamic computedValue(String propertyName) { - String? unsolvedValue = _renderStyle.getCSSVariable(identifier, propertyName) ?? defaultValue; - if (unsolvedValue == null) { + dynamic value = _renderStyle.getCSSVariable(identifier, propertyName) ?? defaultValue; + if (value == null) { return null; } - var resolved = _renderStyle.resolveValue(propertyName, unsolvedValue); - if (resolved is CSSVariable) { - return resolved.computedValue(propertyName); + if (value is CSSVariable) { + return value.computedValue(propertyName); + } else if (propertyName.isNotEmpty) { + return _renderStyle.resolveValue(propertyName, value); } else { - return resolved; + return value; } } diff --git a/webf/lib/src/css/variable.dart b/webf/lib/src/css/variable.dart index 581325c7ad..3717f483f2 100644 --- a/webf/lib/src/css/variable.dart +++ b/webf/lib/src/css/variable.dart @@ -7,7 +7,7 @@ import 'dart:collection'; import 'package:webf/css.dart'; mixin CSSVariableMixin on RenderStyle { - Map? _storage; + Map? _storage; final Map> _propertyDependencies = {}; void _addDependency(String identifier, String propertyName) { @@ -20,17 +20,25 @@ mixin CSSVariableMixin on RenderStyle { } @override - String? getCSSVariable(String identifier, String propertyName) { - Map? storage = _storage; + dynamic getCSSVariable(String identifier, String propertyName) { + if (_checkStorageLoop(identifier)) { + return null; + } + + Map? storage = _storage; _addDependency(identifier, propertyName); if (storage != null && storage[identifier] != null) { - final variable = CSSVariable.tryParse(this, propertyName, storage[identifier]!); - if (variable != null) { + final variable = storage[identifier]; + if (variable != null && variable is CSSVariable) { final id = variable.identifier.trim(); - if (storage[id] != null) { + if (storage[id] != null && id != identifier) { return getCSSVariable(id, propertyName); } + if (variable.defaultValue != null) { + return variable.defaultValue; + } + return null; } else { return storage[identifier]; } @@ -40,13 +48,39 @@ mixin CSSVariableMixin on RenderStyle { } } + bool _checkStorageLoop(String identifier) { + Map? storage = _storage; + if (storage == null) { + return false; + } + if (storage[identifier] == null) { + return false; + } + if (storage[identifier] is! CSSVariable) { + return false; + } + CSSVariable fast = storage[identifier]!; + CSSVariable slow = storage[identifier]!; + while (storage[fast.identifier] != null && + storage[fast.identifier] is CSSVariable && + storage[storage[fast.identifier]!.identifier] != null && + storage[storage[fast.identifier]!.identifier] is CSSVariable) { + fast = storage[storage[fast.identifier]!.identifier]!; + slow = storage[slow.identifier]!; + if (fast == slow) { + return true; + } + } + return false; + } + // --x: red // key: --x // value: red @override void setCSSVariable(String identifier, String value) { - _storage ??= HashMap(); - _storage![identifier] = value; + _storage ??= HashMap(); + _storage![identifier] = CSSVariable.tryParse(this, value) ?? value; if (_propertyDependencies.containsKey(identifier)) { _notifyCSSVariableChanged(_propertyDependencies[identifier]!, value); diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index d073a9db5e..e1f7d01303 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -234,13 +234,22 @@ class Document extends Node { }); } + bool _recalculating = false; void flushStyle({bool rebuild = false}) { if (!needsStyleRecalculate) { return; } - styleNodeManager.updateActiveStyleSheets(rebuild: rebuild); + if (_recalculating) { + return; + } + _recalculating = true; + if (!styleNodeManager.updateActiveStyleSheets(rebuild: rebuild)) { + _recalculating = false; + return; + } recalculateDocumentStyle(); needsStyleRecalculate = false; + _recalculating = false; } void recalculateDocumentStyle() { diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 0e2277233c..963e535e9e 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -59,16 +59,16 @@ class StyleNodeManager { _pendingStyleSheets.removeWhere((element) => element == styleSheet); } - void updateActiveStyleSheets({bool rebuild = false}) { + bool updateActiveStyleSheets({bool rebuild = false}) { List newSheets = _collectActiveStyleSheets(); if (newSheets.isEmpty) { - return; + return false; } newSheets = newSheets.where((element) => element.cssRules.isNotEmpty).toList(); if (rebuild == false) { RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); if (changedRuleSet.isEmpty) { - return; + return false; } invalidateElementStyle(changedRuleSet); } else { @@ -79,6 +79,7 @@ class StyleNodeManager { document.needsStyleRecalculate = true; document.handleStyleSheets(newSheets); _pendingStyleSheets.clear(); + return true; } List _collectActiveStyleSheets() { From 928635bfdc6a6918a1b541dc60eba7b5aab24ff3 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 29 Aug 2022 11:22:15 +0800 Subject: [PATCH 213/498] fix: parse rgba color --- webf/lib/src/css/values/color.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webf/lib/src/css/values/color.dart b/webf/lib/src/css/values/color.dart index 6da1a4ace1..ce7667c824 100644 --- a/webf/lib/src/css/values/color.dart +++ b/webf/lib/src/css/values/color.dart @@ -170,8 +170,8 @@ const Map _namedColors = { final _colorHexRegExp = RegExp(r'^#([a-f0-9]{3,8})$', caseSensitive: false); final _colorHslRegExp = RegExp(r'^(hsla?)\(([0-9.-]+)(deg|rad|grad|turn)?[,\s]+([0-9.]+%)[,\s]+([0-9.]+%)([,\s/]+([0-9.]+%?))?\s*\)$'); -final _colorRgbRegExp = RegExp( - r'^(rgba?)\(([+-]?[^\s,.]+%?)[,\s]+([+-]?[^\s,.]+%?)[,\s]+([+-]?[^\s,.]+%?)([,\s/]+([+-]?[^\s,.]+%?))?\s*\)$'); +final _colorRgbRegExp = + RegExp(r'^(rgba?)\(([+-]?[^\s,]+%?)[,\s]+([+-]?[^\s,]+%?)[,\s]+([+-]?[^\s,]+%?)([,\s/]+([+-]?[^\s,]+%?))?\s*\)$'); final LinkedLruHashMap _cachedParsedColor = LinkedLruHashMap(maximumSize: 100); From be2c975a2486b177d2c4da0924c2ffc72285271c Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 29 Aug 2022 18:16:32 +0800 Subject: [PATCH 214/498] feat: add document.querySelector and document.querySelectorAll bindings. --- bridge/bindings/qjs/dom/document.cc | 32 +++++ bridge/bindings/qjs/dom/document.h | 4 + bridge/bindings/qjs/native_value.cc | 55 +++++--- bridge/bindings/qjs/native_value.h | 15 ++- bridge/polyfill/src/index.ts | 1 - bridge/polyfill/src/query-selector.ts | 179 -------------------------- webf/lib/src/bridge/native_value.dart | 36 ++++-- webf/lib/src/dom/document.dart | 10 +- 8 files changed, 112 insertions(+), 220 deletions(-) delete mode 100644 bridge/polyfill/src/query-selector.ts diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc index 88a799495a..9945683087 100644 --- a/bridge/bindings/qjs/dom/document.cc +++ b/bridge/bindings/qjs/dom/document.cc @@ -306,6 +306,38 @@ JSValue Document::getElementsByClassName(JSContext* ctx, JSValue this_val, int a 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; } diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/bindings/qjs/dom/document.h index b6cff01d3a..6d6cfb4be1 100644 --- a/bridge/bindings/qjs/dom/document.h +++ b/bridge/bindings/qjs/dom/document.h @@ -40,6 +40,8 @@ class Document : public Node { 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); @@ -63,6 +65,8 @@ class Document : public Node { 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); diff --git a/bridge/bindings/qjs/native_value.cc b/bridge/bindings/qjs/native_value.cc index 663ef3588d..9bf5d11818 100644 --- a/bridge/bindings/qjs/native_value.cc +++ b/bridge/bindings/qjs/native_value.cc @@ -16,13 +16,13 @@ namespace webf::binding::qjs { #define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" NativeValue Native_NewNull() { - return (NativeValue){0, .u = {.int64 = 0}, NativeTag::TAG_NULL}; + return (NativeValue){0, 0, NativeTag::TAG_NULL}; } NativeValue Native_NewString(NativeString* string) { return (NativeValue){ + reinterpret_cast(string), 0, - .u = {.ptr = static_cast(string)}, NativeTag::TAG_STRING, }; } @@ -35,20 +35,24 @@ NativeValue Native_NewCString(std::string string) { NativeValue Native_NewFloat64(double value) { return (NativeValue){ - value, - .u = {.ptr = nullptr}, + {.float64 = value}, + 0, NativeTag::TAG_FLOAT64, }; } NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr) { - return (NativeValue){static_cast(pointerType), .u = {.ptr = ptr}, NativeTag::TAG_POINTER}; + return (NativeValue){ + reinterpret_cast(ptr), + static_cast(pointerType), + NativeTag::TAG_POINTER + }; } NativeValue Native_NewBool(bool value) { return (NativeValue){ 0, - .u = {.int64 = value ? 1 : 0}, + value ? 1 : 0, NativeTag::TAG_BOOL, }; } @@ -56,7 +60,7 @@ NativeValue Native_NewBool(bool value) { NativeValue Native_NewInt32(int32_t value) { return (NativeValue){ 0, - .u = {.int64 = value}, + value, NativeTag::TAG_INT, }; } @@ -69,8 +73,8 @@ NativeValue Native_NewJSON(ExecutionContext* context, JSValue& value) { // NativeString owned by NativeValue will be freed by users. NativeString* string = jsValueToNativeString(context->ctx(), stringifiedValue).release(); NativeValue result = (NativeValue){ + reinterpret_cast(string), 0, - .u = {.ptr = static_cast(string)}, NativeTag::TAG_JSON, }; JS_FreeValue(context->ctx(), stringifiedValue); @@ -126,7 +130,7 @@ NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { auto* context = static_cast(JS_GetContextOpaque(ctx)); if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); - return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); + return Native_NewPtr(JSPointerType::NativeBindingObject, imageElementInstance->nativeEventTarget); } return Native_NewJSON(context, value); @@ -223,7 +227,7 @@ static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { switch (value.tag) { case NativeTag::TAG_STRING: { - auto* string = static_cast(value.u.ptr); + 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); @@ -231,41 +235,54 @@ JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { return returnedValue; } case NativeTag::TAG_INT: { - return JS_NewUint32(context->ctx(), value.u.int64); + return JS_NewUint32(context->ctx(), value.int64); } case NativeTag::TAG_BOOL: { - return JS_NewBool(context->ctx(), value.u.int64 == 1); + return JS_NewBool(context->ctx(), value.int64 == 1); } case NativeTag::TAG_FLOAT64: { - return JS_NewFloat64(context->ctx(), value.float64); + return JS_NewFloat64(context->ctx(), value.u.float64); } case NativeTag::TAG_NULL: { return JS_NULL; } case NativeTag::TAG_JSON: { - auto* str = static_cast(value.u.ptr); + 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 = value.u.ptr; - int ptrType = (int)value.float64; + 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::NativeEventTarget)) { + } 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.u.int64; + int64_t functionId = value.int64; return JS_NewCFunctionData(context->ctx(), anonymousFunction, 4, functionId, 0, nullptr); } case NativeTag::TAG_ASYNC_FUNCTION: { - int64_t functionId = value.u.int64; + int64_t functionId = value.int64; return JS_NewCFunctionData(context->ctx(), anonymousAsyncFunction, 4, functionId, 0, nullptr); } } diff --git a/bridge/bindings/qjs/native_value.h b/bridge/bindings/qjs/native_value.h index 3c7e4ab59d..e3c3d46e6e 100644 --- a/bridge/bindings/qjs/native_value.h +++ b/bridge/bindings/qjs/native_value.h @@ -15,22 +15,23 @@ enum NativeTag { TAG_NULL = 3, TAG_FLOAT64 = 4, TAG_JSON = 5, - TAG_POINTER = 6, - TAG_FUNCTION = 7, - TAG_ASYNC_FUNCTION = 8, + TAG_LIST = 6, + TAG_POINTER = 7, + TAG_FUNCTION = 8, + TAG_ASYNC_FUNCTION = 9, }; -enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeEventTarget = 4 }; +enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeBindingObject = 4 }; namespace webf::binding::qjs { // Exchange data struct between dart and C++ struct NativeValue { - double float64; union { - int64_t int64; - void* ptr; + uint64_t uint64; + double float64; } u; + int64_t int64; int64_t tag; }; diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index 05119be5e9..1676c32f68 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -5,7 +5,6 @@ import 'es6-promise/dist/es6-promise.auto'; import './dom'; -import './query-selector'; import { console } from './console'; import { fetch, Request, Response, Headers } from './fetch'; import { matchMedia } from './match-media'; diff --git a/bridge/polyfill/src/query-selector.ts b/bridge/polyfill/src/query-selector.ts deleted file mode 100644 index 2c69a0cc2d..0000000000 --- a/bridge/polyfill/src/query-selector.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ - -function fetchSelector(str: string, regex: RegExp) { - return { - selectors: str.match(regex) || [], - ruleStr: str.replace(regex, ' ') - }; -} - -function getElementsBySelector(selector: string): Array { - let context = document; - let temp, tempElements: Array = [], elements: Array = []; - selector = selector.trim(); - - if (selector === '') return []; - - // Classes. e.g. .row. - let classes: Array = []; - selector = selector.split(' ').map(item => { - if (item && item.charAt(0) === '.') { - temp = fetchSelector(selector, /\.[\w-_]+/g); - classes = classes.concat(temp.selectors) - return temp.ruleStr; - } - return item; - }).join(' '); - - // Ids. e.g. #mail-title. - temp = fetchSelector(selector, /#[\w-_]+/g); - let id = temp.selectors ? temp.selectors[0] : null; - selector = temp.ruleStr; - - // Attributes. e.g. [rel=external]. - temp = fetchSelector(selector, /\[.+?\]/g); - let attributes = temp.selectors; - selector = temp.ruleStr; - - // Elements. e.g. header, div. - temp = fetchSelector(selector, /\w+/g); - let els = temp.selectors; - selector = temp.ruleStr; - - // Get by id. - // Id is supposed to be unique. - // More need to attach other selectors. - if (id) { - id = id.substring(1); - return [document.getElementById(id) || null]; - } - - // If selector starts with *, find all elements. - if (selector.charAt(0) === '*' || els.length === 0) { - let temps: HTMLCollectionOf = context.getElementsByTagName('*'); - tempElements = Array.from(temps); - } else { - // Get by Elements. - let temps: HTMLCollectionOf = context.getElementsByTagName(els[0]); - tempElements = Array.from(temps); - } - - // Get by class name. - for (let i = 0, l = classes.length; i !== l; ++i) { - let className = classes[i].substring(1); - let temps: HTMLCollectionOf = context.getElementsByClassName(className); - let arrTemps: Array = Array.from(temps); - if (tempElements.length === 0) { - // If no temp elements yet, push into tempElements directly. - tempElements = tempElements.concat(arrTemps); - } else { - // Otherwise, find intersection. - let prevs: Array = []; - prevs = prevs.concat(tempElements); - tempElements = []; - - for (let index = 0; index < arrTemps.length; index++) { - let t = arrTemps[index]; - if (prevs.indexOf(t) !== -1) { - tempElements = tempElements.concat([t]); - } - } - } - } - - // Get by attributes. - if (attributes.length !== 0) { - let attrs = {}; - for (let i = 0; i < attributes.length; i++) { - let attribute = attributes[i]; - attribute = attribute.substring(1, attribute.length - 1); - let parts: Array = (attribute.split('=')).map(item => item.trim()); - if (parts[1]) { - parts[1] = parts[1].substring(1, parts[1].length - 1); - } - attrs[parts[0]] = parts[1]; - } - let prevs: Array = []; - prevs = prevs.concat(tempElements); - tempElements = []; - - for (let i = 0, l = prevs.length; i !== l; ++i) { - let t = prevs[i]; - let shouldAdd = true; - for (let key in attrs) { - let lastChar = key.charAt(key.length - 1); - if (/[\^\*\$]$/.test(key)) { - key = key.substring(0, key.length - 1); - } - let tempAttr = t.getAttribute(key) || ''; - // Case: [href*=/en]. - if (lastChar === '*' && tempAttr.indexOf(attrs[key + lastChar]) === -1) { - shouldAdd = false; - break; - } - // Case: [href^=/en]. - else if (lastChar === '^' && tempAttr.indexOf(attrs[key + lastChar]) !== 0) { - shouldAdd = false; - break; - } - // Case: [href$=/en]. - else if (lastChar === '$' && - (tempAttr.lastIndexOf(attrs[key + lastChar]) === -1 - ? false - : tempAttr.lastIndexOf(attrs[key + lastChar])) - !== - tempAttr.length - attrs[key + lastChar].length) { - shouldAdd = false; - break; - } - // Case: [href=/en]. - else if (/[\$\*\^]/.test(lastChar) === false && tempAttr !== attrs[key]) { - shouldAdd = false; - break; - } - - } - - if (shouldAdd) { - tempElements = tempElements.concat([t]); - } - } - } - - elements = elements.concat(tempElements); - return elements; -} - -document.querySelectorAll = function (selector: string): NodeListOf { - if (typeof selector !== 'string') { - throw new TypeError('document.querySelectorAll: Invalid selector type. ' + - 'Expect: string. Found: ' + typeof selector + '.'); - } - let elements: Array = []; - - // Split `selector` into rules by `,`. - let rules: Array = selector.split(',').map(item => item.trim()); - - // Iterate through each rule. - // For the sake of performance, use for-loop here rather than forEach. - for (let i = 0, l = rules.length; i !== l; ++i) { - let rule = rules[i]; - - // TODO: support ' ' and '>'. - elements = elements.concat(getElementsBySelector.call(this, rule)); - } - - return (elements as any) as NodeListOf; -}; - -document.querySelector = function (selector: string): Element | null { - if (typeof selector !== 'string') { - throw new TypeError('document.querySelector: Invalid selector type. ' + - 'Expect: string. Found: ' + typeof selector + '.'); - } - let elements = this.querySelectorAll(selector); - return elements.length > 0 ? elements[0] : null; -} diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index a8fb81ed7b..fc25fcb495 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -8,13 +8,15 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:webf/bridge.dart'; +import 'package:webf/foundation.dart'; class NativeValue extends Struct { - @Double() - external double float64; + // Or Float64 + @Uint64() + external int u; @Int64() - external int u; + external int int64; @Int64() external int tag; @@ -27,6 +29,7 @@ enum JSValueType { TAG_NULL, TAG_FLOAT64, TAG_JSON, + TAG_LIST, TAG_POINTER, TAG_FUNCTION, TAG_ASYNC_FUNCTION @@ -80,9 +83,9 @@ dynamic fromNativeValue(Pointer nativeValue) { case JSValueType.TAG_NULL: return null; case JSValueType.TAG_FLOAT64: - return nativeValue.ref.float64; + return uInt64ToDouble(nativeValue.ref.u); case JSValueType.TAG_POINTER: - JSPointerType pointerType = JSPointerType.values[nativeValue.ref.float64.toInt()]; + JSPointerType pointerType = JSPointerType.values[nativeValue.ref.int64]; switch (pointerType) { case JSPointerType.NativeBoundingClientRect: return Pointer.fromAddress(nativeValue.ref.u).cast(); @@ -115,7 +118,17 @@ void toNativeValue(Pointer target, value) { target.ref.u = value ? 1 : 0; } else if (value is double) { target.ref.tag = JSValueType.TAG_FLOAT64.index; - target.ref.float64 = value; + target.ref.u = 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; + } } else if (value is String) { target.ref.tag = JSValueType.TAG_STRING.index; target.ref.u = stringToNativeString(value).address; @@ -123,12 +136,17 @@ void toNativeValue(Pointer target, value) { target.ref.tag = JSValueType.TAG_POINTER.index; target.ref.u = value.address; if (value is Pointer) { - target.ref.float64 = JSPointerType.NativeBoundingClientRect.index.toDouble(); + target.ref.int64 = JSPointerType.NativeBoundingClientRect.index; } else if (value is Pointer) { - target.ref.float64 = JSPointerType.NativeCanvasRenderingContext2D.index.toDouble(); + target.ref.int64 = JSPointerType.NativeCanvasRenderingContext2D.index; } else if (value is Pointer) { - target.ref.float64 = JSPointerType.NativeBindingObject.index.toDouble(); + 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; diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index e1f7d01303..36a9b3d23e 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -94,14 +94,14 @@ class Document extends Node { } @override - getBindingProperty(String key) { - switch (key) { + invokeBindingMethod(String method, List args) { + switch (method) { case 'querySelectorAll': - return querySelectorAll; + return querySelectorAll(args); case 'querySelector': - return querySelector; + return querySelector(args); } - return super.getBindingProperty(key); + return super.invokeBindingMethod(method, args); } dynamic querySelector(List args) { From c0a42ba39d95ab14e41ee795785827581f0e35cb Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Mon, 29 Aug 2022 10:17:35 +0000 Subject: [PATCH 215/498] Committing clang-format changes --- bridge/bindings/qjs/dom/document.cc | 8 ++------ bridge/bindings/qjs/native_value.cc | 6 +----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc index 9945683087..14cb478976 100644 --- a/bridge/bindings/qjs/dom/document.cc +++ b/bridge/bindings/qjs/dom/document.cc @@ -315,9 +315,7 @@ JSValue Document::querySelector(JSContext* ctx, JSValue this_val, int argc, JSVa auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); std::string selectorText = jsValueToStdString(ctx, argv[0]); - NativeValue arguments[] = { - Native_NewCString(selectorText) - }; + NativeValue arguments[] = {Native_NewCString(selectorText)}; return document->invokeBindingMethod("querySelector", 1, arguments); } @@ -331,9 +329,7 @@ JSValue Document::querySelectorAll(JSContext* ctx, JSValue this_val, int argc, J auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); std::string selectorText = jsValueToStdString(ctx, argv[0]); - NativeValue arguments[] = { - Native_NewCString(selectorText) - }; + NativeValue arguments[] = {Native_NewCString(selectorText)}; return document->invokeBindingMethod("querySelectorAll", 1, arguments); } diff --git a/bridge/bindings/qjs/native_value.cc b/bridge/bindings/qjs/native_value.cc index 9bf5d11818..7647f92564 100644 --- a/bridge/bindings/qjs/native_value.cc +++ b/bridge/bindings/qjs/native_value.cc @@ -42,11 +42,7 @@ NativeValue Native_NewFloat64(double value) { } NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr) { - return (NativeValue){ - reinterpret_cast(ptr), - static_cast(pointerType), - NativeTag::TAG_POINTER - }; + return (NativeValue){reinterpret_cast(ptr), static_cast(pointerType), NativeTag::TAG_POINTER}; } NativeValue Native_NewBool(bool value) { From b62b91ef1f83b63da26aa1327c0f6e8084bed2cf Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 29 Aug 2022 22:35:12 +0800 Subject: [PATCH 216/498] feat: support nestedSelector & selectorText --- webf/lib/src/css/css_rule.dart | 15 ++-- webf/lib/src/css/parser/parser.dart | 63 ++++++++++------ webf/lib/src/css/parser/selector.dart | 46 ++++++++++++ webf/lib/src/css/parser/tree.dart | 19 +++++ webf/lib/src/css/parser/visitor.dart | 91 +++++++++++++++++++++++ webf/lib/src/css/rule.dart | 1 - webf/test/src/css/style_rule_parser.dart | 19 +++++ webf/test/src/css/style_sheet_parser.dart | 27 +++---- 8 files changed, 238 insertions(+), 43 deletions(-) diff --git a/webf/lib/src/css/css_rule.dart b/webf/lib/src/css/css_rule.dart index d7a1114789..4e98f7d6d9 100644 --- a/webf/lib/src/css/css_rule.dart +++ b/webf/lib/src/css/css_rule.dart @@ -7,6 +7,13 @@ import 'package:webf/css.dart'; /// https://drafts.csswg.org/cssom/#the-cssstylerule-interface class CSSStyleRule extends CSSRule { + final SelectorTextVisitor _selectorTextVisitor = SelectorTextVisitor(); + + String get selectorText { + _selectorTextVisitor.visitSelectorGroup(selectorGroup); + return _selectorTextVisitor.toString(); + } + @override String get cssText => declaration.cssText; @@ -21,14 +28,6 @@ class CSSStyleRule extends CSSRule { SimpleSelector? get lastSimpleSelector { return selectorGroup.selectors.last.simpleSelectorSequences.last.simpleSelector; } - - String get selectorText { - var sb = StringBuffer(); - selectorGroup.selectors.forEach((selector) { - sb.write(selector.simpleSelectorSequences.map((ss) => ss.simpleSelector.name).join(' ')); - }); - return sb.toString(); - } } class KeyFrameBlock { diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index d453048706..f6438ef597 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -129,9 +129,9 @@ class CSSParser { List parseRules({int startPosition = 0}) { var rules = []; while (!_maybeEat(TokenKind.END_OF_FILE)) { - final rule = processRule(); - if (rule != null) { - rules.add(rule); + final data = processRule(); + if (data != null) { + rules.addAll(data); } else { _next(); } @@ -378,7 +378,10 @@ class CSSParser { selectors.add(selector); } while (_maybeEat(TokenKind.COMMA)); - keyframe.add(KeyFrameBlock(selectors, processDeclarations())); + final declarations = processDeclarations(); + if (declarations.last is CSSStyleDeclaration) { + keyframe.add(KeyFrameBlock(selectors, declarations.last)); + } } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile()); return keyframe; @@ -424,17 +427,35 @@ class CSSParser { return null; } - CSSRule? processRule([SelectorGroup? selectorGroup]) { + List? processRule([SelectorGroup? selectorGroup]) { if (selectorGroup == null) { final directive = processDirective(); if (directive != null) { _maybeEat(TokenKind.SEMICOLON); - return directive; + return [directive]; } selectorGroup = processSelectorGroup(); } if (selectorGroup != null) { - return CSSStyleRule(selectorGroup, processDeclarations()); + final declarations = processDeclarations(); + CSSStyleDeclaration declaration = declarations.where((element) => element is CSSStyleDeclaration).last!; + Iterable childRules = declarations.where((element) => element is CSSStyleRule); + CSSStyleRule rule = CSSStyleRule(selectorGroup, declaration); + List rules = [rule]; + for (CSSStyleRule childRule in childRules) { + // child Rule + for (Selector selector in childRule.selectorGroup.selectors) { + // parentRule + for (Selector parentSelector in selectorGroup.selectors) { + List newSelectorSequences = + mergeNestedSelector(parentSelector.simpleSelectorSequences, selector.simpleSelectorSequences); + selector.simpleSelectorSequences.clear(); + selector.simpleSelectorSequences.addAll(newSelectorSequences); + } + } + rules.add(childRule); + } + return rules; } return null; } @@ -442,9 +463,9 @@ class CSSParser { List processGroupRuleBody() { var nodes = []; while (!(_peekKind(TokenKind.RBRACE) || _peekKind(TokenKind.END_OF_FILE))) { - var rule = processRule(); - if (rule != null) { - nodes.add(rule); + var rules = processRule(); + if (rules != null) { + nodes.addAll(rules); continue; } break; @@ -475,9 +496,6 @@ class CSSParser { /// Return [:null:] if no selector or [SelectorGroup] if a selector was /// parsed. SelectorGroup? _nestedSelector() { - // var oldMessages = messages; - // _createMessages(); - var markedData = _mark; // Look a head do we have a nested selector instead of a declaration? @@ -489,33 +507,32 @@ class CSSParser { if (!nestedSelector) { // Not a selector so restore the world. _restore(markedData); - // messages = oldMessages; return null; } else { // Remember any messages from look ahead. - // oldMessages.mergeMessages(messages); - // messages = oldMessages; return selGroup; } } - CSSStyleDeclaration processDeclarations({bool checkBrace = true}) { + // return list of rule && CSSStyleDeclaration + List processDeclarations({bool checkBrace = true}) { if (checkBrace) _eat(TokenKind.LBRACE); var declaration = CSSStyleDeclaration(); + List list = [declaration]; do { var selectorGroup = _nestedSelector(); while (selectorGroup != null) { // Nested selector so process as a ruleset. - processRule(selectorGroup)!; + List rule = processRule(selectorGroup)!; + list.addAll(rule); selectorGroup = _nestedSelector(); } processDeclaration(declaration); } while (_maybeEat(TokenKind.SEMICOLON)); if (checkBrace) _eat(TokenKind.RBRACE); - - return declaration; + return list; } SelectorGroup? processSelectorGroup() { @@ -732,9 +749,13 @@ class CSSParser { // TODO(terry): If no identifier specified consider optimizing out the // : or :: and making this a normal selector. For now, // create an empty pseudoName. + // TODO(jiangzhou): Forced to evade Identifier pseudoName; - if (_peekIdentifier() && _peekToken.text != 'var' && _peekToken.text != 'rgb' && _peekToken.text != 'rgba') { + if (_peekIdentifier()) { pseudoName = identifier(); + if (pseudoName.isFunction()) { + return null; + } } else { return null; } diff --git a/webf/lib/src/css/parser/selector.dart b/webf/lib/src/css/parser/selector.dart index 872e5a2c39..0668ab55d4 100644 --- a/webf/lib/src/css/parser/selector.dart +++ b/webf/lib/src/css/parser/selector.dart @@ -299,3 +299,49 @@ class NegationSelector extends SimpleSelector { @override dynamic visit(Visitor visitor) => visitor.visitNegationSelector(this); } + +/// Merge the nested selector sequences [current] to the [parent] sequences or +/// substitue any & with the parent selector. +List mergeNestedSelector( + List parent, List current) { + // If any & operator then the parent selector will be substituted otherwise + // the parent selector is pre-pended to the current selector. + var hasThis = current.any((s) => s.simpleSelector.isThis); + + var newSequence = []; + + if (!hasThis) { + // If no & in the sector group then prefix with the parent selector. + newSequence.addAll(parent); + newSequence.addAll(_convertToDescendentSequence(current)); + } else { + for (var sequence in current) { + if (sequence.simpleSelector.isThis) { + // Substitue the & with the parent selector and only use a combinator + // descendant if & is prefix by a sequence with an empty name e.g., + // "... + &", "&", "... ~ &", etc. + var hasPrefix = newSequence.isNotEmpty && newSequence.last.simpleSelector.name.isNotEmpty; + newSequence.addAll(hasPrefix ? _convertToDescendentSequence(parent) : parent); + } else { + newSequence.add(sequence); + } + } + } + + return newSequence; +} + +/// Return selector sequences with first sequence combinator being a +/// descendant. Used for nested selectors when the parent selector needs to +/// be prefixed to a nested selector or to substitute the this (&) with the +/// parent selector. +List _convertToDescendentSequence(List sequences) { + if (sequences.isEmpty) return sequences; + + var newSequences = []; + var first = sequences.first; + newSequences.add(SimpleSelectorSequence(first.simpleSelector, TokenKind.COMBINATOR_DESCENDANT)); + newSequences.addAll(sequences.skip(1)); + + return newSequences; +} diff --git a/webf/lib/src/css/parser/tree.dart b/webf/lib/src/css/parser/tree.dart index c91a4fdc79..535e3ebbc7 100644 --- a/webf/lib/src/css/parser/tree.dart +++ b/webf/lib/src/css/parser/tree.dart @@ -48,6 +48,25 @@ class Identifier extends TreeNode { // a valid identifier. return name; } + + bool isFunction() { + return name == 'var' || + name == 'rgb' || + name == 'rgba' || + name == 'translate' || + name == 'rotate' || + name == 'calc' || + name == 'hsl' || + name == 'hsla' || + name == 'linear-gradient' || + name == 'radial-gradient' || + name == 'repeating-linear-gradient' || + name == 'repeating-radial-gradient' || + name == 'cubic-bezier' || + name == 'attr' || + name == 'url'; + ; + } } class Wildcard extends TreeNode { diff --git a/webf/lib/src/css/parser/visitor.dart b/webf/lib/src/css/parser/visitor.dart index 1282eb861e..9d12afb8d3 100644 --- a/webf/lib/src/css/parser/visitor.dart +++ b/webf/lib/src/css/parser/visitor.dart @@ -104,3 +104,94 @@ class SelectorVisitor implements Visitor { @override dynamic visitNegationSelector(NegationSelector node) => visitSimpleSelector(node); } + +class SelectorTextVisitor extends Visitor { + StringBuffer _buff = StringBuffer(); + + void emit(String str) { + _buff.write(str); + } + + @override + String toString() => _buff.toString().trim(); + + @override + dynamic visitSelectorGroup(SelectorGroup node) { + _buff = StringBuffer(); + var selectors = node.selectors; + var selectorsLength = selectors.length; + for (var i = 0; i < selectorsLength; i++) { + if (i > 0) emit(','); + selectors[i].visit(this); + } + } + + @override + void visitSelector(Selector node) { + for (var selectorSequences in node.simpleSelectorSequences) { + selectorSequences.visit(this); + } + } + + @override + void visitSimpleSelectorSequence(SimpleSelectorSequence node) { + emit(node.combinatorToString); + node.simpleSelector.visit(this); + } + + @override + void visitSimpleSelector(SimpleSelector node) { + emit(node.name); + } + + @override + void visitElementSelector(ElementSelector node) { + emit(node.toString()); + } + + @override + void visitAttributeSelector(AttributeSelector node) { + emit(node.toString()); + } + + @override + void visitIdSelector(IdSelector node) { + emit(node.toString()); + } + + @override + void visitClassSelector(ClassSelector node) { + emit(node.toString()); + } + + @override + void visitPseudoClassSelector(PseudoClassSelector node) { + emit(node.toString()); + } + + @override + void visitPseudoElementSelector(PseudoElementSelector node) { + emit(node.toString()); + } + + @override + void visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) { + emit(':${node.name}('); + node.argument.visit(this); + emit(')'); + } + + @override + void visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) { + emit('::${node.name}('); + node.expression.join(' , '); + emit(')'); + } + + @override + void visitNegationSelector(NegationSelector node) { + emit(':not('); + node.negationArg!.visit(this); + emit(')'); + } +} diff --git a/webf/lib/src/css/rule.dart b/webf/lib/src/css/rule.dart index fd567c319e..ae780a1fc5 100644 --- a/webf/lib/src/css/rule.dart +++ b/webf/lib/src/css/rule.dart @@ -6,7 +6,6 @@ import 'package:webf/css.dart'; abstract class CSSRule { String cssText = ''; CSSStyleSheet? parentStyleSheet; - CSSRule? parentRule; int position = -1; diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index 3914db8790..ab3281cb65 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -154,5 +154,24 @@ void main() { CSSStyleRule styleRule = rule as CSSStyleRule; expect(styleRule.lastSimpleSelector?.name, 'foo'); }); + + test('19', () { + CSSRule? rule = parseSingleRule(' .foo { transform: translate(30px, 20px) rotate(20deg); }'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + }); + + test('20', () { + CSSStyleSheet sheet = CSSParser('#header { color: red; h1 { font-size: 26px; }}').parse(); + CSSStyleRule styleRule1 = sheet.cssRules[0] as CSSStyleRule; + CSSStyleRule styleRule2 = sheet.cssRules[1] as CSSStyleRule; + final visitor = SelectorTextVisitor(); + visitor.visitSelectorGroup(styleRule1.selectorGroup); + expect(visitor.toString(), '#header'); + expect(styleRule1.declaration.getPropertyValue('color'), 'red'); + visitor.visitSelectorGroup(styleRule2.selectorGroup); + expect(visitor.toString(), '#header h1'); + expect(styleRule2.declaration.getPropertyValue('fontSize'), '26px'); + }); }); } diff --git a/webf/test/src/css/style_sheet_parser.dart b/webf/test/src/css/style_sheet_parser.dart index 1925b9f7c4..d01a1d6014 100644 --- a/webf/test/src/css/style_sheet_parser.dart +++ b/webf/test/src/css/style_sheet_parser.dart @@ -16,35 +16,36 @@ void main() { test('1', () { List rules = parseRules('.foo {color: red} \n .bar {}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).selectorText, '.bar'); }); test('2', () { List rules = parseRules('{} \n .foo {color: red;} ;\n .bar {;;}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).selectorText, '.bar'); }); test('3', () { List rules = parseRules('.foo {color: red;} .bar { .x {}; color: #aaa} .baz {}'); - expect(rules.length, 3); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect(rules.length, 4); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).selectorText, '.bar'); expect((rules[1] as CSSStyleRule).declaration.getPropertyValue('color'), '#aaa'); - expect((rules[2] as CSSStyleRule).selectorText, 'baz'); + expect((rules[2] as CSSStyleRule).selectorText, '.bar .x'); + expect((rules[3] as CSSStyleRule).selectorText, '.baz'); }); test('4', () { List rules = parseRules('.foo {color: red} .bar {background: url(data:image/png;base64...)}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).selectorText, '.bar'); expect( (rules[1] as CSSStyleRule).declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64...)'); }); @@ -52,7 +53,7 @@ void main() { test('5', () { List rules = parseRules('@charset "utf-8"; .foo {color: red}'); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); @@ -64,14 +65,14 @@ void main() { } '''); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); test('7', () { List rules = parseRules('.foo h6{color: red}'); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, 'foo h6'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo h6'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); }); From d541d5e183bfcb75429bbd5228b6f931c3066459 Mon Sep 17 00:00:00 2001 From: questguo Date: Tue, 30 Aug 2022 17:43:20 +0800 Subject: [PATCH 217/498] feat: support css animation --- webf/lib/src/css/animation.dart | 4 + webf/lib/src/css/css_animation.dart | 581 ++++++++++++++++++++++++ webf/lib/src/css/keywords.dart | 20 + webf/lib/src/css/matrix.dart | 3 +- webf/lib/src/css/render_style.dart | 25 +- webf/lib/src/css/style_declaration.dart | 11 +- webf/lib/src/css/style_property.dart | 104 ++++- webf/lib/src/dom/element.dart | 40 +- 8 files changed, 783 insertions(+), 5 deletions(-) create mode 100644 webf/lib/src/css/css_animation.dart diff --git a/webf/lib/src/css/animation.dart b/webf/lib/src/css/animation.dart index e8eb1e4305..713937bfa5 100644 --- a/webf/lib/src/css/animation.dart +++ b/webf/lib/src/css/animation.dart @@ -470,6 +470,10 @@ class KeyframeEffect extends AnimationEffect { _interpolations = _makeInterpolations(_propertySpecificKeyframeGroups, renderStyle); } + Iterable get properties { + return _propertySpecificKeyframeGroups.keys; + } + static _defaultParse(value) { return value; } diff --git a/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart new file mode 100644 index 0000000000..f8855a5c4e --- /dev/null +++ b/webf/lib/src/css/css_animation.dart @@ -0,0 +1,581 @@ +import 'dart:ui'; + +import 'package:webf/css.dart'; +import 'package:vector_math/vector_math_64.dart'; +import 'package:webf/dom.dart'; + +// CSS Animation: https://drafts.csswg.org/css-animations/ + +const String EVENT_ANIMATION_CANCEL = 'animationcancel'; +const String EVENT_ANIMATION_START = 'animationstart'; +const String EVENT_ANIMATION_END = 'animationend'; +const String EVENT_ANIMATION_ITERATION = 'animationiteration'; + +class AnimationEvent extends Event { + AnimationEvent(String type, + {String? animationName, double? elapsedTime, String? pseudoElement}) + : animationName = animationName ?? '', + elapsedTime = elapsedTime ?? 0.0, + pseudoElement = pseudoElement ?? '', + super(type) {} + + String animationName; + double elapsedTime; + String pseudoElement; +} + +const String _0s = '0s'; + +String _toCamelCase(String s) { + var sb = StringBuffer(); + var shouldUpperCase = false; + for (int rune in s.runes) { + // '-' char code is 45 + if (rune == 45) { + shouldUpperCase = true; + } else { + var char = String.fromCharCode(rune); + if (shouldUpperCase) { + sb.write(char.toUpperCase()); + shouldUpperCase = false; + } else { + sb.write(char); + } + } + } + + return sb.toString(); +} + +Color? _parseColor(String color, RenderStyle renderStyle, String propertyName) { + return CSSColor.resolveColor(color, renderStyle, propertyName); +} + +void _updateColor(Color oldColor, Color newColor, double progress, + String property, RenderStyle renderStyle) { + int alphaDiff = newColor.alpha - oldColor.alpha; + int redDiff = newColor.red - oldColor.red; + int greenDiff = newColor.green - oldColor.green; + int blueDiff = newColor.blue - oldColor.blue; + + int alpha = (alphaDiff * progress).toInt() + oldColor.alpha; + int red = (redDiff * progress).toInt() + oldColor.red; + int blue = (blueDiff * progress).toInt() + oldColor.blue; + int green = (greenDiff * progress).toInt() + oldColor.green; + Color color = Color.fromARGB(alpha, red, green, blue); + + renderStyle.target.setRenderStyleProperty(property, color); +} + +double? _parseLength(String length, RenderStyle renderStyle, String property) { + return CSSLength.parseLength(length, renderStyle, property).computedValue; +} + +void _updateLength(double oldLengthValue, double newLengthValue, + double progress, String property, CSSRenderStyle renderStyle) { + double value = oldLengthValue * (1 - progress) + newLengthValue * progress; + renderStyle.target.setRenderStyleProperty( + property, CSSLengthValue(value, CSSLengthType.PX)); +} + +FontWeight _parseFontWeight( + String fontWeight, RenderStyle renderStyle, String property) { + return CSSText.resolveFontWeight(fontWeight); +} + +void _updateFontWeight(FontWeight oldValue, FontWeight newValue, + double progress, String property, CSSRenderStyle renderStyle) { + FontWeight? fontWeight = FontWeight.lerp(oldValue, newValue, progress); + switch (property) { + case FONT_WEIGHT: + renderStyle.fontWeight = fontWeight; + break; + } +} + +double? _parseNumber(String number, RenderStyle renderStyle, String property) { + return CSSNumber.parseNumber(number); +} + +double _getNumber(double oldValue, double newValue, double progress) { + return oldValue * (1 - progress) + newValue * progress; +} + +void _updateNumber(double oldValue, double newValue, double progress, + String property, RenderStyle renderStyle) { + double number = _getNumber(oldValue, newValue, progress); + renderStyle.target.setRenderStyleProperty(property, number); +} + +double _parseLineHeight( + String lineHeight, RenderStyle renderStyle, String property) { + if (CSSNumber.isNumber(lineHeight)) { + return CSSLengthValue(CSSNumber.parseNumber(lineHeight), CSSLengthType.EM, + renderStyle, LINE_HEIGHT) + .computedValue; + } + return CSSLength.parseLength(lineHeight, renderStyle, LINE_HEIGHT) + .computedValue; +} + +void _updateLineHeight(double oldValue, double newValue, double progress, + String property, CSSRenderStyle renderStyle) { + renderStyle.lineHeight = CSSLengthValue( + _getNumber(oldValue, newValue, progress), CSSLengthType.PX); +} + +Matrix4? _parseTransform( + String value, RenderStyle renderStyle, String property) { + return CSSMatrix.computeTransformMatrix( + CSSFunction.parseFunction(value), renderStyle); +} + +void _updateTransform(Matrix4 begin, Matrix4 end, double t, String property, + CSSRenderStyle renderStyle) { + Matrix4 newMatrix4 = CSSMatrix.lerpMatrix(begin, end, t); + renderStyle.transformMatrix = newMatrix4; +} + +const List _colorHandler = [_parseColor, _updateColor]; +const List _lengthHandler = [_parseLength, _updateLength]; +const List _fontWeightHandler = [_parseFontWeight, _updateFontWeight]; +const List _numberHandler = [_parseNumber, _updateNumber]; +const List _lineHeightHandler = [_parseLineHeight, _updateLineHeight]; +const List _transformHandler = [_parseTransform, _updateTransform]; + +Map> CSSTransitionHandlers = { + COLOR: _colorHandler, + BACKGROUND_COLOR: _colorHandler, + BORDER_BOTTOM_COLOR: _colorHandler, + BORDER_LEFT_COLOR: _colorHandler, + BORDER_RIGHT_COLOR: _colorHandler, + BORDER_TOP_COLOR: _colorHandler, + BORDER_COLOR: _colorHandler, + TEXT_DECORATION_COLOR: _colorHandler, + OPACITY: _numberHandler, + Z_INDEX: _numberHandler, + FLEX_GROW: _numberHandler, + FLEX_SHRINK: _numberHandler, + FONT_WEIGHT: _fontWeightHandler, + LINE_HEIGHT: _lineHeightHandler, + TRANSFORM: _transformHandler, + BORDER_BOTTOM_LEFT_RADIUS: _lengthHandler, + BORDER_BOTTOM_RIGHT_RADIUS: _lengthHandler, + BORDER_TOP_LEFT_RADIUS: _lengthHandler, + BORDER_TOP_RIGHT_RADIUS: _lengthHandler, + RIGHT: _lengthHandler, + TOP: _lengthHandler, + BOTTOM: _lengthHandler, + LEFT: _lengthHandler, + LETTER_SPACING: _lengthHandler, + MARGIN_BOTTOM: _lengthHandler, + MARGIN_LEFT: _lengthHandler, + MARGIN_RIGHT: _lengthHandler, + MARGIN_TOP: _lengthHandler, + MIN_HEIGHT: _lengthHandler, + MIN_WIDTH: _lengthHandler, + PADDING_BOTTOM: _lengthHandler, + PADDING_LEFT: _lengthHandler, + PADDING_RIGHT: _lengthHandler, + PADDING_TOP: _lengthHandler, + // should non negative value + BORDER_BOTTOM_WIDTH: _lengthHandler, + BORDER_LEFT_WIDTH: _lengthHandler, + BORDER_RIGHT_WIDTH: _lengthHandler, + BORDER_TOP_WIDTH: _lengthHandler, + FLEX_BASIS: _lengthHandler, + FONT_SIZE: _lengthHandler, + HEIGHT: _lengthHandler, + WIDTH: _lengthHandler, + MAX_HEIGHT: _lengthHandler, + MAX_WIDTH: _lengthHandler, +}; + +/// The types of TransitionEvent +enum CSSTransitionEvent { + /// The transitionrun event occurs when a transition is created + run, + + /// The transitionstart event occurs when a transition’s delay phase ends. + start, + + /// The transitionend event occurs at the completion of the transition. + end, + + /// The transitioncancel event occurs when a transition is canceled. + cancel, +} + +mixin CSSAnimationMixin on RenderStyle { + List? _animationName; + + set animationName(List? value) { + _animationName = value; + } + + @override + List get animationName => _animationName ?? [NONE]; + + List? _animationDuration; + + set animationDuration(List? value) { + _animationDuration = value; + } + + @override + List get animationDuration => _animationDuration ?? const [_0s]; + + List? _animationTimingFunction; + + set animationTimingFunction(List? value) { + _animationTimingFunction = value; + } + + @override + List get animationTimingFunction => + _animationTimingFunction ?? const [EASE]; + + List? _animationDelay; + + set animationDelay(List? value) { + _animationDelay = value; + } + + @override + List get animationDelay => _animationDelay ?? const [_0s]; + + List? _animationIterationCount; + + set animationIterationCount(List? value) { + _animationIterationCount = value; + } + + @override + List get animationIterationCount => _animationIterationCount ?? ['1']; + + List? _animationDirection; + + set animationDirection(List? value) { + _animationDirection = value; + } + + @override + List get animationDirection => _animationDirection ?? ['normal']; + + List? _animationFillMode; + + set animationFillMode(List? value) { + _animationFillMode = value; + } + + @override + List get animationFillMode => _animationFillMode ?? ['none']; + + List? _animationPlayState; + + set animationPlayState(List? value) { + _animationPlayState = value; + } + + @override + List get animationPlayState => + _animationPlayState ?? ['running']; // paused + + bool shouldAnimation(List properties) { + if (renderBoxModel != null) { + bool shouldAnimation = false; + if (properties.any((element) => element.startsWith('animation'))) { + shouldAnimation = true; + } + return shouldAnimation; + } + return false; + } + + final Map _runningAnimation = {}; + final Map _animationProperties = {}; + final Map _cacheOriginProperties = {}; + + @override + String? removeAnimationProperty(String propertyName) { + String? prevValue = EMPTY_STRING; + + if (_animationProperties.containsKey(propertyName)) { + prevValue = _animationProperties[propertyName]; + _animationProperties.remove(propertyName); + } + + return prevValue; + } + + String _getSingleString(List list, int index) { + return list[index]; + } + + List? _getKeyFrames(String animationName) { + final styleSheets = target.ownerDocument.styleSheets; + + CSSKeyframesRule? cssKeyframesRule = null; + for (int j = styleSheets.length - 1; j >= 0; j--) { + final sheet = styleSheets[j]; + List rules = sheet.cssRules; + for (int i = rules.length - 1; i >= 0; i--) { + CSSRule rule = rules[i]; + if (rule is CSSKeyframesRule) { + if (rule.name == animationName) { + cssKeyframesRule = rule; + break; + } + } + } + } + if (cssKeyframesRule != null) { + List keyframes = []; + + cssKeyframesRule.blocks.forEach((rule) { + double? offset; + final keyText = rule.blockSelectors[0]; + if (keyText == 'from') { + offset = 0; + } else if (keyText == 'to') { + offset = 1; + } else { + offset = CSSPercentage.parsePercentage(keyText); + } + rule.declarations.sheetStyle.forEach((key, value) { + final property = _toCamelCase(key); + keyframes.add(Keyframe(property, value, offset ?? 0, LINEAR)); + }); + return; + }); + return keyframes; + } + return null; + } + + void beforeRunningAnimation() { + for (var i = 0; i < animationName.length; i++) { + final name = animationName[i]; + if (name == NONE) { + return; + } + final fillMode = _toCamelCase(_getSingleString(animationFillMode, i)); + List? keyframes = _getKeyFrames(name); + if (keyframes == null) { + return; + } + FillMode mode = FillMode.values.firstWhere((element) { + return element.toString().split('.').last == fillMode; + }); + Animation? animation = _runningAnimation[name]; + if (animation != null) { + return; + } + + if (mode == FillMode.backwards || mode == FillMode.both) { + final styles = getAnimationInitStyle(keyframes); + + styles.forEach((property, value) { + String? originStyle = target.inlineStyle[property]; + if (originStyle != null) { + _cacheOriginProperties.putIfAbsent(property, () => originStyle); + } + target.setInlineStyle(property, value); + }); + + // for (var i = 0; i < keyframes.length; i++) { + // Keyframe keyframe = keyframes[i]; + // String property = keyframe.property; + // String? originStyle = target.inlineStyle[property]; + // if (originStyle == keyframe.value) { + // continue; + // } + // if (originStyle != null) { + // _cacheOriginProperties.putIfAbsent(property, () => originStyle); + // } + // if (keyframe.offset == 0) { + // target.setInlineStyle(property, keyframe.value); + // target.style.flushPendingProperties(); + // } + // } + } + } + target.style.flushPendingProperties(); + } + + void runAnimation() { + final removeKeys = _runningAnimation.keys + .where((element) => !animationName.contains(element)) + .toList(); + + removeKeys.forEach((key) { + Animation animation = _runningAnimation[key]!; + _runningAnimation.remove(key); + animation.cancel(); + }); + + for (var i = 0; i < animationName.length; i++) { + final name = animationName[i]; + if (name == NONE) { + continue; + } + + final duration = _getSingleString(animationDuration, i); + final delay = _getSingleString(animationDelay, i); + final direction = _toCamelCase(_getSingleString(animationDirection, i)); + final iterationCount = _getSingleString(animationIterationCount, i); + final playState = _getSingleString(animationPlayState, i); + final timingFunction = _getSingleString(animationTimingFunction, i); + final fillMode = _toCamelCase(_getSingleString(animationFillMode, i)); + + EffectTiming? options = EffectTiming( + duration: CSSTime.parseTime(duration)!.toDouble(), + easing: timingFunction, + delay: CSSTime.parseTime(delay)!.toDouble(), + fill: FillMode.values.firstWhere((element) { + return element.toString().split('.').last == fillMode; + },orElse:(){ + return FillMode.both; + }), + iterations: iterationCount == 'infinite' + ? -1 + : (double.tryParse(iterationCount) ?? 1), + direction: PlaybackDirection.values.firstWhere( + (element) => element.toString().split('.').last == direction), + ); + + List? keyframes = _getKeyFrames(name); + + if (keyframes != null) { + KeyframeEffect effect = + KeyframeEffect(this, target, keyframes, options); + + Animation? animation = _runningAnimation[name]; + + if (animation != null) { + animation.effect = effect; + } else { + animation = Animation(effect, target.ownerDocument.animationTimeline); + + animation.onstart = () { + target.dispatchEvent( + AnimationEvent(EVENT_ANIMATION_START, animationName: name)); + }; + + animation.oncancel = (AnimationPlaybackEvent event) { + // target.dispatchEvent( + // AnimationEvent(EVENT_ANIMATION_END, animationName: name)); + // _runningAnimation.remove(name); + // animation?.dispose(); + }; + + animation.onfinish = (AnimationPlaybackEvent event) { + if (isBackwardsFillModeAnimation(animation!)) { + _revertOriginProperty(_runningAnimation[name]!); + } + + target.dispatchEvent( + AnimationEvent(EVENT_ANIMATION_END, animationName: name)); + // animation.dispose(); + }; + + _runningAnimation[name] = animation; + } + + if (playState == 'running' && + animation.playState != AnimationPlayState.running && + animation.playState != AnimationPlayState.finished) { + animation.play(); + } else { + if (playState == 'paused' && + animation.playState != AnimationPlayState.paused) { + animation.pause(); + } + } + } + } + } + + void cancelRunningAnimation() { + if (_runningAnimation.isNotEmpty) { + for (Animation animation in _runningAnimation.values) { + animation.cancel(); + } + _runningAnimation.clear(); + } + } + + void finishRunningAnimation() { + if (_runningAnimation.isNotEmpty) { + for (String property in _runningAnimation.keys) { + _runningAnimation[property]!.finish(); + } + _runningAnimation.clear(); + } + } + + void _revertOriginProperty(Animation animation) { + AnimationEffect? effect = animation.effect; + if (effect != null && effect is KeyframeEffect) { + effect.properties.forEach((String property) { + if (_cacheOriginProperties.containsKey(property)) { + target.setInlineStyle(property, _cacheOriginProperties[property]!); + } + _cacheOriginProperties.remove(property); + }); + } + } + + static bool isValidAnimationNameValue(String value) { + return value == NONE || CSSTextual.isCustomIdent(value); + } + + static bool isValidTransitionTimingFunctionValue(String value) { + return value == LINEAR || + value == EASE || + value == EASE_IN || + value == EASE_OUT || + value == EASE_IN_OUT || + value == STEP_END || + value == STEP_START || + CSSFunction.isFunction(value); + } + + static bool isValidAnimationFillModeValue(String value) { + return value == BACKWARDS || + value == FORWARDS || + value == BOTH || + value == NONE; + } + + static bool isValidAnimationPlayStateValue(String value) { + return value == RUNNING || value == PAUSED; + } + + static bool isValidAnimationDirectionValue(String value) { + return value == NORMAL || + value == REVERSE || + value == ALTERNATE || + value == ALTERNATE_REVERSE; + } + + static bool isBackwardsFillModeAnimation(Animation animation) { + final effect = animation.effect; + if (effect == null) { + return false; + } + final isBackwards = effect.timing?.fill != null && + (effect.timing!.fill == FillMode.backwards || + effect.timing!.fill == FillMode.both); + + return isBackwards; + } + + static Map getAnimationInitStyle(List keyframes) { + Map ret = {}; + keyframes.where((element) => element.offset == 0).forEach((element) { + ret[element.property] = element.value; + }); + return ret; + } +} diff --git a/webf/lib/src/css/keywords.dart b/webf/lib/src/css/keywords.dart index b7ba0c7661..38e0ecc0bf 100644 --- a/webf/lib/src/css/keywords.dart +++ b/webf/lib/src/css/keywords.dart @@ -283,3 +283,23 @@ const String VAR = 'var'; const String PERCENTAGE = '%'; const String ZERO = '0'; const String EMPTY_STRING = ''; + +// Animation +const String FORWARDS = 'forwards'; +const String BACKWARDS = 'backwards'; +const String BOTH = 'both'; +const String RUNNING = 'running'; +const String PAUSED = 'paused'; +const String REVERSE = 'reverse'; +const String ALTERNATE = 'alternate'; +const String ALTERNATE_REVERSE = 'alternate-reverse'; + +const String ANIMATION = 'animation'; +const String ANIMATION_NAME = 'animationName'; +const String ANIMATION_DURATION = 'animationDuration'; +const String ANIMATION_TIMING_FUNCTION = 'animationTimingFunction'; +const String ANIMATION_DELAY = 'animationDelay'; +const String ANIMATION_ITERATION_COUNT = 'animationIterationCount'; +const String ANIMATION_DIRECTION = 'animationDirection'; +const String ANIMATION_FILL_MODE = 'animationFillMode'; +const String ANIMATION_PLAY_STATE = 'animationPlayState'; diff --git a/webf/lib/src/css/matrix.dart b/webf/lib/src/css/matrix.dart index 137a8c6898..1b8403e4dd 100644 --- a/webf/lib/src/css/matrix.dart +++ b/webf/lib/src/css/matrix.dart @@ -608,7 +608,8 @@ class CSSMatrix { } static Matrix4? _computeMatrix(CSSFunctionalNotation method, RenderStyle renderStyle) { - switch (method.name) { + final methodName = method.name.toLowerCase(); + switch (methodName) { case MATRIX: if (method.args.length == 6) { List args = List.filled(6, 0); diff --git a/webf/lib/src/css/render_style.dart b/webf/lib/src/css/render_style.dart index 363b6b1d50..42f1615261 100644 --- a/webf/lib/src/css/render_style.dart +++ b/webf/lib/src/css/render_style.dart @@ -10,6 +10,7 @@ import 'package:flutter/rendering.dart'; import 'package:webf/css.dart'; import 'package:webf/dom.dart'; import 'package:webf/rendering.dart'; +import 'package:webf/src/css/css_animation.dart'; typedef RenderStyleVisitor = void Function(T renderStyle); @@ -161,6 +162,16 @@ abstract class RenderStyle { // Sliver Axis get sliverDirection; + // Animation + List get animationName; + List get animationDuration; + List get animationTimingFunction; + List get animationDelay; + List get animationIterationCount; + List get animationDirection; + List get animationFillMode; + List get animationPlayState; + void addFontRelativeProperty(String propertyName); void addRootFontRelativeProperty(String propertyName); void addColorRelativeProperty(String propertyName); @@ -204,7 +215,8 @@ class CSSRenderStyle extends RenderStyle CSSFilterEffectsMixin, CSSOpacityMixin, CSSTransitionMixin, - CSSVariableMixin { + CSSVariableMixin, + CSSAnimationMixin { CSSRenderStyle({required this.target}); @override @@ -599,6 +611,17 @@ class CSSRenderStyle extends RenderStyle case TRANSITION_PROPERTY: value = CSSStyleProperty.getMultipleValues(propertyValue); break; + // Animation + case ANIMATION_DELAY: + case ANIMATION_DIRECTION: + case ANIMATION_DURATION: + case ANIMATION_FILL_MODE: + case ANIMATION_ITERATION_COUNT: + case ANIMATION_NAME: + case ANIMATION_PLAY_STATE: + case ANIMATION_TIMING_FUNCTION: + value = CSSStyleProperty.getMultipleValues(propertyValue); + break; } // --x: foo; diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index cf2eddf30b..7e8d4c8430 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -9,6 +9,7 @@ import 'package:webf/rendering.dart'; import 'package:quiver/collection.dart'; typedef StyleChangeListener = void Function(String property, String? original, String present); +typedef StyleFlushedListener = void Function(List properties); const Map _CSSShorthandProperty = { MARGIN: true, @@ -30,6 +31,7 @@ const Map _CSSShorthandProperty = { OVERFLOW: true, TRANSITION: true, TEXT_DECORATION: true, + ANIMATION: true, }; // Reorder the properties for control render style init order, the last is the largest. @@ -68,11 +70,12 @@ class CSSStyleDeclaration { // TODO(yuanyan): defaultStyle should be longhand properties. Map? defaultStyle; StyleChangeListener? onStyleChanged; + StyleFlushedListener? onStyleFlushed; CSSStyleDeclaration(); // ignore: prefer_initializing_formals - CSSStyleDeclaration.computedStyle(this.target, this.defaultStyle, this.onStyleChanged); + CSSStyleDeclaration.computedStyle(this.target, this.defaultStyle, this.onStyleChanged, [this.onStyleFlushed]); /// An empty style declaration. static CSSStyleDeclaration empty = CSSStyleDeclaration(); @@ -85,6 +88,7 @@ class CSSStyleDeclaration { Map _pendingProperties = {}; final Map _importants = {}; final Map _sheetStyle = {}; + Map get sheetStyle => _sheetStyle; /// Textual representation of the declaration block. /// Setting this attribute changes the style. @@ -228,6 +232,9 @@ class CSSStyleDeclaration { case TEXT_DECORATION: CSSStyleProperty.setShorthandTextDecoration(longhandProperties, normalizedValue); break; + case ANIMATION: + CSSStyleProperty.setShorthandAnimation( + longhandProperties, normalizedValue); } _cachedExpandedShorthand[cacheKey] = longhandProperties; } @@ -447,6 +454,8 @@ class CSSStyleDeclaration { String currentValue = pendingProperties[propertyName]!; _emitPropertyChanged(propertyName, prevValue, currentValue); } + + onStyleFlushed?.call(propertyNames); } void merge(CSSStyleDeclaration declaration) { diff --git a/webf/lib/src/css/style_property.dart b/webf/lib/src/css/style_property.dart index b764e5ca97..faaeb4dcd8 100644 --- a/webf/lib/src/css/style_property.dart +++ b/webf/lib/src/css/style_property.dart @@ -4,6 +4,7 @@ */ import 'package:webf/css.dart'; +import 'package:webf/src/css/css_animation.dart'; // a-b to aB final RegExp _camelCaseReg = RegExp(r'-(\w)'); @@ -394,7 +395,7 @@ class CSSStyleProperty { // all, -moz-specific, sliding; => ['all', '-moz-specific', 'sliding'] static List? getMultipleValues(String property) { if (property.isEmpty) return null; - return property.split(_commaRegExp); + return property.split(_commaRegExp).map((e) => e.trim()).toList(); } static List>? getShadowValues(String property) { @@ -852,4 +853,105 @@ class CSSStyleProperty { return [x, y]; } + + static List? _getAnimationValues(String shorthandProperty) { + List animations = shorthandProperty.split(_commaRegExp); + List values = List.filled(8, null); + + for (String animation in animations) { + List parts = _splitBySpace(animation.trim()); + + String? duration; + String? timingFunction; + String? delay; + String? iterationCount; + String? direction; + String? fillMode; + String? playState; + String? name; + + for (String part in parts) { + if (parts.length <= 4 && + name == null && + CSSAnimationMixin.isValidAnimationNameValue(part)) { + name = part; + } else if (duration == null && CSSTime.isTime(part)) { + duration = part; + } else if (timingFunction == null && + CSSAnimationMixin.isValidTransitionTimingFunctionValue(part)) { + timingFunction = part; + } else if (delay == null && CSSTime.isTime(part)) { + delay = part; + } else if (iterationCount == null && (CSSNumber.isNumber(part) || part == 'infinite')) { + iterationCount = part; + } else if (direction == null && + CSSAnimationMixin.isValidAnimationDirectionValue(part)) { + direction = part; + } else if (fillMode == null && + CSSAnimationMixin.isValidAnimationFillModeValue(part)) { + fillMode = part; + } else if (playState == null && + CSSAnimationMixin.isValidAnimationPlayStateValue(part)) { + playState = part; + } else if (parts.length > 4 && + name == null && + CSSAnimationMixin.isValidAnimationNameValue(part)) { + name = part; + } else { + continue; + // return null; + } + } + + duration = duration ?? _0s; + timingFunction = timingFunction ?? EASE; + delay = delay ?? _0s; + iterationCount = iterationCount ?? _1; + direction = direction ?? NORMAL; + fillMode = fillMode ?? NONE; + playState = playState ?? RUNNING; + name = name ?? NONE; + + values[0] == null + ? values[0] = duration + : values[0] = values[0]! + (_comma + duration); + values[1] == null + ? values[1] = timingFunction + : values[1] = values[1]! + (_comma + timingFunction); + values[2] == null + ? values[2] = delay + : values[2] = values[2]! + (_comma + delay); + values[3] == null + ? values[3] = iterationCount + : values[3] = values[3]! + (_comma + iterationCount); + values[4] == null + ? values[4] = direction + : values[4] = values[4]! + (_comma + direction); + values[5] == null + ? values[5] = fillMode + : values[5] = values[5]! + (_comma + fillMode); + values[6] == null + ? values[6] = playState + : values[6] = values[6]! + (_comma + playState); + values[7] == null + ? values[7] = name + : values[7] = values[7]! + (_comma + name); + } + + return values; + } + + static void setShorthandAnimation(Map properties, String shorthandValue) { + List? values = _getAnimationValues(shorthandValue); + if (values == null) return; + + properties[ANIMATION_DURATION] = values[0]; + properties[ANIMATION_TIMING_FUNCTION] = values[1]; + properties[ANIMATION_DELAY] = values[2]; + properties[ANIMATION_ITERATION_COUNT] = values[3]; + properties[ANIMATION_DIRECTION] = values[4]; + properties[ANIMATION_FILL_MODE] = values[5]; + properties[ANIMATION_PLAY_STATE] = values[6]; + properties[ANIMATION_NAME] = values[7]; + } } diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 7a81b28812..f821f122d9 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -157,7 +157,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element _isDefaultRepaintBoundary = isDefaultRepaintBoundary, super(NodeType.ELEMENT_NODE, context) { // Init style and add change listener. - style = CSSStyleDeclaration.computedStyle(this, _defaultStyle, _onStyleChanged); + style = CSSStyleDeclaration.computedStyle(this, _defaultStyle, _onStyleChanged, _onStyleFlushed); // Init render style. renderStyle = CSSRenderStyle(target: this); @@ -1345,6 +1345,31 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element case TRANSITION_PROPERTY: renderStyle.transitionProperty = value; break; + // Animation + case ANIMATION_DELAY: + renderStyle.animationDelay = value; + break; + case ANIMATION_NAME: + renderStyle.animationName = value; + break; + case ANIMATION_DIRECTION: + renderStyle.animationDirection = value; + break; + case ANIMATION_DURATION: + renderStyle.animationDuration = value; + break; + case ANIMATION_PLAY_STATE: + renderStyle.animationPlayState = value; + break; + case ANIMATION_FILL_MODE: + renderStyle.animationFillMode = value; + break; + case ANIMATION_ITERATION_COUNT: + renderStyle.animationIterationCount = value; + break; + case ANIMATION_TIMING_FUNCTION: + renderStyle.animationTimingFunction = value; + break; // Others case OBJECT_FIT: renderStyle.objectFit = value; @@ -1437,6 +1462,19 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } } + void _onStyleFlushed(List properties) { + if (renderStyle.shouldAnimation(properties)) { + renderStyle.beforeRunningAnimation(); + if (renderBoxModel!.hasSize) { + renderStyle.runAnimation(); + } else { + SchedulerBinding.instance.addPostFrameCallback((callback) { + renderStyle.runAnimation(); + }); + } + } + } + // Set inline style property. void setInlineStyle(String property, String value) { // Current only for mark property is setting by inline style. From 9f78e7a5f8a9a06a58828621a945ad288853794a Mon Sep 17 00:00:00 2001 From: questguo Date: Tue, 30 Aug 2022 17:45:20 +0800 Subject: [PATCH 218/498] chore: update css animation example --- .gitignore | 1 + webf/example/assets/bundle.html | 13 +++---------- webf/example/assets/bundle.js | 2 ++ webf/example/ios/Podfile | 4 ++-- webf/example/lib/main.dart | 2 +- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 7a8a8cb2a6..062b86230f 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,4 @@ Podfile.lock temp coverage pubspec.lock +.fvm diff --git a/webf/example/assets/bundle.html b/webf/example/assets/bundle.html index 3aae4d50de..4a0de4595a 100644 --- a/webf/example/assets/bundle.html +++ b/webf/example/assets/bundle.html @@ -2,16 +2,9 @@ diff --git a/webf/example/assets/bundle.js b/webf/example/assets/bundle.js index bec06e56ed..b3aba1fc89 100644 --- a/webf/example/assets/bundle.js +++ b/webf/example/assets/bundle.js @@ -3,7 +3,9 @@ var br = document.createElement('br'); var text2 = document.createTextNode('你好,北海!'); var p = document.createElement('p'); p.className = 'p'; +p.style.display = 'inline-block'; p.style.textAlign = 'center'; +p.style.animation = '3s ease-in 1s 1 reverse both running example'; p.appendChild(text1); p.appendChild(br); p.appendChild(text2); diff --git a/webf/example/ios/Podfile b/webf/example/ios/Podfile index dc7f7b96f1..80dc684e16 100644 --- a/webf/example/ios/Podfile +++ b/webf/example/ios/Podfile @@ -59,12 +59,12 @@ end post_install do |installer| installer.pods_project.build_configurations.each do |config| - config_valid_archs(config) + config_valid_archs(config) end installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| - config_valid_archs(config) + config_valid_archs(config) end end installer.pods_project.save diff --git a/webf/example/lib/main.dart b/webf/example/lib/main.dart index 4d2db3713a..a395ceaa60 100644 --- a/webf/example/lib/main.dart +++ b/webf/example/lib/main.dart @@ -98,7 +98,7 @@ class _MyHomePageState extends State { devToolsService: ChromeDevToolsService(), viewportWidth: viewportSize.width - queryData.padding.horizontal, viewportHeight: viewportSize.height - appBar.preferredSize.height - queryData.padding.vertical, - bundle: WebFBundle.fromUrl('assets:assets/bundle.js'), + bundle: WebFBundle.fromUrl('assets:assets/bundle.html'), ), ], ), From 1b4d6d536a9c39a5806a00e6836148acc364b485 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 2 Sep 2022 17:22:54 +0800 Subject: [PATCH 219/498] fix: fix quickjs compile --- bridge/bindings/qjs/qjs_engine_patch.cc | 361 ++++++++++++++++++ bridge/bindings/qjs/qjs_engine_patch.h | 126 +++++- bridge/bindings/qjs/qjs_patch.c | 100 ----- bridge/bindings/qjs/script_value.h | 1 + .../quickjs/include/quickjs/quickjs.h | 25 ++ bridge/third_party/quickjs/src/core/base.h | 6 - bridge/third_party/quickjs/src/core/string.h | 4 - bridge/third_party/quickjs/src/core/types.h | 16 - 8 files changed, 508 insertions(+), 131 deletions(-) delete mode 100644 bridge/bindings/qjs/qjs_patch.c diff --git a/bridge/bindings/qjs/qjs_engine_patch.cc b/bridge/bindings/qjs/qjs_engine_patch.cc index e69de29bb2..9d91196bf2 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.cc +++ 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 index 5a578aa34f..bfaa420ae0 100644 --- a/bridge/bindings/qjs/qjs_engine_patch.h +++ b/bridge/bindings/qjs/qjs_engine_patch.h @@ -8,21 +8,137 @@ #include #include -#include "quickjs/cutils.h" + +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(JSRuntime* runtime, JSContext* ctx, const uint16_t* code, 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_HasClassId(JSRuntime* runtime, JSClassID classId); +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 +#endif // BRIDGE_QJS_PATCH_H \ No newline at end of file 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/script_value.h b/bridge/bindings/qjs/script_value.h index c401042822..774aba7276 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -8,6 +8,7 @@ #include #include +#include "qjs_engine_patch.h" #include "atomic_string.h" #include "exception_state.h" #include "foundation/macros.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/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) From 9a04dc891227e37fe268c12c28f59c434c63e3ae Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Fri, 2 Sep 2022 09:23:51 +0000 Subject: [PATCH 220/498] Committing clang-format changes --- bridge/bindings/qjs/script_value.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 774aba7276..ce522d0f97 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -8,11 +8,11 @@ #include #include -#include "qjs_engine_patch.h" #include "atomic_string.h" #include "exception_state.h" #include "foundation/macros.h" #include "foundation/native_string.h" +#include "qjs_engine_patch.h" namespace webf { From 8ce5907fbfcc2d9646cab1b7c09e49f272c13bba Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Wed, 3 Aug 2022 21:25:59 +0800 Subject: [PATCH 221/498] feat: support common selectors --- webf/lib/css.dart | 5 +- webf/lib/src/css/element_rule_collector.dart | 70 + webf/lib/src/css/parser/parser.dart | 1241 ++++++++++++++++- webf/lib/src/css/parser/selector.dart | 338 +++++ .../lib/src/css/parser/style_rule_parser.dart | 181 --- .../src/css/parser/style_sheet_parser.dart | 113 -- webf/lib/src/css/parser/token.dart | 89 ++ webf/lib/src/css/parser/token_kind.dart | 597 ++++++++ webf/lib/src/css/parser/tokenizer.dart | 475 +++++++ webf/lib/src/css/parser/tokenizer_base.dart | 444 ++++++ webf/lib/src/css/parser/tree.dart | 107 ++ webf/lib/src/css/parser/visitor.dart | 106 ++ webf/lib/src/css/rule.dart | 3 +- webf/lib/src/css/rule_set.dart | 97 ++ webf/lib/src/css/selector_checker.dart | 236 ++++ webf/lib/src/css/style_declaration.dart | 129 +- webf/lib/src/css/style_rule.dart | 21 +- webf/lib/src/css/style_sheet.dart | 25 +- webf/lib/src/dom/element.dart | 22 +- webf/lib/src/dom/elements/head.dart | 6 +- webf/lib/src/dom/node.dart | 14 + webf/test/src/css/style_rule_parser.dart | 128 +- webf/test/src/css/style_sheet_parser.dart | 63 +- 23 files changed, 4047 insertions(+), 463 deletions(-) create mode 100644 webf/lib/src/css/element_rule_collector.dart create mode 100644 webf/lib/src/css/parser/selector.dart delete mode 100644 webf/lib/src/css/parser/style_rule_parser.dart delete mode 100644 webf/lib/src/css/parser/style_sheet_parser.dart create mode 100644 webf/lib/src/css/parser/token.dart create mode 100644 webf/lib/src/css/parser/token_kind.dart create mode 100644 webf/lib/src/css/parser/tokenizer.dart create mode 100644 webf/lib/src/css/parser/tokenizer_base.dart create mode 100644 webf/lib/src/css/parser/tree.dart create mode 100644 webf/lib/src/css/parser/visitor.dart create mode 100644 webf/lib/src/css/rule_set.dart create mode 100644 webf/lib/src/css/selector_checker.dart diff --git a/webf/lib/css.dart b/webf/lib/css.dart index 9b8a61baf4..851483723a 100644 --- a/webf/lib/css.dart +++ b/webf/lib/css.dart @@ -26,8 +26,7 @@ export 'src/css/style_rule.dart'; export 'src/css/style_declaration.dart'; export 'src/css/style_property.dart'; export 'src/css/style_sheet.dart'; -export 'src/css/parser/style_rule_parser.dart'; -export 'src/css/parser/style_sheet_parser.dart'; +export 'src/css/rule_set.dart'; export 'src/css/parser/parser.dart'; export 'src/css/text.dart'; export 'src/css/origin.dart'; @@ -44,6 +43,8 @@ export 'src/css/value.dart'; export 'src/css/variable.dart'; export 'src/css/keywords.dart'; +export 'src/css/element_rule_collector.dart'; + // CSS Values // ignore: directives_ordering export 'src/css/values/angle.dart'; diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart new file mode 100644 index 0000000000..cc3b95717f --- /dev/null +++ b/webf/lib/src/css/element_rule_collector.dart @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'package:webf/css.dart'; +import 'package:webf/dom.dart'; +import 'package:webf/src/css/selector_checker.dart'; + +class ElementRuleCollector { + + CSSStyleDeclaration collectionFromRuleSet(RuleSet ruleSet, Element element) { + + List matchedRules = []; + + // #id + String? id = element.id; + if (id != null) { + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.idRules[id], element)); + } + + // .class + for (String className in element.classList) { + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.classRules[className], element)); + } + + // attribute selector + for (String attribute in element.attributes.keys) { + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.attributeRules[attribute], element)); + } + + // tag + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.tagRules[element.tagName], element)); + + // universal + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.universalRules, element)); + + matchedRules.sort((leftRule, rightRule) { + if (leftRule is! CSSStyleRule || rightRule is! CSSStyleRule) { + return 0; + } + return leftRule.selectorGroup.specificity.compareTo(rightRule.selectorGroup.specificity); + }); + + CSSStyleDeclaration declaration = CSSStyleDeclaration(); + for (CSSRule rule in matchedRules.reversed) { + if (rule is CSSStyleRule) { + declaration.merge(rule.declaration); + } + } + return declaration; + } + + List _collectMatchingRulesForList(List? rules, Element element) { + if (rules == null) { + return []; + } + List matchedRules = []; + SelectorEvaluator evaluator = SelectorEvaluator(); + for (CSSRule rule in rules) { + if (rule is! CSSStyleRule) { + continue; + } + if (evaluator.matchSelector(rule.selectorGroup, element)) { + matchedRules.add(rule); + } + } + return matchedRules; + } +} diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 3ce4fd5e93..10a357d3dc 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -1,40 +1,1221 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +import 'dart:math' as math; + import 'package:webf/css.dart'; -const int SLASH_CODE = 47; // / -const int NEWLINE_CODE = 10; // \n -const int SPACE_CODE = 32; // ' ' -const int FEED_CODE = 12; // \f -const int TAB_CODE = 9; // \t -const int CR_CODE = 13; // \r -const int OPEN_CURLY_CODE = 123; // { -const int CLOSE_CURLY_CODE = 125; // } -const int SEMICOLON_CODE = 59; // ; -const int ASTERISK_CODE = 42; // * -const int COLON_CODE = 58; // : -const int OPEN_PARENTHESES_CODE = 40; // ( -const int CLOSE_PARENTHESES_CODE = 41; // ) -const int SINGLE_QUOTE_CODE = 39; // '; -const int DOUBLE_QUOTE_CODE = 34; // " -const int HYPHEN_CODE = 45; // - -const int AT_CODE = 64; // @ -const int DOT_CODE = 46; // . +import 'package:source_span/source_span.dart'; + +part 'tree.dart'; +part 'token.dart'; +part 'token_kind.dart'; +part 'tokenizer.dart'; +part 'tokenizer_base.dart'; +part 'selector.dart'; +part 'visitor.dart'; + +enum ClauseType { + none, + conjunction, + disjunction, +} + +/// Used for parser lookup ahead (used for nested selectors Less support). +class ParserState extends TokenizerState { + final Token peekToken; + final Token? previousToken; + + ParserState(this.peekToken, this.previousToken, Tokenizer tokenizer) : super(tokenizer); +} + +bool get isChecked => true; + +// CSS2.1 pseudo-elements which were defined with a single ':'. +const _legacyPseudoElements = { + 'after', + 'before', + 'first-letter', + 'first-line', +}; class CSSParser { - static CSSRule? parseRule(String text, {CSSStyleSheet? parentStyleSheet}) { - // TODO: parse other css rule - CSSStyleRule? rule = CSSStyleRuleParser.parse(text); - if (rule != null) { - rule.parentStyleSheet = parentStyleSheet; + final Tokenizer tokenizer; + + /// File containing the source being parsed, used to report errors with + /// source-span locations. + // final SourceFile file; + + Token? _previousToken; + late Token _peekToken; + + CSSParser(String text, {int start = 0}) : tokenizer = Tokenizer(SourceFile.fromString(text), text, true, start) { + _peekToken = tokenizer.next(); + } + + /// Main entry point for parsing an entire CSS file. + CSSStyleSheet parse() { + var ruleSet = RuleSet(); + while (!_maybeEat(TokenKind.END_OF_FILE)) { + final rule = processRule(); + if (rule != null) { + ruleSet.addRule(rule); + } else { + _next(); + } + } + checkEndOfFile(); + return CSSStyleSheet(ruleSet); + } + + List parseRules() { + var rules = []; + while (!_maybeEat(TokenKind.END_OF_FILE)) { + final rule = processRule(); + if (rule != null) { + rules.add(rule); + } else { + _next(); + } + } + checkEndOfFile(); + return rules; + } + + /// Main entry point for parsing a simple selector sequence. + List parseSelector() { + var productions = []; + while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) { + var selector = processSelector(); + if (selector != null) { + productions.add(selector); + } else { + break; // Prevent infinite loop if we can't parse something. + } } - return rule; + + checkEndOfFile(); + return productions; } - static List parseRules(String text, {CSSStyleSheet? parentStyleSheet}) { - // TODO: should ingore style rule in at-rules - return CSSStyleSheetParser.parse(text); + /// Generate an error if [file] has not been completely consumed. + void checkEndOfFile() { + if (!(_peekKind(TokenKind.END_OF_FILE) || _peekKind(TokenKind.INCOMPLETE_COMMENT))) { + _error('premature end of file unknown CSS', _peekToken.span); + } + } + + /// Guard to break out of parser when an unexpected end of file is found. + // TODO(jimhug): Failure to call this method can lead to inifinite parser + // loops. Consider embracing exceptions for more errors to reduce + // the danger here. + bool isPrematureEndOfFile() { + if (_maybeEat(TokenKind.END_OF_FILE)) { + _error('unexpected end of file', _peekToken.span); + return true; + } else { + return false; + } } + + /////////////////////////////////////////////////////////////////// + // Basic support methods + /////////////////////////////////////////////////////////////////// + int _peek() { + return _peekToken.kind; + } + + Token _next({bool unicodeRange = false}) { + final next = _previousToken = _peekToken; + _peekToken = tokenizer.next(unicodeRange: unicodeRange); + return next; + } + + bool _peekKind(int kind) { + return _peekToken.kind == kind; + } + + // Is the next token a legal identifier? This includes pseudo-keywords. + bool _peekIdentifier() { + return TokenKind.isIdentifier(_peekToken.kind); + } + + /// Marks the parser/tokenizer look ahead to support Less nested selectors. + ParserState get _mark => ParserState(_peekToken, _previousToken, tokenizer); + + /// Restores the parser/tokenizer state to state remembered by _mark. + void _restore(ParserState markedData) { + tokenizer.restore(markedData); + _peekToken = markedData.peekToken; + _previousToken = markedData.previousToken; + } + + bool _maybeEat(int kind, {bool unicodeRange = false}) { + if (_peekToken.kind == kind) { + _previousToken = _peekToken; + _peekToken = tokenizer.next(unicodeRange: unicodeRange); + return true; + } else { + return false; + } + } + + void _eat(int kind, {bool unicodeRange = false}) { + if (!_maybeEat(kind, unicodeRange: unicodeRange)) { + _errorExpected(TokenKind.kindToString(kind)); + } + } + + void _errorExpected(String expected) { + var tok = _next(); + String message; + try { + message = 'expected $expected, but found $tok'; + } catch (e) { + message = 'parsing error expected $expected'; + } + _error(message, tok.span); + } + + void _error(String message, SourceSpan? location) { + location ??= _peekToken.span; + // messages.error(message, location); + } + + void _warning(String message, SourceSpan? location) { + location ??= _peekToken.span; + // messages.warning(message, location); + } + + /// Directive grammar: + /// + /// import: '@import' [string | URI] media_list? + /// media: '@media' media_query_list '{' ruleset '}' + /// page: '@page' [':' IDENT]? '{' declarations '}' + /// stylet: '@stylet' IDENT '{' ruleset '}' + /// media_query_list: IDENT [',' IDENT] + /// keyframes: '@-webkit-keyframes ...' (see grammar below). + /// font_face: '@font-face' '{' declarations '}' + /// namespace: '@namespace name url("xmlns") + /// host: '@host '{' ruleset '}' + /// mixin: '@mixin name [(args,...)] '{' declarations/ruleset '}' + /// include: '@include name [(@arg,@arg1)] + /// '@include name [(@arg...)] + /// content: '@content' + /// -moz-document: '@-moz-document' [ | url-prefix() | + /// domain() | regexp( processGroupRuleBody() { + var nodes = []; + while (!(_peekKind(TokenKind.RBRACE) || _peekKind(TokenKind.END_OF_FILE))) { + var rule = processRule(); + if (rule != null) { + nodes.add(rule); + continue; + } + break; + } + return nodes; + } + + /// Look ahead to see if what should be a declaration is really a selector. + /// If it's a selector than it's a nested selector. This support's Less' + /// nested selector syntax (requires a look ahead). E.g., + /// + /// div { + /// width : 20px; + /// span { + /// color: red; + /// } + /// } + /// + /// Two tag name selectors div and span equivalent to: + /// + /// div { + /// width: 20px; + /// } + /// div span { + /// color: red; + /// } + /// + /// Return [:null:] if no selector or [SelectorGroup] if a selector was + /// parsed. + SelectorGroup? _nestedSelector() { + // var oldMessages = messages; + // _createMessages(); + + var markedData = _mark; + + // Look a head do we have a nested selector instead of a declaration? + var selGroup = processSelectorGroup(); + + var nestedSelector = selGroup != null && _peekKind(TokenKind.LBRACE); + // && messages.messages.isEmpty; + + if (!nestedSelector) { + // Not a selector so restore the world. + _restore(markedData); + // messages = oldMessages; + return null; + } else { + // Remember any messages from look ahead. + // oldMessages.mergeMessages(messages); + // messages = oldMessages; + return selGroup; + } + } + + CSSStyleDeclaration processDeclarations({bool checkBrace = true}) { + var start = _peekToken.span; + + if (checkBrace) _eat(TokenKind.LBRACE); + + var decls = []; + + var declaration = CSSStyleDeclaration(); + + do { + var selectorGroup = _nestedSelector(); + while (selectorGroup != null) { + // Nested selector so process as a ruleset. + var ruleset = processRule(selectorGroup)!; + decls.add(ruleset); + selectorGroup = _nestedSelector(); + } + + processDeclaration(declaration); + } while (_maybeEat(TokenKind.SEMICOLON)); + + if (checkBrace) _eat(TokenKind.RBRACE); + + return declaration; + } + + SelectorGroup? processSelectorGroup() { + var selectors = []; + var start = _peekToken.span; + + tokenizer.inSelector = true; + do { + var selector = processSelector(); + if (selector != null) { + selectors.add(selector); + } + } while (_maybeEat(TokenKind.COMMA)); + tokenizer.inSelector = false; + + if (selectors.isNotEmpty) { + return SelectorGroup(selectors); + } + return null; + } + + /// Return list of selectors + Selector? processSelector() { + var simpleSequences = []; + var start = _peekToken.span; + while (true) { + // First item is never descendant make sure it's COMBINATOR_NONE. + var selectorItem = simpleSelectorSequence(simpleSequences.isEmpty); + if (selectorItem != null) { + simpleSequences.add(selectorItem); + } else { + break; + } + } + + if (simpleSequences.isEmpty) return null; + + return Selector(simpleSequences); + } + + /// Same as [processSelector] but reports an error for each combinator. + /// + /// This is a quick fix for parsing until the parser + /// supports Selector Level 4 grammar: + /// https://drafts.csswg.org/selectors-4/#typedef-compound-selector + Selector? processCompoundSelector() { + var selector = processSelector(); + if (selector != null) { + for (var sequence in selector.simpleSelectorSequences) { + if (!sequence.isCombinatorNone) { + // _error('compound selector can not contain combinator'); + } + } + } + return selector; + } + + SimpleSelectorSequence? simpleSelectorSequence(bool forceCombinatorNone) { + var start = _peekToken.span; + var combinatorType = TokenKind.COMBINATOR_NONE; + var thisOperator = false; + + switch (_peek()) { + case TokenKind.PLUS: + _eat(TokenKind.PLUS); + combinatorType = TokenKind.COMBINATOR_PLUS; + break; + case TokenKind.GREATER: + _eat(TokenKind.GREATER); + combinatorType = TokenKind.COMBINATOR_GREATER; + break; + case TokenKind.TILDE: + _eat(TokenKind.TILDE); + combinatorType = TokenKind.COMBINATOR_TILDE; + break; + case TokenKind.AMPERSAND: + _eat(TokenKind.AMPERSAND); + thisOperator = true; + break; + } + + // Check if WHITESPACE existed between tokens if so we're descendent. + if (combinatorType == TokenKind.COMBINATOR_NONE && !forceCombinatorNone) { + if (_previousToken != null && _previousToken!.end != _peekToken.start) { + combinatorType = TokenKind.COMBINATOR_DESCENDANT; + } + } + + var simpleSel = thisOperator + ? ElementSelector( + ThisOperator(), + ) + : simpleSelector(); + if (simpleSel == null && (combinatorType == TokenKind.COMBINATOR_PLUS || combinatorType == TokenKind.COMBINATOR_GREATER || combinatorType == TokenKind.COMBINATOR_TILDE)) { + // For "+ &", "~ &" or "> &" a selector sequence with no name is needed + // so that the & will have a combinator too. This is needed to + // disambiguate selector expressions: + // .foo&:hover combinator before & is NONE + // .foo & combinator before & is DESCDENDANT + // .foo > & combinator before & is GREATER + simpleSel = ElementSelector(Identifier('')); + } + if (simpleSel != null) { + return SimpleSelectorSequence(simpleSel, combinatorType); + } + return null; + } + + /// Simple selector grammar: + /// + /// simple_selector_sequence + /// : [ type_selector | universal ] + /// [ HASH | class | attrib | pseudo | negation ]* + /// | [ HASH | class | attrib | pseudo | negation ]+ + /// type_selector + /// : [ namespace_prefix ]? element_name + /// namespace_prefix + /// : [ IDENT | '*' ]? '|' + /// element_name + /// : IDENT + /// universal + /// : [ namespace_prefix ]? '*' + /// class + /// : '.' IDENT + SimpleSelector? simpleSelector() { + // TODO(terry): Natalie makes a good point parsing of namespace and element + // are essentially the same (asterisk or identifier) other + // than the error message for element. Should consolidate the + // code. + // TODO(terry): Need to handle attribute namespace too. + dynamic first; + var start = _peekToken.span; + switch (_peek()) { + case TokenKind.ASTERISK: + // Mark as universal namespace. + var tok = _next(); + first = Wildcard(); + break; + case TokenKind.IDENTIFIER: + first = identifier(); + break; + default: + // Expecting simple selector. + // TODO(terry): Could be a synthesized token like value, etc. + if (TokenKind.isKindIdentifier(_peek())) { + first = identifier(); + } else if (_peekKind(TokenKind.SEMICOLON)) { + // Can't be a selector if we found a semi-colon. + return null; + } + break; + } + + if (_maybeEat(TokenKind.NAMESPACE)) { + _next(); + return null; + } else if (first != null) { + return ElementSelector(first); + } else { + // Check for HASH | class | attrib | pseudo | negation + return simpleSelectorTail(); + } + } + + bool _anyWhiteSpaceBeforePeekToken(int kind) { + if (_previousToken != null && _previousToken!.kind == kind) { + // If end of previous token isn't same as the start of peek token then + // there's something between these tokens probably whitespace. + return _previousToken!.end != _peekToken.start; + } + + return false; + } + + /// type_selector | universal | HASH | class | attrib | pseudo + SimpleSelector? simpleSelectorTail() { + // Check for HASH | class | attrib | pseudo | negation + var start = _peekToken.span; + switch (_peek()) { + case TokenKind.HASH: + _eat(TokenKind.HASH); + + if (_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { + // _error('Not a valid ID selector expected #id'); + return null; + } + return IdSelector(identifier()); + case TokenKind.DOT: + _eat(TokenKind.DOT); + + if (_anyWhiteSpaceBeforePeekToken(TokenKind.DOT)) { + // _error('Not a valid class selector expected .className'); + return null; + } + return ClassSelector(identifier()); + case TokenKind.COLON: + // :pseudo-class ::pseudo-element + return processPseudoSelector(start); + case TokenKind.LBRACK: + return processAttribute(); + case TokenKind.DOUBLE: + _error('name must start with a alpha character, but found a number', _peekToken.span); + _next(); + break; + } + return null; + } + + SimpleSelector? processPseudoSelector(FileSpan start) { + // :pseudo-class ::pseudo-element + // TODO(terry): '::' should be token. + _eat(TokenKind.COLON); + var pseudoElement = _maybeEat(TokenKind.COLON); + + // TODO(terry): If no identifier specified consider optimizing out the + // : or :: and making this a normal selector. For now, + // create an empty pseudoName. + Identifier pseudoName; + if (_peekIdentifier()) { + pseudoName = identifier(); + } else { + return null; + } + var name = pseudoName.name.toLowerCase(); + + // Functional pseudo? + if (_peekToken.kind == TokenKind.LPAREN) { + if (!pseudoElement && name == 'not') { + _eat(TokenKind.LPAREN); + + // Negation : ':NOT(' S* negation_arg S* ')' + var negArg = simpleSelector(); + + _eat(TokenKind.RPAREN); + return NegationSelector(negArg); + } else if (!pseudoElement && (name == 'host' || name == 'host-context' || name == 'global-context' || name == '-acx-global-context')) { + _eat(TokenKind.LPAREN); + var selector = processCompoundSelector(); + if (selector == null) { + _errorExpected('a selector argument'); + return null; + } + _eat(TokenKind.RPAREN); + return PseudoClassFunctionSelector(pseudoName, selector); + } else { + // Special parsing for expressions in pseudo functions. Minus is used + // as operator not identifier. + // TODO(jmesserly): we need to flip this before we eat the "(" as the + // next token will be fetched when we do that. I think we should try to + // refactor so we don't need this boolean; it seems fragile. + tokenizer.inSelectorExpression = true; + _eat(TokenKind.LPAREN); + + // Handle function expression. + var expr = processSelectorExpression(); + + tokenizer.inSelectorExpression = false; + + // Used during selector look-a-head if not a SelectorExpression is + // bad. + _eat(TokenKind.RPAREN); + return (pseudoElement) ? PseudoElementFunctionSelector(pseudoName, expr) : PseudoClassFunctionSelector(pseudoName, expr); + } + } + + // Treat CSS2.1 pseudo-elements defined with pseudo class syntax as pseudo- + // elements for backwards compatibility. + return pseudoElement || _legacyPseudoElements.contains(name) ? PseudoElementSelector(pseudoName, isLegacy: !pseudoElement) : PseudoClassSelector(pseudoName); + } + + /// In CSS3, the expressions are identifiers, strings, or of the form "an+b". + /// + /// : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+ + /// + /// num [0-9]+|[0-9]*\.[0-9]+ + /// PLUS '+' + /// DIMENSION {num}{ident} + /// NUMBER {num} + List /* SelectorExpression | LiteralTerm */ processSelectorExpression() { + var start = _peekToken.span; + + var expressions = []; + + Token? termToken; + + var keepParsing = true; + while (keepParsing) { + switch (_peek()) { + case TokenKind.PLUS: + case TokenKind.MINUS: + case TokenKind.INTEGER: + case TokenKind.DOUBLE: + termToken = _next(); + expressions.add(termToken.text); + break; + case TokenKind.SINGLE_QUOTE: + final value = processQuotedString(false); + return ["'${_escapeString(value as String, single: true)}'"]; + case TokenKind.DOUBLE_QUOTE: + final value = processQuotedString(false); + return ['"${_escapeString(value as String)}"']; + case TokenKind.IDENTIFIER: + final value = identifier(); // Snarf up the ident we'll remap, maybe. + expressions.add(value.name); + break; + default: + keepParsing = false; + } + } + + return expressions; + } + + // Attribute grammar: + // + // attributes : + // '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']' + // + // ATTRIB_MATCHES : + // [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ] + // + // INCLUDES: '~=' + // + // DASHMATCH: '|=' + // + // PREFIXMATCH: '^=' + // + // SUFFIXMATCH: '$=' + // + // SUBSTRMATCH: '*=' + AttributeSelector? processAttribute() { + var start = _peekToken.span; + + if (_maybeEat(TokenKind.LBRACK)) { + var attrName = identifier(); + + int op; + switch (_peek()) { + case TokenKind.EQUALS: + case TokenKind.INCLUDES: // ~= + case TokenKind.DASH_MATCH: // |= + case TokenKind.PREFIX_MATCH: // ^= + case TokenKind.SUFFIX_MATCH: // $= + case TokenKind.SUBSTRING_MATCH: // *= + op = _peek(); + _next(); + break; + default: + op = TokenKind.NO_MATCH; + } + + dynamic value; + if (op != TokenKind.NO_MATCH) { + // Operator hit so we require a value too. + if (_peekIdentifier()) { + value = identifier(); + } else { + value = processQuotedString(false); + } + + if (value == null) { + _error('expected attribute value string or ident', _peekToken.span); + } + } + + _eat(TokenKind.RBRACK); + + return AttributeSelector(attrName, op, value); + } + return null; + } + + // Declaration grammar: + // + // declaration: property ':' expr prio? + // + // property: IDENT [or IE hacks] + // prio: !important + // expr: (see processExpr) + // + // Here are the ugly IE hacks we need to support: + // property: expr prio? \9; - IE8 and below property, /9 before semi-colon + // *IDENT - IE7 or below + // _IDENT - IE6 property (automatically a valid ident) + void processDeclaration(CSSStyleDeclaration style) { + // IDENT ':' expr '!important'? + if (TokenKind.isIdentifier(_peekToken.kind)) { + var propertyIdent = identifier().name; + + var resetProperty = false; + var keepGoing = true; + while (keepGoing) { + switch (_peek()) { + case TokenKind.COLON: + _eat(TokenKind.COLON); + keepGoing = false; + break; + case TokenKind.SEMICOLON: + case TokenKind.NEWLINE: + resetProperty = true; + _next(); + break; + case TokenKind.IDENTIFIER: + if (resetProperty) { + propertyIdent = identifier().name; + } + break; + default: + keepGoing = false; + } + } + + var expr = processExpr().join(' '); + + // Handle !important (prio) + var importantPriority = _maybeEat(TokenKind.IMPORTANT); + style.setProperty(propertyIdent, expr, importantPriority); + } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) { + _next(); + } else if (_peekToken.kind == TokenKind.DIRECTIVE_INCLUDE) { + // TODO @include mixinName in the declaration area. + } else if (_peekToken.kind == TokenKind.DIRECTIVE_EXTEND) { + _next(); + } + } + + // Expression grammar: + // + // expression: term [ operator? term]* + // + // operator: '/' | ',' + // term: (see processTerm) + List processExpr([bool ieFilter = false]) { + var start = _peekToken.span; + + var keepGoing = true; + List result = []; + String? expr; + while (keepGoing && (expr = processTerm()) != null) { + Expression? op; + + var opStart = _peekToken.span; + + switch (_peek()) { + case TokenKind.SLASH: + op = OperatorSlash(); + break; + case TokenKind.COMMA: + op = OperatorComma(); + break; + case TokenKind.BACKSLASH: + _next(); + if (_peekKind(TokenKind.INTEGER)) { + _next(); + if (isChecked) { + _warning('\$value is not valid in an expression', _makeSpan(start)); + } + } + break; + } + + if (expr != null) { + result.add(expr); + } else { + keepGoing = false; + } + + if (op != null) { + result.add(_makeSpan(opStart).text); + _next(); + } + } + return result; + } + + String? processTerm() { + var start = _peekToken.span; + Token? t; // token for term's value + dynamic value; // value of term (numeric values) + + var unary = ''; + switch (_peek()) { + case TokenKind.HASH: + _eat(TokenKind.HASH); + if (!_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { + String? hexText; + if (_peekKind(TokenKind.INTEGER)) { + var hexText1 = _peekToken.text; + _next(); + // Append identifier only if there's no delimiting whitespace. + if (_peekIdentifier() && _previousToken!.end == _peekToken.start) { + hexText = '$hexText1${identifier().name}'; + } else { + hexText = hexText1; + } + } else if (_peekIdentifier()) { + hexText = identifier().name; + } + return '#$hexText'; + } + + if (isChecked) { + _warning('Expected hex number', _makeSpan(start)); + } + // Construct the bad hex value with a #number. + return start.text; + case TokenKind.INTEGER: + case TokenKind.DOUBLE: + t = _next(); + return '$unary${t.text}'; + case TokenKind.SINGLE_QUOTE: + value = processQuotedString(false); + value = "'${_escapeString(value as String, single: true)}'"; + return value; + case TokenKind.DOUBLE_QUOTE: + value = processQuotedString(false); + value = '"${_escapeString(value as String)}"'; + return value; + case TokenKind.LPAREN: + _next(); + return null; + case TokenKind.LBRACK: + _next(); + return null; + case TokenKind.IDENTIFIER: + var nameValue = identifier(); // Snarf up the ident we'll remap, maybe. + + if (_maybeEat(TokenKind.LPAREN)) { + return processFunction(nameValue); + } + return nameValue.name; + case TokenKind.UNICODE_RANGE: + String? first; + String? second; + int firstNumber; + int secondNumber; + _eat(TokenKind.UNICODE_RANGE, unicodeRange: true); + if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { + first = _previousToken!.text; + firstNumber = int.parse('0x$first'); + if (firstNumber > MAX_UNICODE) { + _error('unicode range must be less than 10FFFF', _makeSpan(start)); + } + if (_maybeEat(TokenKind.MINUS, unicodeRange: true)) { + if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { + second = _previousToken!.text; + secondNumber = int.parse('0x$second'); + if (secondNumber > MAX_UNICODE) { + _error('unicode range must be less than 10FFFF', _makeSpan(start)); + } + if (firstNumber > secondNumber) { + _error('unicode first range can not be greater than last', _makeSpan(start)); + } + } + } + } else if (_maybeEat(TokenKind.HEX_RANGE, unicodeRange: true)) { + first = _previousToken!.text; + } + return 'U+$first-$second'; + case TokenKind.AT: + break; + } + + return null; + } + + static const int MAX_UNICODE = 0x10FFFF; + + String processQuotedString([bool urlString = false]) { + var start = _peekToken.span; + + // URI term sucks up everything inside of quotes(' or ") or between parens + var stopToken = urlString ? TokenKind.RPAREN : -1; + + // Note: disable skipping whitespace tokens inside a string. + // TODO(jmesserly): the layering here feels wrong. + var inString = tokenizer._inString; + tokenizer._inString = false; + + switch (_peek()) { + case TokenKind.SINGLE_QUOTE: + stopToken = TokenKind.SINGLE_QUOTE; + _next(); // Skip the SINGLE_QUOTE. + start = _peekToken.span; + break; + case TokenKind.DOUBLE_QUOTE: + stopToken = TokenKind.DOUBLE_QUOTE; + _next(); // Skip the DOUBLE_QUOTE. + start = _peekToken.span; + break; + default: + if (urlString) { + if (_peek() == TokenKind.LPAREN) { + _next(); // Skip the LPAREN. + start = _peekToken.span; + } + stopToken = TokenKind.RPAREN; + } else { + // _error('unexpected string'); + } + break; + } + + // Gobble up everything until we hit our stop token. + var stringValue = StringBuffer(); + while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) { + stringValue.write(_next().text); + } + + tokenizer._inString = inString; + + // All characters between quotes is the string. + if (stopToken != TokenKind.RPAREN) { + _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE; + } + + return stringValue.toString(); + } + + // TODO(terry): Hack to gobble up the calc expression as a string looking + // for the matching RPAREN the expression is not parsed into the + // AST. + // + // grammar should be: + // + // = calc( ) + // = [ [ '+' | '-' ] ]* + // = [ '*' | '/' ]* + // = | | | ( ) + // + String processCalcExpression() { + var inString = tokenizer._inString; + tokenizer._inString = false; + + // Gobble up everything until we hit our stop token. + var stringValue = StringBuffer(); + var left = 1; + var matchingParens = false; + while (_peek() != TokenKind.END_OF_FILE && !matchingParens) { + var token = _peek(); + if (token == TokenKind.LPAREN) { + left++; + } else if (token == TokenKind.RPAREN) { + left--; + } + + matchingParens = left == 0; + if (!matchingParens) stringValue.write(_next().text); + } + + if (!matchingParens) { + _error('problem parsing function expected ), ', _peekToken.span); + } + + tokenizer._inString = inString; + + return stringValue.toString(); + } + + // Function grammar: + // + // function: IDENT '(' expr ')' + // + dynamic processFunction(Identifier func) { + var start = _peekToken.span; + var name = func.name; + + switch (name) { + case 'rgb': + var expr = processExpr(); + if (!_maybeEat(TokenKind.RPAREN)) { + _error('problem parsing function expected ), ', _peekToken.span); + } + return 'rgb(${expr.join()})'; + case 'url': + // URI term sucks up everything inside of quotes(' or ") or between + // parens. + var urlParam = processQuotedString(true); + + // TODO(terry): Better error message and checking for mismatched quotes. + if (_peek() == TokenKind.END_OF_FILE) { + _error('problem parsing URI', _peekToken.span); + } + + if (_peek() == TokenKind.RPAREN) { + _next(); + } + + return 'url($urlParam)'; + case 'var': + // TODO(terry): Consider handling var in IE specific filter/progid. + // This will require parsing entire IE specific syntax + // e.g. `param = value` or `progid:com_id`, etc. + // for example: + // + // var-blur: Blur(Add = 0, Direction = 225, Strength = 10); + // var-gradient: progid:DXImageTransform.Microsoft.gradient" + // (GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); + var expr = processExpr(); + if (!_maybeEat(TokenKind.RPAREN)) { + _error('problem parsing var expected ), ', _peekToken.span); + } + return expr; + default: + var expr = processExpr(); + if (!_maybeEat(TokenKind.RPAREN)) { + _error('problem parsing function expected ), ', _peekToken.span); + } + return expr.join(); + } + } + + Identifier identifier() { + var tok = _next(); + + if (!TokenKind.isIdentifier(tok.kind) && !TokenKind.isKindIdentifier(tok.kind)) { + if (isChecked) { + _warning('expected identifier, but found $tok', tok.span); + } + return Identifier(''); + } + return Identifier(tok.text); + } +} + +/// Escapes [text] for use in a CSS string. +/// [single] specifies single quote `'` vs double quote `"`. +String _escapeString(String text, {bool single = false}) { + StringBuffer? result; + + for (var i = 0; i < text.length; i++) { + var code = text.codeUnitAt(i); + String? replace; + switch (code) { + case 34 /*'"'*/ : + if (!single) replace = r'\"'; + break; + case 39 /*"'"*/ : + if (single) replace = r"\'"; + break; + } + + if (replace != null && result == null) { + result = StringBuffer(text.substring(0, i)); + } + + if (result != null) result.write(replace ?? text[i]); + } + + return result == null ? text : result.toString(); } diff --git a/webf/lib/src/css/parser/selector.dart b/webf/lib/src/css/parser/selector.dart new file mode 100644 index 0000000000..a67bd73527 --- /dev/null +++ b/webf/lib/src/css/parser/selector.dart @@ -0,0 +1,338 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +const kIdSpecificity = 0x010000; +const kClassLikeSpecificity = 0x000100; +const kTagSpecificity = 0x000001; + +// https://drafts.csswg.org/cssom/#parse-a-group-of-selectors +class SelectorGroup extends TreeNode { + final List selectors; + + int _specificity = -1; + + int get specificity { + if (_specificity == -1) { + _specificity = _calcSpecificity(); + } + return _specificity; + } + + SelectorGroup(this.selectors) : super(); + + @override + SelectorGroup clone() => SelectorGroup(selectors); + + @override + dynamic visit(Visitor visitor) => visitor.visitSelectorGroup(this); + + int _calcSpecificity() { + int specificity = 0; + for (final selector in selectors) { + for (final simpleSelectorSequence in selector.simpleSelectorSequences) { + final simpleSelector = simpleSelectorSequence.simpleSelector; + switch (simpleSelector.runtimeType) { + case IdSelector: + specificity += kIdSpecificity; + break; + case ClassSelector: + specificity += kClassLikeSpecificity; + break; + case ElementSelector: + specificity += kTagSpecificity; + break; + } + } + } + return specificity; + } +} + +class Selector extends TreeNode { + final List simpleSelectorSequences; + + Selector(this.simpleSelectorSequences) : super(); + + void add(SimpleSelectorSequence seq) => simpleSelectorSequences.add(seq); + + int get length => simpleSelectorSequences.length; + + @override + Selector clone() { + var simpleSequences = simpleSelectorSequences.map((ss) => ss.clone()).toList(); + + return Selector(simpleSequences); + } + + @override + dynamic visit(Visitor visitor) => visitor.visitSelector(this); +} + +class SimpleSelectorSequence extends TreeNode { + /// +, >, ~, NONE + int combinator; + final SimpleSelector simpleSelector; + + SimpleSelectorSequence(this.simpleSelector, [int combinator = TokenKind.COMBINATOR_NONE]) + : combinator = combinator, + super(); + + bool get isCombinatorNone => combinator == TokenKind.COMBINATOR_NONE; + bool get isCombinatorPlus => combinator == TokenKind.COMBINATOR_PLUS; + bool get isCombinatorGreater => combinator == TokenKind.COMBINATOR_GREATER; + bool get isCombinatorTilde => combinator == TokenKind.COMBINATOR_TILDE; + bool get isCombinatorDescendant => combinator == TokenKind.COMBINATOR_DESCENDANT; + + String get _combinatorToString { + switch (combinator) { + case TokenKind.COMBINATOR_DESCENDANT: + return ' '; + case TokenKind.COMBINATOR_GREATER: + return ' > '; + case TokenKind.COMBINATOR_PLUS: + return ' + '; + case TokenKind.COMBINATOR_TILDE: + return ' ~ '; + default: + return ''; + } + } + + @override + SimpleSelectorSequence clone() => SimpleSelectorSequence(simpleSelector, combinator); + + @override + dynamic visit(Visitor visitor) => visitor.visitSimpleSelectorSequence(this); + + @override + String toString() => simpleSelector.name; +} + +// All other selectors (element, #id, .class, attribute, pseudo, negation, +// namespace, *) are derived from this selector. +abstract class SimpleSelector extends TreeNode { + final dynamic _name; // ThisOperator, Identifier, Negation, others? + + SimpleSelector(this._name) : super(); + + // TOOD(srawlins): Figure this one out. + // ignore: avoid_dynamic_calls + String get name => _name.name as String; + + bool get isThis => _name is ThisOperator; + + @override + dynamic visit(Visitor visitor) => visitor.visitSimpleSelector(this); +} + +// element name +class ElementSelector extends SimpleSelector { + ElementSelector(name) : super(name); + + @override + ElementSelector clone() => ElementSelector(_name); + + @override + String toString() => name; + + @override + dynamic visit(Visitor visitor) => visitor.visitElementSelector(this); +} + +// [attr op value] +class AttributeSelector extends SimpleSelector { + final int _op; + final dynamic value; + + AttributeSelector(Identifier name, this._op, this.value) : super(name); + + int get operatorKind => _op; + + String? matchOperator() { + switch (_op) { + case TokenKind.EQUALS: + return '='; + case TokenKind.INCLUDES: + return '~='; + case TokenKind.DASH_MATCH: + return '|='; + case TokenKind.PREFIX_MATCH: + return '^='; + case TokenKind.SUFFIX_MATCH: + return '\$='; + case TokenKind.SUBSTRING_MATCH: + return '*='; + case TokenKind.NO_MATCH: + return ''; + } + return null; + } + + // Return the TokenKind for operator used by visitAttributeSelector. + String? matchOperatorAsTokenString() { + switch (_op) { + case TokenKind.EQUALS: + return 'EQUALS'; + case TokenKind.INCLUDES: + return 'INCLUDES'; + case TokenKind.DASH_MATCH: + return 'DASH_MATCH'; + case TokenKind.PREFIX_MATCH: + return 'PREFIX_MATCH'; + case TokenKind.SUFFIX_MATCH: + return 'SUFFIX_MATCH'; + case TokenKind.SUBSTRING_MATCH: + return 'SUBSTRING_MATCH'; + } + return null; + } + + String valueToString() { + if (value != null) { + if (value is Identifier) { + return value.toString(); + } else { + return '"$value"'; + } + } else { + return ''; + } + } + + @override + AttributeSelector clone() => AttributeSelector(_name as Identifier, _op, value); + + @override + String toString() => '[$name${matchOperator()}${valueToString()}]'; + + @override + dynamic visit(Visitor visitor) => visitor.visitAttributeSelector(this); +} + +// #id +class IdSelector extends SimpleSelector { + IdSelector(Identifier name) : super(name); + @override + IdSelector clone() => IdSelector(_name as Identifier); + + @override + String toString() => '#$_name'; + + @override + dynamic visit(Visitor visitor) => visitor.visitIdSelector(this); +} + +// .class +class ClassSelector extends SimpleSelector { + ClassSelector(Identifier name) : super(name); + @override + ClassSelector clone() => ClassSelector(_name as Identifier); + @override + String toString() => '.$_name'; + + @override + dynamic visit(Visitor visitor) => visitor.visitClassSelector(this); +} + +// :pseudoClass +class PseudoClassSelector extends SimpleSelector { + PseudoClassSelector(Identifier name) : super(name); + + @override + PseudoClassSelector clone() => PseudoClassSelector(_name as Identifier); + + @override + String toString() => ':$name'; + + @override + dynamic visit(Visitor visitor) => visitor.visitPseudoClassSelector(this); +} + +// ::pseudoElement +class PseudoElementSelector extends SimpleSelector { + // If true, this is a CSS2.1 pseudo-element with only a single ':'. + final bool isLegacy; + + PseudoElementSelector(Identifier name, {this.isLegacy = false}) : super(name); + + @override + PseudoElementSelector clone() => PseudoElementSelector(_name as Identifier); + + @override + String toString() => "${isLegacy ? ':' : '::'}$name"; + + @override + dynamic visit(Visitor visitor) => visitor.visitPseudoElementSelector(this); +} + +// :pseudoClassFunction(argument) +class PseudoClassFunctionSelector extends PseudoClassSelector { + final dynamic argument; // Selector, List + + PseudoClassFunctionSelector(Identifier name, this.argument) : super(name); + + @override + PseudoClassFunctionSelector clone() => PseudoClassFunctionSelector(_name as Identifier, argument); + + Selector get selector => argument as Selector; + + List get expression => argument as List; + + @override + dynamic visit(Visitor visitor) => visitor.visitPseudoClassFunctionSelector(this); +} + +// ::pseudoElementFunction(expression) +class PseudoElementFunctionSelector extends PseudoElementSelector { + final List expression; + + PseudoElementFunctionSelector(Identifier name, this.expression) : super(name); + + @override + PseudoElementFunctionSelector clone() => PseudoElementFunctionSelector(_name as Identifier, expression); + + @override + dynamic visit(Visitor visitor) => visitor.visitPseudoElementFunctionSelector(this); +} + +// :NOT(negation_arg) +class NegationSelector extends SimpleSelector { + final SimpleSelector? negationArg; + + NegationSelector(this.negationArg) : super(Negation()); + + @override + NegationSelector clone() => NegationSelector(negationArg); + + @override + dynamic visit(Visitor visitor) => visitor.visitNegationSelector(this); +} diff --git a/webf/lib/src/css/parser/style_rule_parser.dart b/webf/lib/src/css/parser/style_rule_parser.dart deleted file mode 100644 index f396cc5044..0000000000 --- a/webf/lib/src/css/parser/style_rule_parser.dart +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ -import 'package:webf/css.dart'; - -const int _SELECTOR = 0; -const int _NAME = 1; -const int _VALUE = 2; -const int _FUNCTION = 3; -const int _END = 4; - -const String _END_OF_COMMENT = '*/'; -const String _EMPTY_STRING = ''; - -class CSSStyleRuleParser { - static CSSStyleRule? parse(String ruleText) { - String selectorText = _EMPTY_STRING; - Map style = {}; - - StringBuffer buffer = StringBuffer(); - int state = _SELECTOR; - String propertyName = _EMPTY_STRING; - bool isString = false; - bool isCustomProperty = false; - - for (int pos = 0, length = ruleText.length; pos < length && state != _END; pos++) { - int c = ruleText.codeUnitAt(pos); - - if (c == SINGLE_QUOTE_CODE || c == DOUBLE_QUOTE_CODE) { - isString = !isString; - } - - if (isString) { - buffer.writeCharCode(c); - continue; - } - - switch (c) { - case SPACE_CODE: - case TAB_CODE: - case CR_CODE: - case FEED_CODE: - case NEWLINE_CODE: - if ((state == _SELECTOR || state == _VALUE) && pos > 0) { - // Squash 2 or more white-spaces in the row into 1 space. - switch (ruleText.codeUnitAt(pos - 1)) { - case SPACE_CODE: - case TAB_CODE: - case CR_CODE: - case FEED_CODE: - case NEWLINE_CODE: - break; - default: - buffer.writeCharCode(SPACE_CODE); - break; - } - } else if (state == _FUNCTION) { - buffer.writeCharCode(c); - } - break; - case HYPHEN_CODE: - if (state == _NAME && isCustomProperty == false) { - int letter = ruleText.codeUnitAt(pos + 1); - if (letter == HYPHEN_CODE) { - // Ignore css custom properties: `--main-bg-color`. - buffer.writeCharCode(c); - isCustomProperty = true; - break; - } - // Convert background-image to backgroundImage - // a-z: 97-122 - if (letter > 96 && letter < 123) { - // Convert to upper case: A-Z: 65-90 - letter = letter - 32; - buffer.writeCharCode(letter); - pos++; - } - } else { - buffer.writeCharCode(c); - } - break; - case SLASH_CODE: - if (ruleText.codeUnitAt(pos + 1) == ASTERISK_CODE) { - // This is a comment, find the end of the comment. - pos += 2; - int index = ruleText.indexOf(_END_OF_COMMENT, pos); - if (index == -1) { - // Unterminated comment - state = _END; - } else { - pos = index + 2; - } - } else { - buffer.writeCharCode(c); - } - break; - case OPEN_CURLY_CODE: - if (state == _SELECTOR) { - selectorText = buffer.toString().trim(); - if (selectorText.isEmpty) { - // Invalid syntax - state = _END; - } - buffer.clear(); - state = _NAME; - } else { - // Unexpected { : `.foo { .foo {}; color: red}` - buffer.writeCharCode(c); - } - break; - case COLON_CODE: - if (state == _NAME) { - propertyName = buffer.toString().trim(); - buffer.clear(); - // Reset isCustomProperty flag. - isCustomProperty = false; - state = _VALUE; - } else { - buffer.writeCharCode(c); - } - break; - case CLOSE_PARENTHESES_CODE: - if (state == _FUNCTION) { - buffer.writeCharCode(c); - state = _VALUE; - } else { - buffer.writeCharCode(c); - } - break; - case OPEN_PARENTHESES_CODE: - // This is a function, find the end of the function. - if (state == _VALUE) { - state = _FUNCTION; - } - - // Pseudo-class selector: `th:nth-child(4)` - // Function value: `url()`, `rgb()` - buffer.writeCharCode(c); // Write ( - break; - case SEMICOLON_CODE: - if (state == _FUNCTION) { - // In data uri function - buffer.writeCharCode(c); - } else { - // `{ col;or: red; }` will parsed as {col: '', or: 'red'} - if (propertyName.isNotEmpty) { - String value = buffer.toString().trim(); - if (value.isNotEmpty) style[propertyName] = value; - propertyName = _EMPTY_STRING; - } - buffer.clear(); - // Skip empty property declaration like `color: red; ;;`. - state = _NAME; - } - break; - case CLOSE_CURLY_CODE: - if (state == _VALUE && propertyName.isNotEmpty) { - // `body { color: red }` that not end with semicolon is - // also the end of the declaration. - style[propertyName] = buffer.toString().trim(); - state = _END; - } else if (state == _NAME) { - // `.foo { .foo {}; color: red }` - buffer.writeCharCode(c); - } else { - // Unexpected } : `.fo } { color: red }` - state = _END; - } - break; - default: - buffer.writeCharCode(c); - } - } - - if (selectorText.isNotEmpty) { - return CSSStyleRule(selectorText, style); - } - return null; - } -} diff --git a/webf/lib/src/css/parser/style_sheet_parser.dart b/webf/lib/src/css/parser/style_sheet_parser.dart deleted file mode 100644 index 04e37c8aeb..0000000000 --- a/webf/lib/src/css/parser/style_sheet_parser.dart +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ -import 'package:webf/css.dart'; - -const int _BEFORE_SELECTOR = 0; -const int _SELECTOR = 1; -const int _BEFORE_NAME = 2; -const int _NAME = 3; -const int _VALUE = 4; - -final RegExp _classSelectorRegExp = RegExp(r'^\s*.[-_a-zA-Z][-_a-zA-Z0-9]*\s*$'); - -class CSSStyleSheetParser { - static List parse(String sheetText) { - final List rules = []; - StringBuffer buffer = StringBuffer(); - int state = _BEFORE_SELECTOR; - for (int pos = 0, length = sheetText.length; pos < length; pos++) { - int c = sheetText.codeUnitAt(pos); - switch (c) { - case DOT_CODE: - // Current only support single class selector: `.red`. - int code = sheetText.codeUnitAt(pos + 1); - // `.` must followed by `-`, `_`, `a-z`, `A-Z`. - if (state == _BEFORE_SELECTOR && - (code == 45 || code == 95 || (code < 123 && code > 96) || (code < 91 && code > 64))) { - state = _SELECTOR; - buffer.writeCharCode(c); - } else if (state != _BEFORE_SELECTOR) { - buffer.writeCharCode(c); - } - break; - case SPACE_CODE: - case TAB_CODE: - case CR_CODE: - case FEED_CODE: - case NEWLINE_CODE: - if (state != _BEFORE_SELECTOR && (state == _SELECTOR || state == _VALUE) && pos > 0) { - // Squash 2 or more white-spaces in the row into 1 space. - switch (sheetText.codeUnitAt(pos - 1)) { - case SPACE_CODE: - case TAB_CODE: - case CR_CODE: - case FEED_CODE: - case NEWLINE_CODE: - break; - default: - buffer.writeCharCode(SPACE_CODE); - break; - } - } - break; - case SEMICOLON_CODE: - if (state != _BEFORE_SELECTOR && state == _VALUE || state == _NAME) { - if (state == _NAME) { - state = _BEFORE_NAME; - } - buffer.writeCharCode(c); - } - break; - case OPEN_CURLY_CODE: - if (state == _SELECTOR) { - String selector = buffer.toString(); - // Only support single class selector now. - state = _classSelectorRegExp.hasMatch(selector) ? _BEFORE_NAME : _BEFORE_SELECTOR; - if (state == _BEFORE_SELECTOR) { - buffer.clear(); - } - } - if (state != _BEFORE_SELECTOR) { - buffer.writeCharCode(c); - } - break; - case COLON_CODE: - if (state == _NAME) { - state = _VALUE; - } - if (state != _BEFORE_SELECTOR) { - buffer.writeCharCode(c); - } - break; - case CLOSE_CURLY_CODE: - if (state == _BEFORE_SELECTOR) break; - if (state == _VALUE || state == _BEFORE_NAME) { - buffer.writeCharCode(c); - String ruleText = buffer.toString(); - CSSStyleRule? styleRule = CSSStyleRuleParser.parse(ruleText); - if (styleRule != null) { - rules.add(styleRule); - } - } else if (state == _NAME) { - // `.foo { .x {}; color: red }` - buffer.writeCharCode(c); - } - - if (state != _NAME) { - buffer.clear(); - state = _BEFORE_SELECTOR; - } - break; - default: - if (state == _BEFORE_SELECTOR) break; - buffer.writeCharCode(c); - if (state == _BEFORE_NAME) { - state = _NAME; - } - } - } - return rules; - } -} diff --git a/webf/lib/src/css/parser/token.dart b/webf/lib/src/css/parser/token.dart new file mode 100644 index 0000000000..b3d7823d89 --- /dev/null +++ b/webf/lib/src/css/parser/token.dart @@ -0,0 +1,89 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +/// A single token in the Dart language. +class Token { + /// A member of [TokenKind] specifying what kind of token this is. + final int kind; + + /// The location where this token was parsed from. + final FileSpan span; + + /// The start offset of this token. + int get start => span.start.offset; + + /// The end offset of this token. + int get end => span.end.offset; + + /// Returns the source text corresponding to this [Token]. + String get text => span.text; + + Token(this.kind, this.span); + + /// Returns a pretty representation of this token for error messages. + @override + String toString() { + var kindText = TokenKind.kindToString(kind); + var actualText = text.trim(); + if (kindText != actualText) { + if (actualText.length > 10) { + actualText = '${actualText.substring(0, 8)}...'; + } + return '$kindText($actualText)'; + } else { + return kindText; + } + } +} + +/// A token containing a parsed literal value. +class LiteralToken extends Token { + dynamic value; + LiteralToken(int kind, FileSpan span, this.value) : super(kind, span); +} + +/// A token containing error information. +class ErrorToken extends Token { + String? message; + ErrorToken(int kind, FileSpan span, this.message) : super(kind, span); +} + +/// CSS ident-token. +/// +/// See and +/// . +class IdentifierToken extends Token { + @override + final String text; + + IdentifierToken(this.text, int kind, FileSpan span) : super(kind, span); +} diff --git a/webf/lib/src/css/parser/token_kind.dart b/webf/lib/src/css/parser/token_kind.dart new file mode 100644 index 0000000000..7a9f31f81b --- /dev/null +++ b/webf/lib/src/css/parser/token_kind.dart @@ -0,0 +1,597 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +// TODO(terry): Need to be consistent with tokens either they're ASCII tokens +// e.g., ASTERISK or they're CSS e.g., PSEUDO, COMBINATOR_*. +class TokenKind { + // Common shared tokens used in TokenizerBase. + static const int UNUSED = 0; // Unused place holder... + static const int END_OF_FILE = 1; // EOF + static const int LPAREN = 2; // ( + static const int RPAREN = 3; // ) + static const int LBRACK = 4; // [ + static const int RBRACK = 5; // ] + static const int LBRACE = 6; // { + static const int RBRACE = 7; // } + static const int DOT = 8; // . + static const int SEMICOLON = 9; // ; + + // Unique tokens for CSS. + static const int AT = 10; // @ + static const int HASH = 11; // # + static const int PLUS = 12; // + + static const int GREATER = 13; // > + static const int TILDE = 14; // ~ + static const int ASTERISK = 15; // * + static const int NAMESPACE = 16; // | + static const int COLON = 17; // : + static const int PRIVATE_NAME = 18; // _ prefix private class or id + static const int COMMA = 19; // , + static const int SPACE = 20; + static const int TAB = 21; // /t + static const int NEWLINE = 22; // /n + static const int RETURN = 23; // /r + static const int PERCENT = 24; // % + static const int SINGLE_QUOTE = 25; // ' + static const int DOUBLE_QUOTE = 26; // " + static const int SLASH = 27; // / + static const int EQUALS = 28; // = + static const int CARET = 30; // ^ + static const int DOLLAR = 31; // $ + static const int LESS = 32; // < + static const int BANG = 33; // ! + static const int MINUS = 34; // - + static const int BACKSLASH = 35; // \ + static const int AMPERSAND = 36; // & + + // WARNING: Tokens from this point and above must have the corresponding ASCII + // character in the TokenChar list at the bottom of this file. The + // order of the above tokens should be the same order as TokenChar. + + /// [TokenKind] representing integer tokens. + static const int INTEGER = 60; + + /// [TokenKind] representing hex integer tokens. + static const int HEX_INTEGER = 61; + + /// [TokenKind] representing double tokens. + static const int DOUBLE = 62; + + /// [TokenKind] representing whitespace tokens. + static const int WHITESPACE = 63; + + /// [TokenKind] representing comment tokens. + static const int COMMENT = 64; + + /// [TokenKind] representing error tokens. + static const int ERROR = 65; + + /// [TokenKind] representing incomplete string tokens. + static const int INCOMPLETE_STRING = 66; + + /// [TokenKind] representing incomplete comment tokens. + static const int INCOMPLETE_COMMENT = 67; + + static const int VAR_DEFINITION = 400; // var-NNN-NNN + static const int VAR_USAGE = 401; // var(NNN-NNN [,default]) + + // Synthesized Tokens (no character associated with TOKEN). + static const int STRING = 500; + static const int STRING_PART = 501; + static const int NUMBER = 502; + static const int HEX_NUMBER = 503; + static const int HTML_COMMENT = 504; // (CDC). */ + if (_maybeEatChar(TokenChar.MINUS)) { + if (_maybeEatChar(TokenChar.GREATER)) { + if (_inString) { + return next(); + } else { + return _finishToken(TokenKind.HTML_COMMENT); + } + } + } + } + } + } + + @override + Token finishMultiLineComment() { + while (true) { + var ch = _nextChar(); + if (ch == 0) { + return _finishToken(TokenKind.INCOMPLETE_COMMENT); + } else if (ch == 42 /*'*'*/) { + if (_maybeEatChar(47 /*'/'*/)) { + if (_inString) { + return next(); + } else { + return _finishToken(TokenKind.COMMENT); + } + } + } + } + } +} + +/// Static helper methods. +class TokenizerHelpers { + static bool isIdentifierStart(int c) { + return isIdentifierStartExpr(c) || c == 45 /*-*/; + } + + static bool isDigit(int c) { + return (c >= 48 /*0*/ && c <= 57 /*9*/); + } + + static bool isHexDigit(int c) { + return (isDigit(c) || (c >= 97 /*a*/ && c <= 102 /*f*/) || (c >= 65 /*A*/ && c <= 70 /*F*/)); + } + + static bool isIdentifierPart(int c) { + return isIdentifierPartExpr(c) || c == 45 /*-*/; + } + + /// Pseudo function expressions identifiers can't have a minus sign. + static bool isIdentifierStartExpr(int c) { + return ((c >= 97 /*a*/ && c <= 122 /*z*/) || + (c >= 65 /*A*/ && c <= 90 /*Z*/) || + // Note: Unicode 10646 chars U+00A0 or higher are allowed, see: + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + // http://www.w3.org/TR/CSS21/syndata.html#characters + // Also, escaped character should be allowed. + c == 95 /*_*/ || + c >= 0xA0 || + c == 92 /*\*/); + } + + /// Pseudo function expressions identifiers can't have a minus sign. + static bool isIdentifierPartExpr(int c) { + return (isIdentifierStartExpr(c) || isDigit(c)); + } +} diff --git a/webf/lib/src/css/parser/tokenizer_base.dart b/webf/lib/src/css/parser/tokenizer_base.dart new file mode 100644 index 0000000000..9f839cdaa1 --- /dev/null +++ b/webf/lib/src/css/parser/tokenizer_base.dart @@ -0,0 +1,444 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +/// Tokenizer state to support look ahead for Less' nested selectors. +class TokenizerState { + final int index; + final int startIndex; + final bool inSelectorExpression; + final bool inSelector; + + TokenizerState(TokenizerBase base) + : index = base._index, + startIndex = base._startIndex, + inSelectorExpression = base.inSelectorExpression, + inSelector = base.inSelector; +} + +/// The base class for our tokenizer. The hand coded parts are in this file, +/// with the generated parts in the subclass Tokenizer. +abstract class TokenizerBase { + final SourceFile _file; + final String _text; + + // TODO: this seems like a bug – this field *is* used + // ignore: prefer_final_fields + bool _inString; + + /// Changes tokenization when in a pseudo function expression. If true then + /// minus signs are handled as operators instead of identifiers. + bool inSelectorExpression = false; + + /// Changes tokenization when in selectors. If true, it prevents identifiers + /// from being treated as units. This would break things like ":lang(fr)" or + /// the HTML (unknown) tag name "px", which is legal to use in a selector. + // TODO(jmesserly): is this a problem elsewhere? "fr" for example will be + // processed as a "fraction" unit token, preventing it from working in + // places where an identifier is expected. This was breaking selectors like: + // :lang(fr) + // The assumption that "fr" always means fraction (and similar issue with + // other units) doesn't seem valid. We probably should defer this + // analysis until we reach places in the parser where units are expected. + // I'm not sure this is tokenizing as described in the specs: + // http://dev.w3.org/csswg/css-syntax/ + // http://dev.w3.org/csswg/selectors4/ + bool inSelector = false; + + int _index = 0; + int _startIndex = 0; + + TokenizerBase(this._file, this._text, this._inString, [this._index = 0]); + + Token next(); + int getIdentifierKind(); + + /// Snapshot of Tokenizer scanning state. + TokenizerState get mark => TokenizerState(this); + + /// Restore Tokenizer scanning state. + void restore(TokenizerState markedData) { + _index = markedData.index; + _startIndex = markedData.startIndex; + inSelectorExpression = markedData.inSelectorExpression; + inSelector = markedData.inSelector; + } + + int _nextChar() { + if (_index < _text.length) { + return _text.codeUnitAt(_index++); + } else { + return 0; + } + } + + int _peekChar([int offset = 0]) { + if (_index + offset < _text.length) { + return _text.codeUnitAt(_index + offset); + } else { + return 0; + } + } + + bool _maybeEatChar(int ch) { + if (_index < _text.length) { + if (_text.codeUnitAt(_index) == ch) { + _index++; + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool _nextCharsAreNumber(int first) { + if (TokenizerHelpers.isDigit(first)) return true; + var second = _peekChar(); + if (first == TokenChar.DOT) return TokenizerHelpers.isDigit(second); + if (first == TokenChar.PLUS || first == TokenChar.MINUS) { + return TokenizerHelpers.isDigit(second) || (second == TokenChar.DOT && TokenizerHelpers.isDigit(_peekChar(1))); + } + return false; + } + + Token _finishToken(int kind) { + return Token(kind, _file.span(_startIndex, _index)); + } + + Token _errorToken([String? message]) { + return ErrorToken(TokenKind.ERROR, _file.span(_startIndex, _index), message); + } + + Token finishWhitespace() { + _index--; + while (_index < _text.length) { + final ch = _text.codeUnitAt(_index++); + if (ch == TokenChar.SPACE || ch == TokenChar.TAB || ch == TokenChar.RETURN) { + // do nothing + } else if (ch == TokenChar.NEWLINE) { + if (!_inString) { + return _finishToken(TokenKind.WHITESPACE); // note the newline? + } + } else { + _index--; + if (_inString) { + return next(); + } else { + return _finishToken(TokenKind.WHITESPACE); + } + } + } + return _finishToken(TokenKind.END_OF_FILE); + } + + Token finishMultiLineComment() { + var nesting = 1; + do { + var ch = _nextChar(); + if (ch == 0) { + return _errorToken(); + } else if (ch == TokenChar.ASTERISK) { + if (_maybeEatChar(TokenChar.SLASH)) { + nesting--; + } + } else if (ch == TokenChar.SLASH) { + if (_maybeEatChar(TokenChar.ASTERISK)) { + nesting++; + } + } + } while (nesting > 0); + + if (_inString) { + return next(); + } else { + return _finishToken(TokenKind.COMMENT); + } + } + + void eatDigits() { + while (_index < _text.length) { + if (TokenizerHelpers.isDigit(_text.codeUnitAt(_index))) { + _index++; + } else { + return; + } + } + } + + static int _hexDigit(int c) { + if (c >= 48 /*0*/ && c <= 57 /*9*/) { + return c - 48; + } else if (c >= 97 /*a*/ && c <= 102 /*f*/) { + return c - 87; + } else if (c >= 65 /*A*/ && c <= 70 /*F*/) { + return c - 55; + } else { + return -1; + } + } + + int readHex([int? hexLength]) { + int maxIndex; + if (hexLength == null) { + maxIndex = _text.length - 1; + } else { + // TODO(jimhug): What if this is too long? + maxIndex = _index + hexLength; + if (maxIndex >= _text.length) return -1; + } + var result = 0; + while (_index < maxIndex) { + final digit = _hexDigit(_text.codeUnitAt(_index)); + if (digit == -1) { + if (hexLength == null) { + return result; + } else { + return -1; + } + } + _hexDigit(_text.codeUnitAt(_index)); + // Multiply by 16 rather than shift by 4 since that will result in a + // correct value for numbers that exceed the 32 bit precision of JS + // 'integers'. + // TODO: Figure out a better solution to integer truncation. Issue 638. + result = (result * 16) + digit; + _index++; + } + + return result; + } + + Token finishNumber() { + eatDigits(); + + if (_peekChar() == TokenChar.DOT) { + // Handle the case of 1.toString(). + _nextChar(); + if (TokenizerHelpers.isDigit(_peekChar())) { + eatDigits(); + return finishNumberExtra(TokenKind.DOUBLE); + } else { + _index--; + } + } + + return finishNumberExtra(TokenKind.INTEGER); + } + + Token finishNumberExtra(int kind) { + if (_maybeEatChar(101 /*e*/) || _maybeEatChar(69 /*E*/)) { + kind = TokenKind.DOUBLE; + _maybeEatChar(TokenKind.MINUS); + _maybeEatChar(TokenKind.PLUS); + eatDigits(); + } + if (_peekChar() != 0 && TokenizerHelpers.isIdentifierStart(_peekChar())) { + _nextChar(); + return _errorToken('illegal character in number'); + } + + return _finishToken(kind); + } + + Token _makeStringToken(List buf, bool isPart) { + final s = String.fromCharCodes(buf); + final kind = isPart ? TokenKind.STRING_PART : TokenKind.STRING; + return LiteralToken(kind, _file.span(_startIndex, _index), s); + } + + Token makeIEFilter(int start, int end) { + var filter = _text.substring(start, end); + return LiteralToken(TokenKind.STRING, _file.span(start, end), filter); + } + + Token _makeRawStringToken(bool isMultiline) { + String s; + if (isMultiline) { + // Skip initial newline in multiline strings + var start = _startIndex + 4; + if (_text[start] == '\n') start++; + s = _text.substring(start, _index - 3); + } else { + s = _text.substring(_startIndex + 2, _index - 1); + } + return LiteralToken(TokenKind.STRING, _file.span(_startIndex, _index), s); + } + + Token finishMultilineString(int quote) { + var buf = []; + while (true) { + var ch = _nextChar(); + if (ch == 0) { + return _errorToken(); + } else if (ch == quote) { + if (_maybeEatChar(quote)) { + if (_maybeEatChar(quote)) { + return _makeStringToken(buf, false); + } + buf.add(quote); + } + buf.add(quote); + } else if (ch == TokenChar.BACKSLASH) { + var escapeVal = readEscapeSequence(); + if (escapeVal == -1) { + return _errorToken('invalid hex escape sequence'); + } else { + buf.add(escapeVal); + } + } else { + buf.add(ch); + } + } + } + + Token finishString(int quote) { + if (_maybeEatChar(quote)) { + if (_maybeEatChar(quote)) { + // skip an initial newline + _maybeEatChar(TokenChar.NEWLINE); + return finishMultilineString(quote); + } else { + return _makeStringToken([], false); + } + } + return finishStringBody(quote); + } + + Token finishRawString(int quote) { + if (_maybeEatChar(quote)) { + if (_maybeEatChar(quote)) { + return finishMultilineRawString(quote); + } else { + return _makeStringToken([], false); + } + } + while (true) { + var ch = _nextChar(); + if (ch == quote) { + return _makeRawStringToken(false); + } else if (ch == 0) { + return _errorToken(); + } + } + } + + Token finishMultilineRawString(int quote) { + while (true) { + var ch = _nextChar(); + if (ch == 0) { + return _errorToken(); + } else if (ch == quote && _maybeEatChar(quote) && _maybeEatChar(quote)) { + return _makeRawStringToken(true); + } + } + } + + Token finishStringBody(int quote) { + var buf = []; + while (true) { + var ch = _nextChar(); + if (ch == quote) { + return _makeStringToken(buf, false); + } else if (ch == 0) { + return _errorToken(); + } else if (ch == TokenChar.BACKSLASH) { + var escapeVal = readEscapeSequence(); + if (escapeVal == -1) { + return _errorToken('invalid hex escape sequence'); + } else { + buf.add(escapeVal); + } + } else { + buf.add(ch); + } + } + } + + int readEscapeSequence() { + final ch = _nextChar(); + int hexValue; + switch (ch) { + case 110 /*n*/ : + return TokenChar.NEWLINE; + case 114 /*r*/ : + return TokenChar.RETURN; + case 102 /*f*/ : + return TokenChar.FF; + case 98 /*b*/ : + return TokenChar.BACKSPACE; + case 116 /*t*/ : + return TokenChar.TAB; + case 118 /*v*/ : + return TokenChar.FF; + case 120 /*x*/ : + hexValue = readHex(2); + break; + case 117 /*u*/ : + if (_maybeEatChar(TokenChar.LBRACE)) { + hexValue = readHex(); + if (!_maybeEatChar(TokenChar.RBRACE)) { + return -1; + } + } else { + hexValue = readHex(4); + } + break; + default: + return ch; + } + + if (hexValue == -1) return -1; + + // According to the Unicode standard the high and low surrogate halves + // used by UTF-16 (U+D800 through U+DFFF) and values above U+10FFFF + // are not legal Unicode values. + if (hexValue < 0xD800 || hexValue > 0xDFFF && hexValue <= 0xFFFF) { + return hexValue; + } else if (hexValue <= 0x10FFFF) { + // messages.error('unicode values greater than 2 bytes not implemented yet', + // _file.span(_startIndex, _startIndex + 1)); + return -1; + } else { + return -1; + } + } + + Token finishDot() { + if (TokenizerHelpers.isDigit(_peekChar())) { + eatDigits(); + return finishNumberExtra(TokenKind.DOUBLE); + } else { + return _finishToken(TokenKind.DOT); + } + } +} diff --git a/webf/lib/src/css/parser/tree.dart b/webf/lib/src/css/parser/tree.dart new file mode 100644 index 0000000000..0b2f965b92 --- /dev/null +++ b/webf/lib/src/css/parser/tree.dart @@ -0,0 +1,107 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +///////////////////////////////////////////////////////////////////////// +// CSS specific types: +///////////////////////////////////////////////////////////////////////// + +/// The base type for all nodes in a CSS abstract syntax tree. +abstract class TreeNode { + TreeNode clone(); + + /// Classic double-dispatch visitor for implementing passes. + dynamic visit(Visitor visitor) {} +} + +class Identifier extends TreeNode { + String name; + + Identifier(this.name) : super(); + + @override + Identifier clone() => Identifier(name); + + @override + String toString() { + // Try to use the identifier's original lexeme to preserve any escape codes + // as authored. The name, which may include escaped values, may no longer be + // a valid identifier. + return name; + } +} + +class Wildcard extends TreeNode { + Wildcard() : super(); + @override + Wildcard clone() => Wildcard(); + String get name => '*'; +} + +class ThisOperator extends TreeNode { + ThisOperator() : super(); + @override + ThisOperator clone() => ThisOperator(); + String get name => '&'; +} + +class Negation extends TreeNode { + Negation() : super(); + @override + Negation clone() => Negation(); + + String get name => 'not'; +} + +class NoOp extends TreeNode { + NoOp() : super(); + + @override + NoOp clone() => NoOp(); +} + +/// The base type for expressions. +abstract class Expression extends TreeNode { + @override + Expression clone(); +} + +class OperatorSlash extends Expression { + OperatorSlash() : super(); + @override + OperatorSlash clone() => OperatorSlash(); +} + +class OperatorComma extends Expression { + OperatorComma() : super(); + @override + OperatorComma clone() => OperatorComma(); +} diff --git a/webf/lib/src/css/parser/visitor.dart b/webf/lib/src/css/parser/visitor.dart new file mode 100644 index 0000000000..1282eb861e --- /dev/null +++ b/webf/lib/src/css/parser/visitor.dart @@ -0,0 +1,106 @@ +/* +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + +part of 'parser.dart'; + +abstract class Visitor { + dynamic visitSelectorGroup(SelectorGroup node); + dynamic visitSelector(Selector node); + dynamic visitSimpleSelectorSequence(SimpleSelectorSequence node); + dynamic visitSimpleSelector(SimpleSelector node); + dynamic visitElementSelector(ElementSelector node); + dynamic visitAttributeSelector(AttributeSelector node); + dynamic visitIdSelector(IdSelector node); + dynamic visitClassSelector(ClassSelector node); + dynamic visitPseudoClassSelector(PseudoClassSelector node); + dynamic visitPseudoElementSelector(PseudoElementSelector node); + dynamic visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node); + dynamic visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node); + dynamic visitNegationSelector(NegationSelector node); +} + +class SelectorVisitor implements Visitor { + /// Helper function to walk a list of nodes. + void _visitNodeList(List list) { + // Don't use iterable otherwise the list can't grow while using Visitor. + // It certainly can't have items deleted before the index being iterated + // but items could be added after the index. + for (var index = 0; index < list.length; index++) { + list[index].visit(this); + } + } + + @override + dynamic visitSelectorGroup(SelectorGroup node) { + _visitNodeList(node.selectors); + } + + @override + dynamic visitSelector(Selector node) { + _visitNodeList(node.simpleSelectorSequences); + } + + @override + dynamic visitSimpleSelectorSequence(SimpleSelectorSequence node) { + node.simpleSelector.visit(this); + } + + @override + dynamic visitSimpleSelector(SimpleSelector node) => (node._name as TreeNode).visit(this); + + @override + dynamic visitElementSelector(ElementSelector node) => visitSimpleSelector(node); + + @override + dynamic visitAttributeSelector(AttributeSelector node) { + visitSimpleSelector(node); + } + + @override + dynamic visitIdSelector(IdSelector node) => visitSimpleSelector(node); + + @override + dynamic visitClassSelector(ClassSelector node) => visitSimpleSelector(node); + + @override + dynamic visitPseudoClassSelector(PseudoClassSelector node) => visitSimpleSelector(node); + + @override + dynamic visitPseudoElementSelector(PseudoElementSelector node) => visitSimpleSelector(node); + + @override + dynamic visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) => visitSimpleSelector(node); + + @override + dynamic visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) => visitSimpleSelector(node); + + @override + dynamic visitNegationSelector(NegationSelector node) => visitSimpleSelector(node); +} diff --git a/webf/lib/src/css/rule.dart b/webf/lib/src/css/rule.dart index b1f6dfbb65..57c6238cbb 100644 --- a/webf/lib/src/css/rule.dart +++ b/webf/lib/src/css/rule.dart @@ -1,6 +1,5 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. + * Copyright (C) 2021-present The Kraken authors. All rights reserved. */ import 'package:webf/css.dart'; diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart new file mode 100644 index 0000000000..cb951997c1 --- /dev/null +++ b/webf/lib/src/css/rule_set.dart @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'dart:collection'; + +import 'package:webf/css.dart'; + +typedef CSSMap = HashMap>; + +class RuleSet { + final List rules = []; + + final CSSMap idRules = HashMap(); + final CSSMap classRules = HashMap(); + final CSSMap attributeRules = HashMap(); + final CSSMap tagRules = HashMap(); + final List universalRules = []; + + void addRule(CSSRule rule) { + rules.add(rule); + if (rule is CSSStyleRule) { + findBestRuleSetAndAdd(rule); + } else { + assert(false, 'Unsupported rule type: ${rule.runtimeType}'); + } + } + + void deleteRule(int index) { + CSSRule rule = rules.removeAt(index); + // if (rule is CSSStyleRule) { + // // findBestRuleSetAndRemove(rule); + // } + } + + void reset() { + rules.clear(); + idRules.clear(); + classRules.clear(); + attributeRules.clear(); + tagRules.clear(); + universalRules.clear(); + } + + // indexed by selectorText + void findBestRuleSetAndAdd(CSSStyleRule rule) { + String? id, className, attributeName, tagName, pseudoElementName, pseudoFunctionName; + + for (final selector in rule.selectorGroup.selectors) { + for (final simpleSelectorSequence in selector.simpleSelectorSequences) { + final simpleSelector = simpleSelectorSequence.simpleSelector; + switch (simpleSelector.runtimeType) { + case IdSelector: + id = simpleSelector.name; + break; + case ClassSelector: + className = simpleSelector.name; + break; + case AttributeSelector: + attributeName = simpleSelector.name; + break; + case ElementSelector: + tagName = simpleSelector.name; + break; + } + } + } + + void insertRule(String key, CSSRule rule, CSSMap map) { + List? rules = map[key] ?? []; + rules.add(rule); + map[key] = rules; + } + + if (id != null && id.isNotEmpty == true) { + insertRule(id, rule, idRules); + return; + } + + if (className != null && className.isNotEmpty == true) { + insertRule(className, rule, classRules); + return; + } + + if (attributeName != null && attributeName.isNotEmpty == true) { + insertRule(attributeName, rule, attributeRules); + return; + } + + if (tagName != null && tagName.isNotEmpty == true) { + insertRule(tagName, rule, tagRules); + return; + } + universalRules.add(rule); + } +} diff --git a/webf/lib/src/css/selector_checker.dart b/webf/lib/src/css/selector_checker.dart new file mode 100644 index 0000000000..3540b3326d --- /dev/null +++ b/webf/lib/src/css/selector_checker.dart @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ +import 'package:webf/dom.dart'; +import 'package:webf/css.dart'; + +class SelectorEvaluator extends SelectorVisitor { + Element? _element; + + bool matchSelector(SelectorGroup? selector, Element? element) { + if (selector == null || element == null) { + return false; + } + _element = element; + return visitSelectorGroup(selector); + } + + @override + bool visitSelectorGroup(SelectorGroup node) => node.selectors.any(visitSelector); + + @override + bool visitSelector(Selector node) { + final old = _element; + var result = true; + + // Note: evaluate selectors right-to-left as it's more efficient. + int? combinator; + for (var s in node.simpleSelectorSequences.reversed) { + if (combinator == null) { + result = s.simpleSelector.visit(this) as bool; + } else if (combinator == TokenKind.COMBINATOR_DESCENDANT) { + // descendant combinator + // http://dev.w3.org/csswg/selectors-4/#descendant-combinators + do { + _element = _element!.parentElement; + } while (_element != null && !(s.simpleSelector.visit(this) as bool)); + + if (_element == null) result = false; + } else if (combinator == TokenKind.COMBINATOR_TILDE) { + // Following-sibling combinator + // http://dev.w3.org/csswg/selectors-4/#general-sibling-combinators + do { + _element = _element!.previousElementSibling; + } while (_element != null && !(s.simpleSelector.visit(this) as bool)); + + if (_element == null) result = false; + } + + if (!result) break; + + switch (s.combinator) { + case TokenKind.COMBINATOR_PLUS: + // Next-sibling combinator + // http://dev.w3.org/csswg/selectors-4/#adjacent-sibling-combinators + _element = _element!.previousElementSibling; + break; + case TokenKind.COMBINATOR_GREATER: + // Child combinator + // http://dev.w3.org/csswg/selectors-4/#child-combinators + _element = _element!.parentElement; + break; + case TokenKind.COMBINATOR_DESCENDANT: + case TokenKind.COMBINATOR_TILDE: + // We need to iterate through all siblings or parents. + // For now, just remember what the combinator was. + combinator = s.combinator; + break; + case TokenKind.COMBINATOR_NONE: + break; + default: + throw _unsupported(node); + } + + if (_element == null) { + result = false; + break; + } + } + + _element = old; + return result; + } + + @override + bool visitPseudoClassSelector(PseudoClassSelector node) { + switch (node.name) { + // http://dev.w3.org/csswg/selectors-4/#structural-pseudos + + // http://dev.w3.org/csswg/selectors-4/#the-root-pseudo + case 'root': + // TODO(jmesserly): fix when we have a .ownerDocument pointer + // return _element == _element.ownerDocument.rootElement; + return _element!.nodeName == 'html' && _element!.parentNode == null; + + // http://dev.w3.org/csswg/selectors-4/#the-empty-pseudo + case 'empty': + return _element!.childNodes.any((n) => !(n is Element || n is TextNode && n.data.isNotEmpty)); + + // http://dev.w3.org/csswg/selectors-4/#the-blank-pseudo + case 'blank': + return _element!.childNodes.any((n) => !(n is Element || n is TextNode && n.data.runes.any((r) => !isWhitespaceCC(r)))); + + // http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo + case 'first-child': + return _element!.previousElementSibling == null; + + // http://dev.w3.org/csswg/selectors-4/#the-last-child-pseudo + case 'last-child': + return _element!.nextElementSibling == null; + + // http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo + case 'only-child': + return _element!.previousElementSibling == null && _element!.nextElementSibling == null; + + // http://dev.w3.org/csswg/selectors-4/#link + case 'link': + return _element!.attributes['href'] != null; + + case 'visited': + // Always return false since we aren't a browser. This is allowed per: + // http://dev.w3.org/csswg/selectors-4/#visited-pseudo + return false; + } + + // :before, :after, :first-letter/line can't match DOM elements. + if (_isLegacyPsuedoClass(node.name)) return false; + + throw _unimplemented(node); + } + + @override + bool visitPseudoElementSelector(PseudoElementSelector node) { + // :before, :after, :first-letter/line can't match DOM elements. + if (_isLegacyPsuedoClass(node.name)) return false; + + throw _unimplemented(node); + } + + static bool _isLegacyPsuedoClass(String name) { + switch (name) { + case 'before': + case 'after': + case 'first-line': + case 'first-letter': + return true; + default: + return false; + } + } + + @override + bool visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) => throw _unimplemented(node); + + @override + bool visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) { + switch (node.name) { + // http://dev.w3.org/csswg/selectors-4/#child-index + + // http://dev.w3.org/csswg/selectors-4/#the-nth-child-pseudo + case 'nth-child': + // TODO(jmesserly): support An+B syntax too. + final exprs = node.expression; + if (exprs.length == 1 && _element != null) { + final literal = int.parse(exprs[0]); + final parent = _element!.parentElement; + return parent != null && literal > 0 && parent.children.indexOf(_element!) == literal; + } + break; + } + throw _unimplemented(node); + } + + bool isNumeric(String s) { + return double.tryParse(s) != null; + } + + @override + bool visitElementSelector(ElementSelector node) => _element!.tagName == node.name.toUpperCase(); + + @override + bool visitIdSelector(IdSelector node) => _element!.id == node.name; + + @override + bool visitClassSelector(ClassSelector node) => _element!.classList.contains(node.name); + + // TODO(jmesserly): negation should support any selectors in level 4, + // not just simple selectors. + // http://dev.w3.org/csswg/selectors-4/#negation + @override + bool visitNegationSelector(NegationSelector node) => !(node.negationArg!.visit(this) as bool); + + @override + bool visitAttributeSelector(AttributeSelector node) { + // Match name first + final value = _element!.attributes[node.name.toLowerCase()]; + if (value == null) return false; + + if (node.operatorKind == TokenKind.NO_MATCH) return true; + + final select = '${node.value}'; + switch (node.operatorKind) { + case TokenKind.EQUALS: + return value == select; + case TokenKind.INCLUDES: + return value.split(' ').any((v) => v.isNotEmpty && v == select); + case TokenKind.DASH_MATCH: + return value.startsWith(select) && (value.length == select.length || value[select.length] == '-'); + case TokenKind.PREFIX_MATCH: + return value.startsWith(select); + case TokenKind.SUFFIX_MATCH: + return value.endsWith(select); + case TokenKind.SUBSTRING_MATCH: + return value.contains(select); + default: + throw _unsupported(node); + } + } + + UnimplementedError _unimplemented(SimpleSelector selector) => UnimplementedError("'$selector' selector of type " + '${selector.runtimeType} is not implemented'); + + FormatException _unsupported(selector) => FormatException("'$selector' is not a valid selector"); + + bool isWhitespaceCC(int charCode) { + switch (charCode) { + case TokenChar.TAB: // '\t' + case TokenChar.NEWLINE: // '\n' + case TokenChar.FF: // '\f' + case TokenChar.RETURN: // '\r' + case TokenChar.SPACE: // ' ' + return true; + } + return false; + } +} diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index 0b7c5ca267..b3a5c6b14f 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -46,7 +46,8 @@ List _propertyOrders = [ RegExp _kebabCaseReg = RegExp(r'[A-Z]'); -final LinkedLruHashMap> _cachedExpandedShorthand = LinkedLruHashMap(maximumSize: 500); +final LinkedLruHashMap> _cachedExpandedShorthand = + LinkedLruHashMap(maximumSize: 500); // CSS Object Model: https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface @@ -70,7 +71,8 @@ class CSSStyleDeclaration { CSSStyleDeclaration(); // ignore: prefer_initializing_formals - CSSStyleDeclaration.computedStyle(this.target, this.defaultStyle, this.onStyleChanged); + CSSStyleDeclaration.computedStyle( + this.target, this.defaultStyle, this.onStyleChanged); /// An empty style declaration. static CSSStyleDeclaration empty = CSSStyleDeclaration(); @@ -92,6 +94,10 @@ class CSSStyleDeclaration { if (css.isNotEmpty) css += ' '; css += '${_kebabize(property)}: $value;'; }); + _sheetStyle.forEach((property, value) { + if (css.isNotEmpty) css += ' '; + css += '${_kebabize(property)}: $value;'; + }); return css; } @@ -110,7 +116,9 @@ class CSSStyleDeclaration { /// If not set, returns the empty string. String getPropertyValue(String propertyName) { // Get the latest pending value first. - return _pendingProperties[propertyName] ?? _properties[propertyName] ?? EMPTY_STRING; + return _pendingProperties[propertyName] ?? + _properties[propertyName] ?? + EMPTY_STRING; } /// Removes a property from the CSS declaration. @@ -123,7 +131,8 @@ class CSSStyleDeclaration { case BACKGROUND: return CSSStyleProperty.removeShorthandBackground(this, isImportant); case BACKGROUND_POSITION: - return CSSStyleProperty.removeShorthandBackgroundPosition(this, isImportant); + return CSSStyleProperty.removeShorthandBackgroundPosition( + this, isImportant); case BORDER_RADIUS: return CSSStyleProperty.removeShorthandBorderRadius(this, isImportant); case OVERFLOW: @@ -142,11 +151,13 @@ class CSSStyleDeclaration { case BORDER_COLOR: case BORDER_STYLE: case BORDER_WIDTH: - return CSSStyleProperty.removeShorthandBorder(this, propertyName, isImportant); + return CSSStyleProperty.removeShorthandBorder( + this, propertyName, isImportant); case TRANSITION: return CSSStyleProperty.removeShorthandTransition(this, isImportant); case TEXT_DECORATION: - return CSSStyleProperty.removeShorthandTextDecoration(this, isImportant); + return CSSStyleProperty.removeShorthandTextDecoration( + this, isImportant); } String present = EMPTY_STRING; @@ -162,7 +173,9 @@ class CSSStyleDeclaration { } // Fallback to default style. - if (isNullOrEmptyValue(present) && defaultStyle != null && defaultStyle!.containsKey(propertyName)) { + if (isNullOrEmptyValue(present) && + defaultStyle != null && + defaultStyle!.containsKey(propertyName)) { present = defaultStyle![propertyName]; } @@ -170,7 +183,8 @@ class CSSStyleDeclaration { _pendingProperties[propertyName] = present; } - void _expandShorthand(String propertyName, String normalizedValue, bool? isImportant) { + void _expandShorthand( + String propertyName, String normalizedValue, bool? isImportant) { Map longhandProperties; String cacheKey = '$propertyName:$normalizedValue'; if (_cachedExpandedShorthand.containsKey(cacheKey)) { @@ -180,31 +194,40 @@ class CSSStyleDeclaration { switch (propertyName) { case PADDING: - CSSStyleProperty.setShorthandPadding(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandPadding( + longhandProperties, normalizedValue); break; case MARGIN: - CSSStyleProperty.setShorthandMargin(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandMargin( + longhandProperties, normalizedValue); break; case BACKGROUND: - CSSStyleProperty.setShorthandBackground(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBackground( + longhandProperties, normalizedValue); break; case BACKGROUND_POSITION: - CSSStyleProperty.setShorthandBackgroundPosition(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBackgroundPosition( + longhandProperties, normalizedValue); break; case BORDER_RADIUS: - CSSStyleProperty.setShorthandBorderRadius(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBorderRadius( + longhandProperties, normalizedValue); break; case OVERFLOW: - CSSStyleProperty.setShorthandOverflow(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandOverflow( + longhandProperties, normalizedValue); break; case FONT: - CSSStyleProperty.setShorthandFont(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFont( + longhandProperties, normalizedValue); break; case FLEX: - CSSStyleProperty.setShorthandFlex(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFlex( + longhandProperties, normalizedValue); break; case FLEX_FLOW: - CSSStyleProperty.setShorthandFlexFlow(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFlexFlow( + longhandProperties, normalizedValue); break; case BORDER: case BORDER_TOP: @@ -214,13 +237,16 @@ class CSSStyleDeclaration { case BORDER_COLOR: case BORDER_STYLE: case BORDER_WIDTH: - CSSStyleProperty.setShorthandBorder(longhandProperties, propertyName, normalizedValue); + CSSStyleProperty.setShorthandBorder( + longhandProperties, propertyName, normalizedValue); break; case TRANSITION: - CSSStyleProperty.setShorthandTransition(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandTransition( + longhandProperties, normalizedValue); break; case TEXT_DECORATION: - CSSStyleProperty.setShorthandTextDecoration(longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandTextDecoration( + longhandProperties, normalizedValue); break; } _cachedExpandedShorthand[cacheKey] = longhandProperties; @@ -233,7 +259,9 @@ class CSSStyleDeclaration { } } - String _replacePattern(String string, String lowerCase, String startString, String endString, [int start = 0]) { + String _replacePattern( + String string, String lowerCase, String startString, String endString, + [int start = 0]) { int startIndex = lowerCase.indexOf(startString, start); if (startIndex >= 0) { int? endIndex; @@ -246,7 +274,8 @@ class CSSStyleDeclaration { var replacement = string.substring(startIndex, endIndex); lowerCase = lowerCase.replaceRange(startIndex, endIndex, replacement); if (endIndex < string.length - 1) { - lowerCase = _replacePattern(string, lowerCase, startString, endString, endIndex); + lowerCase = _replacePattern( + string, lowerCase, startString, endString, endIndex); } } } @@ -337,10 +366,12 @@ class CSSStyleDeclaration { if (!CSSColor.isColor(normalizedValue)) return false; break; case BACKGROUND_IMAGE: - if (!CSSBackground.isValidBackgroundImageValue(normalizedValue)) return false; + if (!CSSBackground.isValidBackgroundImageValue(normalizedValue)) + return false; break; case BACKGROUND_REPEAT: - if (!CSSBackground.isValidBackgroundRepeatValue(normalizedValue)) return false; + if (!CSSBackground.isValidBackgroundRepeatValue(normalizedValue)) + return false; break; } return true; @@ -373,7 +404,6 @@ class CSSStyleDeclaration { return; } - // Current only from inline style will mark the property as important. if (isImportant == true) { _importants[propertyName] = true; } @@ -436,6 +466,49 @@ class CSSStyleDeclaration { } } + void merge(CSSStyleDeclaration declaration) { + Map properties = {} + ..addAll(_properties) + ..addAll(_pendingProperties); + + for (String propertyName in properties.keys) { + bool isImportant = _importants[propertyName] ?? false; + String? currentValue = properties[propertyName]; + String? otherValue = declaration._pendingProperties[propertyName]; + + if (isNullOrEmptyValue(otherValue) && isNullOrEmptyValue(currentValue)) { + continue; + } else if (!isNullOrEmptyValue(currentValue) && + isNullOrEmptyValue(otherValue)) { + // Remove property. + _pendingProperties.remove(propertyName); + } else if (!isImportant && + otherValue != null && + currentValue != otherValue) { + // Update property. + _pendingProperties[propertyName] = otherValue; + bool otherIsImportant = declaration._importants[propertyName] ?? false; + if (otherIsImportant) { + _importants[propertyName] = true; + } + } + } + + for (String propertyName in declaration._pendingProperties.keys) { + bool isImportant = _importants[propertyName] ?? false; + String? currentValue = properties[propertyName]; + String? otherValue = declaration._pendingProperties[propertyName]; + if (!isImportant && isNullOrEmptyValue(currentValue) && !isNullOrEmptyValue(otherValue)) { + // Add property. + _pendingProperties[propertyName] = otherValue!; + bool otherIsImportant = declaration._importants[propertyName] ?? false; + if (otherIsImportant) { + _importants[propertyName] = true; + } + } + } + } + Map diff(CSSStyleDeclaration other) { Map diffs = {}; @@ -449,7 +522,8 @@ class CSSStyleDeclaration { if (isNullOrEmptyValue(prevValue) && isNullOrEmptyValue(currentValue)) { continue; - } else if (!isNullOrEmptyValue(prevValue) && isNullOrEmptyValue(currentValue)) { + } else if (!isNullOrEmptyValue(prevValue) && + isNullOrEmptyValue(currentValue)) { // Remove property. diffs[propertyName] = null; } else if (prevValue != currentValue) { @@ -524,5 +598,6 @@ class CSSStyleDeclaration { // aB to a-b String _kebabize(String str) { - return str.replaceAllMapped(_kebabCaseReg, (match) => '-${match[0]!.toLowerCase()}'); + return str.replaceAllMapped( + _kebabCaseReg, (match) => '-${match[0]!.toLowerCase()}'); } diff --git a/webf/lib/src/css/style_rule.dart b/webf/lib/src/css/style_rule.dart index f1e990c323..75f9a40506 100644 --- a/webf/lib/src/css/style_rule.dart +++ b/webf/lib/src/css/style_rule.dart @@ -7,8 +7,23 @@ import 'package:webf/css.dart'; /// https://drafts.csswg.org/cssom/#the-cssstylerule-interface class CSSStyleRule extends CSSRule { - final String selectorText; - final Map style; + @override + String get cssText => declaration.cssText; - CSSStyleRule(this.selectorText, this.style); + final SelectorGroup selectorGroup; + final CSSStyleDeclaration declaration; + + CSSStyleRule(this.selectorGroup, this.declaration) : super(); + + SimpleSelector? get lastSimpleSelector { + return selectorGroup.selectors.last.simpleSelectorSequences.last.simpleSelector; + } + + String get selectorText { + var sb = StringBuffer(); + selectorGroup.selectors.forEach((selector) { + sb.write(selector.simpleSelectorSequences.map((ss) => ss.simpleSelector.name).join(' ')); + }); + return sb.toString(); + } } diff --git a/webf/lib/src/css/style_sheet.dart b/webf/lib/src/css/style_sheet.dart index c4128e3b08..6c13e041b1 100644 --- a/webf/lib/src/css/style_sheet.dart +++ b/webf/lib/src/css/style_sheet.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 'package:webf/css.dart'; abstract class StyleSheet {} @@ -19,31 +18,29 @@ class CSSStyleSheet implements StyleSheet { /// A string containing the baseURL used to resolve relative URLs in the stylesheet. String? herf; - List cssRules = []; + final RuleSet ruleSet; - CSSStyleSheet(String text, {this.disabled = false, this.herf}) { - List rules = CSSParser.parseRules(text, parentStyleSheet: this); - cssRules.addAll(rules); - } + CSSStyleSheet(this.ruleSet, {this.disabled = false, this.herf}); insertRule(String text, int index) { - CSSRule? rule = CSSParser.parseRule(text); - if (rule != null) { - cssRules.insert(index, rule); - } else { - // TODO: throw error + List rules = CSSParser(text).parseRules(); + for (CSSRule rule in rules) { + ruleSet.addRule(rule); } } /// Removes a rule from the stylesheet object. deleteRule(int index) { - cssRules.removeAt(index); + ruleSet.deleteRule(index); } /// Synchronously replaces the content of the stylesheet with the content passed into it. replaceSync(String text) { - cssRules.clear(); - cssRules.addAll(CSSParser.parseRules(text)); + ruleSet.reset(); + List rules = CSSParser(text).parseRules(); + for (CSSRule rule in rules) { + ruleSet.addRule(rule); + } } replace(String text) { diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index b2aab26aaf..6545e1d485 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -80,6 +80,8 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // Default to unknown, assign by [createElement], used by inspector. String tagName = UNKNOWN; + String? get id => getAttribute('id'); + // Is element an replaced element. // https://drafts.csswg.org/css-display/#replaced-element final bool _isReplacedElement; @@ -1413,22 +1415,10 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } void _applySheetStyle(CSSStyleDeclaration style) { - if (classList.isNotEmpty) { - const String classSelectorPrefix = '.'; - for (String className in classList) { - for (CSSStyleSheet sheet in ownerDocument.styleSheets) { - List rules = sheet.cssRules; - for (int i = 0; i < rules.length; i++) { - CSSRule rule = rules[i]; - if (rule is CSSStyleRule && rule.selectorText == (classSelectorPrefix + className)) { - var sheetStyle = rule.style; - for (String propertyName in sheetStyle.keys) { - style.setProperty(propertyName, sheetStyle[propertyName], false); - } - } - } - } - } + for (CSSStyleSheet sheet in ownerDocument.styleSheets) { + RuleSet ruleSet = sheet.ruleSet; + CSSStyleDeclaration? matchRule = ElementRuleCollector().collectionFromRuleSet(ruleSet, this); + style.merge(matchRule); } } diff --git a/webf/lib/src/dom/elements/head.dart b/webf/lib/src/dom/elements/head.dart index 1f64055a3e..cbe01d28b2 100644 --- a/webf/lib/src/dom/elements/head.dart +++ b/webf/lib/src/dom/elements/head.dart @@ -170,7 +170,8 @@ class LinkElement extends Element { } void _addCSSStyleSheet(String css) { - ownerDocument.addStyleSheet(CSSStyleSheet(css)); + final sheet = CSSParser(css).parse(); + ownerDocument.addStyleSheet(sheet); } @override @@ -246,7 +247,8 @@ class StyleElement extends Element { _styleSheet!.replaceSync(text); ownerDocument.recalculateDocumentStyle(); } else { - ownerDocument.addStyleSheet(_styleSheet = CSSStyleSheet(text)); + final sheet = CSSParser(text).parse(); + ownerDocument.addStyleSheet(_styleSheet = sheet); } } } diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index b8db70e756..e53b1208d3 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -100,6 +100,20 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa return null; } + Element? get previousElementSibling { + if (previousSibling != null && previousSibling!.nodeType == NodeType.ELEMENT_NODE) { + return previousSibling as Element; + } + return null; + } + + Element? get nextElementSibling { + if (nextSibling != null && nextSibling!.nodeType == NodeType.ELEMENT_NODE) { + return nextSibling as Element; + } + return null; + } + Node(this.nodeType, [BindingContext? context]) : super(context); // If node is on the tree, the root parent is body. diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index 4bf795e406..779bd49c56 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -6,100 +6,134 @@ import 'package:webf/css.dart'; import 'package:test/test.dart'; +CSSRule? parseSingleRule(String rule) { + CSSStyleSheet sheet = CSSParser(rule).parse(); + return sheet.ruleSet.rules.first; +} + void main() { group('CSSStyleRuleParser', () { - test('1', () { - CSSRule? rule = CSSStyleRuleParser.parse(' .foo { color: red; }'); + test('0', () { + CSSRule? rule = parseSingleRule('div p, #id:first-line { color : red; }'); expect(rule is CSSStyleRule, true); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'first-line'); + expect(styleRule.declaration.getPropertyValue('color'), 'red'); + }); + test('1', () { + CSSRule? rule = parseSingleRule(' .foo { color: red; }'); + expect(rule is CSSStyleRule, true); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.selectorText, '.foo'); - expect(styleRule.style['color'], 'red'); + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('color'), 'red'); }); test('2', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('html{\n color:black;\n}'); - expect(styleRule, isNotNull); - expect(styleRule!.selectorText, 'html'); - expect(styleRule.style['color'], 'black'); + CSSRule? rule = parseSingleRule(' html{\n color:black;\n}'); + expect(rule is CSSStyleRule, true); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'html'); + expect(styleRule.declaration.getPropertyValue('color'), 'black'); }); test('3', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('/*\nSome Comments\nBaby \n*/\nhtml{\n color:black;\n}'); - expect(styleRule!.selectorText, 'html'); - expect(styleRule.style['color'], 'black'); + CSSRule? rule = parseSingleRule('/*\nSome Comments\nBaby \n*/\nhtml{\n color:black;\n}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'html'); + expect(styleRule.declaration.getPropertyValue('color'), 'black'); }); test('4', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('/*\nSome Comments\nBaby \n*/\nhtml{\n color:black;\n}'); - expect(styleRule!.selectorText, 'html'); - expect(styleRule.style['color'], 'black'); + CSSRule? rule = parseSingleRule('/*\nSome Comments\nBaby \n*/\nhtml{\n color:black;\n}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'html'); + expect(styleRule.declaration.getPropertyValue('color'), 'black'); }); test('5', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo{--custom:some\n value;}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['--custom'], 'some value'); + CSSRule? rule = parseSingleRule('.foo{--custom:some\n value;}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('--custom'), 'some value'); }); test('6', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo{zoom;\ncolor: red \n}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['color'], 'red'); + CSSRule? rule = parseSingleRule('.foo{zoom;\ncolor: red \n}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('color'), 'red'); }); test('7', () { - CSSStyleRule? styleRule = - CSSStyleRuleParser.parse('.foo \t {background: url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4) red}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['background'], 'url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4) red'); + CSSRule? rule = parseSingleRule('.foo \t {background: url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4) red}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule. lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('backgroundColor'), 'red'); + expect(styleRule.declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4)'); }); test('8', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo { color: rgb(255, 255, 0)}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['color'], 'rgb(255, 255, 0)'); + CSSRule? rule = parseSingleRule('.foo { color: rgb(255, 255, 0)}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); }); test('9', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo { background : ; color: rgb(255, 255, 0)}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['color'], 'rgb(255, 255, 0)'); + CSSRule? rule = parseSingleRule('.foo { background : ; color: rgb(255, 255, 0)}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); }); test('10', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('th:nth-child(4) {color: rgb(255, 255, 0)}'); - expect(styleRule!.selectorText, 'th:nth-child(4)'); - expect(styleRule.style['color'], 'rgb(255, 255, 0)'); + CSSRule? rule = parseSingleRule('div:nth-child(4) {color: rgb(255, 255, 0)}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + final simpleSelectors = styleRule.selectorGroup.selectors.first.simpleSelectorSequences; + expect(simpleSelectors[0].simpleSelector.name, 'div'); + expect(simpleSelectors[1].simpleSelector.name, 'nth-child'); + expect(simpleSelectors[1].simpleSelector is PseudoClassFunctionSelector, true); + expect((simpleSelectors[1].simpleSelector as PseudoClassFunctionSelector).argument, ['4']); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); }); test('11', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('[hidden] { display: none }'); - expect(styleRule!.selectorText, '[hidden]'); - expect(styleRule.style['display'], 'none'); + CSSRule? rule = parseSingleRule('[hidden] { display: none }'); + CSSStyleRule styleRule = rule as CSSStyleRule; + final simpleSelectors = styleRule.selectorGroup.selectors.first.simpleSelectorSequences; + expect(simpleSelectors[0].simpleSelector is AttributeSelector, true); + expect(simpleSelectors[0].simpleSelector.name, 'hidden'); + expect(styleRule.declaration.getPropertyValue('display'), 'none'); }); test('12', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('/**/ div > p { color: rgb(255, 255, 0); } /**/'); - expect(styleRule!.selectorText, 'div > p'); - expect(styleRule.style['color'], 'rgb(255, 255, 0)'); + CSSRule? rule = parseSingleRule('/**/ div > p { color: rgb(255, 255, 0); } /**/'); + CSSStyleRule styleRule = rule as CSSStyleRule; + final simpleSelectors = styleRule.selectorGroup.selectors.first.simpleSelectorSequences; + expect(simpleSelectors[0].simpleSelector.name, 'div'); + expect(simpleSelectors[1].simpleSelector.name, 'p'); + expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255,255,0)'); }); test('13', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo { background-image: url( "./image (1).jpg" )}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['backgroundImage'], 'url( "./image (1).jpg" )'); + CSSRule? rule = parseSingleRule('.foo { background-image: url( "./image (1).jpg" )}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('background-image'), 'url(./image (1).jpg)'); }); test('14', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse('.foo { .foo{ }; color: red}'); - expect(styleRule!.selectorText, '.foo'); - expect(styleRule.style['color'], 'red'); + CSSRule? rule = parseSingleRule('.foo { .foo{ }; color: red}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.declaration.getPropertyValue('color'), 'red'); }); test('15', () { - CSSStyleRule? styleRule = CSSStyleRuleParser.parse(' .foo {}'); - expect(styleRule!.selectorText, '.foo'); + CSSRule? rule = parseSingleRule(' .foo {}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); }); }); } diff --git a/webf/test/src/css/style_sheet_parser.dart b/webf/test/src/css/style_sheet_parser.dart index 407e50b29a..ca26b4d5ea 100644 --- a/webf/test/src/css/style_sheet_parser.dart +++ b/webf/test/src/css/style_sheet_parser.dart @@ -6,61 +6,72 @@ import 'package:webf/css.dart'; import 'package:test/test.dart'; +List parseRules(String rule) { + CSSStyleSheet sheet = CSSParser(rule).parse(); + return sheet.ruleSet.rules; +} + void main() { group('CSSStyleSheetParser', () { test('1', () { - List rules = CSSStyleSheetParser.parse('.foo {color: red} \n .bar {}'); + List rules = parseRules('.foo {color: red} \n .bar {}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); + expect((rules[1] as CSSStyleRule).selectorText, 'bar'); }); test('2', () { - List rules = CSSStyleSheetParser.parse('{} \n .foo {color: red;} ;\n .bar {;;}'); + List rules = parseRules('{} \n .foo {color: red;} ;\n .bar {;;}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); + expect((rules[1] as CSSStyleRule).selectorText, 'bar'); }); test('3', () { - List rules = CSSStyleSheetParser.parse('.foo {color: red;} .bar { .x {}; color: #aaa} .baz {}'); + List rules = parseRules('.foo {color: red;} .bar { .x {}; color: #aaa} .baz {}'); expect(rules.length, 3); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); - expect((rules[1] as CSSStyleRule).style['color'], '#aaa'); - expect((rules[2] as CSSStyleRule).selectorText, '.baz'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); + expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).declaration.getPropertyValue('color'), '#aaa'); + expect((rules[2] as CSSStyleRule).selectorText, 'baz'); }); test('4', () { - List rules = - CSSStyleSheetParser.parse('.foo {color: red} .bar {background: url(data:image/png;base64...)}'); + List rules = parseRules('.foo {color: red} .bar {background: url(data:image/png;base64...)}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); - expect((rules[1] as CSSStyleRule).style['background'], 'url(data:image/png;base64...)'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); + expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64...)'); }); test('5', () { - List rules = CSSStyleSheetParser.parse('@charset "utf-8"; .foo {color: red}'); + List rules = parseRules('@charset "utf-8"; .foo {color: red}'); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); test('6', () { - List rules = CSSStyleSheetParser.parse(''' + List rules = parseRules(''' @media screen and (min-width: 900.5px) { } .foo { color: red } '''); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); - expect((rules[0] as CSSStyleRule).style['color'], 'red'); + expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); + }); + + test('7', () { + List rules = parseRules('.foo h6{color: red}'); + expect(rules.length, 1); + expect((rules[0] as CSSStyleRule).selectorText, 'foo h6'); + expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); }); } From 18c02216a1a489d9e2fa1904925ff36ccf2399a8 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Thu, 4 Aug 2022 17:37:57 +0800 Subject: [PATCH 222/498] feat: ignore parse shorthand value --- webf/lib/src/css/element_rule_collector.dart | 2 +- webf/lib/src/css/parser/parser.dart | 159 +++--------------- webf/lib/src/css/parser/token_kind.dart | 116 +------------ webf/lib/src/css/parser/tokenizer.dart | 6 +- ...r_checker.dart => selector_evaluator.dart} | 0 webf/lib/src/css/style_declaration.dart | 91 ++++------ webf/lib/src/dom/element.dart | 13 +- webf/pubspec.yaml | 1 + webf/test/src/css/style_rule_parser.dart | 6 + 9 files changed, 66 insertions(+), 328 deletions(-) rename webf/lib/src/css/{selector_checker.dart => selector_evaluator.dart} (100%) diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index cc3b95717f..3299ffaf35 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -5,7 +5,7 @@ import 'package:webf/css.dart'; import 'package:webf/dom.dart'; -import 'package:webf/src/css/selector_checker.dart'; +import 'package:webf/src/css/selector_evaluator.dart'; class ElementRuleCollector { diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 10a357d3dc..82fb6c54cf 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -561,7 +561,10 @@ class CSSParser { ThisOperator(), ) : simpleSelector(); - if (simpleSel == null && (combinatorType == TokenKind.COMBINATOR_PLUS || combinatorType == TokenKind.COMBINATOR_GREATER || combinatorType == TokenKind.COMBINATOR_TILDE)) { + if (simpleSel == null && + (combinatorType == TokenKind.COMBINATOR_PLUS || + combinatorType == TokenKind.COMBINATOR_GREATER || + combinatorType == TokenKind.COMBINATOR_TILDE)) { // For "+ &", "~ &" or "> &" a selector sequence with no name is needed // so that the & will have a combinator too. This is needed to // disambiguate selector expressions: @@ -703,7 +706,8 @@ class CSSParser { _eat(TokenKind.RPAREN); return NegationSelector(negArg); - } else if (!pseudoElement && (name == 'host' || name == 'host-context' || name == 'global-context' || name == '-acx-global-context')) { + } else if (!pseudoElement && + (name == 'host' || name == 'host-context' || name == 'global-context' || name == '-acx-global-context')) { _eat(TokenKind.LPAREN); var selector = processCompoundSelector(); if (selector == null) { @@ -729,13 +733,17 @@ class CSSParser { // Used during selector look-a-head if not a SelectorExpression is // bad. _eat(TokenKind.RPAREN); - return (pseudoElement) ? PseudoElementFunctionSelector(pseudoName, expr) : PseudoClassFunctionSelector(pseudoName, expr); + return (pseudoElement) + ? PseudoElementFunctionSelector(pseudoName, expr) + : PseudoClassFunctionSelector(pseudoName, expr); } } // Treat CSS2.1 pseudo-elements defined with pseudo class syntax as pseudo- // elements for backwards compatibility. - return pseudoElement || _legacyPseudoElements.contains(name) ? PseudoElementSelector(pseudoName, isLegacy: !pseudoElement) : PseudoClassSelector(pseudoName); + return pseudoElement || _legacyPseudoElements.contains(name) + ? PseudoElementSelector(pseudoName, isLegacy: !pseudoElement) + : PseudoClassSelector(pseudoName); } /// In CSS3, the expressions are identifiers, strings, or of the form "an+b". @@ -880,7 +888,7 @@ class CSSParser { } } - var expr = processExpr().join(' '); + var expr = processExpr(); // Handle !important (prio) var importantPriority = _maybeEat(TokenKind.IMPORTANT); @@ -899,139 +907,16 @@ class CSSParser { // expression: term [ operator? term]* // // operator: '/' | ',' - // term: (see processTerm) - List processExpr([bool ieFilter = false]) { + String processExpr([bool ieFilter = false]) { var start = _peekToken.span; - - var keepGoing = true; - List result = []; - String? expr; - while (keepGoing && (expr = processTerm()) != null) { - Expression? op; - - var opStart = _peekToken.span; - - switch (_peek()) { - case TokenKind.SLASH: - op = OperatorSlash(); - break; - case TokenKind.COMMA: - op = OperatorComma(); - break; - case TokenKind.BACKSLASH: - _next(); - if (_peekKind(TokenKind.INTEGER)) { - _next(); - if (isChecked) { - _warning('\$value is not valid in an expression', _makeSpan(start)); - } - } - break; - } - - if (expr != null) { - result.add(expr); - } else { - keepGoing = false; - } - - if (op != null) { - result.add(_makeSpan(opStart).text); - _next(); - } + FileSpan? end; + while (_peek() != TokenKind.SEMICOLON) { + end = _next().span; } - return result; - } - - String? processTerm() { - var start = _peekToken.span; - Token? t; // token for term's value - dynamic value; // value of term (numeric values) - - var unary = ''; - switch (_peek()) { - case TokenKind.HASH: - _eat(TokenKind.HASH); - if (!_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { - String? hexText; - if (_peekKind(TokenKind.INTEGER)) { - var hexText1 = _peekToken.text; - _next(); - // Append identifier only if there's no delimiting whitespace. - if (_peekIdentifier() && _previousToken!.end == _peekToken.start) { - hexText = '$hexText1${identifier().name}'; - } else { - hexText = hexText1; - } - } else if (_peekIdentifier()) { - hexText = identifier().name; - } - return '#$hexText'; - } - - if (isChecked) { - _warning('Expected hex number', _makeSpan(start)); - } - // Construct the bad hex value with a #number. - return start.text; - case TokenKind.INTEGER: - case TokenKind.DOUBLE: - t = _next(); - return '$unary${t.text}'; - case TokenKind.SINGLE_QUOTE: - value = processQuotedString(false); - value = "'${_escapeString(value as String, single: true)}'"; - return value; - case TokenKind.DOUBLE_QUOTE: - value = processQuotedString(false); - value = '"${_escapeString(value as String)}"'; - return value; - case TokenKind.LPAREN: - _next(); - return null; - case TokenKind.LBRACK: - _next(); - return null; - case TokenKind.IDENTIFIER: - var nameValue = identifier(); // Snarf up the ident we'll remap, maybe. - - if (_maybeEat(TokenKind.LPAREN)) { - return processFunction(nameValue); - } - return nameValue.name; - case TokenKind.UNICODE_RANGE: - String? first; - String? second; - int firstNumber; - int secondNumber; - _eat(TokenKind.UNICODE_RANGE, unicodeRange: true); - if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { - first = _previousToken!.text; - firstNumber = int.parse('0x$first'); - if (firstNumber > MAX_UNICODE) { - _error('unicode range must be less than 10FFFF', _makeSpan(start)); - } - if (_maybeEat(TokenKind.MINUS, unicodeRange: true)) { - if (_maybeEat(TokenKind.HEX_INTEGER, unicodeRange: true)) { - second = _previousToken!.text; - secondNumber = int.parse('0x$second'); - if (secondNumber > MAX_UNICODE) { - _error('unicode range must be less than 10FFFF', _makeSpan(start)); - } - if (firstNumber > secondNumber) { - _error('unicode first range can not be greater than last', _makeSpan(start)); - } - } - } - } else if (_maybeEat(TokenKind.HEX_RANGE, unicodeRange: true)) { - first = _previousToken!.text; - } - return 'U+$first-$second'; - case TokenKind.AT: - break; + if (end != null) { + return start.expand(end).text; } - - return null; + return ''; } static const int MAX_UNICODE = 0x10FFFF; @@ -1141,7 +1026,7 @@ class CSSParser { if (!_maybeEat(TokenKind.RPAREN)) { _error('problem parsing function expected ), ', _peekToken.span); } - return 'rgb(${expr.join()})'; + return 'rgb($expr)'; case 'url': // URI term sucks up everything inside of quotes(' or ") or between // parens. @@ -1176,7 +1061,7 @@ class CSSParser { if (!_maybeEat(TokenKind.RPAREN)) { _error('problem parsing function expected ), ', _peekToken.span); } - return expr.join(); + return expr; } } diff --git a/webf/lib/src/css/parser/token_kind.dart b/webf/lib/src/css/parser/token_kind.dart index 7a9f31f81b..f860279284 100644 --- a/webf/lib/src/css/parser/token_kind.dart +++ b/webf/lib/src/css/parser/token_kind.dart @@ -1,5 +1,5 @@ /* -Copyright 2013, the Dart project authors. +Copyright 2013, the Dart project authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -137,35 +137,6 @@ class TokenKind { static const int SUBSTRING_MATCH = 534; // '*=' static const int NO_MATCH = 535; // No operator. - // Unit types: - static const int UNIT_EM = 600; - static const int UNIT_EX = 601; - static const int UNIT_LENGTH_PX = 602; - static const int UNIT_LENGTH_CM = 603; - static const int UNIT_LENGTH_MM = 604; - static const int UNIT_LENGTH_IN = 605; - static const int UNIT_LENGTH_PT = 606; - static const int UNIT_LENGTH_PC = 607; - static const int UNIT_ANGLE_DEG = 608; - static const int UNIT_ANGLE_RAD = 609; - static const int UNIT_ANGLE_GRAD = 610; - static const int UNIT_ANGLE_TURN = 611; - static const int UNIT_TIME_MS = 612; - static const int UNIT_TIME_S = 613; - static const int UNIT_FREQ_HZ = 614; - static const int UNIT_FREQ_KHZ = 615; - static const int UNIT_PERCENT = 616; - static const int UNIT_FRACTION = 617; - static const int UNIT_RESOLUTION_DPI = 618; - static const int UNIT_RESOLUTION_DPCM = 619; - static const int UNIT_RESOLUTION_DPPX = 620; - static const int UNIT_CH = 621; // Measure of "0" U+0030 glyph. - static const int UNIT_REM = 622; // computed value ‘font-size’ on root elem. - static const int UNIT_VIEWPORT_VW = 623; - static const int UNIT_VIEWPORT_VH = 624; - static const int UNIT_VIEWPORT_VMIN = 625; - static const int UNIT_VIEWPORT_VMAX = 626; - // Directives (@nnnn) static const int DIRECTIVE_NONE = 640; static const int DIRECTIVE_IMPORT = 641; @@ -271,58 +242,10 @@ class TokenKind { {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTBOTTOM, 'value': 'right-bottom'}, ]; - static const List> _UNITS = [ - {'unit': TokenKind.UNIT_EM, 'value': 'em'}, - {'unit': TokenKind.UNIT_EX, 'value': 'ex'}, - {'unit': TokenKind.UNIT_LENGTH_PX, 'value': 'px'}, - {'unit': TokenKind.UNIT_LENGTH_CM, 'value': 'cm'}, - {'unit': TokenKind.UNIT_LENGTH_MM, 'value': 'mm'}, - {'unit': TokenKind.UNIT_LENGTH_IN, 'value': 'in'}, - {'unit': TokenKind.UNIT_LENGTH_PT, 'value': 'pt'}, - {'unit': TokenKind.UNIT_LENGTH_PC, 'value': 'pc'}, - {'unit': TokenKind.UNIT_ANGLE_DEG, 'value': 'deg'}, - {'unit': TokenKind.UNIT_ANGLE_RAD, 'value': 'rad'}, - {'unit': TokenKind.UNIT_ANGLE_GRAD, 'value': 'grad'}, - {'unit': TokenKind.UNIT_ANGLE_TURN, 'value': 'turn'}, - {'unit': TokenKind.UNIT_TIME_MS, 'value': 'ms'}, - {'unit': TokenKind.UNIT_TIME_S, 'value': 's'}, - {'unit': TokenKind.UNIT_FREQ_HZ, 'value': 'hz'}, - {'unit': TokenKind.UNIT_FREQ_KHZ, 'value': 'khz'}, - {'unit': TokenKind.UNIT_FRACTION, 'value': 'fr'}, - {'unit': TokenKind.UNIT_RESOLUTION_DPI, 'value': 'dpi'}, - {'unit': TokenKind.UNIT_RESOLUTION_DPCM, 'value': 'dpcm'}, - {'unit': TokenKind.UNIT_RESOLUTION_DPPX, 'value': 'dppx'}, - {'unit': TokenKind.UNIT_CH, 'value': 'ch'}, - {'unit': TokenKind.UNIT_REM, 'value': 'rem'}, - {'unit': TokenKind.UNIT_VIEWPORT_VW, 'value': 'vw'}, - {'unit': TokenKind.UNIT_VIEWPORT_VH, 'value': 'vh'}, - {'unit': TokenKind.UNIT_VIEWPORT_VMIN, 'value': 'vmin'}, - {'unit': TokenKind.UNIT_VIEWPORT_VMAX, 'value': 'vmax'}, - ]; - // Some more constants: static const int ASCII_UPPER_A = 65; // ASCII value for uppercase A static const int ASCII_UPPER_Z = 90; // ASCII value for uppercase Z - // TODO(terry): Should used Dart mirroring for parameter values and types - // especially for enumeration (e.g., counter's second parameter - // is list-style-type which is an enumerated list for ordering - // of a list 'circle', 'decimal', 'lower-roman', 'square', etc. - // see http://www.w3schools.com/cssref/pr_list-style-type.asp - // for list of possible values. - - /// Check if name is a pre-defined CSS name. Used by error handler to report - /// if name is unknown or used improperly. - static bool isPredefinedName(String name) { - var nameLen = name.length; - // TODO(terry): Add more pre-defined names (hidden, bolder, inherit, etc.). - if (matchUnits(name, 0, nameLen) == -1 || matchDirectives(name, 0, nameLen) == -1 || matchMarginDirectives(name, 0, nameLen) == -1) { - return false; - } - - return true; - } - /// Return the token that matches the unit ident found. static int matchList(Iterable> identList, String tokenField, String text, int offset, int length) { for (final entry in identList) { @@ -351,11 +274,6 @@ class TokenKind { return -1; // Not a unit token. } - /// Return the token that matches the unit ident found. - static int matchUnits(String text, int offset, int length) { - return matchList(_UNITS, 'unit', text, offset, length); - } - /// Return the token that matches the directive name found. static int matchDirectives(String text, int offset, int length) { return matchList(_DIRECTIVES, 'type', text, offset, length); @@ -382,22 +300,6 @@ class TokenKind { return null; } - /// Return the unit token as its pretty name. - static String? unitToString(int unitTokenToFind) { - if (unitTokenToFind == TokenKind.PERCENT) { - return '%'; - } else { - for (final entry in _UNITS) { - final unit = entry['unit'] as int; - if (unit == unitTokenToFind) { - return entry['value'] as String?; - } - } - } - - return ''; // Not a unit token. - } - /// Return RGB value as [int] from a color entry in _EXTENDED_COLOR_NAMES. static int colorValue(Map entry) { return entry['value'] as int; @@ -525,22 +427,6 @@ class TokenKind { case TokenKind.DIRECTIVE_MIXIN: case TokenKind.DIRECTIVE_INCLUDE: case TokenKind.DIRECTIVE_CONTENT: - case TokenKind.UNIT_EM: - case TokenKind.UNIT_EX: - case TokenKind.UNIT_LENGTH_PX: - case TokenKind.UNIT_LENGTH_CM: - case TokenKind.UNIT_LENGTH_MM: - case TokenKind.UNIT_LENGTH_IN: - case TokenKind.UNIT_LENGTH_PT: - case TokenKind.UNIT_LENGTH_PC: - case TokenKind.UNIT_ANGLE_DEG: - case TokenKind.UNIT_ANGLE_RAD: - case TokenKind.UNIT_ANGLE_GRAD: - case TokenKind.UNIT_TIME_MS: - case TokenKind.UNIT_TIME_S: - case TokenKind.UNIT_FREQ_HZ: - case TokenKind.UNIT_FREQ_KHZ: - case TokenKind.UNIT_FRACTION: return true; default: return false; diff --git a/webf/lib/src/css/parser/tokenizer.dart b/webf/lib/src/css/parser/tokenizer.dart index a0139ac0bd..666ab8acaf 100644 --- a/webf/lib/src/css/parser/tokenizer.dart +++ b/webf/lib/src/css/parser/tokenizer.dart @@ -1,5 +1,5 @@ /* -Copyright 2013, the Dart project authors. +Copyright 2013, the Dart project authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -264,10 +264,6 @@ class Tokenizer extends TokenizerBase { // Is the identifier a unit type? var tokId = -1; - // Don't match units in selectors or selector expressions. - if (!inSelectorExpression && !inSelector) { - tokId = TokenKind.matchUnits(_text, _startIndex, _index - _startIndex); - } if (tokId == -1) { tokId = (_text.substring(_startIndex, _index) == '!important') ? TokenKind.IMPORTANT : -1; } diff --git a/webf/lib/src/css/selector_checker.dart b/webf/lib/src/css/selector_evaluator.dart similarity index 100% rename from webf/lib/src/css/selector_checker.dart rename to webf/lib/src/css/selector_evaluator.dart diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index b3a5c6b14f..b9878a0225 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -46,8 +46,7 @@ List _propertyOrders = [ RegExp _kebabCaseReg = RegExp(r'[A-Z]'); -final LinkedLruHashMap> _cachedExpandedShorthand = - LinkedLruHashMap(maximumSize: 500); +final LinkedLruHashMap> _cachedExpandedShorthand = LinkedLruHashMap(maximumSize: 500); // CSS Object Model: https://drafts.csswg.org/cssom/#the-cssstyledeclaration-interface @@ -65,14 +64,15 @@ final LinkedLruHashMap> _cachedExpandedShorthand = /// object as a read-only interface. class CSSStyleDeclaration { Element? target; + // TODO(yuanyan): defaultStyle should be longhand properties. Map? defaultStyle; StyleChangeListener? onStyleChanged; CSSStyleDeclaration(); + // ignore: prefer_initializing_formals - CSSStyleDeclaration.computedStyle( - this.target, this.defaultStyle, this.onStyleChanged); + CSSStyleDeclaration.computedStyle(this.target, this.defaultStyle, this.onStyleChanged); /// An empty style declaration. static CSSStyleDeclaration empty = CSSStyleDeclaration(); @@ -116,9 +116,7 @@ class CSSStyleDeclaration { /// If not set, returns the empty string. String getPropertyValue(String propertyName) { // Get the latest pending value first. - return _pendingProperties[propertyName] ?? - _properties[propertyName] ?? - EMPTY_STRING; + return _pendingProperties[propertyName] ?? _properties[propertyName] ?? EMPTY_STRING; } /// Removes a property from the CSS declaration. @@ -131,8 +129,7 @@ class CSSStyleDeclaration { case BACKGROUND: return CSSStyleProperty.removeShorthandBackground(this, isImportant); case BACKGROUND_POSITION: - return CSSStyleProperty.removeShorthandBackgroundPosition( - this, isImportant); + return CSSStyleProperty.removeShorthandBackgroundPosition(this, isImportant); case BORDER_RADIUS: return CSSStyleProperty.removeShorthandBorderRadius(this, isImportant); case OVERFLOW: @@ -151,13 +148,11 @@ class CSSStyleDeclaration { case BORDER_COLOR: case BORDER_STYLE: case BORDER_WIDTH: - return CSSStyleProperty.removeShorthandBorder( - this, propertyName, isImportant); + return CSSStyleProperty.removeShorthandBorder(this, propertyName, isImportant); case TRANSITION: return CSSStyleProperty.removeShorthandTransition(this, isImportant); case TEXT_DECORATION: - return CSSStyleProperty.removeShorthandTextDecoration( - this, isImportant); + return CSSStyleProperty.removeShorthandTextDecoration(this, isImportant); } String present = EMPTY_STRING; @@ -173,9 +168,7 @@ class CSSStyleDeclaration { } // Fallback to default style. - if (isNullOrEmptyValue(present) && - defaultStyle != null && - defaultStyle!.containsKey(propertyName)) { + if (isNullOrEmptyValue(present) && defaultStyle != null && defaultStyle!.containsKey(propertyName)) { present = defaultStyle![propertyName]; } @@ -183,8 +176,7 @@ class CSSStyleDeclaration { _pendingProperties[propertyName] = present; } - void _expandShorthand( - String propertyName, String normalizedValue, bool? isImportant) { + void _expandShorthand(String propertyName, String normalizedValue, bool? isImportant) { Map longhandProperties; String cacheKey = '$propertyName:$normalizedValue'; if (_cachedExpandedShorthand.containsKey(cacheKey)) { @@ -194,40 +186,31 @@ class CSSStyleDeclaration { switch (propertyName) { case PADDING: - CSSStyleProperty.setShorthandPadding( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandPadding(longhandProperties, normalizedValue); break; case MARGIN: - CSSStyleProperty.setShorthandMargin( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandMargin(longhandProperties, normalizedValue); break; case BACKGROUND: - CSSStyleProperty.setShorthandBackground( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBackground(longhandProperties, normalizedValue); break; case BACKGROUND_POSITION: - CSSStyleProperty.setShorthandBackgroundPosition( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBackgroundPosition(longhandProperties, normalizedValue); break; case BORDER_RADIUS: - CSSStyleProperty.setShorthandBorderRadius( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandBorderRadius(longhandProperties, normalizedValue); break; case OVERFLOW: - CSSStyleProperty.setShorthandOverflow( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandOverflow(longhandProperties, normalizedValue); break; case FONT: - CSSStyleProperty.setShorthandFont( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFont(longhandProperties, normalizedValue); break; case FLEX: - CSSStyleProperty.setShorthandFlex( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFlex(longhandProperties, normalizedValue); break; case FLEX_FLOW: - CSSStyleProperty.setShorthandFlexFlow( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandFlexFlow(longhandProperties, normalizedValue); break; case BORDER: case BORDER_TOP: @@ -237,16 +220,13 @@ class CSSStyleDeclaration { case BORDER_COLOR: case BORDER_STYLE: case BORDER_WIDTH: - CSSStyleProperty.setShorthandBorder( - longhandProperties, propertyName, normalizedValue); + CSSStyleProperty.setShorthandBorder(longhandProperties, propertyName, normalizedValue); break; case TRANSITION: - CSSStyleProperty.setShorthandTransition( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandTransition(longhandProperties, normalizedValue); break; case TEXT_DECORATION: - CSSStyleProperty.setShorthandTextDecoration( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandTextDecoration(longhandProperties, normalizedValue); break; } _cachedExpandedShorthand[cacheKey] = longhandProperties; @@ -259,9 +239,7 @@ class CSSStyleDeclaration { } } - String _replacePattern( - String string, String lowerCase, String startString, String endString, - [int start = 0]) { + String _replacePattern(String string, String lowerCase, String startString, String endString, [int start = 0]) { int startIndex = lowerCase.indexOf(startString, start); if (startIndex >= 0) { int? endIndex; @@ -274,8 +252,7 @@ class CSSStyleDeclaration { var replacement = string.substring(startIndex, endIndex); lowerCase = lowerCase.replaceRange(startIndex, endIndex, replacement); if (endIndex < string.length - 1) { - lowerCase = _replacePattern( - string, lowerCase, startString, endString, endIndex); + lowerCase = _replacePattern(string, lowerCase, startString, endString, endIndex); } } } @@ -366,12 +343,10 @@ class CSSStyleDeclaration { if (!CSSColor.isColor(normalizedValue)) return false; break; case BACKGROUND_IMAGE: - if (!CSSBackground.isValidBackgroundImageValue(normalizedValue)) - return false; + if (!CSSBackground.isValidBackgroundImageValue(normalizedValue)) return false; break; case BACKGROUND_REPEAT: - if (!CSSBackground.isValidBackgroundRepeatValue(normalizedValue)) - return false; + if (!CSSBackground.isValidBackgroundRepeatValue(normalizedValue)) return false; break; } return true; @@ -478,13 +453,10 @@ class CSSStyleDeclaration { if (isNullOrEmptyValue(otherValue) && isNullOrEmptyValue(currentValue)) { continue; - } else if (!isNullOrEmptyValue(currentValue) && - isNullOrEmptyValue(otherValue)) { + } else if (!isNullOrEmptyValue(currentValue) && isNullOrEmptyValue(otherValue)) { // Remove property. _pendingProperties.remove(propertyName); - } else if (!isImportant && - otherValue != null && - currentValue != otherValue) { + } else if (!isImportant && otherValue != null && currentValue != otherValue) { // Update property. _pendingProperties[propertyName] = otherValue; bool otherIsImportant = declaration._importants[propertyName] ?? false; @@ -522,8 +494,7 @@ class CSSStyleDeclaration { if (isNullOrEmptyValue(prevValue) && isNullOrEmptyValue(currentValue)) { continue; - } else if (!isNullOrEmptyValue(prevValue) && - isNullOrEmptyValue(currentValue)) { + } else if (!isNullOrEmptyValue(prevValue) && isNullOrEmptyValue(currentValue)) { // Remove property. diffs[propertyName] = null; } else if (prevValue != currentValue) { @@ -545,6 +516,7 @@ class CSSStyleDeclaration { /// Override [] and []= operator to get/set style properties. operator [](String property) => getPropertyValue(property); + operator []=(String property, value) { setProperty(property, value); } @@ -598,6 +570,5 @@ class CSSStyleDeclaration { // aB to a-b String _kebabize(String str) { - return str.replaceAllMapped( - _kebabCaseReg, (match) => '-${match[0]!.toLowerCase()}'); + return str.replaceAllMapped(_kebabCaseReg, (match) => '-${match[0]!.toLowerCase()}'); } diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 6545e1d485..1599819123 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1448,19 +1448,12 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } void recalculateStyle() { - // TODO: current only support class selector in stylesheet - if (renderBoxModel != null && classList.isNotEmpty) { + if (renderBoxModel != null) { // Diff style. CSSStyleDeclaration newStyle = CSSStyleDeclaration(); applyStyle(newStyle); - Map diffs = style.diff(newStyle); - if (diffs.isNotEmpty) { - // Update render style. - diffs.forEach((String propertyName, String? value) { - style.setProperty(propertyName, value); - }); - style.flushPendingProperties(); - } + style.merge(newStyle); + style.flushPendingProperties(); } } diff --git a/webf/pubspec.yaml b/webf/pubspec.yaml index d6e865a48d..f7018c58c0 100644 --- a/webf/pubspec.yaml +++ b/webf/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: async: ^2.8.2 # Pure dart module. quiver: ^3.1.0 # Pure dart module. vector_math: ^2.1.2 # Pure dart module. + source_span: ^1.8.2 # Pure dart module. connectivity_plus: ^2.3.5 # No AndroidX used. shared_preferences: ^2.0.15 # No AndroidX used. diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index 779bd49c56..3c7f62ecce 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -135,5 +135,11 @@ void main() { CSSStyleRule styleRule = rule as CSSStyleRule; expect(styleRule.lastSimpleSelector?.name, 'foo'); }); + + test('16', () { + CSSRule? rule = parseSingleRule(' .foo { margin: 64px 0 32px; text-align: center;}'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + }); }); } From e79fc43f64c9729ddc62e4927ace52ccaa7498a7 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Fri, 5 Aug 2022 00:17:56 +0800 Subject: [PATCH 223/498] feat: camelize property name example works --- webf/lib/src/css/element_rule_collector.dart | 7 ++-- webf/lib/src/css/parser/parser.dart | 2 +- webf/lib/src/css/parser/tree.dart | 29 ---------------- webf/lib/src/css/selector_evaluator.dart | 35 +++++++++++++++++--- webf/lib/src/css/style_declaration.dart | 9 ++--- webf/lib/src/css/style_property.dart | 10 ++++++ webf/lib/src/css/transition.dart | 23 +------------ webf/lib/src/dom/element.dart | 10 ++++-- 8 files changed, 56 insertions(+), 69 deletions(-) diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index 3299ffaf35..e97b3cba7b 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -1,5 +1,4 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ @@ -8,15 +7,13 @@ import 'package:webf/dom.dart'; import 'package:webf/src/css/selector_evaluator.dart'; class ElementRuleCollector { - CSSStyleDeclaration collectionFromRuleSet(RuleSet ruleSet, Element element) { - List matchedRules = []; // #id String? id = element.id; if (id != null) { - matchedRules.addAll(_collectMatchingRulesForList(ruleSet.idRules[id], element)); + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.idRules[id], element)); } // .class @@ -35,6 +32,7 @@ class ElementRuleCollector { // universal matchedRules.addAll(_collectMatchingRulesForList(ruleSet.universalRules, element)); + // sort selector matchedRules.sort((leftRule, rightRule) { if (leftRule is! CSSStyleRule || rightRule is! CSSStyleRule) { return 0; @@ -42,6 +40,7 @@ class ElementRuleCollector { return leftRule.selectorGroup.specificity.compareTo(rightRule.selectorGroup.specificity); }); + // Merge all the rules CSSStyleDeclaration declaration = CSSStyleDeclaration(); for (CSSRule rule in matchedRules.reversed) { if (rule is CSSStyleRule) { diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 82fb6c54cf..c18c33b9b0 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -863,7 +863,7 @@ class CSSParser { void processDeclaration(CSSStyleDeclaration style) { // IDENT ':' expr '!important'? if (TokenKind.isIdentifier(_peekToken.kind)) { - var propertyIdent = identifier().name; + var propertyIdent = camelize(identifier().name); var resetProperty = false; var keepGoing = true; diff --git a/webf/lib/src/css/parser/tree.dart b/webf/lib/src/css/parser/tree.dart index 0b2f965b92..9b614fa1bd 100644 --- a/webf/lib/src/css/parser/tree.dart +++ b/webf/lib/src/css/parser/tree.dart @@ -30,10 +30,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. part of 'parser.dart'; -///////////////////////////////////////////////////////////////////////// -// CSS specific types: -///////////////////////////////////////////////////////////////////////// - /// The base type for all nodes in a CSS abstract syntax tree. abstract class TreeNode { TreeNode clone(); @@ -80,28 +76,3 @@ class Negation extends TreeNode { String get name => 'not'; } - -class NoOp extends TreeNode { - NoOp() : super(); - - @override - NoOp clone() => NoOp(); -} - -/// The base type for expressions. -abstract class Expression extends TreeNode { - @override - Expression clone(); -} - -class OperatorSlash extends Expression { - OperatorSlash() : super(); - @override - OperatorSlash clone() => OperatorSlash(); -} - -class OperatorComma extends Expression { - OperatorComma() : super(); - @override - OperatorComma clone() => OperatorComma(); -} diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/selector_evaluator.dart index 3540b3326d..7c8ae50427 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/selector_evaluator.dart @@ -1,7 +1,33 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ +Copyright 2013, the Dart project authors. + +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 LLC 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. +*/ + import 'package:webf/dom.dart'; import 'package:webf/css.dart'; @@ -99,7 +125,8 @@ class SelectorEvaluator extends SelectorVisitor { // http://dev.w3.org/csswg/selectors-4/#the-blank-pseudo case 'blank': - return _element!.childNodes.any((n) => !(n is Element || n is TextNode && n.data.runes.any((r) => !isWhitespaceCC(r)))); + return _element!.childNodes + .any((n) => !(n is Element || n is TextNode && n.data.runes.any((r) => !isWhitespaceCC(r)))); // http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo case 'first-child': diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index b9878a0225..831e4ad7a8 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -451,14 +451,9 @@ class CSSStyleDeclaration { String? currentValue = properties[propertyName]; String? otherValue = declaration._pendingProperties[propertyName]; - if (isNullOrEmptyValue(otherValue) && isNullOrEmptyValue(currentValue)) { - continue; - } else if (!isNullOrEmptyValue(currentValue) && isNullOrEmptyValue(otherValue)) { - // Remove property. - _pendingProperties.remove(propertyName); - } else if (!isImportant && otherValue != null && currentValue != otherValue) { + if (!isImportant && !isNullOrEmptyValue(otherValue) && currentValue != otherValue) { // Update property. - _pendingProperties[propertyName] = otherValue; + _pendingProperties[propertyName] = otherValue!; bool otherIsImportant = declaration._importants[propertyName] ?? false; if (otherIsImportant) { _importants[propertyName] = true; diff --git a/webf/lib/src/css/style_property.dart b/webf/lib/src/css/style_property.dart index 5576bf55e2..09d36ce7ce 100644 --- a/webf/lib/src/css/style_property.dart +++ b/webf/lib/src/css/style_property.dart @@ -5,6 +5,8 @@ import 'package:webf/css.dart'; +// a-b to aB +final RegExp _camelCaseReg = RegExp(r'-(\w)'); final RegExp _spaceRegExp = RegExp(r'\s+(?![^(]*\))'); final RegExp _commaRegExp = RegExp(r',(?![^\(]*\))'); final RegExp _slashRegExp = RegExp(r'\/(?![^(]*\))'); @@ -15,6 +17,14 @@ const String _0 = '0'; const String _1 = '1'; const String _0Percent = '0%'; +// a-b -> aB +String camelize(String str) { + return str.replaceAllMapped(RegExp(r'-(\w)'), (match) { + String subStr = match[0]!.substring(1); + return subStr.isNotEmpty ? subStr.toUpperCase() : ''; + }); +} + // Origin version: https://github.com/jedmao/css-list-helpers/blob/master/src/index.ts List _splitBySpace(String value) { List array = List.empty(growable: true); diff --git a/webf/lib/src/css/transition.dart b/webf/lib/src/css/transition.dart index 926ed05ac9..c3858d80d7 100644 --- a/webf/lib/src/css/transition.dart +++ b/webf/lib/src/css/transition.dart @@ -11,27 +11,6 @@ import 'package:webf/dom.dart'; // CSS Transitions: https://drafts.csswg.org/css-transitions/ const String _0s = '0s'; -String _toCamelCase(String s) { - var sb = StringBuffer(); - var shouldUpperCase = false; - for (int rune in s.runes) { - // '-' char code is 45 - if (rune == 45) { - shouldUpperCase = true; - } else { - var char = String.fromCharCode(rune); - if (shouldUpperCase) { - sb.write(char.toUpperCase()); - shouldUpperCase = false; - } else { - sb.write(char); - } - } - } - - return sb.toString(); -} - Color? _parseColor(String color, RenderStyle renderStyle, String propertyName) { return CSSColor.resolveColor(color, renderStyle, propertyName); } @@ -279,7 +258,7 @@ mixin CSSTransitionMixin on RenderStyle { Map transitions = {}; for (int i = 0; i < transitionProperty.length; i++) { - String property = _toCamelCase(transitionProperty[i]); + String property = camelize(transitionProperty[i]); String duration = transitionDuration.length == 1 ? transitionDuration[0] : transitionDuration[i]; String function = transitionTimingFunction.length == 1 ? transitionTimingFunction[0] : transitionTimingFunction[i]; diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 1599819123..7d64a94237 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1452,8 +1452,14 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // Diff style. CSSStyleDeclaration newStyle = CSSStyleDeclaration(); applyStyle(newStyle); - style.merge(newStyle); - style.flushPendingProperties(); + Map diffs = style.diff(newStyle); + if (diffs.isNotEmpty) { + // Update render style. + diffs.forEach((String propertyName, String? value) { + style.setProperty(propertyName, value); + }); + style.flushPendingProperties(); + } } } From 2b2f391f513ac6e4dfa7d88dda9f96676074054c Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 8 Aug 2022 10:55:19 +0800 Subject: [PATCH 224/498] fix: selector intergration test --- .../attribute-selector.ts.0dc1e8a21.png | Bin 0 -> 4108 bytes .../attribute-selector.ts.2abd507c1.png | Bin 0 -> 5553 bytes .../attribute-selector.ts.6e92b4361.png | Bin 0 -> 4119 bytes .../attribute-selector.ts.9342f0c31.png | Bin 0 -> 4041 bytes .../attribute-selector.ts.b2982c691.png | Bin 0 -> 4611 bytes .../attribute-selector.ts.d28ef93e1.png | Bin 0 -> 4629 bytes .../tag-selector.ts.c2987aa51.png | Bin 0 -> 6471 bytes .../css/css-selectors/attribute-selector.ts | 51 ++++++++++++++++++ .../specs/css/css-selectors/tag-selector.ts | 17 ++++++ webf/lib/src/css/element_rule_collector.dart | 2 +- webf/lib/src/css/rule_set.dart | 4 +- webf/lib/src/dom/element.dart | 2 +- 12 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0dc1e8a21.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.2abd507c1.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.6e92b4361.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.9342f0c31.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.b2982c691.png create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.d28ef93e1.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png create mode 100644 integration_tests/specs/css/css-selectors/attribute-selector.ts create mode 100644 integration_tests/specs/css/css-selectors/tag-selector.ts diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0dc1e8a21.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.0dc1e8a21.png new file mode 100644 index 0000000000000000000000000000000000000000..fb9fd614d42b42c13168f1fc69c27822ef8125c9 GIT binary patch literal 4108 zcmeHK{Z~?H9>;33sV&;dakJ9d_Uzc6Uf41o%O+pO z)U&4-H(#Q1CiEIwgI7vLPHDO`D%cC4n4+L!7+e7n;c~glp8Y502cKWQ=XsuAp7Z&9 zzMs$cDTawM-lV@IPo~yW}9}5p7G)OjnbZwTT zhegCJbFG|ZX9@`fVspfSuVb>R^dFeD8z(k(8^r-DN7O4z;*LSQHD7C_$O8^Vz=^WW9(nnNfvaD(7M@sFWPJpbDc;BV*O5Wv z^HjN}s5@RsS8Esde3JAUdV@&qe%gD30)WZ)AY5v8<{|_FH}Zj7*yp?V*Y^4L<2LDx z4Miy1sV6L-1RYm$QL5}nAck01B(5FQX^MY9#B`Q|21xf&U_}DpzA+j$OV+B4qt}75 z&;%Hp1I&{EsAtynAR?izjt7lg;eDr|)lW+JcDq6$^1Q5~Sq!afwTO@|dlCy!5@K0z zBLL#wexc1_2G|G<&Pb8Hx&aQ~ifXX;&>^QyYMJ-X$!8<_Q0bY;zItT}uS856E#|{= z<<`cgfLsm}=;-df$KPlA1|4wsX==VWHWdu;Y3vIdcBW5uvUapR<-Z#k=(NVbnQsn) zHoZ`oIdF=<&vqsk`;ZRW-u~PnJ=s(XulJLSsxAHu2@g??UeZ=?V1$TmN0&>Bkv8^^+UG%2A$an5+!Nz0o>4UO%G|5n$p0gH7Fw=x3^I<>fC$5VkZW`6M`n@vmGrq{!b#de$M2o=OLrT*Z{>APjj)X8G znHtUXh-YGP0DmOAp6lk5(VzT+xc6xk?zBSSTwS*PBRQ>ApFh3rWIvBde}ew>a=^4I zj9PH9L`jmdeDQ?TdEG&cmaYgMk7?c{fL zqXJsydfKim%+v$g$8UkPr!IsT26!aVZrc|-Ui=iL?TxkG-;3wSJSaCMA*TOCiKBx& zdhPyL;ERSmd0G z^TsmtwXD?u#r<*yn_E2a=rEe)$Qrs3UVdJ1guCjl7@O;4H9Mzvh=6)B_Ab}j%K7Sw z@%36Ff!p-`#7ybUYm15T_=AzT;74NzQLR!8%S z^KTx@dhcO_iCdCJ?@;U+t5vZAJ8eCaZIO|O)1_l=a%ji%2l(H_$wy^p%Yh59jDh@G z-o87yUG#9N`&}20s8CZ{nR@s#G?a~qAhWd5Yr`}{sja54EV9aT+^c;fa(Fls5HPF) z1&lu%{?P*rW@mCGl?pN6T9*3<#S48idDBf2j;erA`_VGt{rBK|n5^N(0B3f1SAP4ong zBoMm<<)p?Iv&8AxO8=-CSI||&%_>`@cz*7>PYmX?Vr0YF9!L_zP)GlkU-4VBp|ufD z@FW>7iygB(a6akHBaUzPD5Dl?nSBd|7^d9!juzLu46VYSkLf5is#0`}NM)P*-c*_z zT2r$cQvqhWwt22|ZO25o&pw+_KPjnpoD)l1h;}6tWju)wsuE^h;syrD>7jEcnSygA zoL1v~_E(%07rSoCQ`;_O40U^l+z43{x@!>ZpH|9sGHgR$Qc{ieU!jhR^t7>8PP;{c zA}KS@ZXd3l<3_C-!_~FA+_t<8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.2abd507c1.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.2abd507c1.png new file mode 100644 index 0000000000000000000000000000000000000000..86df41c688577365c92c79477792469144db692f GIT binary patch literal 5553 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 R^xG8{awzN|ZU5;X{{x?ang{>@ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.6e92b4361.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.6e92b4361.png new file mode 100644 index 0000000000000000000000000000000000000000..8ec30b41efdf044b59830d1bf19f0311d9fd93ea GIT binary patch literal 4119 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 { + it('001', async () => { + const style = ; + const div =
001- Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('002', async () => { + const style = ; + const div =
002 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('003', async () => { + const style = ; + const div1 =
Filler Text
; + const div2 =
003 Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + await snapshot(); + }); + it('004', async () => { + const style = ; + const div =
004 Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + // error + it('005', async () => { + const style = ; + const div =
005 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + // error + it('006', async () => { + const style = ; + const div =
006 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + +}); diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts new file mode 100644 index 0000000000..9b8ce89d0d --- /dev/null +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -0,0 +1,17 @@ +describe('css tag selector', () => { + fit('001', async () => { + const style = ; + const p1 =

This sentence must be green.

; + const p2 =

This sentence must be green.

; + const p3 =

This sentence must be green.

; + const p4 =

This sentence must be green.

; + const p5 =

This sentence must be green.

; + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + await snapshot(); + }); +}); diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index e97b3cba7b..47ea96d6e9 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -23,7 +23,7 @@ class ElementRuleCollector { // attribute selector for (String attribute in element.attributes.keys) { - matchedRules.addAll(_collectMatchingRulesForList(ruleSet.attributeRules[attribute], element)); + matchedRules.addAll(_collectMatchingRulesForList(ruleSet.attributeRules[attribute.toUpperCase()], element)); } // tag diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index cb951997c1..4b99384047 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -84,12 +84,12 @@ class RuleSet { } if (attributeName != null && attributeName.isNotEmpty == true) { - insertRule(attributeName, rule, attributeRules); + insertRule(attributeName.toUpperCase(), rule, attributeRules); return; } if (tagName != null && tagName.isNotEmpty == true) { - insertRule(tagName, rule, tagRules); + insertRule(tagName.toUpperCase(), rule, tagRules); return; } universalRules.add(rule); diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 7d64a94237..4398fcc71e 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -971,12 +971,12 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element @mustCallSuper void setAttribute(String qualifiedName, String value) { + internalSetAttribute(qualifiedName, value); if (_STYLE_PROPERTY == qualifiedName) { // @TODO: Parse inline style css text. } else if (_CLASS_NAME == qualifiedName) { className = value; } - internalSetAttribute(qualifiedName, value); } void internalSetAttribute(String qualifiedName, String value) { From 95ba26d0b3ba5743995cbfa402d4b8e4e25a0be1 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Tue, 9 Aug 2022 11:11:51 +0800 Subject: [PATCH 225/498] test: css selector test --- .../attribute-selector.ts.b2982c691.png | Bin 4611 -> 4147 bytes .../child-selectors.ts.6222b33c1.png | Bin 0 -> 4826 bytes .../child-selectors.ts.8daacc0a1.png | Bin 0 -> 4672 bytes .../child-selectors.ts.a21b79841.png | Bin 0 -> 4685 bytes .../class-selector.ts.01eba5b41.png | Bin 0 -> 4119 bytes .../class-selector.ts.094758831.png | Bin 0 -> 6435 bytes .../class-selector.ts.4a4efefc1.png | Bin 0 -> 4481 bytes .../class-selector.ts.4adca9a21.png | Bin 0 -> 5689 bytes .../class-selector.ts.4adca9a22.png | Bin 0 -> 6551 bytes .../class-selector.ts.4ddf9d331.png | Bin 0 -> 6185 bytes .../class-selector.ts.6e63ddeb1.png | Bin 0 -> 4108 bytes .../class-selector.ts.70ea920c1.png | Bin 0 -> 4167 bytes .../class-selector.ts.74cf8d9d1.png | Bin 0 -> 8541 bytes .../class-selector.ts.82cf20d01.png | Bin 0 -> 5614 bytes .../class-selector.ts.82cf20d02.png | Bin 0 -> 6585 bytes .../class-selector.ts.8f160df51.png | Bin 0 -> 4394 bytes .../class-selector.ts.8fc91ac51.png | Bin 0 -> 8643 bytes .../class-selector.ts.903a90f71.png | Bin 0 -> 7991 bytes .../class-selector.ts.903a90f72.png | Bin 0 -> 10426 bytes .../class-selector.ts.903a90f73.png | Bin 0 -> 6663 bytes .../class-selector.ts.a30301261.png | Bin 0 -> 7593 bytes .../class-selector.ts.db8dd49a1.png | Bin 0 -> 7233 bytes .../class-selector.ts.fb97fa6a1.png | Bin 0 -> 8620 bytes .../css-selectors/combinator.ts.06fd22981.png | Bin 0 -> 4522 bytes .../css-selectors/combinator.ts.0e8424a11.png | Bin 0 -> 4695 bytes .../css-selectors/combinator.ts.6d2121b91.png | Bin 0 -> 4759 bytes .../descendent-selector.ts.1b50a0d81.png | Bin 0 -> 4813 bytes .../descendent-selector.ts.1ffa58061.png | Bin 0 -> 4395 bytes .../descendent-selector.ts.21091ed21.png | Bin 0 -> 4384 bytes .../descendent-selector.ts.52ec30781.png | Bin 0 -> 4394 bytes .../descendent-selector.ts.5911c3671.png | Bin 0 -> 4834 bytes .../descendent-selector.ts.c48dfe211.png | Bin 0 -> 4913 bytes .../descendent-selector.ts.c536672a1.png | Bin 0 -> 4393 bytes .../descendent-selector.ts.c56704881.png | Bin 0 -> 4611 bytes .../descendent-selector.ts.c6d8bc4d1.png | Bin 0 -> 4866 bytes .../descendent-selector.ts.d0f8fff41.png | Bin 0 -> 4785 bytes .../css/css-selectors/attribute-selector.ts | 8 +- .../css/css-selectors/child-selectors.ts | 23 +++++ .../specs/css/css-selectors/class-selector.ts | 86 ++++++++++++++++++ .../specs/css/css-selectors/combinator.ts | 35 +++++++ .../css/css-selectors/descendent-selector.ts | 84 +++++++++++++++++ .../specs/css/css-selectors/tag-selector.ts | 2 +- webf/lib/src/css/element_rule_collector.dart | 4 +- webf/lib/src/css/rule_set.dart | 4 - 44 files changed, 234 insertions(+), 12 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.6222b33c1.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.8daacc0a1.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.a21b79841.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.01eba5b41.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.094758831.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.4a4efefc1.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a21.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a22.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.4ddf9d331.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.6e63ddeb1.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.70ea920c1.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.74cf8d9d1.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d01.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d02.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.8f160df51.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.8fc91ac51.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f71.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f72.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f73.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.a30301261.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.db8dd49a1.png create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.fb97fa6a1.png create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1b50a0d81.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1ffa58061.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.21091ed21.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.52ec30781.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.5911c3671.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c48dfe211.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c536672a1.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c56704881.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c6d8bc4d1.png create mode 100644 integration_tests/snapshots/css/css-selectors/descendent-selector.ts.d0f8fff41.png create mode 100644 integration_tests/specs/css/css-selectors/child-selectors.ts create mode 100644 integration_tests/specs/css/css-selectors/combinator.ts create mode 100644 integration_tests/specs/css/css-selectors/descendent-selector.ts diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.b2982c691.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.b2982c691.png index 1fbc55004fb1b0ac80443d5c11917cee44902f95..11a7add1c83bedee3943ed25cc5bd052f16e831e 100644 GIT binary patch delta 1868 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a21b79841.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a21b79841.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe365b66aeae8249ab03ecd93fad08b84b5cd0b GIT binary patch literal 4685 zcmeHL`Bzid77o<{4oKBHFa$aZh>(ghiAh8d*5^0 z|D3nMR^zP@2*kkW+mithh|XmQ(^8zw2wwzLvL)jdlijoTxrqveM``n=h>OAL{S?^{f5JSZh1;mceDUAp2<=Yhq-^ zt&Y1*w_F`I?Z&mA8Dw>-8ICmp4}-Qsl64h|&5)Y2l{zQ39QClw($)X^1?1Pw#-HVR zZ2I@E%h&urGWf*zpPD>3C*5KS?=P^iejGDoJKmNA^UB^~v-`jq%RBpEwlNh1+;ow& zTgy1UwlW+Zk;*{?=NBJ|!Hvl$Go<-Z{RP-DoU}_(&=2d@yt09;!j|3*$`swM1^k&$+ZTG|csE3}m?(F5VGG&CxX+t^^7D#&$|FXm)vTXc4ddhYBi z%r$W|8;#R7Gz%*!DPh)}uky8`#PjGh^gwIExav~ora_HzxnC|BC#}jXD)3i;H|NY* z`cAK|ZCUwgk2P~i*l(!D4p*PG-WQ&7-infUzUp*7)CHH4mig?~PJ&%`R`kmUr^4ON zn%;ir!A}=A2;2CX;+6zXEh1{+yKda~ulIkGHNWk?1>E+|UYpsL!NUR~HFYwLiVmd& z%w!k;=dK!TQN~5Vj>NDIb>Jiip-`x>48yXTE>Ddh!najcYlABFfd97b@9wRWbv>H4 zi#&$PUE2DM_S4FK_;X9SmNzjGHrj;Z3zh2mj;5nWkDA_BLTjq3s_HK`Kfr4idxg}B z%!8$lNwy{q@s?ODRyc5E=umEMZWQ}BA7Mdg;*8z&8e?50UYf0I!j0hu?YViSNsRXT zh%Wk}uqTi3GEQ)mVxC87XlN+A`u10CY`1c%w3ejFkk46B%W=rTW>*5+i7lhGwH0zc&rt6+;bvm1<=>QW|_ z8=l%{@7Z0q{S(KDAG|1eY@xU~hU{EVI8%O%hBmLe&@gKk{nx#(5s~wBabqok2UZ{i z1&2ZcmI=WPNQ9AlCZ4m3MBP7dwl;pUvmI@2j$iQPyfQix9YiLsD{Cccxx}Sj+Mn5x zDs6P8o-K8NE@8bp810zpQPXh%p)7*7PVlwLr;^iamu9FHa`X;L9&=5>C!XYg=|tA} zs`snkWScW-+=y}UkpoC1($~w&O9?*6Fje>LL*-?gXFfrzD?M-Q$RK;y!bTOMD$=Ot z)8$u@q8`Lcg?L$UKQlprHEcUCF}iW@*-=Jj*U|S z)AY5!TcZ5BSxK^4PLzet)co0VeW4QEWKkBkuQJ~AZL_-;?@8tgy6urvWvw> zV*@zy&lE)gQBBaX%m{-vZ;htM^|7wITMn^40<`=>WchDhe_jNWDb z8~%45dMZG_Mml^s%a*bFR^U|1;11`O>8f&x^iV#7psuwIbAHhn-obR&Y6J_q+VoAw zUVv;r=*Y)oi14aVKy02_bxmEJHK2RCv*h<-=Bwt&{-VQ?%!gDm@-)dT{B*7{Vq-jT zrs#*V82r_rIjX6FVocw1ZD?qyUtnNhnlL|DbqV?l+C%Rc5AZK!&!ZuCZD&~e3}tP} zuZw{@G};D$E};%XKAN- zbv{B5F!Qaody;J_Qf?b&npHAwfx<8L*c%4(Km=UZzmRzV;XC#q&6BuO6mdB=_Tf)S zHbBm8G8FR?Jicd7a8PcsV^T!g45g3>Vu_H20e3?f__Sh&1Y}58y6ojym6J#;8!WM} z)2s?eh;dYggjN!Kp|!OY*Unz>&|5G6Yt2n1sw#>6`~tD!7B93{9PJY~T0bMd{tKN> zXC(tsn!yd^Sp@=`P?kX05iB?LT%y7;`Ry~NJvS^cHa@<7gv(7$OI&>06F$Y^4c=3+ z-$c}~3&rYp_u=SyL6)AG$TU#=XHkG}6z~Lri33ZVahlb|e9Vw*8yj3yzKhdd&1S+c z$%5xt^+c9ARjcUMVqK!Oa1yobif@`ySlGeit7PJSASM@q2aijr^%QZPY)v^`VQ-kQ z;eqlo5`fr{)^vA6+n}>GvSEUimZ-gFPSi99@6+)l?iSt1F>!1RR>T(BMfo}_CNLSM z8nvmoaP^Dm2v zF#AO5WLLSn&JGiY`W&cB>-abdC|(OeEoXzS0o3{nH@6!FoX**QeuKgxG3A`5U|so- z&;D1lY(iU8$UTRzyz2`_3fLxR2UQ;Gj0%T(@nVB5Edhhs$GNC@I{>fFi36W?9`FIZGr+#Q5|6KfMWj=&MnOnB*0D1R5h>3=qM#zSf)5TU&w!Xly?;6qU3-an z{9H$CQt~$U^ccy*BO4A}anA1g*~03)a#4dEXd!&j@oC@D6M47DH->@Rxi>GQG!s+| zhoM|p(k0O3n&vC9YNpIuAq$^yh)s<~btBztYjxI_sCV0KMMe}V;hSGqMX&#A|9!@; zf%>Qrr~Ri`C?xMb(ue6c74RQuS%eC$)gu!d{uV^9$CUl9g4Ckvoq*&zz?kv@ zjC7LRGt?Yny;CM0Dy&o)mu#Piv@_I1s@%Lnc%PPWMB)JfCa4U&aJem`vh}-~BILVI-v*ovf zf#t+{`lFJ3c@s_A2x{&DYL3#aDma&8tDH{^)3xGbxUUkx6w9bW3gQ~llPvP~q$LB* zL_$B5={{%6KzOP`4gk24#B`9s9pumrrGFf|9U;ZIO1a0Jb{QbXObn_z^Ji{}7Jgxy zo;@2|oPM^cr9hC6ObLy1ig6>FmPa`Uoioiu z{W~C&OUv|(k)z?`#joQ4V`;4XfE&hakVaub(i8IhGV zw!`H}3bp5+b_eDGfBGs!gugGK?+YA^@x~F@ksTz5)7rwu6(BcEX=7y+`8G!O-?R~Lb5V20`FYgfhGOTl>>UE2|J+x2Va2@URaWgTg-Jsz*-&7$>M!M>^LsR7V z$VbgM)&<8RL=(1%782r1($u=AZuBv_nB60yI2N7Xq;@5xmuA(sxMpJ+Z^NdqK-S*o z27!GB?hXqabh{TSDpOdg0m=wlK9$ON}L6GEDY8B9h9%uss zqa1bXf`jD*-@<2R;$fL9chn+F8+!FiToYnwgTPmHObBZ*}e|H&{ zoF?X}BHnPD^6865iL!|qFy5`(8ge( z@+BP&6g*+(AMh_lj`ifSA!kTU`H_+V|VqEf(MPKB0?3 z$&R~@>%`Vs4Fp8-Dr8YpWah#(4liwl27b^}f-qP{4);(7ZJgtI9>DzLyCCn=JuWA7 zmpv*#kInGgY6G;jO~j0mt`Md!6;PxFF_(V*XF1n^*j7!J zl&cU+lo2i3TK&uJW{_7?ll513A=0VfF&37TBVzopuFN*6F>&)!aU~o2E^ijLvHZM6 z>QIs9@8#{Q_KF-34GlV|!eWDCou1b3s=W(|QWB0?l1+K&+3Gu@Ke1^xv&&U8=n{vS z2RIvJxOD*;W7(rPY-q4NW=||esFqGsl+E^nT-~s92zg%XsL!aR{I03`=(B#D#Bcn1`0I+Ny&%F-q^ zEk`Z95SEZ2cp6bonVOlGL?9tEQ6LmV0pZ=)dCvRJfAIbg&$HQIzN}}l)@Og#XRW=t ze!|bk^2gObnwXea`u^&D(!|8{f{Dq3^dHQD9qMXP6>u_*Kk4&}NnNMi81Uh{_+Na3 zegIynKb*}pFwO?7rC7?x-{@7P85C+~=EQI5A1p)PrI-?-KIHY$?^1l5z?TBc zJG=hUEsK2E5WGBac_7t&gZNlrOxXqJo@G(HdZHt16ot0F$4~k!ispph$v%8&WViwf)x5WkchWno%g5S$MKkA3kz6m`fRlH(ggbmjeRyAHo2eO4{7Vo#s8+dgq)rq5QG)qU_Vw@1E@ zKYngBV(%uSQKjeh4fZJ`SL&_eC$4alm?aem*y;=!{=(wMs9!G$XpIx&Kff*X*TqG+ zDHthnWBJs$o>y6XSx)?UPQ7KjNFO2-j*P@=J|4$(4`hq(kIJr(fD4ngs{1m;TFO1V zGaFghb)tyo)@tE|7$|qbC-zo%KW}s`K!3K+VB;_&*#Xwj?q{sWPsYESaeuT&uAho5 z9ZXxlUW6}9b;(It~IUUe_ZX*zE8Pr9g+KvW(thjX3T>K?X z(eduxtgWZ2=xyO{Z0igwNK+42aQT%bb-F0VgFmg>glKN(@$#qq>>=1fIxHLphG_B~ zS~G}KXP)_?bNj|>sfuv0Sl&6y^y`26!O0?W_IQcmnQKxrwR>mnmh*7;_`=bq;oIPc z3UF@4I3jND)&M*{GTws+W=y1{R zG4O$r_A?+5!tk*_O63+@*xB&K#is3QDRJAq%3ZBju3F_}mXL;(Pb;~a@ID`&lD29` z!m)S3{cS8_9QsmY;?XfSOrf&IOy13BrpcJN2$Dl$Du7WILMKE_OpaUEJB?Sl`$0Y` zR|W0ZY!6pn*2!4H)-Rf2#hVeUJA}Eiv?W;#)5Hqo#}Iq`xm4*pO6zo=RN>#H4@{M- zh1tjgufRT2@U`6>w#uWWhX4*KO-Za7dcNHrCw-8D77s684h^t=>T++5O&;qBmp9mBM7lA7ceH=2G>He9nXnRNbBV6I(L6gf zI|c3e<&{|g6lzT`!G`Np7l|;&(7)F?iql^#Okl_WK)w0tjFYc%akH+4+!_mH>l4Nq zMfQf~%u*K(Hph1NMn8{_&-lUzAk2H$!q!dGbKbgyVSjP=22$IH!56ODhwm{jpC3I^ zh>ey6Vk9rB+K3d5{JFM~8706^iT)X|i8F=3virwNJPC0_Sbd4>*$+2T5)|c%FMnl- zZ=A{CSaac^L^Da`hx98T>Xd8K+yt+@v0{&XnC{maLH%BW`&fqJcX9L5n4-QmD;wQ5 z0__XZHLmogD_YX@w*4}G^7#_7?FyrZw*mP_5R|FH4*9g|$M?Or#^Z(;Ml?~r>?KWa88a(kLVB*U;`UnzYYg)X5r^q~A_ z#ap_E-rAMR?4S%v>^*#74Fd}u0^|xaDwj>PS4G6}!sv&*IamV@?m63;4+6P9BzPN* z-UZ3^>-hDkqd9tRcSXO7;yL|lM%`t=58*!5D%#x$wpBTMHY{-^j_*Na}`yK<0)5HQGt&=NM)u`3`P#*0;3C&nuvRXXHQ%Bx)0n%Gc8*ZU6Glw6{mzE%s~+j{Z!8&@ip_vz8& zy6aOUPl!=*ogtqlFJ#l;#AC7kT|5=7e^;#&e;S}15FgkA5;`!(jvg9XB}e0kbQjRX z^_FW6D?7G!RJi+i*Pu>;ErOi~%1J~~?WVnHJ*pS|_Bpf**+v@>sLDPy8jw6d1dVko zt}l1V2KzTsl|Ls<5Tfu+f~y&8?83uo1tL2T^)WV3N0^o4n{>~F$DHO2(^?cpEOIBj zQy94{8qBZD#b|TK6N*MW>tM}rVVJ?sTd4;)bz8NVQPEJ*!>dKrGMjWcn~O?T)eO{wa^afhO0(mbTWt&|W4>o>T#JKW=F>me`}kD-~X8dfqA89r(Sy zksJ^RgqgbN#_g!$M=*}Xgvacp@8q_9%JqVyVaH*lQHk_@wASsRW0C;9{IGwf4FyY8 z>H(lowY(!p=TVGQ(YCcJb;#xlzxE>`N5DKp|C5S1p+f3JQU8nd8FT%*;X|anJfB z&wK*V!tpCcGc%zMbw-UZAxIvwD+#2|UP5!O?cR+mYa*69xz$c6-0E4_2zMCbp@o55qvCm`lH%GaEz(r`b zTpjmk?~5kKh}eZPCzwptOW~ervC5te=^|+nY=~d9`<9KQ(jlpac1gI*@*p^J@Q#cHZ%Ab;lxLR zK+U+R5GsJ6`{f(mPJ{Zg+DgeeYy5 zU!iptvMgpP(*tn8WU+@7yK#8!!Y;$wjXC$12KwJ!?N%IjaBPkjPFHKN6JuDWAUGsM zr_4%8ofOYEHJ~xx5g5kl#5IUCU3CSWGH2*-0~*^tqXT1+Wo8;ebqDl8dmD=)T!ZwRj45WQ^BXudYtd2nD;9;?`Id?|v;CBm3<$aKhn?!Y>vJ0Lg4FA7khG@gTcyd8_*Q5L=5 zF6~2i_h8>@prNQW;Ff{8wl?GVqmlGSwe;OLtnUJ51I(L!3lsMm(#fWnlP$i~N0%t*P$odEH}dV|+(30V@F+|DqLR?)}=BEM;q8QGkJ zaU^9Q*mYHuy9=$<9B+%W?-%3@AN_jS3VTHy6Wg6aiteih@{drX z|74`m|J|mCCxEkiFl)fNF5br;X{Jjhy#i5(fB>n#}qIIEzA6mGe>GKM=SX zNZsy=`LC}&T7L!gKM{wioUcOBWw&Z@g)hi%Cwzlhrnl-P^PoG|;N(Cino_8INewPs zb?w;S;*zysYmK`{o=Er^Q>ToG-8tRM{P1;oh$`WK%@KIVV+4YENDl$FSBxm%xDJ&A z{*%uG{z)&LemvI;&Wh?SnvXig{P=?!+(FEDtJiMH5BXmj01*QiE)Hg0IZ`iynO6(; zZwONX8Ij%gpA2R(folk4@|hUS^7wjDfe8LjTvnqE#!LN=7k?wjZ(Z|TS4m`fgoN-*Ijgg_#Mdo$@;{UhcF&M)WebN1S2 z?QfrN@3l|q=Lb@ky}jmb6biL$|Gqt6piuMjQKhV87_YbZiccM>h+)Gm)esTCn z#;WyS?vd3l{iLiY+Iaqc_mL|bYkF$eZMwKMI`C@e(wdLX5q>B?x9%RiueZ0@*iE_=G^8qS`aM<*KJN1YVVYNJq(Ti#l;?ms)j z&RkRObX!SNUSTHe6~;13mq3FrC4uqgo(j$DJAf`&GGdQ2#V8I@OmSYL5|RTXxL`yY z)={-;lmY_0U?9(Tl?ed^8Q@UpN^*tFBwc{0cIm5oDoP9-fHymQRoIhXd5l0urepcl zVhNB0PhQn6|2Ug)Q>p?SG1R*b^NTH8zo0)^g15r1Ki^pMIH)MC+>QjS07#Xx$D8ts zlTecvS4>iQ2d7j&+W|bi09IA%9yx~5Qnu|BQYD1zhq|Q&vcKCg4x? z;idc%e#cLl8CXxtq!DgL0~VhgP|^bS5KNcRBzc{KOcEy*9e3DELKX~sUXjJkWA<5* ze)L0H9!E=-+)Z-U#p?<2k?$GSD(sEJfPVXJM;ZxW4@Za(B>uNxdZN^T{1%n<0uyqf zDjRTV6pqo`i(0wU?Ud=?BCPq&?n+|6AlRP;dfp zt3NCxaUn3_%`7O#YitV+>YhI|NKB3O1)bMcd6QkWY@_-PpLzDIDV%*hUPiMbJ~`j4 z0-O?Z7peYMLBQv0Kf!85cT@Rl#~=+P2l^F+NZm{y5O7Ng%5Nsfg;tBj`{@N>v#PcL zne}NuWDM9m>(hYv6SYZ0=KxYr)tTeA_|kH=YKo;mBiUEfmDEmGgK-GN#H{f7I~hj+K8u{r|s$7F_#+j z988B)t!TG9*b-$zE?x4y5#cBm35Agk2lp;ty{-mj12#?vEgawsgji)`Q2;YST?1jnt7mv+gjcPi>mv5ZqO(U+(;a@YZ#EN_+=e&IR8)5 zI(8@dj@EJ|2I}#6TGM&6Ei1vOu`afhnx8-9WqVy3DK@IQSFRE6HeB0=$FRG4X$x|D z->fe`eG;CcKvOqprr`w5UB|z0W2ub=AM00Q5u?t;a1A!W7@o)G5dn%^EY)H|>MKNB z*?KiY1`RyM936PVe)B7g$+R2L!K0~Y^OeL6v2k&{tD|j^ZjFGb-cZh=S~Pz8*kJ2f zc)U4E7&czVVhe9pZoW733Im$#zm(zmFgXiMS&b?A8r(7Q%od8NoOu6uQLqWoRhMtS zMM0N*TNE6?ObY5OT!}^wPptmnYub!oCq_IKtJIclt=c=lYxl(?jYYHJHR&TPHvv=J z*6S6FqQ_HwhdIaAJ#RV1vv^Gmm3lsVL{CZkfXdo^p|@$Y#DsW-i~PY?7z$^so&p9w zD)PIKs+t-P|C&DqVS(DQ915pB=5(vjfxCI+Q6t42Vq@*?4aXK%B`adeF2dobSEPJS z_jVq{1*4C3UCG&NZQ9!9>2mMJ_X&}>?Yg!RO|jR8B)jsv1Sj?{*#6aqV?$L~^@5SJ z5rl8UH?0nSh6u2f((KpWay{F54=VgVy~JV5ymf4Z` z!jKwKr;QPhnL*c$)y~@6Cf|$`Gk;T1>p@R#hD5t&837Yjm<04Q>^tnX1W-nrvM((U!3-W_o&_x8GXd! z=}D&=uVK~Z*Au7FOC=F#+CSAJ%Mu#0V76B~bY8ya!8bXa6&P(}8O~WJ^6^lyq7Y-V z6ouoP%R}51OA7;l%5^dJ7rv%3J5`-_Ga z#+`0uNdLFXYL^^bXu!H3HEqcgyrjEXsm7Ag-SC!0%NuB7)7ljp<)kl^4sLx)WSZ#| zeK;|0IDBvZPJ%? zA`_B9s@uZAk7;*y@O&?c3J#Os5h4#gLzbMg;ky5K>%(QRcSArq&!9R&On7?Bbt()x zI576|GXLwzXMdq2et8QcT;Bg|pSqIO^gR%+Et=R8oK6qyN%UQK+Vvs#KDKp~^RwSY zA$J`tS;G8yd1O9GyOwb{s~L(}Rwn23BObCmZ!r5ru_=Qtp#aApGA4yq?~oerJCrMD zxAf8}9gm9PKrP3s4I;kG?pO~kEVrfi)I!_8J;Bop7q5STs`;bBznA~r=ku4n`;VLR zb}fn9`L|PZhjSpzy@WYB%;ki+#29vFt~!L8;5iWfUqJ|l)hZ63IXZrb8~R2;?f>+^ K9?7ob-~SJCVnmey literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a21.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a21.png new file mode 100644 index 0000000000000000000000000000000000000000..55aec11b1a8f6e3946db96942d01bf66063a8c72 GIT binary patch literal 5689 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 W>e>CD`pUn42lgHLyy>&!-~JcM-W!hq literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a22.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.4adca9a22.png new file mode 100644 index 0000000000000000000000000000000000000000..fed37b16eca73bd46fe59ce74e70fedcf9f2d5b6 GIT binary patch literal 6551 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 N9X@f0e(=JLe*?g)OG^L% literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.6e63ddeb1.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.6e63ddeb1.png new file mode 100644 index 0000000000000000000000000000000000000000..fb9fd614d42b42c13168f1fc69c27822ef8125c9 GIT binary patch literal 4108 zcmeHK{Z~?H9>;33sV&;dakJ9d_Uzc6Uf41o%O+pO z)U&4-H(#Q1CiEIwgI7vLPHDO`D%cC4n4+L!7+e7n;c~glp8Y502cKWQ=XsuAp7Z&9 zzMs$cDTawM-lV@IPo~yW}9}5p7G)OjnbZwTT zhegCJbFG|ZX9@`fVspfSuVb>R^dFeD8z(k(8^r-DN7O4z;*LSQHD7C_$O8^Vz=^WW9(nnNfvaD(7M@sFWPJpbDc;BV*O5Wv z^HjN}s5@RsS8Esde3JAUdV@&qe%gD30)WZ)AY5v8<{|_FH}Zj7*yp?V*Y^4L<2LDx z4Miy1sV6L-1RYm$QL5}nAck01B(5FQX^MY9#B`Q|21xf&U_}DpzA+j$OV+B4qt}75 z&;%Hp1I&{EsAtynAR?izjt7lg;eDr|)lW+JcDq6$^1Q5~Sq!afwTO@|dlCy!5@K0z zBLL#wexc1_2G|G<&Pb8Hx&aQ~ifXX;&>^QyYMJ-X$!8<_Q0bY;zItT}uS856E#|{= z<<`cgfLsm}=;-df$KPlA1|4wsX==VWHWdu;Y3vIdcBW5uvUapR<-Z#k=(NVbnQsn) zHoZ`oIdF=<&vqsk`;ZRW-u~PnJ=s(XulJLSsxAHu2@g??UeZ=?V1$TmN0&>Bkv8^^+UG%2A$an5+!Nz0o>4UO%G|5n$p0gH7Fw=x3^I<>fC$5VkZW`6M`n@vmGrq{!b#de$M2o=OLrT*Z{>APjj)X8G znHtUXh-YGP0DmOAp6lk5(VzT+xc6xk?zBSSTwS*PBRQ>ApFh3rWIvBde}ew>a=^4I zj9PH9L`jmdeDQ?TdEG&cmaYgMk7?c{fL zqXJsydfKim%+v$g$8UkPr!IsT26!aVZrc|-Ui=iL?TxkG-;3wSJSaCMA*TOCiKBx& zdhPyL;ERSmd0G z^TsmtwXD?u#r<*yn_E2a=rEe)$Qrs3UVdJ1guCjl7@O;4H9Mzvh=6)B_Ab}j%K7Sw z@%36Ff!p-`#7ybUYm15T_=AzT;74NzQLR!8%S z^KTx@dhcO_iCdCJ?@;U+t5vZAJ8eCaZIO|O)1_l=a%ji%2l(H_$wy^p%Yh59jDh@G z-o87yUG#9N`&}20s8CZ{nR@s#G?a~qAhWd5Yr`}{sja54EV9aT+^c;fa(Fls5HPF) z1&lu%{?P*rW@mCGl?pN6T9*3<#S48idDBf2j;erA`_VGt{rBK|n5^N(0B3f1SAP4ong zBoMm<<)p?Iv&8AxO8=-CSI||&%_>`@cz*7>PYmX?Vr0YF9!L_zP)GlkU-4VBp|ufD z@FW>7iygB(a6akHBaUzPD5Dl?nSBd|7^d9!juzLu46VYSkLf5is#0`}NM)P*-c*_z zT2r$cQvqhWwt22|ZO25o&pw+_KPjnpoD)l1h;}6tWju)wsuE^h;syrD>7jEcnSygA zoL1v~_E(%07rSoCQ`;_O40U^l+z43{x@!>ZpH|9sGHgR$Qc{ieU!jhR^t7>8PP;{c zA}KS@ZXd3l<3_C-!_~FA+_t<8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.70ea920c1.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.70ea920c1.png new file mode 100644 index 0000000000000000000000000000000000000000..3277b2b99d85c451550fba80ce3c2854a033f337 GIT binary patch literal 4167 zcmeHL>s!)!7AKqTW3|mr?&EcywQ}7uXVNfr)NbZD1(f4Y8b>5o3pJ6an!>j!-?%qT-aK-5{pef5M}DR=hhob-70f%X2P( zKx<}__wGvlxxp}$*Aie35Drt`Y=3%!&hg!TFPF7$*Typ&UjO}W*Gf)!S6aUJh^#-I zaWFSMqbN~x;j4lCCfSmVON_OCK3CE&9b`y3ii218>dzpq(UK2HH*A&okg7t2^<#BI z?a1re)31giB2dSsh%?dJIn;Qe9E*U1x@xE0@gR^4a%a)*6_GnzV6ZzHlE7`!NdeP1 zz@JSasgd(3<_{Ha=cw1fZjSTQhGCCS;6zA8;cx>VCna~Lvh~bn0$5K5)J{O?o2~ND4x*B~ZWkOTr*lnN{`Pv}VVhd|f~7r^1I08JBjujAk(1 zgwrf4uI@oSZM-K>U#^V>1hzz_@n@&YNLQERT_RLZi{dDGMic%Bt>-cIHCrZlwGkam zn)}wb&-Ay69Ov)Lf=;O#qAT>NSQP??U3~6*603N$Qnj@5%9U~mAV(BKewzQ(jIiEh zol=dwHrPes+_5NCzoxqu#UWx=NT@8Qt*lxY=tps7;L&nOJvd(J6>nCo6=LOV7!{{M zu#Z8c*I^u3d%|3n;Nz(?@ZN^O_-D+FL8P%3MN{Kh-pN}s4r4!@ z22d0PJ70QE6S_LKRcF^O3qm!#uj#D=%r6!LPGf=?1Iv!YWV&xbV;Q}8%i#R;+6W;k zVLv%7EIsnSU)Yo?2V6EGkQ4xzD>12P0a5BBIEeV{pzlN57YT z32)z_!DC35gM)!Rdw?%O(gFx4{OVfrcWM4TJPf|qW*IG}92ZRq1iCvFON9->5e$hQ zWB*8tv$iS<3iVwOUT9D^w1oipbm|1elA4jH7;D3mjdI~EiWDm-%0?l3g@Y%2-qX3DO{Rm(>2Y-u?Of@_7 zhHqayq;d?fUl;wlx9;jT%P zfp%%hDuvvlETq>xy1Hs#X6b!*C||2bBCQwSw%v+i>DcmgLUkOcH`e|U99d-Qb?vmy zO~C5of8AC?gizF-MHwfPrIUk(%HfAp1^34AFbzZM(4F;3ztb0skyI@npnXn^EPp;) zRk6Qhpb3H(!I}PiXgF_k+N_puf=a_lgWbpA%!1J;!NGnB*Ag!~;5oDY<+caQcu_U@ z>6d6**5G5yz)|vhiVe0S%hb&k@>nJ@-63VAKS$yjx(*G)@h3ToD0q>EZYk05U9&SH zi6=y`D!Uro7=!(~3ozsy0sL@=+HLoBYe+4Q=~wHiqHtEs$OTI%p2e4uHas~7^imRb zu&kTfLdz(+1h@UA{d!1s{6p(Z)a@`fEBU(OZ&pu`_MvC>T;vftxXDw+1oOFVWi?y! zB$)&%G=O-Qgx8R-Ca?4}FzS8`@dr*KgLu&Ey!%ig?hybp6|qV&UQa3h!jLG|R0 zzIEG@NR64Ns4%2Eu$7x>~KWb$!un^zsj!;@M5h zti-pWe$ObVumsyKrMSbmTC923DQnHcEw8$AN~OeDk*1yqG(WmnVTw}ck|b(91z$KR zTvC8_xGy);i0n*WJ25{~vx3JwjvNbtq+8vT!<^Q}0}YW6D|;=S9Lx&7ySmLJc4gyD zy(MG9u8S4=FodF;Z5_xqb#sI#57c+CzTB)xmLYKHC_YGe@8!*et)y)7ywF6gzjbTK62(igCSrd literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.74cf8d9d1.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.74cf8d9d1.png new file mode 100644 index 0000000000000000000000000000000000000000..d63652918b2d891a5553f2e3893762b312ca9477 GIT binary patch literal 8541 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 DhP`pC literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d01.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d01.png new file mode 100644 index 0000000000000000000000000000000000000000..9053853f8a7968004331b4bf03ed889fd52d571a GIT binary patch literal 5614 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d02.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.82cf20d02.png new file mode 100644 index 0000000000000000000000000000000000000000..d976a8ef602aace3965442ef8f10af0951771534 GIT binary patch literal 6585 zcmeHM`Bzit+C}ZB?M1M`7Lf5aTt&*%ROTVHB|?P)MT96pqNFq^28_rILF*J0f&-I+ zL_r91ib^01Nh?E?2nZNJ0zncPLxK<>KuEan=~~|p-{0`bTCC)7&N|PupS}0HNdMK> zQ{!KH|58&^(>U@o>ZqF9wg@$~?Uz2^3I0;2C!~RoZ81kZe^RUMF_;AxpT+!ij+)1t3LiFXH5~&0jC`Kv-$-R!p1H8<({ovG`I=Vyc5pKK2?d%_R{AUHLzI4K>y_Lg!%R6i6dzVOhez^)^v*H!19sUO8{ zYrWSwGT*tQiq6vWb6t6BH^jf=`0Dp$j%wUxT=nsWTl+0*R^x!Y*^;kNExA=^AAL|z zz#Ke^~2$HH$D5k8(Iew1R5We)`)qZ%Fn9gQ($C?yp=-bzFV?8JaC*Z|z2M2RO z#CW_$46X6~AL?ENg5>`M($mxHI)Q&!|G#nAkqu-rc2lTB++5^%uCsSJ4OXAno2ppN zbY5tYiPmfGw2v}lL=!iRDT^Wwr!ufgSd=VXZ@lr`JKyHa(Par=E;8CVzvR@?zNk_x za0(q`!X~?Nw%D9|U6|vI!^g)5 z+-T2ZEtlrICdcudEG;b&Ny{@cW6kl<4ZNG;-I_F>t?!*{QBf@`7~edwbGPo7=0Vlv zBdgf~zUk)lmZ#h+A#74=hM}|F#;Qc$*47qr>aR-)!gSN7(W%#mdNZQa6nq$r*trjn z3J3^jSsjV2J3sLv;_TU*&C(|G9;9NvyrQBa#56_9oECDistfbY3oQSDfW06)#F_pl z!;M5-INQ%<5oKd*+wtMmJ<&`_R&B;K)XDpY@SO;bLBM@3)KdsBR-AoxlX|lsC)!HN z$_^ySrG|M}Qerih&EgU$uB*e1vtMKv~GNZLyr>%3zWGK;XJ9Zuua3uGsJuQo!`Z+`>L#TnBo9nU$<`|f(`8XYfqc~3G z=i3p2c}@j(gqz?YX9{v-rmG1;0b41FHk8ULXhk=n38^;3&PtXftDeO|N?YXuU+lPm zdA$B?;3fcYbs=|UWo2=Np62>{_k+L_w5piwlU%7p#~^9uG2-mo^0ilfBrWXP3!Si; zH;?<+Xlwb6^2Oc*?O?oGy>4465Ehmuxgs7K zJNB(h+LmG*M{ix7Tl#me<8)#E_u*b42@}_dlR4;42;xuf`OaM^m}tAfiE5T;Ln|BL z=W46z_~CXrot{-6Iph%&6H}O`TJNJ{+4dQm!=1$qF`e_~v3p5f9U=;Jc^FSk z4cl0rMa0byM-T{^k@QwOI6SB}WM6l8H-u4L`>2q@l%-chg`|NqOQRaWU?rYelmTC7 zODX95Kkp^2P2^y|GCa|m6q{k>>SWb4Q|zEz9uU35Q!_D9%>B8R7;X7@S{jYtUF7PF zlrwt+EirXrhi&Zb(NhV^jTOYjnKw{$W95t|)Xa|M*#XgAZ>#jEv8E2@bWt;pJsG0?~(ieiX-;sdfNwP!g0~VZ0y_@!QMW&{l`Z$Ktc5rly8l z5@kV(0^7)--Ns0loqLQ7a2$%{V=}`q(%s!XBz1+?2Zoq$A03(^+)FJ_Qo7y0g}`;T z@7Uw=B|u4fWG(YRuEhy0dqDzjw~>nj$f+1j4~3W~K7sO7{oB%PZ!6{#6zW||VvJd7 z_Sfb?+2n)vV;2MRbpA9c-h98yZRTS=qrk51x`sIbd!@yeK+ZzLi>psOE6YCGb!89o zIK23m``?$IRZx}lc)+r+?;r9uD;}1?(5R%ON%5+Ej<&_|U*W|m z)6U}+BT|a9ba=|=g;_uuh)QQ|%ms0H0Oef>@ahg7YzVx#1yFdP!eQd-UN2Pkig*Z} zxd6sqTP@>{s~9MhBjDU`xw%#+gW<(o{d+ng=YLEmFAd|e0xgXZVW@QS)0Fik&FLrE zN6$t^a`O>5Cl|qqXTRqhMJ7ZSCCyb-u58E`u-A9*>z{3`ZJMiGN}~mJtoHaN6L9R! zC2m#jc#^m#5XTo3Ts?&Ev4Hs-rF{Im>nSpIBOCe!V`H}wMxwN=f}fY$*w|!~lpBFl z3=;xpW2zuJZheC>Rjl-R(J)SG25R)-u6HFPdE%N5C`CWIr7pXF-Lb27+QjwuAy9Oq zT#ziPZI<4Io^FHWXFm&HTYK*lNuBNZ%lZHw|?(kPJ=sA`rB zbOTw&oM3FJ6rS$xZE>!8Ln``ZM&y)is+(WrK6{Lx9P{Chr-!$yS;hm+uNO^5fFG#fCC zb0=tg2=j>FU+0s_ZXLd* zeHg0HI0%hy2pr~9OXic1{iZV}6bE<_;5>+uz;3j2t=R@cAu-*Wk`l?T1)w5T!7Lyc zWbzEJ3e8)3dSxq`mb&`R6b>J(J&CDrk?h^kZ3b6lR;Q7mko@J1h2&M5piEtku4YGU|odmd;Ky%5Xi@b z@hSrsm$=sGnjyeBn(}c0@I_w(rX!AC9Pn9O6J^CSqwt)jnxO_;9fF&>p)#-@@Mn2d zmH0x)e3H}di+rCv3A5R#ZvST{o|Fs`#o0==n3XpFMr{O1XxPdNwr*WMib5G&c=-*0 zy8dPFJgb`OXQ%%D!`FCvpb7F0rOWiTTL>`VzGTe1Bl%7~z#)H~0E9@^@pd@>%&Uzbgo{)(L2 ze7<$G+HNZV}RO(EGI)8 zj(h*tWsIwBUnswhv}X2%5<1?;jKHYC?j5tFI>$#xhB+M>%uU|8c!53wN|x_!kZ9g#I?;)RZ7Ez_m^RH zlS|%w5mBj-XQl5og}jeJ>T=&AV5iPTq9`t+kbj}?uucc07x+S=M@TF$unrIr2Q<(a zXwZ0?GWh)&+C%NVroI`yp#2EYr$(^oxDPMyjvQy*cJwGI~ZP5S_D#%i+?b5a#!h9TM z{iQ5sg@^V8u5h=W&B3Ye0-e0jq%&{9+WmRD4KBRGbs8x1xdp8Vplz+p%uFK3Fuwit z4MXkZ`8rP*fD6b?)~u#NeOp@;i8f`uS+$`BTpsjLxjIO%a*g6>w;C50F*BoX1={{JqTB8$3E-fAYZ+kIa!d zsSunQ0)R4^i*4-ea=}yo2=Gy48aD2pq*_E^FEtypWquHz%Qhf!xDA`b3x`S`;b zpu=cvYAh}OS7Ttsv|y89EL9fb`9=^498%0;V`BqxF4KiEUHzrr=HlnBIp5)X3Tsy1 zslK=hlr86uiD#S>OWmsR(QX5WKQ}k0-RK)dCLS3F&C#{<7kooW>y!O7KWdj9cv1L^ zt6u;E07%`OAGa)k&RHDew3Y#)C5BOL5c}3YXQiN^0Qjeo$O8JAfwCNco31!d!`Nft zY(gi+5Cbm;LO=sXV1XoWuEd=K$%Db0!~7wZ?uru+)|iVrGpqDKOWXjBlxAD{B?9-? z^i(zA=XMfa3`F6{9Db_J$!QRnN(Og`-`fGQ@dmWc3ow;U_tITTWfQXXK2PJCb3;6! z#HD4~J=Swkqm76ldZ=OYLTfzD-ar68hXMJ}hPKDIKd_AqTVI-lmUm{b2165DU>Fv_ zr1nkP4(6DnJsP6MDvRz!ffWQmk4xNv#~){hMPWx@YqA^&%MM1O?E5foO0=NG5k!H3Ls!lukR8a+ zCQItkyzpDAqKx4s^r`k-TnZ4n5==vsKNdSa$+C|Y7~ImarfBHffdiq^m-&hToDg*I zSO!gKlf`0U{JqjAxp@HEu9Z>_ymSeyYPt$J~81ZCj7*N|8JR4Wo&xebN91Msss4+Fw%CUQ^u`IHAx=mA7jwxB1Ij!c*pjp0|8&;+mxMYSTsI*u*-O{>E zS(s2`Nxll?f|?6DsnH_tyabI$pGKg;(#@9)Pw z+|0kR`UZtUnY$l5d;*0s2}7Y4TwS^ZjO1H=W`WKm=7ifp6sKYR5O`S>bI{#;DQNLa z&!?bJE85%-AMn2NRNdj90+Fl*Jwc)W`S`~X57Rs0OK+r@9sFzL&wEJ~Tems=vIVQ& zxXAbM>cy+&N!?h8Vd|r(=iQt zf7v73AY5QzAWaa4nyEYBVrfR3>`C@33iC=BYjz_I=L?Xv_@`bsmY7TXO@4i}J?Ei~ z4^n+?A#rk~hxFEL%dBE0?dyjvyig(CX{zsmsVVKbJ!d=%9(ht{V20}JsIr7~GY$frjbm&EBIR_NO# zv&z&QSRuhPBj#jBECbt$N=iz`UBA?>6f%bf2iyDOa^bk{Yo=fox)?`guXTt`F{`tFzM5PzbqID(g` zpSH3)cNj}|$SIDLOPx4uHai=eamqflzCFwG_Wez62n^*?^Z_wK38T4Ltq z7I*5nvM{Ya)42rG3i$*=5cKKM4r0VQyK}YseOcw@<)^r~N>9eJJ*e5)(MGJlzkdwk zi`Q+N7#nKMfC&gEQ05@C)!xa4CB+f`s>d7-CmW`3Va8ud|7z=7a6YZ`K-yY+1$m-s ztmRkXR@T!NdI-7e0Bz(iQ(=F4P$$KW_HirA!NO(iI5+rRDq8ki#%}f-Eq{hSK0dA} zmPO9g_(t)k6kt)ctVG-F*DFYiI-0s(5-u&u;YAF!yu{-1czYs}r>GchAWEfDzclsm zaCljHx#8k>-POp1id)5D`7Nf)j#R0wA}laam; z4~VVVSWIL`sZv%F712?e|J@EhZy#J-2n6X=;;1yd$+znh00OzNGG$ZhATMh`%cRZ4 zK~SY(ZpH{9POv_<{VbRbc&XNHR3YqKH|vBVaq+U9rQMFU-kQ`mF#`yTndk?kNNU(S z;0=jNqk9RGf)L$cR$B1_c`(QyZEDp=O`b?1@tx?1@syLqfh0*ty6E<58^V{*b-Ir> zTyKDSAXUvX%F(D{Y{qW2tFM#Ou9~hY8W)|sff5Ems@bnVf#ki}p3ERki>x5z z6V9T5qEjO1wJt~RTItdk%42p;w4L;1^N3g4x zg;wR%0FxWt`%Yv$y*Q8@n3*sxp~VFn0ezK~3d7R=@uH|IeK8C#vDgp*L5FTFmu#+e zotq4XAT%%;s>}Pq<}6}oMO) z>glT2@Sh(_J_U*1-Cd^w*^qKAJ7URfuv5t?PJl0%a9Q|tZy_U2@r3fF&eDIzVsGC) zSgFr(@9xU=6xmi0fTqiS@MHvV`O^eB&7jv!_NIwnxIa+7-;|aI!$2~87~TmCQK?kI zECvNO@)?arV}MN=R820HKLt^SUiy_@1nfhTJED0c6+-E`X-TNLWlox#8t^eP=F*4F zI#3M6*B37%Xgc1I1ZeW`rEMWm>IOf6T&!_l3`yB$wfE9_IdFE&x!M%^p6GKCK-jvd zS1<)wgi0q4;sP^>WHy-1);M5WXTd>*ndDB1sx>+tN4ZcKH>$u*^a-EXvq;=X2m|Os zDI|#Ct_qqKOq-HZUl_zpxv74*uyHpe$^&upB9v`8SO^2c0q!gWSrac`yX%FRwXQ3E zR2aB_aXW$_^%;&_vD7CZx{le<-aDbWF_*=Q9d0*TbRIDLL6Y?pKvU)o*`Ixz1WaE6 zKq7d!>c@~?A5Op%Zob$Ye4acy$%^8s`FCS{fY;)UAy&%>B;L3fAno zcm~q%k^!$KG(9^&0x?fGae(NTEMIf{hN(rQJfWZ}R4U^Yhf7Yl8fIh6tsGu`>ooXG z|I_9X&nBBDRNtf^UjAcs)m>1G{~31m_yY44zdSQJXy)+qkJpwg-Q0wF{=Z5+|8YJ; x|Gl%#Ghv-#HV-n*!;9+E=I~n07-d~i;06tKVt1Co6 z0VIjxyn?hY{>O$Q#yiV7D0U zUh62u51l3?1E1wMhWwa$Y8!rr9GC`|Tp#HCoC*)`XxUjE$Sf)ShZ6h_F1_Jk*l@H; zx869i>$1@f%`P`BJH*QgC^KBB& z+Ropwk|KValcYNdt*Q<9g1P|qbuDX9wHPmCd?M$$Ye#K=!EN;Vu{T~fN`aqN-wYr| zw`5ZTmn~o{iDmM*04$Wc-_pH3f*r$FgyvEDm^I7}1}1tR)o!ig5Oxnsi|a6IU#Q`x zK$@Pc5%b+=rR!v(Q(b~9euU_yvkvk({AoJK?}+C;jXkp?4T*LLO^$%Ra#6_4C$2_# zf3BD)Lq#y>c6NSIQEQEwj4c%MSxi7aSfc%2i~rz)=_3`M3nS9}r@sP|_n zk^io$C_AhJVSI}Hwxf4Y^mqtYaYI-Rqu$>Py1T+Bg5yW;dORoUxFqNM+RsgD^6gF@ zI@I(>$epA(dXyo>4v~@ zO%W@_kU7{)zsZft*=yd}TuTo6F8t{-VJVQvQIKeG86oZJ`lNYwlFdVWM`gUZ614Ri zmtY87w_=rj6^s|#L`aWZ2JSoQfeGd!iW3xKO%Z8l^2&ihu-m_>ne|3~`6|%0ZT?#S z4+G(ICo1C&L^T&lsY~CdA_dW~D7snZ_r~ScPB#(IZDfhszmP{aAH{Drmo0)cV`xDU z2+*ozWc}U-6TX*Cgu(cV6u7O}@)DmqU-iP^DSo5t(3Z)PS@dKdptC&Twul_p~sl6Ce|U zh{!9vIG=WDE*z}kz_0>-&rbPDev`^1=VdyW73hP5ih&=){<{p2 zR&y40kJs*lMmaY04;@`;>Acz}UqC%ow)`R{AO+4z3FMCOYoR8tGJD6-$qQ%6GKADq z#X@XK15{>VTM{glVBz~8YpA z>lI-IPG9Q>CVTKPq_4(bWv^%(%h@`V54%==5*47-FO$|sDb!qglh?SIl(Dq0dHd-q ztq)c;Z) zTM_X@IFud<9CW;W>s@tVXv6ax67M78=str9sa?@lDePDaqvcC@;#zdxMlR)}*Fmyu zX}V;E(_Iy?EpP>p!Z%9y&H&iZ5Jd~FGQCs8iv;Olfy1YTKjcSR4da*fUBg#Gv$Y;p zZ+I<}Yw_LomyypMQrxOOKDilP&$)bFJ>q7Zf*5jf%mp+4)5UV@?lVal%^Oa@OUP~r z`x$i|<2|_0E6yxAwf070Ao8${!1_vlO1kU2sv`LW#*Qi#fGF!}-7_qU&DkPF5da1{ z)>6;`6IcedzF-o!As??RR(;>P2B|!KZPqZ68~uxRj9<{s)*tbQO9@8cao{%?BX8K5 zRvL>op=zVv{W=qUeS;qeWxZx-gJ6MXrFb%Yp1Zthj_{2q2QQxmrbc1KbtavvK^GP| zRRSq?l(~ArRL;)9w|@o1I5DQ@dNzdo{N-8UJ?g`V)_hs$kx(=hVtV(*m5|&YuO|9d z^30Pb3R(hg3k|&w;|JS%#gx(%U(cL-E2&a3Vu%nveXg`v!(^`%+-;RANlqzOtnet_ znVLBc5BpY!t6?U~28%ANwr8Thxgw>hy4!W4VGxEaJGuk|Yk34OqFrBsvHgeaBHAJt z>|l&+udeDPhqHsK^P_4Q`EW5ztMB#3c-uN#DKKoO_d7ne$?;&e;J{pQ4q1wj1k^=? z$^-OhFO)tGbx8<mSQh9(ESW%3DVmW5p|gNQ!~TrW!^X2qbT^P1IuSOE`|6 zpk-|=2?~nhexbvGkm68n5T~qKTXR*#@2;-PgYuc?!q+khaL?A1qg`2i6 zUE)=O>VBqxFx$9&(B(i$-*qMd)~J9AwaHpr;F!5rS-HxC;a&sE#{`d3QWpz`(q`>) zeRNt^3|xc0i}2*nX@8enQ^D!8<2BCfci+;6_nOC^tLTNIGNabJOTeh#1v?%&d2U5|JfvTevDi2Ga^~?PU!Ug>NrgqzO@p-MJet=B?K9Ur-HL*L%pCA8 zPDRR_G@2sn&Sk1JSs>P@mXGf(Zw{7buK$E6qI{}K)3v)3$%5!A->2kysRv2Qd{PV3 zAPL9qu5?so2PM?W?y`mfM91QH z(qe&wy=mIBgU=ea=e-&*G`E$vE55gji|T^Z(B#VT*`&WxzJH@!PQP_oPw#+~s93}- zP*|qTn?5JVHvy=T?n9*6r4?gH1d4?o-pD0mG2uu@nk$wyzp7ug)_31@i(JzLDd- zSGrFR-ybT*OUSDfd?^#u3d-oKM2o5teyB~nXNfK(CRN|Ebs4liw%+_et>xNSbJLW* z|5?hRlKiXx7!pKB%fcQ9fCqtzMIcG}q>{r$EG(48NuR=$JAVmu+M3Xxz%K=gT(V!i|BwFCbBx#cXUgkH#u86K^`++Y1zb%#TCxOEj!5} z(>L~~$go-AS#@ZJNQFO$aD7!E*&kHfL)$(FXub0tVe^Qc%&Xe!E&!4zCcPp7Oa)nz zp-43-zLk_}yW#^Vj+hi+kHct1y%JrE@a3_5lkZcP;B8QtXU7SHXZl^^f2nJI_SOoI z69C})(ajHG5o_^(VLJ5X)@@2U%MRN=WtSk8 z_fH2MJv0pR1G&El_>x`d+7S+x)tG~+()gG7AAeeSX4_#Ltys-^WZG{Gnq7h4@L7B-aLi7BtB6Rs>u zTD4vM7Nd>MZvPo+u!jGX*E<`WjvE@7)g-2h5`Gm#gOS#^9}P$JqW>k}sjtTWZpfio znZV3d2Y^&vZyQ$JEsdT-d*9ek+|C{hO{_bz`Asy%6tUsW%#>uRV-3`ZusR-l6*rs6 z;XluHKDF*zWTO_7T=zDfPbkX@y1Z@qrqLILt#wPN70y>k)B0F7skrF?x55q9l5(#v z!L(UTl0L(JU+Sla1<&h*4lDYK>U)@aR1|E7{TG}V9AZt4Oatl=ZR@y@DCMZ`r~%TN zxnpUj35z1U4|;_x}U%z+m&0Q8lYYqahMd9)0U{aXEz)s4F?CoZk8= z7q3n5+R9~x-O6x>k8Q~x;w8|f@VBFsfW`3DA6;{fW(b{${R&AL6*)+eUXiam5wF}E z>$>BPm}xB!#5gNtcC!rgkY?8Ky+NSP*Eccy)z+J_vY9UFF{uhn`)uxRXF+;afeh_p zA!;?xpE-ua(E1~#H!{o>=-s*m12chQF-_7T+#lT_0-aM{5GHHs7O~g&pSf-KyNtw2 z2l%v(JAoQ*FnUUGWUBh#DF9d}pe+N^Tp4n9*s zZl54-r>P0{ht~c;9##;)3U-R@J%fwkMl<`Ij$Xhma)SY+v)PlY% z`KN5?E9Pxqrxa>&|3+*nFhnrIjmz~U(l8cxDggeF3k^n{hPqV(DM=eqh4hC~cy; zmAog;^%CzBu#;GbVqla_g7tX^7mwV-H_jHWuu`bFxW#qdg8h{CdWmA`!`elFc5DFn z6`=5;IveXMGuM)T3i`?iA3(gQc`fThJ7sB(xn;Uidtki-dVQkpbFs?q)0(eHJUgab zUwX>#3L~3BO&eWMk36R!0SWH(eRTH&cX&RfuLPr0u>Y&sD@xNqIkI=C9eg(Zm&Y@N zt~q5`@Ik0!X3G%H%I|!SPI-PZT(h83Zra(g6O;;(tIWW9`jY^g?F(QXAWVI@YEteu z(TuQL5XgjTl=P;Z6|#ImS(nv1peSRCxT26({x$n0gYqF~ZawF6#mujdPujdyU!`nB zAv=gsE_OJp?>%nHQt3-sZhJfAzRC9aa1%LzL!wV{)Tx%{gMi#J7f394UpxI+9Mi#i zH}t~ca&Ah3IL72i-El_#>}FcCw4+ulHt=C&9M4W65h*>+O4x_d7p$ zZdAexpa*r!V*gBZJ^eD+HxVsUjBnqMJhO5ez95GEoH7U zrx<9&Xm&$p6l_<$JKKOW2JGXXa3SN6I~Mm7hGbxg86A{oR_z z^DtIoB%ATVQKjfXr^rdS2=-*(<`6ol&GBc?*fndwOppQYxbdERRHQknesN1dq$P=b z9s8gHKLP9bSVz-FBZnoE7q9iPNS}TBeY{8a?r>CA;$CK}JTW5J0Hu)0I{`v$V-!R}lUu>^#E51%aojmB^KZ)gsidJsuzN(SIIqRC zIOawT6?LbF|4po=(27$l*g7@W0Jn zy!YexEzN>&NA$2B?z`ytY;uYoqg zEx`AMogWMRM2(%EZ$~-fs|gyrmRZxPvXW+*LoJ4YeNTT4p{a*Nj*n*7H1vE?gYW<( z)OxqTdY!J);#J0R!#1h44dBPO=deQOJUX@Ku;T6g5KV?H0> zm0bmHrySpwwPE3bk5~T?;4?n?kndXJ&4W*D4vF!-;+-n||NG8Hzfbucm*2_oyGVW` z1MhFR-*EVwCVq3{|3%Gk^PWTnuuk9}Z!N%Y(e%Hj6xl@9;Wc1MKz;(S5Q7hFWMx>b IfA`t{0Q8gU00000 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f71.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f71.png new file mode 100644 index 0000000000000000000000000000000000000000..81e74267c5c371dd1143358f24faf7006339803a GIT binary patch literal 7991 zcmeHMc~p|=+qRl=X3A`SO{JMKGr7!SX_}fVwvCZnO_SwH|p!${A6RZe~b?8lRlq z1^fBa#ZvTX+HW>CE@QAgJAclvo|a;p{4mL~S%D^eD`{nU_`g2icYaOyXH}1XJ%4*= z{NYy}yLK*F`84Tq!tHN9ztgaE{pahyY;D@=x%5*1FS`!!+4<#Xm*PWrbD5GT>_JdI{R3=kWX^sP)}wV` zy|OiHhk(`(q3U2c+U7(B0w^{snqih`66L-%^{$7jA)jQeP^1jb-btnwdDicMdi985 zsBHcElH?gcdt|qaq9`pXh##7|4&$_Xn`&XB<^0$^jD2%jSEC1S!M&h>MB8*_`N&Nt zR4AAXJojjv3FTA;_>$`~Z>--DIU#vMK>n#PFYNAm5b z+R(b*hkZ{Otmra6@3(HRMFSSHRx&j=ro+Wc>*Vuc&14C(=Bh_8CY_{nZOsK!Pd7nv zNXsEuOyB9XZ{jp2jaC9oZ5oNxYe+@zxW>1muCj8Mzgi9E*>IOh7-6X?3q)d-%aXe6 zx2`e{XxH64o57<7R@dIdCzh(hOy<{FtF+oYSfP`}2RCi{z2}@!D5-34Q6EaoJ^gnA zg5J^ITqYXkgVgEeJeh{tfkX&4)>Tr~0UpBkG=~K#y_xM9g=L6_4>ua{51R9cmrm)L zL@uHPV_z1S%@HIX?GhGed&M8EJG7yWkdBysDSq!FO~7`|7^64z`4-N1`VgVAjdg*j zry&6w@!Xy?7cb|nXuO5O6peWEpK^Vd9JM^>Q> z%0RYH3r^FejY#}b@u}Moe-o6LMK_;Gq(9pv_J8ELB~(PXjWi!645f{HM~7orRKD`q zGMpjJMJ`9T6Q|9r?bF)jKLu_h#BbBcjLWUmORm+Z`7!^%Qll`_q@1^pD>K zmr_jUAUcqYYCjh--*MZ|6-UuK6J5!8;f#+Xv>=pn^Hm@@n2oQzw|b_^GtOv;<#9wyW6g)D_3EM zy|dcwXb2Acbp23Pdb_%&#ak7QX1NL1jI`55`UJ$ZiJ4GP1@$M~N8&w+jH>I7DaKgWd#9sr12 zoc&-fw>U~zKL#s_Hj+%z8Do8hTvevH$u260jh4|fKbXd}tPCD;-&`+GO--p7>;VS( z6SeV3CmhNjh1p~!0=XQ;3m)Ln&oW<6DOXf!k-c)}8{%pj_eQA%PmGpHy)IE40Voyl$WXlHc>bwqUPLPaq2DAEN?vU#IhKk0JDk@NRa4?0W(tj zmn*AveQ*Brv|3?@k0ozMXb#_Sy(&6jzBsGMQ~FJPx76s)vu-yNK#8j$cJA77ePp9F zPoKO>yS7BgjpB|Gw&SQT%!oQ@_VFS9`eL(eOuHH}?yDNqS2}yjFI0@a86S%x*{bH= zhZbG!g9zJysju0Nojr20=2o1D1j$}5(qd57Z_JLy@pyck^g(v?Nnk}Zn6T%mksVyBNz`3A zRv&sOkEg#-nH%sX-(YNPw9jdv2zC^CFp9d8Z>B)2Q*J64C7Lj-kMuAPP}ec`a<<^; z>b1Or>oKxM#j$Ie6x~-Y@b9XFtHV$`%>8G;Hm$AH;ng<0d@aAFn=f!t%uO#$GZno& z;64yrW_80Id17y0PS^vw{Csydh@D+Fy?q%mdTv77y`b!eelj;OmMOh5bin~$(2z(8 zy%Bos#|^Y@gA+j4r}hm*WI6@KnpLI5tuhDcd{kX4|?--p=J@m8zHP8Vk1#*ew#4P~nZCml_guLog?$OPoGF`n+X5d`4qk%{ zo}(esPc|H zTU!_7?y#h^pA|^0;u|9%4cViSgFSoWLvPnn!n?{B&@r+wPC)&wV(KP~B?S^&kIHX50E4>~Rit?urvQd=nv{y>&e$^+iOEXn?1tx1(NIO+4eu9sBm z`=geDC9fx?0HRN}A8s_WvqmBc3M;YwM_fkzopdFNdW%X!+l03b_81!9Tma;)2|rWUHK5M6O%tJIt%o^tly)y`oGp zm?=c37d=aP>a98;LuJxrnO>3bwG{SO^*R)3fY~3AE@%F8l^jw~QXYuf-+scMXa-`} zl%s;WFJRL^YcrG*u*8%XhaN^NUjPu#*6+=qO=-m7vOJwb3?Z=IV+04t1isEtojz-- zr@Zk)XA9E$p+`;JPnd(S^%3zM7uY|_DpRGXJm{M^c94`lZ}uDvY@APSQH_GCy{xSI zbH-KmCa=*LlbEcIU~(n_{-rZ+WcfHpnOgStQbjtatxGx*48pPoZ zxP#S}kq7hzCGJfYXk-r5O#ML~^UA7sAeI;8Od3t9?SDi4qKr2#WhX9gYqmVUkqsXO z1pGtf7HW|4Whbhdtrey0V?L@T=2^jMr)2>hvP~}+DIq&tji86#r$3?^V2H90;P195R#x*rP??c<@zD3fBJPTgm z3S;7ZkI5ca5eh&RdUDh+?5I%)e^j&;S57xzyj~~Yv`E(v-c#RclsZ8-&svZvps=o zA)Nt>)f}zFcrVM+cO!dLN#m{xt-u%<^m-x2p6!lHG@1zPMYqkUgWF?;veya2qM){~ zaBGF}VoUH*QD6Y>2v_22uw1O}&Rd|t(LK4wy-0W@f%=b2yk)fFKI6GL9321Mn8A`f z=7sO>Pt{g6Fy}2XaoC;DpeqKab-$xExp%$64sAeSmr`TgQ$6-~PSmcviB2tM2wkO! z57ZM+FAGjzYa4!0^dAbyCmU~n`#%JcMe|SImlHT3!e2j^w>Bo}CjRYju`VRHrGI98 zs-=;uD+k6dtiA+~wEEm!+s{3q&}!9L(!TsNf2;RHRl}xZB%8rl9wxi!8*}6Y#@K>C zB;bEo;}slek?w`5Y6fop9J3Ev#5cWrxcDB{c+hbx#4=KDxkcHKX{bnq^FMrRV_R7C zSa}ZudnXZ#mjnv-6E(9yNe$QAd?O$-B9XJhiP$##zTx_|fM#gD72UIr*RtEX<* z(7;^UUo^>mAmxg60A)9dg4d2o>zAUd2flb1p{yeuQJ0cUI%m(#&;Dp; zL1a^k0cxvo=Iprl6v5SSN!#4JR>*BIPibdxzUKVI$<-5ukGs@PmH{o@+q%VU*O`mp zz^t8G8javQ9?ugT1R}^Bc;R_s?A+XO@JNRId6rdtX4JRe-pl?>zMU*XSElvrTiZB$ z{ss*C@Rt24CunnHQr@a_H>Snfk)w;RFAp4%ex(W@!9qMUW z_Da8e7`8|)&c#JEw;Xd=sL@){NxoseIEwKBVEY&DF!(%x=&%*d-psJsR2V$OSN$iW zhklNFx=DO7ck!0aD#{<5f^3Q*ieKynYsa*~;+FVtfSt}d zF-Mb?6gj85WH5`*6bgGD>*~1Ms$7-#80qjT#ZB8M?KUM{YbMk#miDycByzFPBVAsl zy#-70HgTG8;IL=^W#BpPb=F8~$(a1OKB)R+6lDnhue(=OzKoN ze8}{*Z=ZlAI<*~x<9Vf$HVaP wF#LPc=OY$AV&NkeK4M`p|NH;w3M^2BmNR|X&u-mf^4kWsH)hYHUB`a;H??<=K>z>% literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f72.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.903a90f72.png new file mode 100644 index 0000000000000000000000000000000000000000..647430fe0b62041c5489c03c06448607cba578d5 GIT binary patch literal 10426 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 tAGz>R3O*{~Mv=m||8H3E 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png new file mode 100644 index 0000000000000000000000000000000000000000..fc37a56c29245da1207fc1abbf499761a1ff667c GIT binary patch literal 4759 zcmeHL`Bzid8V#k0)*{RLT0|ya4N@Kv1e6d!f>^{<3Ouc#G6WTkEC>h@LLh@PwFu~C zhLG4&!ImK+vkajsLm(tYhR7HoAps2uBtQt4kQe@gx7z;5_sd!5oI9LvpS}0}?s@3% z>uvDazRw^Khym)72InDb5r1!**>D zw}<}-fqZcs_3iN=5~=fJ$&W*?Ug}HFUyAv?Bte(qTu7NK|C|M%y6{Ug0@ey8{Q zo}YgG#mswm&GLiV+IHjnE@#ZV{5u9Newx(IGA*8nkY!)6>+W+>JxxP`p&r z`3_>fd(TO20EhZOv1VO7cI4(+5!TgiCmx-5p&x@?wrC}MgyWXy12H$c!*&%={J)aC z2rCotDw=KaQOySw7!J2bnMl}H`uT+TYuhH(%kt5h=(5F9E+HvlG`gNRJ8IGWFqfJa zV+y939??97ph2W(( zsU0Dc0y{7ZQovmXQ;Z^R?PPR znw62V-ipZR-u#rJe6M7??k|xoU#Gwj&{VtgR2g#$L;=rucD-K6=5IPBBp-MAyNvMb zZ~STa6@|RRj%n{LjSsIYtP25zBkcj-ljFa>>xUXSY>4&iZ%$Mt#5I6ysnx z=QmtjH{8z{&uPU-3I(>#wpO8WmrFGBOR(0k#Q@&$l_B#hkt4Ak=_r6tn3g?C>(zom z7AFm3&^hDC=H!41&2p2#hMeRjqB+53njugjsiqZa8IJwDZVu^iZ#u%})_ri-ecVa=7zAWJeAS@Y#j|8oN=D><10#pleb3N$6Q=9?+TU?Hj?G#)wFD0$m%QDvABo_a6|EPu0?ij z;gHCHOFVJVk4pX7;KYU2BtrTyml626wL8gB)pgiOW zvuRq^k!DuSwKq)St1MbIh8OB1LO5WtBJORB#S^HiTCIWM{0a**LR3{%9vQFJmIE;i zGiB0SkxN*vuDk@(VqHoRFTsLZVS$wm4)v9dN5vNLOOxoK?V8Xn|>=lKcIZLVP;j!stS&An$*lhw$~&``}&PJeNC2D@ZpJy8zL@M zb8F(5p*9s1`dGx+`=6Qdvv+qb#oI7W0}?o%IrieigYt{a|C9|ex}DC}vu0D@Jr}#p zAq-tOOjfXk^O7z?_DUiDT@jicUpO?Ao!sh>B8y$UOEXH0GTA#WHZxd!-q+ckuy4$o zm2+%bV>}HVUP2dk6lFr2IAcfasW(nLWkhkb{wcc0pguODh}u=GATmH0SZUYD#1)#-QprLVl*N^;6SUaP?QWS`@en8V zSjMol`Vgghlk7Js9yi>ZJ7d96cUB29t)f-t<2;_88`;MPs6Qsg8jl>#3ZyE2+RO+s zS7vo}SbmkzPGfZKp)b6JvPS9P3Y7KQawkq2FBTQ^)z^KSg>N^LVGu`bGNV-gnkuba z`@vStJ&l_(v}0TI-7Fa{vUB-}_-pCo;`b@ozGC*Y)j0QDPLK^afDQ8%rx zzP!;gbtN?G_CsC7^w)dqm`c2D)Rr|+KEAzz5(Hk?p2thLD zQHzcHDobBno=ZQPFzuL-bdr;#xyAtSn#(5=Hsp~M zMD(4OgfSb8X~M}8p91+)BSge>&c}?2tTFP)%7q%`FR;dIsPv8XzDB`3Uf4j&=qY8W zZia%-c$Fi934dW{ZO#(2AE|J#m zZ1y-z?LKe0aQ(3DpOB@*f%RAaz13;*PgYHTz24fo^KX@_Tkvhc_jiAJwm7uKp)C&m l17`mWrrDxck{{arZ-{t@S literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1b50a0d81.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1b50a0d81.png new file mode 100644 index 0000000000000000000000000000000000000000..92433d5e3faea413800dbfba226e82643c377a58 GIT binary patch literal 4813 zcmeHL|6kH~9>>01-Fle2-8OHMy1U&T-Cd=#rZ~S~S1s54mf9x^Qd(cMK(J;?qCn-A z+oL60Zl#HuxpICGoGEaEfYqsUYHA872xxvw5Yd$H-|o%s{)qcQe&O-?yuW;2@8`D{ ze7-)M80-C--)jg2!u!xC2T~9SuX6~*x+|N12ktccy{HFYUb!i;`w*OG+vdT=hTMIJ zj%)^>qRnT%LLlBCA3E^QBbS-_aokrY$g1w;oR*EOO#GXIjmMK^yC+`UyfO48G5dV> zgYVx>`Su?hHs-)zTzl=y&BVXA;#uFPlx+!)@3JrRerw6t)kc9TL>NKU8BKghGr+>tBQCZ1YfiOS*p zaeLy<@cBZGT}>G%ROc#OV`F0q9#MJCY#l5?AE`>LCyA}Iwl0)7d3bm@Z@JS~K0q_I z*hah2N6X5}j6)1sD=Pow$$PvUjo6ZQ>JKhs6J07}V+StVJ398JU+jD<@9{=o=5WE) zl_QazX0cf$o4f+vciuLj4GFs_SNbq7vL_ z?hc5YqGl8=cgipXzM4Y7wqzz4&^YU_X&8uGyvc_r!$e+~*vo?Rdw217hIi9n_)09D z$7|$+gM%McWRXamJfSMT+n>xV>t3HxV_WB(E=%IUcve~y$!^jUHu(f(%QFgzEZFqx zjwV2aQbMOAw{6R=tE(f*Iq5{OgNkO?vth-1jHu_Jq?bc=MDwDmo+!uBsk%!M`UiJ1 zFiR&%LOt&&MAXUHo@FAvS>krSQ`7NkrdK&Sddy&m4Wzb0P$iS;VBnak{^1wyk>iml zQZ^)A6zDi=ade@HSylj`RKswzWuka)dijdHKI0xEFh&hfLIwnoa&vPb`Fx}FN|JPZ0IiaC#0q4LArdQez10bR@Apvbr%v66;iH@rW2j6Q{%aDyP*L(N&TW(H=3?9h(Z-Qf<86NXOsjIPALoD3 z?KI1sW{pNk*RlDsUw_y^pil(hoXPz0&*Kcdo}L~*B$Aa_p9(=Bf*{EF9i!++7_O$- z<|(gww(F`37aQj1=P54hoKBjCV{fm%44$qwa>!t=Oa1d_`$g9N9Q0JkGi6(D76hdi z%={pt-l!sgTp3s8Go@T<`B$oKrTQScryW|8c8$M z*U$tIl;fQTfCaV}hr}`hh#<$Pj;X*HwhPNA>4omGzNVOgzP?qWXOLYbW;T}_=d3>d zNvheHX(lcr+}P*P96#qQXwK#4f4+l@<9dSWE4havJD#Tzeqp zWi1Tb{=FTY3_$}?EAiB&;=l#uie-u+@9T@FYEx^pN;e4XI*!C;69BK{`|pNkQOhg< z?c+)wkwxINJ-i*1uHg6M=uWUK4U(3`I40o;XLg%*b&po#TqK8vhaVe2+p|TESG}Q; zkytiYAtdMM$J<(h1TXKaE35!os_b+TyT1}82kK4FA3Fs(EfNA#h)cc(y* z#4!_`RJcImV+}=fk-pTZ4&?AH7=ACd^ay06Hc#i!?O0FT#PCwhB(197q_b}YfzHbCV7f23%v&&V#lFKYI5Vdqu%h92uE45Yb zN1sZ2{uG4eH3kV69toyz#c30AD8ui}Wh~r!vapbykeDdr$}^OFdPuHbCFBED9yid; zLeM+mg9i`dflWp7R_@WtFe1$22=K|>>)M-lHNX_mw9+7mtf7(<;^RkrXiLSI=2IVg zZ`~^6i;ZkJqAg!gWPL?hdb~*|)Hq3=NuX#v@Q$%#z0WE;xCti_2NXy!>PG9YyMEJ6NUkT{Ic>9d1xru15P;nPy%?P3iLbVa?j$%@>ton(nOpZ0Oqu-B z7vFn(m+{G#;GNp?E2y8W+)|@XZe~|ykX3p50lF(!L-_^tWSjVGIB+=+1UPDzFL8SM z)l4wGU`V%f$I-@copAtkWZ;8323ny+(XBX$3%Ys!M90;z@bFO>rs@ee0=uexfe5T& zD9DSb;?bNo_0%^EhPy^qV*o8^V-p>%JmE0~*zp|5OHY>**%y@jCbIDpusHnUefF{D z`((Eqjhk=`o3YyfoUA)ITAO;gl)Ng7_uWj_1bz^_l0qE%IPm~y-^pwL0r4Q{ Am;e9( literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1ffa58061.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1ffa58061.png new file mode 100644 index 0000000000000000000000000000000000000000..7d2952477e9d7fb6a9d435b52767a453aeedea4e GIT binary patch literal 4395 zcmeHL>tB*-8m64>Y&M;28ta|b-%hjJ7~5ExrO;LzIhAIHs9|JgI+BOU7>`KSj&_(} zXFE7~;GJ?iy$Fl2h<$%MmRDs|GG0~F+?`X6imy1_*#4kf8-n7`Cm*Sio~&@!Ew2Yw z-fMsDdA+t2-H`U(fuGYhMEm~bfah|0+|B4D!|Dh1x2R3apT=uh%Tu&R8^?>AdL(w1 zUuOjfm1AstzVg9Nfg7k1>ZjVmxG!%F5Y_){O|F1xZs z)tEJ7uHMDrwKeDg&l;ZM*<-z2jH*HZ=Sk-W-(eRvW^#w0+|{?w=d2Ebc22#3@-6!t zhuVaC7NfRB$A*%bY3g`s#7E-%;dLQ6XMFmu#c9tr7s}&M+cERWss4}y{G1Z*IiNrPQAJr^1 ze_s;#w)K+C_4`I5=r|;0uIr$IuG3Oh@m8|R+iz_>grfEjEibQeGDZ1o-amc}`7!d& zs~4|6Hc~*98Yp=5tc(xrx9zs8)u88|Kaoow(vXQX&2=}r&OByUG!QeCakQzI!L7fi zp49wyN5Y5n?~*wtq4^G=yRs2dxwKFe9nxQ;6ltA4>>K+Z?$~~IYzsO@;H)orl`FzJ*&v#_zP+ZW(zzW!)BZ;BHATh3-}pQ;Eu-sSGIY9Jv9&S+L@Gwa{JUoK_WJ<{mFY4US+1Z z^-!V^5?QLDRhu9t-plOXYBwt2S37RJCyDj;jbIo$)w$jf6JTa|-ZI!JmT=8+ZC(AZoi7iUfS{^|O!UhQ^ ziUFL-sl5@Y=O@8>QESRU>7O53?XFe}q?*26j9amSsH=*Br&y$Qxyk`>@ubUo43g~P zvxXbaes*}~>MFV$b#oI7Gdy-|@s^ezA_;~+4`F)NG{G`-W++wSivHdW(<@hPN)RIY z4@SgLruf>%s2ZaJ0kvDg3@DmT&5oC(6sC@NI(XU8%Uvl0+e515xW3 zG88~ni*VnttIlS0GI4vn+4!N%sVTHsjU--}H6J6Pc3>AWkOACunD&?ci~Pjnf}(tB zRVY#HI83pt?99yIri4?}m6sek0DYpKx4HS>K$fzcQC7ha z85_`-A8(OtI(1B48u(FPv$%P|4X2~=)4ngHBozLPQ4{0%P;*A;1VunrU6NZID$J$w8kvo!}sqoHtvFBYU;x;yt+Kd@B3T0lh zc&DbezSQu8sTOhRIK4Y+D6}aH=tpSQ90D0^Jj1%Z2}PcFkDm$lxa4A6vvQZY7O{(+ zG#M8*2(y~+>&I%!0xdC-p)UwSTQyoRHK!KB)!{ThL0w&%0n zs&G?6E=QcY6_tL~NV1ewdaJwgZ;2qa+myU z;}sgFp|iSbg3_{7H6Vj{LY_X{d}pNDOOCdxtb!Oy%jnhhiq7?%ma%)dKL(I<+W^z9&ezCi|w-CX_ktO;PH+EXlb=-?|6kt; b2tIo`e#vUz1n*Y}1{3l3$bGy$ColdNA#@dz literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.21091ed21.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.21091ed21.png new file mode 100644 index 0000000000000000000000000000000000000000..692d94e6d524e0382d63a8db905fd9188d449e3b GIT binary patch literal 4384 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 CDh0v- literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c536672a1.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c536672a1.png new file mode 100644 index 0000000000000000000000000000000000000000..5eff860098acaa79a98332ee1c8fd47967522537 GIT binary patch literal 4393 zcmeHL`B#%?77j(hqJT$77}mH9gPvBSqb#yTP>KpdM~P7cf}&uFNGwYrn0#uh)CE$P zf{;S2GDbd3Vt{~vNy>19B1=F>fUtxRAuL&hkc52MhSM|iKgO%0)eZ)O*&jQ4J3gl^d4pByHue$~+kn24nQN&#pLYGQ*C= zu3NF|>n{ETgKg|Tb|f@5_gBSe!t3};oc0A|x&Vw_xcz8n+ZuM84dTjc-{I|rc1M4H zwQpO}ErNI1oi&K4d!Zkfoq9LEz)2!S>tA@KZC|*3&udrRuAx{r`(4k@guXeRzPoO% z=wq=gKFB4NpOVuYBg!=PkwQ8yY6-%!v)Jj$KrFIW8$xFb?QZzjnn!G5zvq5ozw!7p zt1!-9YvS_f&$85tswPD}$TME@ufG&2Kmd9R5D)Y2{VtjJw^4R=TK`MXBM|I4NSg~* zRh8{e&7xS_f=W)qoTQ+-C!jh*{Q>PT2oE}w%@0NamVANPgkl0j!ENW$oSE=`c=t_f zl$&1>M#sRTx+!Rlo{uJ)=27|DSkB}Y6iBH?-B zT3VWV8k}cProG?=-4q&<3xc(L20_k}iutwt(;SlmMO{83aOUESeA{ z@#P%G5&*f(B#qs%H#OQe*uq~E#^#}>Enk6LlR}a#WYFVkD%T-Gx(mjHfsS<6us_wKv z^zsBTZ@7kQ>^M16<1Au|y_ zhNEDaisXEqL%UhqAPiBLnjtVQ&>$CbHS|6i*SIKTG=XHqnO@N^=xD&5&ejT;Ar|7( z4sJ0*#XLXAkPKNn*45`;!(zbDF37w*Gx+ z_(;R>jPF-y?Ls%DAQ?MYUwzpt!xI00_{|hZsR^GbIjPrRnmM6nesus!#^+C!9 zgpNm^T^*?dfH7ID6rM9ImiZ9%A0rI~y)3y{++g|S9nqdz!)z_c|Jiwu9De-1lZJ{{ zDd!zM_C@B_c_HdVRBU${N>h!|uyi~ii_#+4^jH>^ z!C~{g9Jxo0L+#-BV)3jj&PL5gE{F2$uP@vu0#dFqP-s*WbsEH;jHW82`C$a)4nZ2C z$!)w>W!t%u7-AcFO2<>_B1;_FQ<|8=wbcx66Q=MV`}$Z1Ggw%30-bHH z&PHL;nBX#%K{+q|;oyK*Bs4mAvA{ut=|CDK3verg&XM1v;sFr(UW-x9CBA4lTmV5; z{Vw}U0j?R647~p|V$_?uviD~1)pQxpv~?r-Ss1#eaj6B;%tufSZEl8TtztjYbE0y9 zIr&%P!}dm5kMYS|N)iJ|j&u~!VhAqFAkHtR+pvNy##wS!+I3p9Id3uh z13BnqcAHCZWopG)&7+bio~kr@m%D#2d1~_hpMBvG2xWDX2FaosPit9C&J9|^3shvpE z638djTl%`0K!WBsM|-9ZAZLNi$~l!ZN$KjZF{ai?i_d){i$*k7vU%q0tgwsHwh(d9 zP>ST=A=H4qZ-se_=@*`}F~-T6&AVmg_@#O3AVa~=Zue+qwM+phx`%(StWw5Tbz%&0 z;*^p4kcJ?A?h2Qg*+~~B0YA(2R(mNE2oZ4|Jm#3!%toOnDR=p4d0+vWTqi_?2C3dj zq~5&5i6{FZp1%WJsirYW8eGqkK3oM{^2aMb^XcCClXppNhDm1~Mnl%}{E7z0Ii@c2 z6awyPsR$;5=e6>_WD%#C%C5*uK{lIs-*4BlQ)8%C1Ed=)gVxdKrIxdsV3uq|7vN`y zv)g$2#<%~~;4cpx+IiDvomVgHw>7R?pB%FOantJKDhR8cuu6y3nXoz-SJlBPY*ivx h{_6jaAiT0zsdu9NwjmQ=t*kp>$HJqJFv5~<{RiX0|DXT> literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c56704881.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c56704881.png new file mode 100644 index 0000000000000000000000000000000000000000..1fbc55004fb1b0ac80443d5c11917cee44902f95 GIT binary patch literal 4611 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 O5IeW0e$C!?xavQcj}p=V literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.d0f8fff41.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.d0f8fff41.png new file mode 100644 index 0000000000000000000000000000000000000000..333080e54e88fbd05886de1327baf2b6d48b27f2 GIT binary patch literal 4785 zcmeI0{Z~^*8poq(p}I(qY^4P?-Q!w%)b34_6S$9#I^I0myW6N6cQ&g(T0Y4IO#1dny}$=e z@Unq^IOc>GXhmKpzsF#!>h-~2{*Zq6Sm~B~ zTQ0S9Y1LadNN?SE>yMkZ9^Ev1^rJrom58-oE%(fagZ=ltffSCFykGA7>F0dLrJ&Cm z;t~*q_U@^;4`LS8@we+sBj{7T{eQkrC|J45i8}L+NP)6uL;MUQ&CORgCsX;D?-#4x zW^c^Cu;SOeb`M}06g7tk1cJ@QHcvfXwkx@*Tzem-Fzs^Ga;9_KPrgJL^}j2B{!NK1 zaI7y_o>Em+g)rw(e}8|NP&Dq#7%TBp;9gEWzHV8XxU4aobIZ!h6$SjdaHhJ75S8Rd z&i#VSu|KzT`Ebsl1(&h-RG&!mK$z2wq=^9mV z>*B?W%jQPBK0NQsFMr<2SsE)T8r{gUsEDR6Pi(EtSzKJ)KK{d=nQu1l&HvcA5#69& ze_Gpmi-D_4grpkpu+af#e>=mZO}|1`v$*zG&qV?e%^(wheg6lU=>()}qZ>QvG<`cm zy$~YwOU-XU)Y@!`UM&L&*cA53F!=}!@4)5$3gTVgdXgYS)kF+_HsOlpICKUDiwmh< z^Yk=S6vWql`1t&V3%WWcYCoUi^N^|7UEbT<+dg(aV1~|fq)5$AVi{8d4eeGfl{$Dc z&Z6YHM9k66!JWq_OaJzp=`c8RAc#Gaz{4$iex%QzC_~bE%KfoeA(6sH9`y7y!86hL z<;R_i%80tCrKjIo?)ehTsU3N!Y#k*1S2C+}+(xduTc5UZ2rq>#YjZ z&Cj0+@b}LOjIW(4o;2G;B@Po zdlLIuK51!b*_oM{s7*pMpr3s5iI(RwH>$OxguaZZFC=QM!R@kHRYDHp?c-ys&(-ir z8s@A?D3|e*5tvVs2tZu&;~t`dh2&l^c>gob%m2Uq*HyIuJ(8x%AKuvvp4x~!tCm%4bT=2n0nH>DOa-C@+JSqx> z;keH~Hz#z^hfyJsw=o9&O zPV*o^v#7*VDE8lp%MT=+50n=AGqf);7>rR=%`nf2aUWWE_&x|TK4FjmN1Sk zTcY}rn3|N3kbuyQiezaaV@^uvgOFmZ=3~66mz_hbV`NJv_5|%6CAJrP!zF zXBf6woHhf6#&zh7?suKS=$!?>z;Jai1q~ogrFeLFFb3noRD21V40uMkZr8Qi+GeKQ zp@$fSPP>J-F2Cw0;b?UE_EV55r+xaSY@uf-s~J)?TeRv)3O7|+XsIQPYsgHpDHq2! z>4ae-SJBe^m`p%RAQva@Vr|_U*L{#nMuK5JlFBE|fFPWpa?PsN89l$jK1Yl*qqEcU>J$ zJ$wJnMReurLS)ErNJE`H{YQB4{glr9v*f?`?q)39_tPBpBk!LZ-Aq3IA;=t@*&#%e zstc1L=a~F)8UK&#q&gnXA+%3RB4>Ne-Ws({@AMoRq*cu$kn% zk#xEOFLFhMhK3p)^vLH?ju)a;e3tHA^hGk1^)Sv!svtz!PXh6Ok%n#3cQ7?UlF{Qe zVKf1lYG~PFa;RIBe{i<-Bd*ShwkW#T zr?FO*(2)WxM-SF5HC-wzbH;z8(;=IY;(e+rQ1{hst9WI?*Y?gKT)%?;P^TmRO%OOWkx=Y z`?`z!*VoUvDaJ> { +describe('css attribute selector', () => { it('001', async () => { const style = ; const div =
001- Filler Text
; @@ -31,10 +31,9 @@ fdescribe('css attribute selector', () => { document.body.appendChild(div); await snapshot(); }); - // error it('005', async () => { const style = ; - const div =
005 Filler Text < /div>; + const div =
005 Filler Text
; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -42,10 +41,9 @@ fdescribe('css attribute selector', () => { // error it('006', async () => { const style = ; - const div =
006 Filler Text < /div>; + const div =
006 Filler Text
document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); - }); diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts new file mode 100644 index 0000000000..f4af50717c --- /dev/null +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -0,0 +1,23 @@ +describe("css child selector", () => { + it("001", async () => { + const style = ; + const h1 =

Filler Text

; + document.head.appendChild(style); + document.body.appendChild(h1); + await snapshot(); + }); + it("002", async () => { + const style = ; + const div =

Filler Text

; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it("003", async () => { + const style = ; + const div =

Filler Text

; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); +}) diff --git a/integration_tests/specs/css/css-selectors/class-selector.ts b/integration_tests/specs/css/css-selectors/class-selector.ts index 635a5e25a7..fe9843c1a1 100644 --- a/integration_tests/specs/css/css-selectors/class-selector.ts +++ b/integration_tests/specs/css/css-selectors/class-selector.ts @@ -66,4 +66,90 @@ describe('css class selector', () => { document.head.removeChild(style2); await snapshot(); }); + + it('001', async () => { + const style = ; + const div =
001 Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('002', async () => { + const style = ; + const div =
002 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('003', async () => { + const style = ; + const div =
003 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('004', async () => { + const style = ; + const div =
004 Filler Text < /div>; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('005', async () => { + const style = ; + const p =

005 This text should be green.

; + document.head.appendChild(style); + document.body.appendChild(p); + await snapshot(); + }); + + it('006', async () => { + const style = ; + const p =

006 This should have a green background.

; + document.head.appendChild(style); + document.body.appendChild(p); + await snapshot(); + }); + + it('007', async () => { + const style = ; + const p =

007 This should have a green background.< /p>; + document.head.appendChild(style); + document.body.appendChild(p); + await snapshot(); + }); + + it('008', async () => { + const style = ; + const p =

008 This should have a green background.< /p>; + document.head.appendChild(style); + document.body.appendChild(p); + await snapshot(); + }); + + it('009', async () => { + const style = ; + const p1 =

This line should be green.< /p> + const p2 =

This line should be green.< /p> + const p3 =

This line should be green.< /p> + const p4 =

This line should be green.< /p> + const p5 =

This line should be green.< /p> + const p6 = < p class= "line test " > This line should be green.< /p> + const p7 = < p class= " test line " > This line should be green.< /p> + const p8 = < p class= " line test " > This line should be green.< /p> + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-selectors/combinator.ts b/integration_tests/specs/css/css-selectors/combinator.ts new file mode 100644 index 0000000000..2bbae9247a --- /dev/null +++ b/integration_tests/specs/css/css-selectors/combinator.ts @@ -0,0 +1,35 @@ +describe('css combinator selector', () => { + it('001', async () => { + const style = ; + const div =

001 Filler Text < /p>

; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('002', async () => { + const style = ; + const div =
; + const p =

002 Filler Text < /p> + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p); + await snapshot(); + }); + + it('003', async () => { + const style = ; + const div =

; + const p =

003 Filler Text < /p> + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p); + await snapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-selectors/descendent-selector.ts b/integration_tests/specs/css/css-selectors/descendent-selector.ts new file mode 100644 index 0000000000..fc546629b8 --- /dev/null +++ b/integration_tests/specs/css/css-selectors/descendent-selector.ts @@ -0,0 +1,84 @@ +describe('css descendent selector', () => { + it('001', async () => { + const style = ; + const div =

001 Filler Text < /div >; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('002', async () => { + const style = ; + const div =
002 Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('003', async () => { + const style = ; + const div =
003 Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('004', async () => { + const style = ; + const div =
004 Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('005', async () => { + const style = ; + const div =
005 Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('006', async () => { + const style = ; + const div =
006 Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + // error + it('007', async () => { + const style = ; + const div =
007 Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it('008', async () => { + const style = ; + const div =
008 Filler Text < /em>
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + // error + it('009', async () => { + const style = ; + const div =
009 Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + // error + it('010', async () => { + const style = ; + const div =
010 Filler Text < /em>
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 9b8ce89d0d..3d4a04226c 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -1,5 +1,5 @@ describe('css tag selector', () => { - fit('001', async () => { + it('001', async () => { const style = ; const p1 =

This sentence must be green.

; const p2 =

This sentence must be green.

; diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index 47ea96d6e9..80f765b9d5 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -42,7 +42,7 @@ class ElementRuleCollector { // Merge all the rules CSSStyleDeclaration declaration = CSSStyleDeclaration(); - for (CSSRule rule in matchedRules.reversed) { + for (CSSRule rule in matchedRules) { if (rule is CSSStyleRule) { declaration.merge(rule.declaration); } @@ -51,7 +51,7 @@ class ElementRuleCollector { } List _collectMatchingRulesForList(List? rules, Element element) { - if (rules == null) { + if (rules == null || rules.isEmpty) { return []; } List matchedRules = []; diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index 4b99384047..a823db14a4 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -75,22 +75,18 @@ class RuleSet { if (id != null && id.isNotEmpty == true) { insertRule(id, rule, idRules); - return; } if (className != null && className.isNotEmpty == true) { insertRule(className, rule, classRules); - return; } if (attributeName != null && attributeName.isNotEmpty == true) { insertRule(attributeName.toUpperCase(), rule, attributeRules); - return; } if (tagName != null && tagName.isNotEmpty == true) { insertRule(tagName.toUpperCase(), rule, tagRules); - return; } universalRules.add(rule); } From e221e462f14517a31727fb08025fa5a388a5ec99 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Tue, 9 Aug 2022 22:16:52 +0800 Subject: [PATCH 226/498] test: child selector --- .../child-selectors.ts.2d9c94aa1.png | Bin 0 -> 6522 bytes .../child-selectors.ts.dd63c52a1.png | Bin 0 -> 12885 bytes .../descendent-selector.ts.c48dfe211.png | Bin 4913 -> 4372 bytes .../descendent-selector.ts.d0f8fff41.png | Bin 4785 -> 4298 bytes .../css/css-selectors/child-selectors.ts | 27 ++++++++++++ .../css/css-selectors/descendent-selector.ts | 7 ++-- .../css/css-selectors/sibling-selector.ts | 39 ++++++++++++++++++ .../specs/css/css-selectors/tag-selector.ts | 32 +++++++------- 8 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.2d9c94aa1.png create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.dd63c52a1.png create mode 100644 integration_tests/specs/css/css-selectors/sibling-selector.ts diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.2d9c94aa1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.2d9c94aa1.png new file mode 100644 index 0000000000000000000000000000000000000000..9ca0af24a201975695d009b6057339439fe8ed3b GIT binary patch literal 6522 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 PL)cI{M%rbUuHFBCi1;mD literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c48dfe211.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.c48dfe211.png index 8a9e181af3045afea4ad493f2af1a55f2e18b9fd..46894d9eca1498c15751deadda8afae2c1784a97 100644 GIT binary patch delta 2094 zcmWNSeOQu*8phks*{F5Oc9q$ey3Sd3noBg-ObKW$^KxdM&JnyODK=-Oq?Txk`0{SK zv=779ht5ld%$oWl;unD;P_v}cL{t1i!H*OL2@L_|<>fs-|J?sR_wTvx`)SyF@1M7i zf}3O^$G?lnDl?8!3P$4*#Ul3J@qRL|#mP*xXnyON%jOkIu3TmGV*Ae8!cm)A4$(R?{+09*iKV`@1?XDm#mcR zy{f)G1WF!mWPRelAQV`5?a6ApHNMzl4EA(qGZvo7OzWw$YO4LeT1_*UxMX|)3D;xG zVB=3|jh>))=9p^ahAV#z1hpQS!XxmFR^b2(g$D>9exD$rZy)o4mCj0m^l~z-IA+8a zezN&V%Gju`H;T5p$|Aba957(FAelo_rr0!*PocClQhljuAz=4-ix;`s`to1<2yWwy z#BHCvc;Fd{QgFG@3)F={Pw1pMGlrl4qtx#YZOH z=-gl$zZ_)ILW>ICmns`rOO-O9eFFev!MWWar|g2!6P;C3nycm31VUdb#)Y$d<`>=L zvk!>QMGy8q&NlaklxQsxKQ`i?xx`JC1L<9P8vxX5L}5tP{!QOFWLIlFz`eOEx(q}1 z(5Ip|rTKB^6qy9Ga`kwz-E9w@(^y!nPBEx=`df{MX!g>crhu&aLizG!FNmxH>#fAU z>@2qVKX&!9D|1)i_I!A`#616Wf1>!CDW=vyZ%HUW6o_ez4^_SUSv6M`i7ey;T88cx zdwXrswyu&>CnLhbNPvY0xW1fu155{zR7@?7RTuArI{2v;^;1%$Wy&~45duur{m6}; z%qJ^4j7PP7Macu} zTOA){JL1zC^)*#?hI~FAS#-yp05RkJswe{(6)C*DGF`dC8vj)W`sBAJk+_udxtG5? z_pQJh|4|Wxj&25gl6{u_&^w_sEZ(**qDe~xvvFyUhy-R}+$yd}DxM{Lac2+mI>RT! z2Y&4sE+_>#riLg3eJ)oaN^u8_hPs{6!jH=u?O(B<V(dt+3t75& zt1|ArOd{xB?#a)CyKw@+_>fd0_^>>Vft}&3G>7E57S#+A<%Vb=<7Jl`j72PCnKsy8 z-l8p}@O|MM_KJL{=+5n~IRO&MXJHf5FLmYbdXhI-P)|881YaDO?iiFR* z>1Mkv`*5Fx&3ZPf;F#|HRq;J?##{Svng8 zj4q`Y>cad<(&+;vH|iP73oIE}Zl`l!#5x&iX^yF>x-?I)O`AG4B6!QqdYEd`L|b@N z3s<|4C*v${=FaLQm*QL|yBPDs72vm1Gh7|L7SFU(*Cv$pdQ!#p`btlbZ=dqyu^dx` zbM*rb7qGh3P{(p9Yvq*8ix)e3Tl#EmRYI-va{94%8fGvtUYx_=hLk6B)E!PK$sU!VVb2ly@{pO01^$}DIpwcjp=yU& zILM}=IS=F91HiYSVozukbG~7M&dEs=XFn1Su-$heOVeFix5Lu~EH_1OCE#3|#(H*_ zcu<#|``i7L^V+Mv>eaTwd2Piy{iTn)>QkxmMsV>aZ0)T?l9-E=GH`tL1teX+x*lF? zpF=xL*8;aIMkJze{{u1eO#JwkZ=IcY)+`H{m=xC7zn|_u+Vt$RsI_-Dtxw+S#Ju|9 W+Gp2Dud~*yPDs$H<8{YkulyfNRnyG? delta 2640 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 { document.body.appendChild(div); await snapshot(); }); + it("003", async () => { const style = ; const div =

Filler Text

; @@ -20,4 +21,30 @@ describe("css child selector", () => { document.body.appendChild(div); await snapshot(); }); + + it("004", async () => { + const style = ; + const div1 =
Filler Text
; + const div2 =
Filler Text
; + const p =

Test passes if the first "Filler Text" above is green and the second one is black.

; + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(p); + await snapshot(); + }); + + it("005", async () => { + const style = ; + document.body = Filler Text
Filler Text
Filler Text
; + document.head.appendChild(style); + await snapshot(); + }); + + fit("006", async () => { + const style = ; + document.body =

This text should be green.

; + document.head.appendChild(style); + await snapshot(); + }); }) diff --git a/integration_tests/specs/css/css-selectors/descendent-selector.ts b/integration_tests/specs/css/css-selectors/descendent-selector.ts index fc546629b8..f4a6b91b6c 100644 --- a/integration_tests/specs/css/css-selectors/descendent-selector.ts +++ b/integration_tests/specs/css/css-selectors/descendent-selector.ts @@ -63,20 +63,19 @@ describe('css descendent selector', () => { document.body.appendChild(div); await snapshot(); }); - // error + it('009', async () => { const style = ; - const div =
009 Filler Text
; + const div =
009 Filler Text
; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); - // error it('010', async () => { const style = ; - const div =
010 Filler Text < /em>
; + const div =
010 Filler Text < /em>
; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); diff --git a/integration_tests/specs/css/css-selectors/sibling-selector.ts b/integration_tests/specs/css/css-selectors/sibling-selector.ts new file mode 100644 index 0000000000..f658abad53 --- /dev/null +++ b/integration_tests/specs/css/css-selectors/sibling-selector.ts @@ -0,0 +1,39 @@ +describe("css sibling selector", () => { + it("001", async () => { + const style = ; + const div =
+ const p1 =
This sentence must be green.
; + const p2 =
This sentence must be green.
; + const p3 =
This sentence must be green.
; + const p4 =
This sentence must be green.
; + const p5 =
This sentence must be green.
; + const p6 =
This sentence must be green.
; + const p7 =
This sentence must be green.
; + const p8 =
This sentence must be green.
; + const p9 =
This sentence must be green.
; + const p10 =
This sentence must be green.
; + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + document.body.appendChild(p10); + await snapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 3d4a04226c..5a2d8c9d57 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -1,17 +1,17 @@ -describe('css tag selector', () => { - it('001', async () => { - const style = ; - const p1 =

This sentence must be green.

; - const p2 =

This sentence must be green.

; - const p3 =

This sentence must be green.

; - const p4 =

This sentence must be green.

; - const p5 =

This sentence must be green.

; - document.head.appendChild(style); - document.body.appendChild(p1); - document.body.appendChild(p2); - document.body.appendChild(p3); - document.body.appendChild(p4); - document.body.appendChild(p5); - await snapshot(); - }); +describe("css tag selector", () => { + it("001", async () => { + const style = ; + const p1 =

This sentence must be green.

; + const p2 =

This sentence must be green.

; + const p3 =

This sentence must be green.

; + const p4 =

This sentence must be green.

; + const p5 =

This sentence must be green.

; + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + await snapshot(); + }); }); From c79df8c820aa4e0f94f0553d163b79027ebc67c9 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Tue, 9 Aug 2022 22:40:37 +0800 Subject: [PATCH 227/498] test: id selector --- .../child-selectors.ts.3df2d0281.png | Bin 0 -> 5299 bytes .../id-selector.ts.0c9eb2421.png | Bin 0 -> 5542 bytes .../id-selector.ts.53d002db1.png | Bin 0 -> 5339 bytes .../id-selector.ts.555cba3f1.png | Bin 0 -> 5575 bytes .../id-selector.ts.f3a7cf311.png | Bin 0 -> 5572 bytes .../specs/css/css-selectors/id-selector.ts | 49 ++++++++++++++++++ 6 files changed, 49 insertions(+) create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.3df2d0281.png create mode 100644 integration_tests/snapshots/css/css-selectors/id-selector.ts.0c9eb2421.png create mode 100644 integration_tests/snapshots/css/css-selectors/id-selector.ts.53d002db1.png create mode 100644 integration_tests/snapshots/css/css-selectors/id-selector.ts.555cba3f1.png create mode 100644 integration_tests/snapshots/css/css-selectors/id-selector.ts.f3a7cf311.png create mode 100644 integration_tests/specs/css/css-selectors/id-selector.ts diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.3df2d0281.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.3df2d0281.png new file mode 100644 index 0000000000000000000000000000000000000000..680f5c3fc4f005f03a0b24c776318143ba373013 GIT binary patch literal 5299 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 GAO8Wz@A~2Z literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/id-selector.ts.0c9eb2421.png b/integration_tests/snapshots/css/css-selectors/id-selector.ts.0c9eb2421.png new file mode 100644 index 0000000000000000000000000000000000000000..58535e68c5dde16d9b747c2f4de7008dd4a5cb1d GIT binary patch literal 5542 zcmeHL`&&|Z{&%`r)0i{sGvlPGv(u*g?2P)9kt3v3Ho2~wnug{D$($r1MKiP@q}IOH zd`q3aTDGWIF^}QHe1GWt@_EiV&w0-0yxy08ymaezn+e;vH9$Zjm@^(yLVZx)H_X8Tb9o{)6u)zf1f-pTyZi z9S;-t%%y+de^B<9#wx*3@)JgI*uu4e{f`gZl^mUEeYJY(Z+(N*H-6;nQRFjmdC@<< zy|jDJY1QYgch-0b`fA8Un?M46W#s~z*`vm95dxqWp<7FQtyVwSgjEXZJ5Q8(sn>Vd zj0W7Oov>ak+5?(^={+J0HZEt4VEfYvte3j3Qm} zw#g%DQ-5?on|7|z07YeV0{4vm(szKcVhH=o0rzM6~HGNj7U=RBd`y^6>%FMLD2JFc6LsR4lfZsq4JMMqt! zcn&iDz949+q;vJJvz@ayl_f-Y&?514{-STg3U--mYra=>ft3O$pa!Y|v%pAOK%WKO z&`q$lZFuzZ`b+FqZi#P!p5|tPx!|UsYd9XA(MF~U5!e~f1OQQK7hR-zyXvHY0$eH@zV+d8xeC8pUZi}cvrHy zuZ@BUK+yIo>oy+DEsa$=-C!*=Og;YEij?~o6(3S~cXP!U?^|s~=k@=Ad5_#To;e?) zebRfFXAk89V#;{pTk#>#@BH6%MzLH})nA&G@&^woE<(ETF%>&rH?1H!FsYII{3@&7 zlOwzt`1&Wc@S&g51{FUyPgL&2&X)Noy91%|C2kT|b(l=^mc6x7ik^D&D|YyGsbXAP z5hus5;z5@;ihJ8c=1!&Z-5NK(Tepwq9NPBW3%aq2yR|Tl-F)Be?6{neqnU|q$%!E> z1R;RI?Po-fd59RE9fUFKSr+YR-t6ZkTbXp#*dWbGZi8e77*)fxXjTGttG*;#=Od0x za`9Pz>g1FDcerU{M)Q6Z2>^-~4-5_e&3NRTOGZL|s!LpTp&thTdcQajIoumc>-=fL zI}7jHr6fVNjbhXTjyoV;Uz&23)HDFKz^(+PnKQ}@##lV=60bG|I%}^qho{Dg6?ql% za9@KV52YH4_c(C<6d`LIi5{v6mXvp`c|#F}Kj!@IUPy}1CQLDUH@afu*{B6sM6_Z3f1QSqe>E<4 zRDcTh*URsNr&T^E*{hqNmEV5NUqa|6N)+rOCJg|XBhi4`tcWwS=MgMOi7(qol_-CO z9v^J=Z;-zMV?RFS=y&o7iHN65DtT~&-fh^6B(UCA1UG{<7 znHnNF#=D+^B0iP@b~G_RMHRy`iX@5&f;~gj(hH2=dAH# z4)eRkDU?uU6G1$hBC)%kSu(48-^xFl=6xgOBn*~MjxX!DkZH9Ux&QJlC z1}sbz$*w>1ZAu-b`ynjfi9wY~_oK~EjObaakJ8XKC#u;`L0MvmgGTQ6SN34NEfTKXYD5VQF-+*D zSHA$s3;@-Mx?E)DjWqQe|B8<4;fQkx5Oe)u!))Z52i1>FxZ(7m5 z0O}e0Z;e&Fou;JzT)aI45S1JOU%$&tGT-LdjtpT#ATGlz`Gl?j+r{+Th53dHuvGm+ zW_OOimIjF3O=w@bw9@v&SB1(@E~CmYR;-Xv)W^_u%42j3!QN8bMbI*Xy?_-YR}53a zK(Jx2{CXM=tJG8pi7aH7Y&)Q!SFEWDH2sKMjn>+1$J^E3+&d>o3UQ^`e_`r!t#Akz zR950cG|Dli%3{yW%?ajb4Ar&YU$HO~ zCaU9h1DHZCCU`zeH0xQ)8WUPP5s=CJ)QSraqPzib!zkKE&%>)i86BRBGJZwE_8 z7ORn>yD<(5pfDcx4iS^!Xxx0EXpF8P-J1Uhpol?IYwqq7YoCcbu$lb`a3=$|30Mte z?m*5!mPh(%S2t??_Mj$rC$xr|eU`prTfnsDu=>4uz{~&k;qAPUr+1PR&gPALaRWBQ z#sA~HVrZn2yxHy0oU&HM*1e`#GI%X0hg$_b!U^rryawx+8_`S#UZ9RLGhA;?zGSN( z>a&jYk$r0}hx@ZX4w8)Pi=>~fU7G%WNy;CGtI-5R`H`t&7IvWhh_HAp5=ae+lWxgX zwMLvO+LvgU)s2*e3FM}Ca-qN@0nTaHGG@K{LHdO;&7(;-sxX00hIdyyYXcN7RvZO; ze?Zh=OjD)#$R3_Ogv&LqV0`!8GC$LbSWwdvdxE%_v#HdS z(rMFdATiQWMoc`*L8JW5J#$K{aW(dmVb&&fSPkYx!5b7|q&3w57Maw4>uRzUKAqmd z#9d);`1F>Y^KmPU5g8UqVDw@P)>2AE-!!u}#5^+u*0e;TUoG#^bj8?T%|3o{jwIW> zW3+?_%96VRqS_?sxJ2m|iep1Wo^%ddCAa|1p@5u4R?X6oEV4jMYhFoGtJuhw{-CXa zx8(^DEFWiIOl=SPn`luL1JtUWSSsLLRz9)zy{Y03>aON5#7XF0Os^%(Q5M^>zQmGz zXf>z3)1>0EG6(RNYV%8dg%+mCL^e zk_BzrGXZUXdLp~MA#QqG3{2nC9p@nzZ`Qfr&Mtj=f;nE-VCf1X;#$(ElZ1Pnln{HH zJ(srGoj}Scd%yhKv(HZLIeOyH|NR+o`mXrI_Pc+$ayxvR-KU$MK={OjPjdL=36?3z pClEe?@Sl0vCno%VVS7|wks3_WI-JxZpyDQ6LP%@GC^1AD0)#16 zj>R4a^k7B95YP%FfrN+&Lx3O@QpPZ3074RJkYR@q0)#*U$xYT>_kXxQxYyciPw!sq z-QVZ)e4pptzn%$4d4Ayift#C~=c(^boO5$?zwGAr&h_^E!9p_B||h-@fgBd~NF*K9@NP@%!>=%hYOG?^o>>>)QGw_4InS)<~uwBB_KV zjQh39&*uj)N~dVI0IPz%4o+zS#kb1e5V zhEV(2+zeRhZ(qzc-fN})wP};eSp+)G1r`xZec}AN@DV=(tf{Z9~(5^)U}*J z7p0v90MAt%1l-u8#Ua;g{i6VRZ4exjoBd{>ab=RIAL=TYA6~1D7C-$@Ui3Ocm238o z%14E3D)8^{a1_AewJ?d~#tfQEx^bkU zkA*~nQlB1)Iz2Wpj$aRJgt6J+FOlR*X_N6Fc@;wYBFiyqR0h~*cZ8^g26`fAqPksQ z3CUx(|Khi@Pg7wd_=1rk}jBrb}HoLb`9G8a;m zv4FXQzHuQCQ&-ipQi0>t1{7nxg6Q>#Lzd&zi5pw=CWk2vpo%}ktuEc9_Ww=+JCP0i zC~^$eHNPw?6Nx>zueVb8NXTZxN{#M!3D?o^WI1NJM&lhE(cExvEeD?xjaLj&^-7(-X^)f>5QP}5-aYAkD zZH8W0xy3NIR&{1G*E9sMrf@5V3^!GK|75=JZGLkW00iauFl_znwocOq7eo{KketKz zM7(Y!P$iVjRluHZaq~x@gs4Cpx2zj(IUNG6Q zm#SE=1tHH6XKL>m?^z`C)@b$OOdymxE^DNLeN?+h%5w?JK#kh5!m;Y5pL`k-b4wm}=A4eGDA_Js)ap*zUvpOYpBa*+P&2094zK zIwd@Gp0!0o`8F(pd7cn{w3f}@x3w^O`ZTPmn<4;;bhA z(Zq#%7-{^9XzjO=!IIv!J0^lGm5^!i^wkR;DHz{O^(*tBG4rJ#%u&|(!>jk8&hd@w zBRw2+B+vTSW?YqzW2w`+Fyf`uhOeobOSTMD)6`Q`C;g6Wn`LBxEY5bGbDw2oC>31a zm07uL@_z>-h|-=%L=K#2WUC}7)rI~A)w5;O{p z>SG=do3h;IFnxv%6$g}WBU=`y3OHJ*zD@3q-q~ck;Y7AT;o^DncsO_S@qjsVeX9erbjy7Y8E=q%<)fP{KCzaK^_H-$S2yj^4RQW zUa&@&2zi$8gVHX2YY}co8O`r@dipuJw0zl6C$XxS^;vcIkQWC&0RVFkR9xk((BoG= z#@Jp)g;GM{0+8kHjOJL(nCAX~f#r0*xf(8!hz6P)C>k~2#Q@Vj9icqy;Ri&}!P$f> ziL5KB;l%NpxnWTR0FX2<);ymCG^r1vfsGD-$4P98Bc9snOOVk}C1|g}jLH_voCS2E zf2gD^E@dtrYGl!%@$(NG!5p-ljgFGu$qIP@77GAO zm^Bh=n;4}Ikpkoms1i(8hNT+Evr*v-;}F5MsW$AL7e-IAZ`^J4el9JdkHgZHA5I+7 zY}AVMw^sImGJZu1i=rv!yG+$pz4{&51Wz!yGI8zCK(lGhpC`5!h}oQo#>P#{yUz4A zGl-hVB`^n%h<0)f7yY6vYhZ)_?kbQ`h$S4JeUfvR&=GJC!EdoY;032-S{723O5~!s z@$$q;sE)e}kZ)}H$kNxuOAj_{CjJNuQ%hS@rmcpYX?XPXI3+yL8QZ>$ggBQ&Qo;)( zr!|yB-KBVZTSx;}^t$&w=+gWX>hNEMV0lL{RDsJ}?zk()CH?iGK!_)~44>)|&5OAdUi9A-zbBE(^;HHeV`!ARTGOuY-odNt zalBcC8CHf9x*vZ|b&Oh!$wz-sGO`^In@E@<|CjlTgg@=aQ_>`zDXIiabIrs}P$eSk ztR|m{lD!t$%a96lTBw?PRr8|Zk+`YJsS9^Iz0Qg-`sfo4P+o+JNVxFdk5Ug-u3?Y;WvSXu{ J?y(wss*Q3e4S4?%iVKnV~K6094OB2Y^ULV!qtq)0=A03jQ3YF%1Z zgR+LC1(Bm5NZAZqMAWbZLInv+LSzeIHXuOAJ};d!|HK^MFZbMc&wclu@AtF4_w!f5 z0hXT~`pnGC%o6^u^Iw~p?fBlzZ0C*LyMQ~5hvsU4VF&K(fU{=x!*(mceL5GJw*$IF7LC{=?4z~~WIOSBh`@s1#XR^|I zDTb@^K*gtLb!rCL4^^_Jo!4Z+iovQviGkc8seIhk)de-XKe{Mnn3+u<9WlGG$K%Z9 zoq8*^UM#{-G4!vb0>XD_fU3V6rQ}P0nRO5K@ zWM|+(cBl6~sA?ok+kuxc;w~O8v?%r~x!IlmRd-rnaq8NvwKJTkZ-`MZkK~Iv#(55$ zR)Zv-CQ)&UDB@VP4^^keuIP&DVrUjce$RyS=6L&(wR0SHb=*#D4DRuV{;>!OzpLTTSS1NPVx0@R=xtKv_;oU6&TNk;vQ| zrwCFhXJ*5@7E(Wa7n{@fsRgpYElZk>)eu)>_;e6bt!jw>_02 z;p)ym8m=e{Fn;(0gvfGYE5q^Pv6LF|6ZZ3CgM1g>5xxTrXOPh^*?^rWks1(OY<#E7 zk@~s^1qBhdS$Du|kA7BbnL3I=>_{z)ZkMggp`-X0p8Fg6IfjKegvn#-N(Nm)g z2{nasQRPta*rBXU9^J7tGJ8k7p%3Bds+G1$mEi`?130bLF!!W613R=j(~FzDTwIO8 zA-a{z_(RrqLTXxBheu6PDBsR|;l(BW$QY=gT{Y|D&%numo&(?{y|90K*8Yc zg*<0PpJ}Wlxn85I`2O1xDQUA?E!Qa?8J+ZCoo#O+==gX;-xw>@HeaD`VfYZX`XjX3 zmRwHqHPQV+Is3#pS6kYn2CwSr=4ACmy2Ieh1AA?k1nUznHfbxrMy)x7$8SS!Lxz1d zz5(dT;sJlFP0JK9D$&4-eskLHz?Bpr<@1j8~fcKaGT zO8t^j=NrRy?BnaN2}mx3vc-MCi7L&u>iT@2zw!DOvynQ#YX$(uz}uEUoO~Z|sANZ+ zyKFV_&-1afopfUrs@hjSkct+Q=VZ<(esYJn@|+v-T#~`}kfdPtPQ3I* zBWszFy*`-0zC;sv7$0#XPCBErmbp2$9ywusSNI?t})c9PA3A-CcUnrn0UuCr}qa#GEu<*mU zgmw>%*3vPAy^$tIlBq{?8TY4Q>4-R^;iigbowa%k0~441%wMQ8=Y2^#5!HB7d2o0T z?Y}Xp8+0~SrwmW!CgFM=A@M_RJQHOzW!Y3>%I(d=MNFnz`Nqdlo5zK>G4B)hE+x#( z?6=kO^vf)kZh5c}WGtaxVj6|c{)mkx%UR_D4F|g@XlhWNc)Z4cfOnMqvGa#fE=?vOpa~$Nbe7~(tcU5;nl7$ zIU4@7)a9UlY`8w7wh@`cgON-)n5J0S z8#ZUDvS!LyOhDJ{nFcI^9g52WLt>GND;;feh7D)f#AdMO$seX}HPvuu72O|?`fWXN9J{LdOAa8&=!*8n&GtfMWSNSw#seds>#?uc5&_)hZ+^1%p(C8Oz(1Z< zVJbW7pNNjON7XE+d^eIGX=@S;ylAJ(Z_^RwUf8K?NDiq_)bGQdX*~Ee^1jL1?4D_N z5ayI24}mR($rWSIV<)?kRG|LbLAEnRgqNfs98D)<>M^d*wLj9Y5AN}y7)NoUO6~g1 z8fwA*i_DSCTg09o>-h2cCihtJPh{EMh6F&@Q)lvr0~Lq6z=f^OHd(;9I<}xH8N@>F->+49S%T@ z!E(kk(w3|)5+s(%_H-b!6h!3n23}p>ZwpDp8W&|I^I0gl$LXxXWkKVD7@j_u^669w zmF-C(Z4IS!qKi#FX_$3376j&Xg1XvOU*d|zTp~Fv?;k1Wcgaro)h1=`FDqQ=Tw7W! zJ-}O-x>JwoJO(54dX=*IZlU+u&71xuSO=Aip1u@OnmTh;luIOfG>mkp*%sM zn^9ZrYk3m&q9jV&pJdxm-?}(6su+5V3my_UFQH7;O&)p9_0G5$T#k#JMRrg=DIi%A)o%r$mzuVy!;>Ib4!tLZE)9}n5em-`cIC3?Q+lKoRB z#rL`Gj~^%2Dh+a;!4)k^&v+#8atfi&u1Gh#yHW?9hq?pN(`T)9f7AMp53tvUeSP`% zxt{YqHiWDF=IWXdUFa53R3wTFK1BK&<>&1+LFo>8~bf}9YwvRq6eXpGiiRiRW_B2Ssb2+6O99+GexT1+z6n0$fgq3V-A z?J)M7G#)Xr^C7}Hofs(0`(dK_NH#KtULO<~|DFq*#vLeZRRF5g*%kO1YX-j%e7^qd)gS)@&owXX literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/id-selector.ts.f3a7cf311.png b/integration_tests/snapshots/css/css-selectors/id-selector.ts.f3a7cf311.png new file mode 100644 index 0000000000000000000000000000000000000000..79841e517084778eb325f36ceeba848a35214d5d GIT binary patch literal 5572 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/id-selector.ts b/integration_tests/specs/css/css-selectors/id-selector.ts new file mode 100644 index 0000000000..dfdd677988 --- /dev/null +++ b/integration_tests/specs/css/css-selectors/id-selector.ts @@ -0,0 +1,49 @@ +fdescribe("css id selector", () => { + it("001", async () => { + const style = ; + const div =
001 green Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + xit("002", async () => { + const style = ; + const div =
002 black Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it("003", async () => { + const style = ; + const div =
003 green Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + xit("004", async () => { + const style = ; + const div =
004 black Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it("005", async () => { + const style = ; + const div =
005 green Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it("006", async () => { + const style = ; + const div =
006 green Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); +}); From 09efb8ecb885c563f6c81dec1d2756be077de4f0 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Tue, 9 Aug 2022 22:43:04 +0800 Subject: [PATCH 228/498] test: tag selector --- .../tag-selector.ts.0c6a7df41.png | Bin 0 -> 10563 bytes .../tag-selector.ts.40c7c0241.png | Bin 0 -> 3841 bytes .../specs/css/css-selectors/tag-selector.ts | 20 ++++++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png new file mode 100644 index 0000000000000000000000000000000000000000..362ea65742a95071af2fc17805374e8163d79bdc GIT binary patch literal 10563 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 ZSyE#ae--`;eh3MH>Kfg`==}NSzW~A!KWzX2 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png new file mode 100644 index 0000000000000000000000000000000000000000..67c90840eb2d31f9f844a68fda6398104903956d GIT binary patch literal 3841 zcmeH~|5p-LAICAKeVcLGcBU!Wv&S#=9KTgs3fehCx-l>q_|d!GFf`@#FmJ@?+vx#x4<_jT|4 zbIVW1#klV9*Z}|lT#^5dN&oGUObZb%O%f- zEv47G&!m!nV&E`pZFRMv3Q{{ie1B>Kf6=|hMs7-sw8L%BvgHJe>U3go8bXVcS_WX#Z#J(sZDxv@-K zWz+nSnenxVAhib83L$`&hI^p9s3IaD?V!SIwJeK->zDJ5!~e)!3B7Fes+=pam_DAPxu83G(#r&kRuV@}r8VvcDHQ8y)-Y<40UH$^I2S7`_m@f{HQUNBr&8X#|lBlAbm zs5?o8!@?iukATP3&hGJ)%VgxC?{NE%8nr{qKprhyuO35JIaJA#_GTo(3-g{WO;lRy z_z}Ea8le&jXU=}HQAbvpCR%3(P^9J2Ha)|iWv!dWamm4w#`QAKkN^>24} zUNnC6z!h1 z{s@_o9zyw?cyOFyT$x;G|8xG*MvRs&kjOYASo3m zJA%5)L;v}EA|!6_?uF*Nj{WJg%Ob7#B`9$CH{`366EEH41)Q;Y`?3K^U#wuwQ^|a9 z|DZ4Ode@x~e4J+f)i~o_CJ!rEee;I@ii&X3E0}H}#@B6-(C@Y1J#v-g4Ax5`s)MaP zRr-Z1v`p66#GS%mF3S1v|z4V-qes`s^HQ2 z6oN1G+tyFA_{JMV)63xb+3k@3(Z;)yQyGY=6ouoYD~X=+4esT+!4Yp*;XoS<`}Usl zS$v7|;3s&Cr3(lYUwpW~&_7cjS#-;IvEhr?bnO$-%PP|+IqCs^aLI^=y9l%n+Uq6kjIqjEji9D zF^+ji(Z`J}1F2Y+;_3>QIN>6T!7PWI0?V50AZ7E|pX}PzmJpJDHP1BfbigBRM<@V7 zl~Y{y`~D7Sc+cbK+sA;nKlr*NVT { document.body.appendChild(p5); await snapshot(); }); + + fit("002", async () => { + const style = ; + const p =

Test passes if the "Filler Text" below is green.

; + const blockquote =
Filler Text
; + const div =
Filler Text
; + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(blockquote); + document.body.appendChild(div); + await snapshot(); + }); + + fit("003", async () => { + const style = ; + const div =
Filler Text
; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); }); From 2d78f730d3ada5cf2509f8e49f1d645ff1bc27c3 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Tue, 9 Aug 2022 23:45:55 +0800 Subject: [PATCH 229/498] test: fix wildcard selector --- .../descendent-selector.ts.1b50a0d81.png | Bin 4813 -> 4278 bytes .../id-selector.ts.a4da180e1.png | Bin 0 -> 2356 bytes .../tag-selector.ts.5fddfcf51.png | Bin 0 -> 34430 bytes .../css/css-selectors/attribute-selector.ts | 2 +- .../css/css-selectors/child-selectors.ts | 10 +++++- .../css/css-selectors/descendent-selector.ts | 1 - .../specs/css/css-selectors/id-selector.ts | 4 +-- .../specs/css/css-selectors/tag-selector.ts | 30 ++++++++++++++++-- webf/lib/src/css/element_rule_collector.dart | 8 +++-- webf/lib/src/css/parser/selector.dart | 2 ++ webf/lib/src/css/selector_evaluator.dart | 2 +- 11 files changed, 49 insertions(+), 10 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/id-selector.ts.a4da180e1.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png diff --git a/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1b50a0d81.png b/integration_tests/snapshots/css/css-selectors/descendent-selector.ts.1b50a0d81.png index 92433d5e3faea413800dbfba226e82643c377a58..d7310c9e1d114d69b0f7fb378aba3b54aed28956 100644 GIT binary patch delta 1999 zcmWlZdsI>h0>(YF+vAKi+U``RF>z=2^f)u)Xye*c7`N%g@wi1z;fpCOGc8gmB_D9P zJ+_*rls!GvQehm&N)X5tQB#o;&S#cQ)}!~^l$(iSPLe~g~}CFRTSzPc8E>hlGD zd*D}r>z=K|-|zixf$vS*d)4Lt-Lt|&k24x&O*i8lSX`fH&x9-2u$-et6qe@gp6bo{GJ3sMOl&x;EJUDg`*~x9N6c9xO z?sH3>7A46*8w;yXr-n6$d#jlo8cZ|hdC=Bd;tVzD*_J9ukY7lED~nk* zb2t)!G-7s@ok1_bE0VekRT@4Ng^}*x$33-;PMuCyea_ZP#O4~HzPX+?Y=&gFhs6-0 z{j7fV=>rVoV#gGNo^y(^o<6LXEdjl`x|U96NWkfsP9I@1Q+lWbQ`X?5uuEq-lpqr^Xj0yf#rN!#s+&tglhf~bW$N*k%|6{B5DT)Iyhi%xRS0`K`YDNl<~{Z4;>V@c zyWHa=~XB!6WN<XM7GaiFCM5N7tW3|x); zOf&>q))B@kazoxY90`>&W#~$%#u{X)1?OEDPx=m(uj)*V^FS zgT83*s{n$~7fd|J7j`Fk-J`GiI{PX>tY`YJy9c0OY+q~&-Mkrn!wzSMXP)8x-83mQ zbEG=-Co~Cj*(zk96RyaX_}z(x)OV6UZsKbxArgeo>lXr^F)tS}P*Y2{DK+T|YH(D% zvxc89g)CJ(eaEu($J2**AWz!>Z=VC=`7q4zr8eX`CSx{90JZf5o;z&#B7)oxd6MC_ z{VCGXv&T&%d@b~@J4bo>87lqdZ!-5hM-&4C7(s47v3qdI96uwj&OJe z>LtFIGc5h5&(oZHLU#2RJ%MV*Wspr#cOvH${L5NuXMr=2;#+5|Nv4);ciOD_0KFpC z*+p^bahHPPY0v-Rf`-I#foD6i&puyK9?VUs6m_!PbA$3yV3e`d>q>DQ}Q`W7zYl zyWY@(swxtMqWS>yzxZK!zQVDpr!Mu4+0(mNzw&43xJ4Be36qDqixB6ZG*Mbd;nsL% z9R(=$0G^sH?Xn}5wZ3%zFV X&)=N?_w$E-ONl;kc)xUS(zX8qPy)I7 delta 2538 zcmW+%c~nzZ8rNEno$(+uomQqspq);SXGVph1EDOzDXomdCg2lIgisgIY-kZ8f#g|f zWqPc{N~<7Skt*edgd!vv5|W?}Pzo3jk_QP%KsHGT0U`S~H+}!T_wN1fcYoU_cyIWF zTZiB`=|?{K=dmlb#wp@|PSG{}Yxx~pIk}{_L|ad$DGyA)xOrprD=Osz<-vFFrr-Me z>s#}YFR%Unt2d~B>m+f$ORwA>n$&Ymf&Y0cdO}#$8TK&on`4PL@njN-e}Qyw_}*F6 zWseQ`Y;ozYHUITrc$`{;Zr^cM?#gB5Exd@`Bm3we9MGx6Z~jBx0PNhf=|t6`EnmKQ z{_4@T!9&5(MXgrT({g|Gsejxl*fG|?aI)M6BFctcv);bO&9w&u_HDd3v$w4~JtHH7 zPd90I1qJa2LuK6|-K$TeVI3VEe5TW)l9bT17)uWd?elk0wbmlg8f&omyg%(p*%!G~ zqJy1UhP*^upz=&kPO1b{^);(~q#SpwE~S|!wJ$jOu+p@#v9ZFn-T+k2vUE5m`fTY*r_>C@1F!LB39E%RK!@kGTz2D7ggr6=zS+p{z^2Zx=q^8c;h{{KfjQ=w7ST zDp4+7#zSU7D!2^uOwr2Jq)0=9byYN>+G5h_@ML4xV!c6Z9H-9q-E2-#gAeWfLD93! z?9#PfC7vwQGRTCE++-7uX#X`65An;k`3scz=!?^bI7rdpe&N2ze)vUz%+`Cd0Ua3` z`KUUNM&lKVHAVe_^xDe)E!ho@*W7cJsRD$=$!w!JEk^P-*guGZW|vSoh~?M4ZGa24 z42ChgcT<|0ny4r*iwa6;SWY7sQN7QOc@BDdIod?Eu4tO6C=q5FE=L(3+|9wc%9=hgh6dSc>)$&?Vx?1Z42+FBQzSep?TdGQB&5(Z0yDUf1C zWZ-F~u_cz;%3^?~7C~Zd(`AcuYgf_c>>t>{aasTrCMbwjP*5P*tn*le1c=|#(gH9c zD_bu>ih8DHQd^Z*U;Ge)oC{c4ao^VwRMj|wxL+p3Ma@>Nt*u4G#FX}_g_OzXhZ7Ri z8L6=fJi$h>kHZJ`O1yKB=y{Iw+U3?~j`Hi*RZOb}wN7BCF2;-3`Y>wWE9ihAVVK`x zH}REHhoY_C616Xv$0wtL(a}*YCY#JPs`#A!L}|C?Qh%VaqrpwV?AoQ*&@W%!Xw5&` zgUJjXS@1V#f!w*_6vd3@wIzLzemZeq1p z`nFV8S6_e%m0@I*XS`whIB&4IJ}u5VRlprv>YSOGDKbf{f}0qu02twsDLD_f0V%GR zm#e8ljgn>N5bM?=0cmdbxXVL09x^}W+_eY;A&urFKF~0BsflVO#&iQf$CsT-!`q{zx{&B9= zYk7~s-OfXbqUDK8s@MA~0?CBw8ft3DkV5!uM{NV+Zmw&!Jg&99yaEuz%ahMo*$?LYF zZmUkGh7DYy^4IV8k{JvUxM`Yj>hnaCU|?Wi7Y4&gY0iKkkVOz={+3<(BZAbk97~K> z1A7ehB`YmUOG^xoebJ!EBy#uEUjfH!%{)2`7JIZme-RQMrsf8g z2!itc3IVjh{?f1nb`TW=8q+l!9LMz#gfyerJ2})ACm$MuH>ke9+&XD(dzE?7?jM+{ zS%{mfB}PS>hg`ao=iSBa1^lAV_cJ{fU)QE~lhnB)UHiFdU#~y2|3P5k+@zBhDS#e zxO|nEo^PD$>If0Nysxdc19X|nGo{?&8Y~JVoK-Y=4&YeLLG<>;hBlw{!w(!thaj12 zJ~XvtnFb39rqade0A@@NX6!bCyq{2U3^Fs@=kj4E!54gmPyMjtBO^xx<5=Cfxw#H$ zFWqHZ!;6cketv#Hp&W*jKsI;DnLOZEE}SXpeLJ)E(ooAu%rQf-rPWZ6*ZKbxeA4Qqs6TYqbpDe)?0t z9XpglshNvJbrp$9?XMWCkGC1bIycR?2y{&X4l?&_@rO6Kp>0H&G^lv)TR>rX3Slh! z7nft*X0u%rhF-oS&Xd=J8mt&BHZxSRkq*_lcFqNRR?7j|J=<_c!7vUsBoZCzzPb;S zYkYk&4HM1RJ-=xutiYG_ysm|NqSYtc$-s4hVLZ|HBtYK=yUFeixH*oCRC*0RA&i-? z0!V{*_M4w<58bD)x{CeD&aW^VP%F1Co31G|%3)7|j`0f^(GKakNZ@t>2y^U$Q0Ddx zulZ28c+{|O@A1}1gINv+G;l)`8>doZVLK7xgW+B@-Sb66WaI>bFpXp)nOoPrOr=oZ zR#S0dRGon3c4%k6VY9sr$_5jdM_b!qocff{8ldEP5S_l6Cvh&Tg>7{6zrgOuOZT}a z+V9i7D2_Po8nY6114KD@iL5T|S_OSWnH2B_!w6RUbSbjExZx#g|FP3B{kSar!w-+< zGYtfeer_!_H9i%6bZ(FYY>r*=C4<{(MV~)NG~7-MY55@0<7K_CKQ$B{8=JK!rS_+D zgLmv%{*mncwlhaIxvY{9nD*9m>AM|`Z+N#==KSuLo!%|3Hw&}EHjzd)f#=AlDWC8@ H&bjt~+l|iV diff --git a/integration_tests/snapshots/css/css-selectors/id-selector.ts.a4da180e1.png b/integration_tests/snapshots/css/css-selectors/id-selector.ts.a4da180e1.png new file mode 100644 index 0000000000000000000000000000000000000000..deaa789156ee8d399f5684d7a693b36cdca89ff2 GIT binary patch literal 2356 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_q9Mo-U3d6?5KRF=S*=;5lG$VE^;PhWT=xO>uXPjkz1bP0l+XkK DSbYf` literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png new file mode 100644 index 0000000000000000000000000000000000000000..3e942d5feac1306706d98f1874b82dd5231f1fbf GIT binary patch literal 34430 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 { await snapshot(); }); // error - it('006', async () => { + xit('006', async () => { const style = ; const div =
006 Filler Text
document.head.appendChild(style); diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index 8ef9e6c9ce..0deb2442b8 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -41,7 +41,15 @@ describe("css child selector", () => { await snapshot(); }); - fit("006", async () => { + it("006", async () => { + const style = ; + const div =
Filler Text
; + document.body.appendChild(div); + document.head.appendChild(style); + await snapshot(); + }); + + it("007", async () => { const style = ; document.body =

This text should be green.

; document.head.appendChild(style); diff --git a/integration_tests/specs/css/css-selectors/descendent-selector.ts b/integration_tests/specs/css/css-selectors/descendent-selector.ts index f4a6b91b6c..55087156df 100644 --- a/integration_tests/specs/css/css-selectors/descendent-selector.ts +++ b/integration_tests/specs/css/css-selectors/descendent-selector.ts @@ -47,7 +47,6 @@ describe('css descendent selector', () => { await snapshot(); }); - // error it('007', async () => { const style = ; const div =
007 Filler Text
; diff --git a/integration_tests/specs/css/css-selectors/id-selector.ts b/integration_tests/specs/css/css-selectors/id-selector.ts index dfdd677988..13c7d853a8 100644 --- a/integration_tests/specs/css/css-selectors/id-selector.ts +++ b/integration_tests/specs/css/css-selectors/id-selector.ts @@ -1,4 +1,4 @@ -fdescribe("css id selector", () => { +describe("css id selector", () => { it("001", async () => { const style = ; const div =
001 green Filler Text
; @@ -7,7 +7,7 @@ fdescribe("css id selector", () => { await snapshot(); }); - xit("002", async () => { + it("002", async () => { const style = ; const div =
002 black Filler Text
; document.head.appendChild(style); diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index d5f3836f89..7f79a10760 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -15,7 +15,7 @@ describe("css tag selector", () => { await snapshot(); }); - fit("002", async () => { + it("002", async () => { const style = ; const p =

Test passes if the "Filler Text" below is green.

; const blockquote =
Filler Text
; @@ -27,11 +27,37 @@ describe("css tag selector", () => { await snapshot(); }); - fit("003", async () => { + it("003", async () => { const style = ; const div =
Filler Text
; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); + + fit("004", async () => { + const style = ; + const e1 =

This text should be green. (element)

; + const e2 =
This text should be green. (class)
+ const e3 =
This text should be green. (id)
+ const e4 =
This text should be green. (child)
+ const e5 =
This text should be green. (descendant)
+ const e6 =
This text should be green. (sibling)
+ const e7 =
This text should be green. (attribute)
+ document.head.appendChild(style); + document.body.appendChild(e1); + document.body.appendChild(e2); + document.body.appendChild(e3); + document.body.appendChild(e4); + document.body.appendChild(e5); + document.body.appendChild(e6); + document.body.appendChild(e7); + await snapshot(); + }); }); diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index 80f765b9d5..91791d1a14 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -60,8 +60,12 @@ class ElementRuleCollector { if (rule is! CSSStyleRule) { continue; } - if (evaluator.matchSelector(rule.selectorGroup, element)) { - matchedRules.add(rule); + try { + if (evaluator.matchSelector(rule.selectorGroup, element)) { + matchedRules.add(rule); + } + } catch(error) { + print('selector evaluator error: $error'); } } return matchedRules; diff --git a/webf/lib/src/css/parser/selector.dart b/webf/lib/src/css/parser/selector.dart index a67bd73527..3bdf96d129 100644 --- a/webf/lib/src/css/parser/selector.dart +++ b/webf/lib/src/css/parser/selector.dart @@ -148,6 +148,8 @@ abstract class SimpleSelector extends TreeNode { // ignore: avoid_dynamic_calls String get name => _name.name as String; + bool get isWildcard => _name is Wildcard; + bool get isThis => _name is ThisOperator; @override diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/selector_evaluator.dart index 7c8ae50427..37455d0e3e 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/selector_evaluator.dart @@ -203,7 +203,7 @@ class SelectorEvaluator extends SelectorVisitor { } @override - bool visitElementSelector(ElementSelector node) => _element!.tagName == node.name.toUpperCase(); + bool visitElementSelector(ElementSelector node) => node.isWildcard || _element!.tagName == node.name.toUpperCase(); @override bool visitIdSelector(IdSelector node) => _element!.id == node.name; From bdeab0c76dee0783799109718eff6b07f1fd0712 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Wed, 10 Aug 2022 10:55:40 +0800 Subject: [PATCH 230/498] test: sibling selector --- .../sibling-selector.ts.0c64aab11.png | Bin 0 -> 15700 bytes .../sibling-selector.ts.0df520ad1.png | Bin 0 -> 14512 bytes .../sibling-selector.ts.bc6540e01.png | Bin 0 -> 7048 bytes .../tag-selector.ts.0c6a7df41.png | Bin 10563 -> 10727 bytes .../css/css-selectors/sibling-selector.ts | 36 +++++++++++++++++- .../specs/css/css-selectors/tag-selector.ts | 6 +-- 6 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0c64aab11.png create mode 100644 integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0df520ad1.png create mode 100644 integration_tests/snapshots/css/css-selectors/sibling-selector.ts.bc6540e01.png diff --git a/integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0c64aab11.png b/integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0c64aab11.png new file mode 100644 index 0000000000000000000000000000000000000000..9580ae71ca28494fbd26d8d189a7f82ecbdc2240 GIT binary patch literal 15700 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0df520ad1.png b/integration_tests/snapshots/css/css-selectors/sibling-selector.ts.0df520ad1.png new file mode 100644 index 0000000000000000000000000000000000000000..f8db56d76d433a680cc0460244e6dbabbe8aba0b GIT binary patch literal 14512 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png index 362ea65742a95071af2fc17805374e8163d79bdc..b3849a302e280880002ec08d8b17fa692af205e9 100644 GIT binary patch literal 10727 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 literal 10563 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 ZSyE#ae--`;eh3MH>Kfg`==}NSzW~A!KWzX2 diff --git a/integration_tests/specs/css/css-selectors/sibling-selector.ts b/integration_tests/specs/css/css-selectors/sibling-selector.ts index f658abad53..daacccc170 100644 --- a/integration_tests/specs/css/css-selectors/sibling-selector.ts +++ b/integration_tests/specs/css/css-selectors/sibling-selector.ts @@ -1,4 +1,4 @@ -describe("css sibling selector", () => { +fdescribe("css sibling selector", () => { it("001", async () => { const style = ; + const p =

Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.

; + const div1 =
002 Filler Text
; + const div2 =
Filler Text
; + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(div1); + document.body.appendChild(div2); + await snapshot(); + }); + + it("003", async () => { + const style = ; + document.head.appendChild(style); + document.body.innerHTML = ` +

Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.

+ +
003 Filler Text
+ `; + await snapshot(); + }); + + xit("004", async () => { + const style = ; + document.head.appendChild(style); + document.body.innerHTML = ` +

Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.

+ Filler Text +
004 Filler Text
+ `; + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 7f79a10760..12bc15c401 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -15,8 +15,8 @@ describe("css tag selector", () => { await snapshot(); }); - it("002", async () => { - const style = ; + fit("002", async () => { + const style = ; const p =

Test passes if the "Filler Text" below is green.

; const blockquote =
Filler Text
; const div =
Filler Text
; @@ -35,7 +35,7 @@ describe("css tag selector", () => { await snapshot(); }); - fit("004", async () => { + it("004", async () => { const style = ; + const div =
Filler Text
; + const p =

Test passes if there is a blue border around the viewport and around "Filler Text" above.

+ document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p); + await snapshot(); + }); + + fit("009", async () => { + const style = ; + const p1 =

Should be green

; + const p2 =

Should be green

; + const p3 =

Should be green

; + const p4 =

Should be green

; + const p5 =

Should be green

; + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + await snapshot(); + }); + + fit("0010", async () => { + const style = ; + document.head.appendChild(style); + + const p1 =

Should be green

; + const p2 =

Should be green

; + const p3 =

Should be green

; + const p4 =

Should be green

; + const p5 =

Should be green

; + const p6 =

Should be green

; + const p7 =

Should be green

; + const p8 =

Should be green

; + const p9 =

Should be green

; + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + await snapshot(); + }); + + fit("0011", async () => { + const style = ; + + const p1 =

Should be green

; + const p2 =

Should be green

; + const p3 =

Should be green

; + const p4 =

Should be green

; + const p5 =

Should be green

; + const p6 =

Should be green

; + const p7 =

Should be green

; + const p8 =

Should be green

; + const p9 =

Should be green

; + + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + await snapshot(); + }); + }) diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 12bc15c401..64aae20dbb 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -60,4 +60,35 @@ describe("css tag selector", () => { document.body.appendChild(e7); await snapshot(); }); + + it("005", async () => { + const style = ; + const p =

Test passes if all text on this page is green.

; + const div =
Filler Text
; + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(div); + await snapshot(); + }); + + it("006", async () => { + const style = ; + const p =

Test passes if all text on this page is green.

; + const div =
Filler Text
; + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(div); + await snapshot(); + }); + + fit("007", async () => { + const style = ; + const p =

Test passes if all text on this page is green.

; + const div =
Filler Text
; + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(div); + await snapshot(); + }); + }); diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/selector_evaluator.dart index 37455d0e3e..20f0d2da6b 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/selector_evaluator.dart @@ -115,13 +115,11 @@ class SelectorEvaluator extends SelectorVisitor { // http://dev.w3.org/csswg/selectors-4/#the-root-pseudo case 'root': - // TODO(jmesserly): fix when we have a .ownerDocument pointer - // return _element == _element.ownerDocument.rootElement; - return _element!.nodeName == 'html' && _element!.parentNode == null; + return _element!.nodeName == HTML && (_element!.parentElement == null || _element!.parentElement is Document); // http://dev.w3.org/csswg/selectors-4/#the-empty-pseudo case 'empty': - return _element!.childNodes.any((n) => !(n is Element || n is TextNode && n.data.isNotEmpty)); + return _element!.childNodes.every((n) => !(n is Element || n is TextNode && n.data.isNotEmpty)); // http://dev.w3.org/csswg/selectors-4/#the-blank-pseudo case 'blank': @@ -130,12 +128,47 @@ class SelectorEvaluator extends SelectorVisitor { // http://dev.w3.org/csswg/selectors-4/#the-first-child-pseudo case 'first-child': - return _element!.previousElementSibling == null; + if (_element!.previousElementSibling != null) { + return _element!.previousElementSibling is HeadElement; + } + return true; // http://dev.w3.org/csswg/selectors-4/#the-last-child-pseudo case 'last-child': return _element!.nextElementSibling == null; + //http://drafts.csswg.org/selectors-4/#first-of-type-pseudo + //http://drafts.csswg.org/selectors-4/#last-of-type-pseudo + //http://drafts.csswg.org/selectors-4/#only-of-type-pseudo + case 'first-of-type': + case 'last-of-type': + case 'only-of-type': + var parent = _element!.parentElement; + if (parent != null) { + var children = parent.children.where((Element el) { + return el.nodeName == _element!.nodeName; + }).toList(); + + var index = children.indexOf(_element!); + var isFirst = index == 0; + var isLast = index == children.length - 1; + + if (isFirst && node.name == 'first-of-type') { + return true; + } + + if (isLast && node.name == 'last-of-type') { + return true; + } + + if (isFirst && isLast && node.name == 'only-of-type') { + return true; + } + + return false; + } + + break; // http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo case 'only-child': return _element!.previousElementSibling == null && _element!.nextElementSibling == null; @@ -180,26 +213,138 @@ class SelectorEvaluator extends SelectorVisitor { bool visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) => throw _unimplemented(node); @override - bool visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) { - switch (node.name) { + bool visitPseudoClassFunctionSelector(PseudoClassFunctionSelector selector) { + switch (selector.name) { // http://dev.w3.org/csswg/selectors-4/#child-index // http://dev.w3.org/csswg/selectors-4/#the-nth-child-pseudo case 'nth-child': - // TODO(jmesserly): support An+B syntax too. - final exprs = node.expression; - if (exprs.length == 1 && _element != null) { - final literal = int.parse(exprs[0]); - final parent = _element!.parentElement; - return parent != null && literal > 0 && parent.children.indexOf(_element!) == literal; + case 'nth-last-child': + case 'nth-of-type': + case 'nth-last-of-type': + // i = An + B + var nthData = _parseNthExpressions(selector.expression); + if (nthData == null) { + break; + } + + var A = nthData['A']; + var B = nthData['B'] ?? 0; + + var parent = _element?.parentElement; + + if (parent != null) { + var elIndex; + var children = parent.children; + + if (selector.name == 'nth-of-type' || selector.name == 'nth-last-of-type') { + children = children.where((Element el) { + return el.tagName == _element!.tagName; + }).toList(); + } + + if (selector.name == 'nth-last-child' || selector.name == 'nth-last-of-type') { + elIndex = children.length - children.indexOf(_element!); + } else { + elIndex = children.indexOf(_element!) + 1; + } + + if (A == null) { + return B > 0 && elIndex == B; + } else { + var divideResult = (elIndex - B) / A; + + if (divideResult >= 1) { + return divideResult % divideResult.ceil() == 0; + } else { + return divideResult == 0; + } + } + } else { + return false; } break; } - throw _unimplemented(node); + throw _unimplemented(selector); + } + + num _countExpressionList(List list) { + String first = list[0]; + num sum = 0; + num modulus = 1; + if (first == '-') { + modulus = -1; + list = list.sublist(1); + } + list.forEach((String item) { + num? value = num.tryParse(item); + if (value != null) { + sum += value; + } + }); + return sum * modulus; + } + + Map? _parseNthExpressions(List exprs) { + num? A; + num B = 0; + + if (exprs.isNotEmpty) { + if (exprs.length == 1) { + String value = exprs[0]; + if (isNumeric(value)) { + B = num.parse(value); + } else { + if (value == 'even') { + A = 2; + B = 1; + } else if (value == 'odd') { + A = 2; + B = 0; + } else if (value == 'n') { + A = 1; + B = 0; + } else { + return null; + } + } + } + + List bTerms = []; + List aTerms = []; + var nIndex = exprs.indexWhere((expr) { + return expr.toString() == 'n'; + }); + + if (nIndex > -1) { + bTerms.addAll(exprs.sublist(nIndex + 1)); + aTerms.addAll(exprs.sublist(0, nIndex)); + } else { + bTerms.addAll(exprs); + } + + if (bTerms.isNotEmpty) { + B = _countExpressionList(bTerms); + } + + if (aTerms.isNotEmpty) { + if (aTerms.length == 1 && aTerms[0] == '-') { + A = -1; + } else { + A = _countExpressionList(aTerms); + } + } else { + if (nIndex == 0) { + A = 1; + } + } + } + + return {'A': A, 'B': B}; } bool isNumeric(String s) { - return double.tryParse(s) != null; + return num.tryParse(s) != null; } @override diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 4398fcc71e..43cafef101 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -18,6 +18,7 @@ import 'package:webf/rendering.dart'; final RegExp _splitRegExp = RegExp(r'\s+'); const String _ONE_SPACE = ' '; const String _STYLE_PROPERTY = 'style'; +const String _ID = 'id'; const String _CLASS_NAME = 'class'; /// Defined by W3C Standard, @@ -80,7 +81,12 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // Default to unknown, assign by [createElement], used by inspector. String tagName = UNKNOWN; - String? get id => getAttribute('id'); + String? _id; + String? get id => _id; + set id(String? id) { + _id = id; + recalculateStyle(); + } // Is element an replaced element. // https://drafts.csswg.org/css-display/#replaced-element @@ -976,6 +982,8 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // @TODO: Parse inline style css text. } else if (_CLASS_NAME == qualifiedName) { className = value; + } else if (_ID == qualifiedName) { + id = value; } } From b692a772977cb461cc2cc89a982f8af659bef941 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Wed, 10 Aug 2022 23:40:02 +0800 Subject: [PATCH 232/498] test: only child selector --- .../child-selectors.ts.a98cfcea1.png | Bin 0 -> 6227 bytes .../css/css-selectors/child-selectors.ts | 381 +++++++++++------- .../css/css-selectors/sibling-selector.ts | 2 +- .../specs/css/css-selectors/tag-selector.ts | 5 +- 4 files changed, 247 insertions(+), 141 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/child-selectors.ts.a98cfcea1.png diff --git a/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a98cfcea1.png b/integration_tests/snapshots/css/css-selectors/child-selectors.ts.a98cfcea1.png new file mode 100644 index 0000000000000000000000000000000000000000..622e896b11dcc60051718f6a325f73c630dc6afe GIT binary patch literal 6227 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 AH~;_u literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index 3717224ff9..4855b241c4 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -1,89 +1,123 @@ describe("css child selector", () => { - it("001", async () => { - const style = ; - const h1 =

Filler Text

; - document.head.appendChild(style); - document.body.appendChild(h1); - await snapshot(); - }); - it("002", async () => { - const style = ; - const div =

Filler Text

; - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it("003", async () => { - const style = ; - const div =

Filler Text

; - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it("004", async () => { - const style = ; - const div1 =
Filler Text
; - const div2 =
Filler Text
; - const p =

Test passes if the first "Filler Text" above is green and the second one is black.

; - document.head.appendChild(style); - document.body.appendChild(div1); - document.body.appendChild(div2); - document.body.appendChild(p); - await snapshot(); - }); - - it("005", async () => { - const style = ; - document.body = Filler Text
Filler Text
Filler Text
; - document.head.appendChild(style); - await snapshot(); - }); - - it("006", async () => { - const style = ; - const div =
Filler Text
; - document.body.appendChild(div); - document.head.appendChild(style); - await snapshot(); - }); - - it("007", async () => { - const style = ; - document.body =

This text should be green.

; - document.head.appendChild(style); - await snapshot(); - }); - - it("008", async () => { - const style = ; - const div =
Filler Text
; - const p =

Test passes if there is a blue border around the viewport and around "Filler Text" above.

- document.head.appendChild(style); - document.body.appendChild(div); - document.body.appendChild(p); - await snapshot(); - }); - - fit("009", async () => { - const style = ; - const p1 =

Should be green

; - const p2 =

Should be green

; - const p3 =

Should be green

; - const p4 =

Should be green

; - const p5 =

Should be green

; - document.head.appendChild(style); - document.body.appendChild(p1); - document.body.appendChild(p2); - document.body.appendChild(p3); - document.body.appendChild(p4); - document.body.appendChild(p5); - await snapshot(); - }); - - fit("0010", async () => { - const style = ; + const h1 =

Filler Text

; + document.head.appendChild(style); + document.body.appendChild(h1); + await snapshot(); + }); + it("002", async () => { + const style = ; + const div = ( +
+

Filler Text

+
+ ); + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it("003", async () => { + const style = ; + const div = ( +
+
+

Filler Text

{" "} +
{" "} +
+ ); + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + + it("004", async () => { + const style = ; + const div1 =
Filler Text
; + const div2 =
Filler Text
; + const p = ( +

+ Test passes if the first "Filler Text" above is green and the second one + is black. +

+ ); + document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(p); + await snapshot(); + }); + + it("005", async () => { + const style = ; + document.body = ( + + Filler Text
Filler Text
+
Filler Text
+ + ); + document.head.appendChild(style); + await snapshot(); + }); + + it("006", async () => { + const style = ; + const div =
Filler Text
; + document.body.appendChild(div); + document.head.appendChild(style); + await snapshot(); + }); + + it("007", async () => { + const style = ( + + ); + document.body = ( + +

This text should be green.

+ + ); + document.head.appendChild(style); + await snapshot(); + }); + + it("008", async () => { + const style = ( + + ); + const div =
Filler Text
; + const p = ( +

+ Test passes if there is a blue border around the viewport and around + "Filler Text" above. +

+ ); + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p); + await snapshot(); + }); + + it("009", async () => { + const style = ; + const p1 =

Should be green

; + const p2 =

Should be green

; + const p3 =

Should be green

; + const p4 =

Should be green

; + const p5 =

Should be green

; + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + await snapshot(); + }); + + it("010", async () => { + const style = ( + ; - document.head.appendChild(style); - - const p1 =

Should be green

; - const p2 =

Should be green

; - const p3 =

Should be green

; - const p4 =

Should be green

; - const p5 =

Should be green

; - const p6 =

Should be green

; - const p7 =

Should be green

; - const p8 =

Should be green

; - const p9 =

Should be green

; - document.body.appendChild(p1); - document.body.appendChild(p2); - document.body.appendChild(p3); - document.body.appendChild(p4); - document.body.appendChild(p5); - document.body.appendChild(p6); - document.body.appendChild(p7); - document.body.appendChild(p8); - document.body.appendChild(p9); - await snapshot(); - }); - - fit("0011", async () => { - const style = + ); + document.head.appendChild(style); + + const p1 =

Should be green

; + const p2 =

Should be green

; + const p3 =

Should be green

; + const p4 =

Should be green

; + const p5 =

Should be green

; + const p6 =

Should be green

; + const p7 =

Should be green

; + const p8 =

Should be green

; + const p9 =

Should be green

; + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + await snapshot(); + }); + + it("011", async () => { + const style = ( + ; - - const p1 =

Should be green

; - const p2 =

Should be green

; - const p3 =

Should be green

; - const p4 =

Should be green

; - const p5 =

Should be green

; - const p6 =

Should be green

; - const p7 =

Should be green

; - const p8 =

Should be green

; - const p9 =

Should be green

; - - document.head.appendChild(style); - document.body.appendChild(p1); - document.body.appendChild(p2); - document.body.appendChild(p3); - document.body.appendChild(p4); - document.body.appendChild(p5); - document.body.appendChild(p6); - document.body.appendChild(p7); - document.body.appendChild(p8); - document.body.appendChild(p9); - await snapshot(); - }); - -}) + `} + ); + + const p1 =

Should be green

; + const p2 =

Should be green

; + const p3 =

Should be green

; + const p4 =

Should be green

; + const p5 =

Should be green

; + const p6 =

Should be green

; + const p7 =

Should be green

; + const p8 =

Should be green

; + const p9 =

Should be green

; + + document.head.appendChild(style); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + await snapshot(); + }); + + it("012", async () => { + const style = ( + + ); + const ul = ( +
    +
  • 012 Should be green
  • +
+ ); + document.head.appendChild(style); + document.body.appendChild(ul); + await snapshot(); + }); + + it("013", async () => { + const style = ( + + ); + const ul = ( +
    +
  • 013 Should not be green
  • +
  • 013 Should not be green
  • +
+ ); + document.head.appendChild(style); + document.body.appendChild(ul); + await snapshot(); + }); + + fit("014", async () => { + const style = ( + + ); + + const p =

014

; + const p1 =

Should be green

; + const p2 =

Should be green

; + const p3 =

Should be green

; + const p4 =

Should be green

; + const p5 =

Should be green

; + const p6 =

Should be green

; + const p7 =

Should be green

; + const p8 =

Should be green

; + const p9 =

Should be green

; + + document.head.appendChild(style); + document.body.appendChild(p); + document.body.appendChild(p1); + document.body.appendChild(p2); + document.body.appendChild(p3); + document.body.appendChild(p4); + document.body.appendChild(p5); + document.body.appendChild(p6); + document.body.appendChild(p7); + document.body.appendChild(p8); + document.body.appendChild(p9); + await snapshot(); + }); +}); diff --git a/integration_tests/specs/css/css-selectors/sibling-selector.ts b/integration_tests/specs/css/css-selectors/sibling-selector.ts index daacccc170..7d9582a2cb 100644 --- a/integration_tests/specs/css/css-selectors/sibling-selector.ts +++ b/integration_tests/specs/css/css-selectors/sibling-selector.ts @@ -1,4 +1,4 @@ -fdescribe("css sibling selector", () => { +describe("css sibling selector", () => { it("001", async () => { const style = ; const p =

Test passes if the "Filler Text" below is green.

; const blockquote =
Filler Text
; @@ -81,7 +81,7 @@ describe("css tag selector", () => { await snapshot(); }); - fit("007", async () => { + it("007", async () => { const style = ; const p =

Test passes if all text on this page is green.

; const div =
Filler Text
; @@ -90,5 +90,4 @@ describe("css tag selector", () => { document.body.appendChild(div); await snapshot(); }); - }); From c05658830c42a0abd10ac908d2da892d160a23e6 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Wed, 10 Aug 2022 23:58:35 +0800 Subject: [PATCH 233/498] test: supplement attribute the remaining selectors --- .../attribute-selector.ts.ac47caa61.png | Bin 0 -> 8302 bytes .../css/css-selectors/attribute-selector.ts | 62 ++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 integration_tests/snapshots/css/css-selectors/attribute-selector.ts.ac47caa61.png diff --git a/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.ac47caa61.png b/integration_tests/snapshots/css/css-selectors/attribute-selector.ts.ac47caa61.png new file mode 100644 index 0000000000000000000000000000000000000000..8d7ac483d67bce11848c6500f134be2d1d61ca01 GIT binary patch literal 8302 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-selectors/attribute-selector.ts b/integration_tests/specs/css/css-selectors/attribute-selector.ts index 61a1503b96..5089a15137 100644 --- a/integration_tests/specs/css/css-selectors/attribute-selector.ts +++ b/integration_tests/specs/css/css-selectors/attribute-selector.ts @@ -46,4 +46,66 @@ describe('css attribute selector', () => { document.body.appendChild(div); await snapshot(); }); + + fit('007', async () => { + const style = ; + const div1 =
should be green
+ const div2 =
should be green
+ const div3 =
should not be green
+ document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + await snapshot(); + }); + + it('007', async () => { + const style = ; + const div1 =
should be green
+ const div2 =
should be green
+ const div3 =
should not be green
+ document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + await snapshot(); + }); + + it('008', async () => { + const style = ; + const div1 =
should be green
+ const div2 =
should not be green
+ const div3 =
should be green
+ document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + await snapshot(); + }); + + it('009', async () => { + const style = ; + const div1 =
should be green
+ const div2 =
should be green
+ const div3 =
should be green
+ document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + await snapshot(); + }); + + it('010', async () => { + const style = ; + const div1 =
should be green
+ const div2 =
should be green
+ const div3 =
should not be green
+ const div4 =
should not be green
+ document.head.appendChild(style); + document.body.appendChild(div1); + document.body.appendChild(div2); + document.body.appendChild(div3); + document.body.appendChild(div4); + await snapshot(); + }); }); From 6f4e949fb63d57cf8c6edadc622a24f43cf0ebc1 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sat, 13 Aug 2022 20:17:35 +0800 Subject: [PATCH 234/498] feat: inline style --- .../css/css-selectors/attribute-selector.ts | 2 +- .../css/css-selectors/child-selectors.ts | 2 +- webf/lib/src/css/element_rule_collector.dart | 12 ++++- webf/lib/src/css/parser/parser.dart | 50 ++++++++++--------- webf/lib/src/css/rule_set.dart | 14 +++++- webf/lib/src/css/style_property.dart | 3 ++ webf/lib/src/dom/element.dart | 3 +- webf/test/src/css/style_inline_parser.dart | 19 +++++++ webf/test/src/css/style_rule_parser.dart | 29 +++++------ webf/test/webf_test.dart | 2 + 10 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 webf/test/src/css/style_inline_parser.dart diff --git a/integration_tests/specs/css/css-selectors/attribute-selector.ts b/integration_tests/specs/css/css-selectors/attribute-selector.ts index 5089a15137..0ee05e890e 100644 --- a/integration_tests/specs/css/css-selectors/attribute-selector.ts +++ b/integration_tests/specs/css/css-selectors/attribute-selector.ts @@ -47,7 +47,7 @@ describe('css attribute selector', () => { await snapshot(); }); - fit('007', async () => { + it('007', async () => { const style = ; const div1 =
should be green
const div2 =
should be green
diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index 4855b241c4..085f18d3af 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -275,7 +275,7 @@ describe("css child selector", () => { await snapshot(); }); - fit("014", async () => { + it("014", async () => { const style = ( ; - const div =
001- Filler Text
; + const style = ; + const div =
001 should be green
; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it('002', async () => { - const style = ; - const div =
002 Filler Text < /div>; + const style = ; + const div =
002 should be green < /div>; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it('003', async () => { - const style = ; - const div1 =
Filler Text
; - const div2 =
003 Filler Text
; + const style = ; + const div1 =
should not be green
; + const div2 =
003 should be green
; document.head.appendChild(style); document.body.appendChild(div1); document.body.appendChild(div2); await snapshot(); }); it('004', async () => { - const style = ; - const div =
004 Filler Text
; + const style = ; + const div =
004 should be green
; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it('005', async () => { - const style = ; - const div =
005 Filler Text
; + const style = ; + const div =
005 should be green
; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); // error xit('006', async () => { - const style = ; + const style = ; const div =
006 Filler Text
document.head.appendChild(style); document.body.appendChild(div); @@ -49,7 +49,7 @@ describe('css attribute selector', () => { it('007', async () => { const style = ; - const div1 =
should be green
+ const div1 =
7 should be green
const div2 =
should be green
const div3 =
should not be green
document.head.appendChild(style); @@ -59,9 +59,9 @@ describe('css attribute selector', () => { await snapshot(); }); - it('007', async () => { + it('008', async () => { const style = ; - const div1 =
should be green
+ const div1 =
8 should be green
const div2 =
should be green
const div3 =
should not be green
document.head.appendChild(style); @@ -71,11 +71,14 @@ describe('css attribute selector', () => { await snapshot(); }); - it('008', async () => { + it('009', async () => { const style = ; - const div1 =
should be green
+ const div1 =
9 should be green
const div2 =
should not be green
const div3 =
should be green
+ for (var oldStyle in document.getElementsByTagName("style")) { + document.head.removeChild(oldStyle); + } document.head.appendChild(style); document.body.appendChild(div1); document.body.appendChild(div2); @@ -83,9 +86,9 @@ describe('css attribute selector', () => { await snapshot(); }); - it('009', async () => { + it('010', async () => { const style = ; - const div1 =
should be green
+ const div1 =
10 should be green
const div2 =
should be green
const div3 =
should be green
document.head.appendChild(style); @@ -95,9 +98,9 @@ describe('css attribute selector', () => { await snapshot(); }); - it('010', async () => { + it('011', async () => { const style = ; - const div1 =
should be green
+ const div1 =
11 should be green
const div2 =
should be green
const div3 =
should not be green
const div4 =
should not be green
diff --git a/integration_tests/specs/css/css-selectors/child-indexed.ts b/integration_tests/specs/css/css-selectors/child-indexed.ts deleted file mode 100644 index b2396dbae2..0000000000 --- a/integration_tests/specs/css/css-selectors/child-indexed.ts +++ /dev/null @@ -1,188 +0,0 @@ -/*auto generated*/ -describe('child-indexed', () => { - it('pseudo-class', async () => { - var check = function (element, selectors, qsRoot) { - for (var i = 0; i < selectors.length; ++i) { - var selector = selectors[i][0]; - var expected = selectors[i][1]; - test(function () { - assert_equals(expected, element.matches(selector)); - - if (qsRoot) { - assert_equals(expected, element === qsRoot.querySelector(selector)); - var qsa = qsRoot.querySelectorAll(selector); - assert_equals(expected, !!qsa.length && element === qsa[0]); - } - }, 'Expected ' + - element.tagName + - ' element to ' + - (expected ? 'match ' : 'not match ') + - selector + - ' with matches' + - (qsRoot ? ', querySelector(), and querySelectorAll()' : '')); - } - }; - - var rootOfSubtreeSelectors = [ - [':first-child', true], - [':last-child', true], - [':only-child', true], - [':first-of-type', true], - [':last-of-type', true], - [':only-of-type', true], - [':nth-child(1)', true], - [':nth-child(n)', true], - [':nth-last-child(1)', true], - [':nth-last-child(n)', true], - [':nth-of-type(1)', true], - [':nth-of-type(n)', true], - [':nth-last-of-type(1)', true], - [':nth-last-of-type(n)', true], - [':nth-child(2)', false], - [':nth-last-child(2)', false], - [':nth-of-type(2)', false], - [':nth-last-of-type(2)', false], - ]; - - check(document.documentElement, rootOfSubtreeSelectors, document); - check(document.createElement('div'), rootOfSubtreeSelectors); - - var fragment = document.createDocumentFragment(); - var div = document.createElement('div'); - fragment.appendChild(div); - check(div, rootOfSubtreeSelectors, fragment); - - await matchViewportSnapshot(); - }); - - it('no-parent-ref', async () => { - let p; - let p_1; - let p_2; - let p_3; - let p_4; - let p_5; - let p_6; - let p_7; - let p_8; - p = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_1 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_2 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_3 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_4 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_5 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_6 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_7 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - p_8 = createElement( - 'p', - { - style: { - 'box-sizing': 'border-box', - }, - }, - [ - createText(`Should be green -`), - ] - ); - BODY.appendChild(p); - BODY.appendChild(p_1); - BODY.appendChild(p_2); - BODY.appendChild(p_3); - BODY.appendChild(p_4); - BODY.appendChild(p_5); - BODY.appendChild(p_6); - BODY.appendChild(p_7); - BODY.appendChild(p_8); - - await matchViewportSnapshot(); - }); -}); diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index 085f18d3af..406ec70741 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -1,18 +1,21 @@ describe("css child selector", () => { it("001", async () => { const style = ; - const h1 =

Filler Text

; + const h1 =

001 Text should not be green

; + document.head.appendChild(style); document.body.appendChild(h1); await snapshot(); }); + it("002", async () => { const style = ; const div = (
-

Filler Text

+

002 Text should be green

); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -23,10 +26,11 @@ describe("css child selector", () => { const div = (
-

Filler Text

{" "} +

003 Text should not be green

{" "}
{" "}
); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -34,14 +38,10 @@ describe("css child selector", () => { it("004", async () => { const style = ; - const div1 =
Filler Text
; - const div2 =
Filler Text
; - const p = ( -

- Test passes if the first "Filler Text" above is green and the second one - is black. -

- ); + const div1 =
004 Text should be green
; + const div2 =
Text should not be green
; + const p =

Text should not be green

; + document.head.appendChild(style); document.body.appendChild(div1); document.body.appendChild(div2); @@ -53,17 +53,18 @@ describe("css child selector", () => { const style = ; document.body = ( - Filler Text
Filler Text
-
Filler Text
+ 005
should be green
+
should not be green
); + document.head.appendChild(style); await snapshot(); }); it("006", async () => { const style = ; - const div =
Filler Text
; + const div =
006 should be green
; document.body.appendChild(div); document.head.appendChild(style); await snapshot(); @@ -75,7 +76,7 @@ describe("css child selector", () => { ); document.body = ( -

This text should be green.

+

007 This text should be green.

); document.head.appendChild(style); @@ -83,16 +84,10 @@ describe("css child selector", () => { }); it("008", async () => { - const style = ( - - ); - const div =
Filler Text
; - const p = ( -

- Test passes if there is a blue border around the viewport and around - "Filler Text" above. -

- ); + const style = ; + const div =
008 Filler Text should be green
; + const p =

Filler Text

; + document.head.appendChild(style); document.body.appendChild(div); document.body.appendChild(p); @@ -101,11 +96,12 @@ describe("css child selector", () => { it("009", async () => { const style = ; - const p1 =

Should be green

; + const p1 =

009 Should be green

; const p2 =

Should be green

; const p3 =

Should be green

; const p4 =

Should be green

; const p5 =

Should be green

; + document.head.appendChild(style); document.body.appendChild(p1); document.body.appendChild(p2); @@ -153,9 +149,10 @@ describe("css child selector", () => { } `} ); + document.head.appendChild(style); - const p1 =

Should be green

; + const p1 =

10 Should be green

; const p2 =

Should be green

; const p3 =

Should be green

; const p4 =

Should be green

; @@ -215,7 +212,7 @@ describe("css child selector", () => { `} ); - const p1 =

Should be green

; + const p1 =

11 Should be green

; const p2 =

Should be green

; const p3 =

Should be green

; const p4 =

Should be green

; @@ -225,6 +222,7 @@ describe("css child selector", () => { const p8 =

Should be green

; const p9 =

Should be green

; + document.head.appendChild(style); document.body.appendChild(p1); document.body.appendChild(p2); @@ -251,12 +249,13 @@ describe("css child selector", () => {
  • 012 Should be green
  • ); + document.head.appendChild(style); document.body.appendChild(ul); await snapshot(); }); - it("013", async () => { + xit("013", async () => { const style = ( + ); - const p =

    014

    ; - const p1 =

    Should be green

    ; - const p2 =

    Should be green

    ; - const p3 =

    Should be green

    ; - const p4 =

    Should be green

    ; - const p5 =

    Should be green

    ; + const p1 =

    Should not be green

    ; + const p2 =

    Should not be green

    ; + const p3 =

    Should not be green

    ; + const p4 =

    Should not be green

    ; + const p5 =

    Should not be green

    ; const p6 =

    Should be green

    ; - const p7 =

    Should be green

    ; - const p8 =

    Should be green

    ; - const p9 =

    Should be green

    ; - + const p7 =

    Should not be green

    ; + const p8 =

    Should not be green

    ; + const p9 =

    Should not be green

    ; + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(p1); diff --git a/integration_tests/specs/css/css-selectors/descendent-selector.ts b/integration_tests/specs/css/css-selectors/descendent-selector.ts index 55087156df..ec8f0afbd3 100644 --- a/integration_tests/specs/css/css-selectors/descendent-selector.ts +++ b/integration_tests/specs/css/css-selectors/descendent-selector.ts @@ -2,7 +2,8 @@ describe('css descendent selector', () => { it('001', async () => { const style = ; const div =
    001 Filler Text < /div >; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -10,7 +11,8 @@ describe('css descendent selector', () => { it('002', async () => { const style = ; const div =
    002 Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -18,7 +20,8 @@ describe('css descendent selector', () => { it('003', async () => { const style = ; const div =
    003 Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -26,7 +29,8 @@ describe('css descendent selector', () => { it('004', async () => { const style = ; const div =
    004 Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -34,7 +38,8 @@ describe('css descendent selector', () => { it('005', async () => { const style = ; const div =
    005 Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -42,7 +47,8 @@ describe('css descendent selector', () => { it('006', async () => { const style = ; const div =
    006 Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -50,7 +56,8 @@ describe('css descendent selector', () => { it('007', async () => { const style = ; const div =
    007 Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -58,6 +65,7 @@ describe('css descendent selector', () => { it('008', async () => { const style = ; const div =
    008 Filler Text < /em>
    ; + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -66,7 +74,8 @@ describe('css descendent selector', () => { it('009', async () => { const style = ; const div =
    009 Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -75,7 +84,8 @@ describe('css descendent selector', () => { const style = ; const div =
    010 Filler Text < /em>
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); diff --git a/integration_tests/specs/css/css-selectors/id-selector.ts b/integration_tests/specs/css/css-selectors/id-selector.ts index 13c7d853a8..210db3f8ba 100644 --- a/integration_tests/specs/css/css-selectors/id-selector.ts +++ b/integration_tests/specs/css/css-selectors/id-selector.ts @@ -2,7 +2,8 @@ describe("css id selector", () => { it("001", async () => { const style = ; const div =
    001 green Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -10,7 +11,8 @@ describe("css id selector", () => { it("002", async () => { const style = ; const div =
    002 black Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -18,7 +20,8 @@ describe("css id selector", () => { it("003", async () => { const style = ; const div =
    003 green Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -26,7 +29,8 @@ describe("css id selector", () => { xit("004", async () => { const style = ; const div =
    004 black Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -34,7 +38,8 @@ describe("css id selector", () => { it("005", async () => { const style = ; const div =
    005 green Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); @@ -42,7 +47,8 @@ describe("css id selector", () => { it("006", async () => { const style = ; const div =
    006 green Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); diff --git a/integration_tests/specs/css/css-selectors/sibling-selector.ts b/integration_tests/specs/css/css-selectors/sibling-selector.ts index 7d9582a2cb..9567985d8b 100644 --- a/integration_tests/specs/css/css-selectors/sibling-selector.ts +++ b/integration_tests/specs/css/css-selectors/sibling-selector.ts @@ -22,7 +22,8 @@ describe("css sibling selector", () => { const p8 =
    This sentence must be green.
    ; const p9 =
    This sentence must be green.
    ; const p10 =
    This sentence must be green.
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(div); document.body.appendChild(p1); document.body.appendChild(p2); @@ -42,7 +43,8 @@ describe("css sibling selector", () => { const p =

    Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.

    ; const div1 =
    002 Filler Text
    ; const div2 =
    Filler Text
    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(div1); document.body.appendChild(div2); @@ -51,7 +53,8 @@ describe("css sibling selector", () => { it("003", async () => { const style = ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.innerHTML = `

    Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.

    @@ -62,7 +65,8 @@ describe("css sibling selector", () => { xit("004", async () => { const style = ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.innerHTML = `

    Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.

    Filler Text diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index b13a67eae9..6f34fde38e 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -1,12 +1,13 @@ describe("css tag selector", () => { it("001", async () => { const style = ; - const p1 =

    This sentence must be green.

    ; + const p1 =

    001 This sentence must be green.

    ; const p2 =

    This sentence must be green.

    ; const p3 =

    This sentence must be green.

    ; const p4 =

    This sentence must be green.

    ; const p5 =

    This sentence must be green.

    ; - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(p1); document.body.appendChild(p2); document.body.appendChild(p3); @@ -19,8 +20,9 @@ describe("css tag selector", () => { const style = ; const p =

    Test passes if the "Filler Text" below is green.

    ; const blockquote =
    Filler Text
    ; - const div =
    Filler Text
    ; - document.head.appendChild(style); + const div =
    002 Filler Text
    ; + + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(blockquote); document.body.appendChild(div); @@ -29,20 +31,15 @@ describe("css tag selector", () => { it("003", async () => { const style = ; - const div =
    Filler Text
    ; - document.head.appendChild(style); + const div =
    003 Filler Text
    ; + + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it("004", async () => { - const style = ; + const style = ; const e1 =

    This text should be green. (element)

    ; const e2 =
    This text should be green. (class)
    const e3 =
    This text should be green. (id)
    @@ -50,7 +47,8 @@ describe("css tag selector", () => { const e5 =
    This text should be green. (descendant)
    const e6 =
    This text should be green. (sibling)
    const e7 =
    This text should be green. (attribute)
    - document.head.appendChild(style); + + document.head.appendChild(style); document.body.appendChild(e1); document.body.appendChild(e2); document.body.appendChild(e3); @@ -64,8 +62,9 @@ describe("css tag selector", () => { it("005", async () => { const style = ; const p =

    Test passes if all text on this page is green.

    ; - const div =
    Filler Text
    ; - document.head.appendChild(style); + const div =
    005 Filler Text
    ; + + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(div); await snapshot(); @@ -74,7 +73,8 @@ describe("css tag selector", () => { it("006", async () => { const style = ; const p =

    Test passes if all text on this page is green.

    ; - const div =
    Filler Text
    ; + const div =
    006 Filler Text
    ; + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(div); @@ -82,9 +82,10 @@ describe("css tag selector", () => { }); it("007", async () => { - const style = ; + const style = ; const p =

    Test passes if all text on this page is green.

    ; - const div =
    Filler Text
    ; + const div =
    007 Filler Text
    ; + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(div); diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index a50809be46..d825bbaeff 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -27,7 +27,9 @@ class RuleSet { void addRule(CSSRule rule) { rules.add(rule); if (rule is CSSStyleRule) { - findBestRuleSetAndAdd(rule); + for (final selector in rule.selectorGroup.selectors) { + findBestRuleSetAndAdd(selector, rule); + } } else { assert(false, 'Unsupported rule type: ${rule.runtimeType}'); } @@ -43,31 +45,33 @@ class RuleSet { } // indexed by selectorText - void findBestRuleSetAndAdd(CSSStyleRule rule) { - String? id, className, attributeName, tagName; - - for (final selector in rule.selectorGroup.selectors) { - for (final simpleSelectorSequence in selector.simpleSelectorSequences) { - final simpleSelector = simpleSelectorSequence.simpleSelector; - switch (simpleSelector.runtimeType) { - case IdSelector: - id = simpleSelector.name; - break; - case ClassSelector: - className = simpleSelector.name; - break; - case AttributeSelector: - attributeName = simpleSelector.name; - break; - case ElementSelector: - tagName = simpleSelector.name; - break; + void findBestRuleSetAndAdd(Selector selector, CSSRule rule) { + String? id, className, attributeName, tagName, pseudoName; + + for (final simpleSelectorSequence in selector.simpleSelectorSequences.reversed) { + final simpleSelector = simpleSelectorSequence.simpleSelector; + if (simpleSelector.runtimeType == IdSelector) { + id = simpleSelector.name; + } else if (simpleSelector.runtimeType == ClassSelector) { + className = simpleSelector.name; + } else if (simpleSelector.runtimeType == AttributeSelector) { + attributeName = simpleSelector.name; + } else if (simpleSelector.runtimeType == ElementSelector) { + if (simpleSelector.isWildcard) { + break; } + tagName = simpleSelector.name; + } else if (simpleSelector.runtimeType == PseudoClassSelector || + simpleSelector.runtimeType == PseudoElementSelector) { + pseudoName = simpleSelector.name; + } + + if (id != null || className != null || attributeName != null || tagName != null || pseudoName != null) { + break; } } - bool isInserted = false; + void insertRule(String key, CSSRule rule, CSSMap map) { - isInserted = true; List? rules = map[key] ?? []; rules.add(rule); map[key] = rules; @@ -75,22 +79,24 @@ class RuleSet { if (id != null && id.isNotEmpty == true) { insertRule(id, rule, idRules); + return; } if (className != null && className.isNotEmpty == true) { insertRule(className, rule, classRules); + return; } if (attributeName != null && attributeName.isNotEmpty == true) { insertRule(attributeName.toUpperCase(), rule, attributeRules); + return; } if (tagName != null && tagName.isNotEmpty == true) { insertRule(tagName.toUpperCase(), rule, tagRules); + return; } - if (!isInserted) { - universalRules.add(rule); - } + universalRules.add(rule); } } diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/selector_evaluator.dart index 308d9a4673..479b2b2801 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/selector_evaluator.dart @@ -110,7 +110,7 @@ class SelectorEvaluator extends SelectorVisitor { @override bool visitPseudoClassSelector(PseudoClassSelector node) { - switch (node.name) { + switch (node.name.toLowerCase()) { // http://dev.w3.org/csswg/selectors-4/#structural-pseudos // http://dev.w3.org/csswg/selectors-4/#the-root-pseudo @@ -135,7 +135,7 @@ class SelectorEvaluator extends SelectorVisitor { // http://dev.w3.org/csswg/selectors-4/#the-last-child-pseudo case 'last-child': - return _element!.nextElementSibling == null; + return _element!.nextSibling == null; //http://drafts.csswg.org/selectors-4/#first-of-type-pseudo //http://drafts.csswg.org/selectors-4/#last-of-type-pseudo @@ -171,7 +171,7 @@ class SelectorEvaluator extends SelectorVisitor { break; // http://dev.w3.org/csswg/selectors-4/#the-only-child-pseudo case 'only-child': - return _element!.previousElementSibling == null && _element!.nextElementSibling == null; + return _element!.previousSibling == null && _element!.nextSibling == null; // http://dev.w3.org/csswg/selectors-4/#link case 'link': diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 87788267da..d7d8a42458 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -139,6 +139,8 @@ class Document extends Node { if (documentElement == child) { documentElement = null; } + ruleSet.reset(); + styleSheets.clear(); return super.removeChild(child); } From 72871416db116aa279ed8d01f2f31941a313c5d1 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Fri, 19 Aug 2022 01:17:25 +0800 Subject: [PATCH 242/498] feat: update sheet style --- webf/lib/dom.dart | 1 + webf/lib/src/css/parser/parser.dart | 5 +- webf/lib/src/css/style_sheet.dart | 43 +++++-- webf/lib/src/dom/document.dart | 25 ++-- webf/lib/src/dom/element.dart | 4 +- webf/lib/src/dom/elements/head.dart | 57 ++++++--- webf/lib/src/dom/node.dart | 100 +++++++++++++++- webf/lib/src/dom/style_node_manager.dart | 141 +++++++++++++++++++++++ 8 files changed, 341 insertions(+), 35 deletions(-) create mode 100644 webf/lib/src/dom/style_node_manager.dart diff --git a/webf/lib/dom.dart b/webf/lib/dom.dart index 8e2ea6e846..2fa036b76d 100644 --- a/webf/lib/dom.dart +++ b/webf/lib/dom.dart @@ -20,6 +20,7 @@ export 'src/dom/screen.dart'; export 'src/dom/element_registry.dart'; // Elements +export 'src/dom/style_node_manager.dart'; export 'src/dom/elements/semantics_text.dart'; export 'src/dom/elements/grouping_content.dart'; export 'src/dom/elements/sections.dart'; diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index cf070f02f6..8d308f09af 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -83,10 +83,7 @@ class CSSParser { /// Main entry point for parsing an entire CSS file. CSSStyleSheet parse() { final rules = parseRules(); - - final ruleSet = RuleSet(); - ruleSet.addRules(rules); - return CSSStyleSheet(rules); + return CSSStyleSheet(tokenizer._text, rules); } Map parseInlineStyle() { diff --git a/webf/lib/src/css/style_sheet.dart b/webf/lib/src/css/style_sheet.dart index 93eec5a0f7..08a57f8590 100644 --- a/webf/lib/src/css/style_sheet.dart +++ b/webf/lib/src/css/style_sheet.dart @@ -9,18 +9,25 @@ abstract class StyleSheet {} const String _CSSStyleSheetType = 'text/css'; // https://drafts.csswg.org/cssom-1/#cssstylesheet -class CSSStyleSheet implements StyleSheet { +class CSSStyleSheet implements StyleSheet, Comparable { String type = _CSSStyleSheetType; /// A Boolean indicating whether the stylesheet is disabled. False by default. bool disabled = false; /// A string containing the baseURL used to resolve relative URLs in the stylesheet. - String? herf; + String? href; - final List cssRules; + final String content; - CSSStyleSheet(this.cssRules, {this.disabled = false, this.herf}); + List get cssRules => _cssRules; + late List _cssRules; + + CSSStyleSheet(this.content, this._cssRules, {this.disabled = false, this.href}); + + CSSStyleSheet.from(this.content, {this.disabled = false, this.href}) { + _cssRules = CSSParser(content).parseRules(); + } insertRule(String text, int index) { List rules = CSSParser(text).parseRules(); @@ -39,8 +46,30 @@ class CSSStyleSheet implements StyleSheet { cssRules.addAll(rules); } - replace(String text) { - // TODO: put in next frame and return a future - replaceSync(text); + Future replace(String text) async { + return Future(() { + replaceSync(text); + }); + } + + @override + bool operator ==(Object other) { + return other is CSSStyleSheet && other.content == content; + } + + @override + int get hashCode => content.hashCode; + + CSSStyleSheet clone() { + CSSStyleSheet sheet = CSSStyleSheet(content, List.from(cssRules), disabled: disabled, href: href); + return sheet; + } + + @override + int compareTo(other) { + if (other is! CSSStyleSheet) { + return 0; + } + return hashCode.compareTo(other.hashCode); } } diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index d7d8a42458..b33d8a63a4 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -19,7 +19,10 @@ class Document extends Node { GestureListener? gestureListener; WidgetDelegate? widgetDelegate; - RuleSet ruleSet = RuleSet(); + StyleNodeManager get styleNodeManager => _styleNodeManager; + late StyleNodeManager _styleNodeManager; + + final RuleSet ruleSet = RuleSet(); Document( BindingContext context, { @@ -29,6 +32,7 @@ class Document extends Node { this.widgetDelegate, }) : _viewport = viewport, super(NodeType.DOCUMENT_NODE, context) { + _styleNodeManager = StyleNodeManager(this); _scriptRunner = ScriptRunner(this, context.contextId); } @@ -181,20 +185,27 @@ class Document extends Node { // The styleSheets attribute is readonly attribute. final List styleSheets = []; - void addStyleSheet(CSSStyleSheet sheet) { - styleSheets.add(sheet); - ruleSet.addRules(sheet.cssRules); - recalculateDocumentStyle(); + void handleStyleSheets(List sheets) { + styleSheets.clear(); + styleSheets.addAll(sheets.map((e) => e.clone())); + ruleSet.reset(); + for (var sheet in sheets) { + ruleSet.addRules(sheet.cssRules); + } } - void removeStyleSheet(CSSStyleSheet sheet) { - styleSheets.remove(sheet); + void updateStyleIfNeeded() { + styleNodeManager.updateActiveStyleSheets(); recalculateDocumentStyle(); } void recalculateDocumentStyle() { + if (!needsStyleRecalculate) { + return; + } // Recalculate style for all nodes sync. documentElement?.recalculateNestedStyle(); + needsStyleRecalculate = false; } @override diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 5605adcaf4..c09141815b 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -882,7 +882,6 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element return child; } - void updateStyleIfNeeded() {} // 2. Node::invalidateStyle 方法 将元素所有祖先都标记需要更新样式 // 3. Document updateStyleIfNeeded() // 3.1 flush pending sheet @@ -1485,6 +1484,9 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } void recalculateNestedStyle() { + if (!needsStyleRecalculate) { + return; + } recalculateStyle(); // Update children style. children.forEach((Element child) { diff --git a/webf/lib/src/dom/elements/head.dart b/webf/lib/src/dom/elements/head.dart index cbe01d28b2..c3230771f4 100644 --- a/webf/lib/src/dom/elements/head.dart +++ b/webf/lib/src/dom/elements/head.dart @@ -31,6 +31,12 @@ const String _REL_STYLESHEET = 'stylesheet'; class LinkElement extends Element { LinkElement([BindingContext? context]) : super(context, defaultStyle: _defaultStyle); + CSSStyleSheet? get styleSheet => _styleSheet; + CSSStyleSheet? _styleSheet; + + bool _loading = false; + bool get loading => _loading; + Uri? _resolvedHyperlink; final Map _stylesheetLoaded = {}; @@ -103,15 +109,13 @@ class LinkElement extends Element { set href(String value) { internalSetAttribute('href', value); _resolveHyperlink(); - // Should waiting for all properties had set up. - Future.microtask(() { - _fetchAndApplyCSSStyle(); - }); + _process(); } String get rel => getAttribute('rel') ?? ''; set rel(String value) { internalSetAttribute('rel', value); + _process(); } String get type => getAttribute('type') ?? ''; @@ -132,6 +136,15 @@ class LinkElement extends Element { } } + void _process() { + if (_resolvedHyperlink != null && _stylesheetLoaded.containsKey(_resolvedHyperlink.toString())) { + return; + } + Future.microtask(() { + _fetchAndApplyCSSStyle(); + }); + } + void _fetchAndApplyCSSStyle() async { if (_resolvedHyperlink != null && rel == _REL_STYLESHEET && @@ -141,12 +154,13 @@ class LinkElement extends Element { WebFBundle bundle = WebFBundle.fromUrl(url); _stylesheetLoaded[url] = true; try { + _loading = true; // Increment count when request. ownerDocument.incrementRequestCount(); await bundle.resolve(contextId); assert(bundle.isResolved, 'Failed to obtain $url'); - + _loading = false; // Decrement count when response. ownerDocument.decrementRequestCount(); @@ -170,16 +184,26 @@ class LinkElement extends Element { } void _addCSSStyleSheet(String css) { - final sheet = CSSParser(css).parse(); - ownerDocument.addStyleSheet(sheet); + _styleSheet = CSSParser(css).parse(); } @override - void connectedCallback() async { - super.connectedCallback(); + void connectedCallback() { + if (rel == _REL_STYLESHEET) { + ownerDocument.styleNodeManager.addStyleSheetCandidateNode(this); + } if (_resolvedHyperlink != null) { _fetchAndApplyCSSStyle(); } + super.connectedCallback(); + } + + @override + void disconnectedCallback() { + if (rel == _REL_STYLESHEET) { + ownerDocument.styleNodeManager.removeStyleSheetCandidateNode(this); + } + super.disconnectedCallback(); } } @@ -201,6 +225,8 @@ const String _CSS_MIME = 'text/css'; class StyleElement extends Element { StyleElement([BindingContext? context]) : super(context, defaultStyle: _defaultStyle); final String _type = _CSS_MIME; + + CSSStyleSheet? get styleSheet => _styleSheet; CSSStyleSheet? _styleSheet; // Bindings. @@ -244,11 +270,9 @@ class StyleElement extends Element { String? text = collectElementChildText(); if (text != null) { if (_styleSheet != null) { - _styleSheet!.replaceSync(text); - ownerDocument.recalculateDocumentStyle(); + _styleSheet!.replace(text); } else { - final sheet = CSSParser(text).parse(); - ownerDocument.addStyleSheet(_styleSheet = sheet); + _styleSheet = CSSParser(text).parse(); } } } @@ -277,7 +301,10 @@ class StyleElement extends Element { @override void connectedCallback() { if (_type == _CSS_MIME) { - _recalculateStyle(); + if (_styleSheet == null) { + _recalculateStyle(); + } + ownerDocument.styleNodeManager.addStyleSheetCandidateNode(this); } super.connectedCallback(); } @@ -285,7 +312,7 @@ class StyleElement extends Element { @override void disconnectedCallback() { if (_styleSheet != null) { - ownerDocument.removeStyleSheet(_styleSheet!); + ownerDocument.styleNodeManager.removeStyleSheetCandidateNode(this); } super.disconnectedCallback(); } diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index e53b1208d3..f5a46c4fab 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -2,8 +2,11 @@ * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'dart:math' as math; + import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; import 'package:webf/dom.dart'; import 'package:webf/foundation.dart'; import 'package:webf/widget.dart'; @@ -16,8 +19,20 @@ enum NodeType { DOCUMENT_FRAGMENT_NODE, } +enum DocumentPosition { + EQUIVALENT, + DISCONNECTED, + PRECEDING, + FOLLOWING, + CONTAINS, + CONTAINED_BY, + IMPLEMENTATION_SPECIFIC, +} + enum RenderObjectManagerType { FLUTTER_ELEMENT, WEBF_NODE } +typedef NoteVisitor = void Function(Node node); + /// [RenderObjectNode] provide the renderObject related abstract life cycle for /// [Node] or [Element]s, which wrap [RenderObject]s, which provide the actual /// rendering of the application. @@ -85,12 +100,31 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa // Children changed steps for node. // https://dom.spec.whatwg.org/#concept-node-children-changed-ext - void childrenChanged() {} + void childrenChanged() { + if (!this.isConnected) { + return; + } + Node parent = this; + // invalidate + while (parent.parentNode != null) { + parent.needsStyleRecalculate = true; + parent = parent.parentNode!; + } + + SchedulerBinding.instance.addPostFrameCallback((_) { + if (!ownerDocument.needsStyleRecalculate) { + return; + } + ownerDocument.updateStyleIfNeeded(); + }); + } // FIXME: The ownerDocument getter steps are to return null, if this is a document; otherwise this’s node document. // https://dom.spec.whatwg.org/#dom-node-ownerdocument late Document ownerDocument; + bool needsStyleRecalculate = false; + /// The Node.parentElement read-only property returns the DOM node's parent Element, /// or null if the node either has no parent, or its parent isn't a DOM Element. Element? get parentElement { @@ -182,6 +216,13 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa @override void didDetachRenderer() {} + void visitChild(NoteVisitor visitor) { + childNodes.forEach((node) { + node.visitChild(visitor); + visitor(node); + }); + } + @mustCallSuper Node appendChild(Node child) { child._ensureOrphan(); @@ -197,6 +238,13 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa child.connectedCallback(); } + // invalid style + Node parent = this; + while (parent.parentNode != null) { + parent.needsStyleRecalculate = true; + parent = parent.parentNode!; + } + ownerDocument.needsStyleRecalculate = true; return child; } @@ -309,6 +357,56 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa // Whether Kraken Node need to manage render object. RenderObjectManagerType get renderObjectManagerType => RenderObjectManagerType.WEBF_NODE; + + DocumentPosition compareDocumentPosition(Node other) { + if (this == other) { + return DocumentPosition.EQUIVALENT; + } + + // We need to find a common ancestor container, and then compare the indices of the two immediate children. + List chain1 = []; + List chain2 = []; + Node? current = this; + while (current != null && current.parentNode != null) { + chain1.add(current); + current = current.parentNode; + } + current = other; + while (current != null && current.parentNode != null) { + chain2.add(current); + current = current.parentNode; + } + + // If the two elements don't have a common root, they're not in the same tree. + if (chain1.first != chain2.first) { + return DocumentPosition.DISCONNECTED; + } + + // Walk the two chains backwards and look for the first difference. + for (int i = 0; i < math.min(chain1.length, chain2.length) - 1; i++) { + if (chain1[i] != chain2[i]) { + if (chain2[i].nextSibling == null) { + return DocumentPosition.FOLLOWING; + } + if (chain1[i].nextSibling == null) { + return DocumentPosition.PRECEDING; + } + + // Otherwise we need to see which node occurs first. Crawl backwards from child2 looking for child1. + Node? previousSibling = chain2[i].previousSibling; + while (previousSibling != null) { + if (chain1[i] == previousSibling) { + return DocumentPosition.FOLLOWING; + } + previousSibling = previousSibling.previousSibling; + } + return DocumentPosition.PRECEDING; + } + } + // There was no difference between the two parent chains, i.e., one was a subset of the other. The shorter + // chain is the ancestor. + return chain1.length < chain2.length ? DocumentPosition.FOLLOWING : DocumentPosition.PRECEDING; + } } /// https://dom.spec.whatwg.org/#dom-node-nodetype diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart new file mode 100644 index 0000000000..02cd414781 --- /dev/null +++ b/webf/lib/src/dom/style_node_manager.dart @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'dart:math' as math; +import 'package:collection/collection.dart'; + +import 'package:webf/css.dart'; +import 'package:webf/dom.dart'; + +/* + Handling element style updates + 1. log all style element + 2. + */ +class StyleNodeManager { + final List _styleSheetCandidateNodes = []; + + final Document document; + + StyleNodeManager(this.document); + + void addStyleSheetCandidateNode(Node node) { + if (!node.isConnected) { + return; + } + if (_styleSheetCandidateNodes.isEmpty) { + _styleSheetCandidateNodes.add(node); + return; + } + + // Determine an appropriate insertion point. + for (int i = _styleSheetCandidateNodes.length - 1; i >= 0; i--) { + DocumentPosition position = _styleSheetCandidateNodes[i].compareDocumentPosition(node); + if (position == DocumentPosition.FOLLOWING) { + _styleSheetCandidateNodes.insert(i + 1, node); + return; + } + } + + _styleSheetCandidateNodes.insert(0, node); + } + + void removeStyleSheetCandidateNode(Node node) { + _styleSheetCandidateNodes.remove(node); + } + + void updateActiveStyleSheets() { + List newSheets = _collectActiveStyleSheets(); + if (newSheets.isEmpty) { + return; + } + newSheets = _collectActiveStyleSheets().where((element) => element.cssRules.isNotEmpty).toList(); + RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); + invalidateElementStyle(changedRuleSet); + document.handleStyleSheets(newSheets); + } + + List _collectActiveStyleSheets() { + List styleSheetsForStyleSheetsList = []; + for (Node node in _styleSheetCandidateNodes) { + if (node is LinkElement && !node.disabled && !node.loading && node.styleSheet != null) { + styleSheetsForStyleSheetsList.add(node.styleSheet!); + } else if (node is StyleElement && node.styleSheet != null) { + styleSheetsForStyleSheetsList.add(node.styleSheet!); + } + } + return styleSheetsForStyleSheetsList; + } + + void invalidateElementStyle(RuleSet changedRuleSet) { + ElementRuleCollector collector = ElementRuleCollector(); + document.visitChild((node) { + if (node.childNodes.where((node) => node.needsStyleRecalculate).isNotEmpty) { + node.needsStyleRecalculate = true; + } else if (node is Element && node.isConnected) { + if (collector.matchedAnyRule(changedRuleSet, node)) { + node.needsStyleRecalculate = true; + } + } + }); + } + + RuleSet analyzeStyleSheetChangeRuleSet(List oldSheets, List newSheets) { + RuleSet ruleSet = RuleSet(); + + final oldSheetsCount = oldSheets.length; + final newSheetsCount = newSheets.length; + + final minCount = math.min(oldSheetsCount, newSheetsCount); + + Function equals = ListEquality().equals; + + int index = 0; + for (; index < minCount && oldSheets[index] == newSheets[index]; index++) { + if (equals(oldSheets[index].cssRules, newSheets[index].cssRules)) { + continue; + } + ruleSet.addRules(newSheets[index].cssRules); + ruleSet.addRules(oldSheets[index].cssRules); + } + + if (index == oldSheetsCount) { + for (; index < newSheetsCount; index++) { + ruleSet.addRules(newSheets[index].cssRules); + } + return ruleSet; + } + + if (index == newSheetsCount) { + for (; index < oldSheetsCount; index++) { + ruleSet.addRules(oldSheets[index].cssRules); + } + return ruleSet; + } + + List mergeSorted = []; + mergeSorted.addAll(oldSheets.sublist(index)); + mergeSorted.addAll(newSheets.sublist(index)); + mergeSorted.sort(); + + for (int index = 0; index < mergeSorted.length; index++) { + CSSStyleSheet sheet = mergeSorted[index]; + CSSStyleSheet sheet1 = mergeSorted[++index]; + if (index == mergeSorted.length || sheet != sheet1) { + ruleSet.addRules(sheet1.cssRules); + continue; + } + + CSSStyleSheet sheet2 = mergeSorted[++index]; + if (equals(sheet1.cssRules, sheet2.cssRules)) { + continue; + } + + ruleSet.addRules(sheet1.cssRules); + ruleSet.addRules(sheet2.cssRules); + } + + return ruleSet; + } +} From 9589be2dff308fc15dceaa50b08289c3a0051822 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 21 Aug 2022 12:05:21 +0800 Subject: [PATCH 243/498] feat: udpate Link Element --- integration_tests/assets/bad.css | 4 +++ integration_tests/assets/good.css | 3 ++ .../dom/elements/link.ts.4a71e55a1.png | Bin 4408 -> 4467 bytes .../dom/elements/link.ts.c15dbee01.png | Bin 0 -> 6396 bytes .../dom/elements/link.ts.c15dbee02.png | Bin 0 -> 8937 bytes .../dom/elements/link.ts.c15dbee03.png | Bin 0 -> 8937 bytes integration_tests/specs/dom/elements/link.ts | 21 ++++++++++- webf/lib/src/css/element_rule_collector.dart | 4 +++ webf/lib/src/css/parser/parser.dart | 4 ++- webf/lib/src/css/rule.dart | 2 ++ webf/lib/src/css/rule_set.dart | 5 ++- webf/lib/src/dom/document.dart | 3 ++ webf/lib/src/dom/elements/head.dart | 33 +++++++++++------- webf/lib/src/dom/style_node_manager.dart | 15 ++++++-- 14 files changed, 74 insertions(+), 20 deletions(-) create mode 100644 integration_tests/assets/bad.css create mode 100644 integration_tests/assets/good.css create mode 100644 integration_tests/snapshots/dom/elements/link.ts.c15dbee01.png create mode 100644 integration_tests/snapshots/dom/elements/link.ts.c15dbee02.png create mode 100644 integration_tests/snapshots/dom/elements/link.ts.c15dbee03.png diff --git a/integration_tests/assets/bad.css b/integration_tests/assets/bad.css new file mode 100644 index 0000000000..4e1fe36165 --- /dev/null +++ b/integration_tests/assets/bad.css @@ -0,0 +1,4 @@ +p { + background-color: red; + color: black; +} \ No newline at end of file diff --git a/integration_tests/assets/good.css b/integration_tests/assets/good.css new file mode 100644 index 0000000000..1da5e2b8cf --- /dev/null +++ b/integration_tests/assets/good.css @@ -0,0 +1,3 @@ +p { + color: green; +} \ No newline at end of file diff --git a/integration_tests/snapshots/dom/elements/link.ts.4a71e55a1.png b/integration_tests/snapshots/dom/elements/link.ts.4a71e55a1.png index 662d311d86f4ba295c1aac3450425f9b6e9d2734..028376ab09a064939d9ad73d1cbe682252012a58 100644 GIT binary patch delta 2150 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 delta 2087 zcmV+?2-x@YBDf-uKmrirkwPke0gc3*r6~jg7YN)epmZ@zrEh|eyhp9EAL)=4{g}6VM73@ zGv@z$0RU{CKm(K#Xn;~a@oAgvTV(=!Pv}y-^|QSzrX1@ zng98=x%EGt8}Gd_*Zuc(e}`28|A@vU$=Nrbop;ZC_j5Ag5AOLvPW2J7 z?(FSy>JLxN;~O3yy#LsLhR1UL&(F_`&wp`-&KSzrv&QDNUw>^DKC*DDPdsbEtn7c` z{uz7L*bJR9l#%mB^65FB&f3S;CP^-xcWFk>8_C@d-90$ww@ZJUk@H3}-AG$k-eB+Sp@cA7wbjDC7oii!#{LVXb|H}J+2gh9T>nk#H-bk+a z^%Z%^S6-5#GlufWnn#{?y`R73^V$2$dk?P7$8Y%fCf5ewxi=vDWblwB=a`-n7&(7QJoX@`gv)OUm9rL}rzn8ObK0A-Bc_d#w{HvKU zWkyCb?8-aGA`HS5>p z)t9_FtNyYor@ZQv?6cE8xqZp)x%k$LbNiCpbI*tG$xw!W^6=`1GrDRt$6tMX4w`aM zKJ%}i$%JhtY<9ip+;UDndE+N@z^(`6?0-8u%hoK*1;4l;Yu2yH9uxQYoBja+p3d-J zzqt1JYjfDN!;&N?9duH5|M%T<%c5Hbfj;vmXJ(Iyd*o*){cLc|w8_(Q!qq2a_QKgY zV&5aS=%i(9?WX#Z*TsrU4EML1k7r*u5y#JN&ADnaY zc9V1NPtVPqd*|ehd%bZme$@1%a?NqqJmY%L``LManYiu5%>CfpZ2N+3lO!h`a6%6H z+Cf|Nc>$h{!4}8xnBhU3lVrCYcT1A2TDK}mvS$67EML1kuix|aS+smnZd-C&W-pwb z`BVgKWyC%HWc)}T{PTm){9yr}r7aHh3%B1G)OSpON51&xFJ{uXNtrlqVumub)hC_2-Q=u) zSi9mW^W)AOd2=o8RPta*oMb8ef{{8ulL<~-;Ayr&61T%Hu;{L7u}pB zgRPILJ50@sCcG%K7R<`51+#M8%a0q3?=@+!Tsi;B%(-{Y;O;p|_MN@>^a_Wv7?_@8z^^)ASMyHu+G|cr zlDz4%H)ZB;W@h%n+4<;?KAImc_+gHke$*yUQyjV9k(s@4cGj$4lVe|Y>|p#|FMn5l z^824;`P$`!{pBQi?=ciWX`>F@|9nHC8ytbdUoD!=S}w6H)nI} zKYst?vv|eg9Q_|h=ll13KbPNec@Dn#;H|lI09$Ra#W6f)^Sd2mhMsKrnQu5V%hoK* z*Khm!VDC4{;4!!)Ic(ZtnKpS^E}VN|?tk?D%sg&pKK8ng<>c#5&T&_N9hU{CEXbkz z9GWk^p2 znQYcf1$ILGgQe|%Q2U!7n5=~tV*ZUBF)p=a|4`yb!%cowf%oOO?_%ajRI zHv9bW($z~de(d85wk|8yu6W81+eTN7X7&2jnYzQ&r~Oaj*?M%{qZwT_nkf^e zWc=9iTXpRJUVpOjNtVSc7H99B_I^s(0iJ8mD$oEt|J()wloDvOmk7rQ6#+|qo3Qk9 R3U&Yh002ovPDHLkV1jdek^%q# diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee01.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee01.png new file mode 100644 index 0000000000000000000000000000000000000000..6c1b3b5455fb2b391422bf20246a1736f26785ec GIT binary patch literal 6396 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee03.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee03.png new file mode 100644 index 0000000000000000000000000000000000000000..266137a02c2af9a7942b5b9c57d53c4d0b62772f GIT binary patch literal 8937 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/dom/elements/link.ts b/integration_tests/specs/dom/elements/link.ts index f3da0b1ee7..4a621d3872 100644 --- a/integration_tests/specs/dom/elements/link.ts +++ b/integration_tests/specs/dom/elements/link.ts @@ -16,4 +16,23 @@ describe('Link Element', () => { div.appendChild(document.createTextNode('helloworld')); BODY.appendChild(div); }); -}); \ No newline at end of file + + fit('should work with local css', async (done) => { + let link = document.createElement('link'); + link.setAttribute('href', 'assets:assets/bad.css'); + link.setAttribute('rel', 'stylesheet'); + link.addEventListener('load', async () => { + await snapshot(); + link.setAttribute('href', 'assets:assets/good.css'); + await snapshot(0.5); + done(); + }); + document.head.appendChild(link); + + let p = document.createElement('p'); + p.appendChild(document.createTextNode('This text should be green on a white background')); + BODY.appendChild(p); + }); + + +}); diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index d10fbb2160..3951e7dda8 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -14,6 +14,10 @@ class ElementRuleCollector { List _matchedRules(RuleSet ruleSet, Element element) { List matchedRules = []; + if (ruleSet.rules.isEmpty) { + return matchedRules; + } + // #id String? id = element.id; if (id != null) { diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 8d308f09af..df8c53d432 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -126,10 +126,12 @@ class CSSParser { return style; } - List parseRules() { + List parseRules({int startPosition = 0}) { var rules = []; + int position = startPosition; while (!_maybeEat(TokenKind.END_OF_FILE)) { final rule = processRule(); + rule?.position = position++; if (rule != null) { rules.add(rule); } else { diff --git a/webf/lib/src/css/rule.dart b/webf/lib/src/css/rule.dart index 57c6238cbb..f8887c112e 100644 --- a/webf/lib/src/css/rule.dart +++ b/webf/lib/src/css/rule.dart @@ -8,6 +8,8 @@ abstract class CSSRule { CSSStyleSheet? parentStyleSheet; CSSRule? parentRule; + int position = -1; + // https://drafts.csswg.org/cssom/#dom-cssrule-type // The following attribute and constants are historical. int? type; diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index d825bbaeff..fac03debcd 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -10,7 +10,8 @@ import 'package:webf/css.dart'; typedef CSSMap = HashMap>; class RuleSet { - final List rules = []; + bool get isEmpty => + idRules.isEmpty && classRules.isEmpty && attributeRules.isEmpty && tagRules.isEmpty && universalRules.isEmpty; final CSSMap idRules = HashMap(); final CSSMap classRules = HashMap(); @@ -25,7 +26,6 @@ class RuleSet { } void addRule(CSSRule rule) { - rules.add(rule); if (rule is CSSStyleRule) { for (final selector in rule.selectorGroup.selectors) { findBestRuleSetAndAdd(selector, rule); @@ -36,7 +36,6 @@ class RuleSet { } void reset() { - rules.clear(); idRules.clear(); classRules.clear(); attributeRules.clear(); diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index b33d8a63a4..76706f9ff3 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -195,6 +195,9 @@ class Document extends Node { } void updateStyleIfNeeded() { + if (!styleNodeManager.hasPendingStyleSheet) { + return; + } styleNodeManager.updateActiveStyleSheets(); recalculateDocumentStyle(); } diff --git a/webf/lib/src/dom/elements/head.dart b/webf/lib/src/dom/elements/head.dart index c3230771f4..7efaf80498 100644 --- a/webf/lib/src/dom/elements/head.dart +++ b/webf/lib/src/dom/elements/head.dart @@ -140,6 +140,12 @@ class LinkElement extends Element { if (_resolvedHyperlink != null && _stylesheetLoaded.containsKey(_resolvedHyperlink.toString())) { return; } + if (_resolvedHyperlink != null) { + _stylesheetLoaded.remove(_resolvedHyperlink.toString()); + } + if (_styleSheet != null) { + ownerDocument.styleNodeManager.removePendingStyleSheet(_styleSheet!); + } Future.microtask(() { _fetchAndApplyCSSStyle(); }); @@ -165,10 +171,12 @@ class LinkElement extends Element { ownerDocument.decrementRequestCount(); final String cssString = await resolveStringFromData(bundle.data!); - _addCSSStyleSheet(cssString); + _styleSheet = CSSParser(cssString).parse(); + ownerDocument.styleNodeManager.appendPendingStyleSheet(_styleSheet!); // Successful load. SchedulerBinding.instance.addPostFrameCallback((_) { + ownerDocument.updateStyleIfNeeded(); dispatchEvent(Event(EVENT_LOAD)); }); } catch (e) { @@ -183,27 +191,22 @@ class LinkElement extends Element { } } - void _addCSSStyleSheet(String css) { - _styleSheet = CSSParser(css).parse(); - } - @override void connectedCallback() { - if (rel == _REL_STYLESHEET) { - ownerDocument.styleNodeManager.addStyleSheetCandidateNode(this); - } + super.connectedCallback(); + ownerDocument.styleNodeManager.addStyleSheetCandidateNode(this); if (_resolvedHyperlink != null) { _fetchAndApplyCSSStyle(); } - super.connectedCallback(); } @override void disconnectedCallback() { - if (rel == _REL_STYLESHEET) { - ownerDocument.styleNodeManager.removeStyleSheetCandidateNode(this); - } super.disconnectedCallback(); + if (_styleSheet != null) { + ownerDocument.styleNodeManager.removePendingStyleSheet(_styleSheet!); + } + ownerDocument.styleNodeManager.removeStyleSheetCandidateNode(this); } } @@ -274,6 +277,9 @@ class StyleElement extends Element { } else { _styleSheet = CSSParser(text).parse(); } + if (_styleSheet != null) { + ownerDocument.styleNodeManager.appendPendingStyleSheet(_styleSheet!); + } } } @@ -300,18 +306,19 @@ class StyleElement extends Element { @override void connectedCallback() { + super.connectedCallback(); if (_type == _CSS_MIME) { if (_styleSheet == null) { _recalculateStyle(); } ownerDocument.styleNodeManager.addStyleSheetCandidateNode(this); } - super.connectedCallback(); } @override void disconnectedCallback() { if (_styleSheet != null) { + ownerDocument.styleNodeManager.removePendingStyleSheet(_styleSheet!); ownerDocument.styleNodeManager.removeStyleSheetCandidateNode(this); } super.disconnectedCallback(); diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 02cd414781..0a837c8b5a 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -16,6 +16,10 @@ import 'package:webf/dom.dart'; class StyleNodeManager { final List _styleSheetCandidateNodes = []; + final List _pendingStyleSheets = []; + + bool get hasPendingStyleSheet => _pendingStyleSheets.isNotEmpty; + final Document document; StyleNodeManager(this.document); @@ -41,8 +45,14 @@ class StyleNodeManager { _styleSheetCandidateNodes.insert(0, node); } - void removeStyleSheetCandidateNode(Node node) { - _styleSheetCandidateNodes.remove(node); + void removeStyleSheetCandidateNode(Node node) {} + + void appendPendingStyleSheet(CSSStyleSheet styleSheet) { + _pendingStyleSheets.add(styleSheet); + } + + void removePendingStyleSheet(CSSStyleSheet styleSheet) { + _pendingStyleSheets.removeWhere((element) => element == styleSheet); } void updateActiveStyleSheets() { @@ -79,6 +89,7 @@ class StyleNodeManager { } } }); + document.needsStyleRecalculate = true; } RuleSet analyzeStyleSheetChangeRuleSet(List oldSheets, List newSheets) { From 8d797721427fbc54a05c0acb0c83943f642523f9 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 21 Aug 2022 18:33:17 +0800 Subject: [PATCH 244/498] feat: remove & append link element --- .../class-selector.ts.e3342df01.png | Bin 0 -> 8441 bytes .../specs/css/css-selectors/class-selector.ts | 8 +++ integration_tests/specs/dom/elements/link.ts | 46 +++++++++++++++++- webf/lib/src/css/element_rule_collector.dart | 4 +- webf/lib/src/css/parser/parser.dart | 2 - webf/lib/src/dom/document.dart | 2 +- webf/lib/src/dom/element.dart | 13 ----- webf/lib/src/dom/node.dart | 27 +++++----- webf/lib/src/dom/style_node_manager.dart | 19 ++++++-- 9 files changed, 85 insertions(+), 36 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/class-selector.ts.e3342df01.png diff --git a/integration_tests/snapshots/css/css-selectors/class-selector.ts.e3342df01.png b/integration_tests/snapshots/css/css-selectors/class-selector.ts.e3342df01.png new file mode 100644 index 0000000000000000000000000000000000000000..d2ce5d73c6d9ec075ac7394a2014d6b1514db70d GIT binary patch literal 8441 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 { document.body.appendChild(p8); await snapshot(); }); + + it('010', async () => { + const style = ; + const p =

    010 This should have a green background.< /p>; + document.head.appendChild(style); + document.body.appendChild(p); + await snapshot(); + }); }); diff --git a/integration_tests/specs/dom/elements/link.ts b/integration_tests/specs/dom/elements/link.ts index 4a621d3872..7510818478 100644 --- a/integration_tests/specs/dom/elements/link.ts +++ b/integration_tests/specs/dom/elements/link.ts @@ -17,7 +17,7 @@ describe('Link Element', () => { BODY.appendChild(div); }); - fit('should work with local css', async (done) => { + it('should work with local css', async (done) => { let link = document.createElement('link'); link.setAttribute('href', 'assets:assets/bad.css'); link.setAttribute('rel', 'stylesheet'); @@ -34,5 +34,47 @@ describe('Link Element', () => { BODY.appendChild(p); }); - + it('insert style sheet', async (done) => { + let link1 = document.createElement('link'); + link1.setAttribute('href', 'assets:assets/bad.css'); + link1.setAttribute('rel', 'stylesheet'); + document.head.appendChild(link1); + + let link2 = document.createElement('link'); + link2.setAttribute('href', 'assets:assets/good.css'); + link2.setAttribute('rel', 'stylesheet'); + document.head.appendChild(link2); + + link2.addEventListener('load', async () => { + await snapshot(); + done(); + }); + + let p = document.createElement('p'); + p.appendChild(document.createTextNode('This text should be green on a red background')); + BODY.appendChild(p); + }); + + it('remove style sheet', async (done) => { + let link1 = document.createElement('link'); + link1.setAttribute('href', 'assets:assets/bad.css'); + link1.setAttribute('rel', 'stylesheet'); + document.head.appendChild(link1); + + let link2 = document.createElement('link'); + link2.setAttribute('href', 'assets:assets/good.css'); + link2.setAttribute('rel', 'stylesheet'); + document.head.appendChild(link2); + + link1.addEventListener('load', async () => { + document.head.removeChild(link1); + await sleep(0.5); + await snapshot(); + done(); + }); + + let p = document.createElement('p'); + p.appendChild(document.createTextNode('remove: This text should be green on a red background')); + BODY.appendChild(p); + }); }); diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index 3951e7dda8..e49b27c618 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -14,7 +14,7 @@ class ElementRuleCollector { List _matchedRules(RuleSet ruleSet, Element element) { List matchedRules = []; - if (ruleSet.rules.isEmpty) { + if (ruleSet.isEmpty) { return matchedRules; } @@ -57,7 +57,7 @@ class ElementRuleCollector { } int isCompare = leftRule.selectorGroup.specificity.compareTo(rightRule.selectorGroup.specificity); if (isCompare == 0) { - return ruleSet.rules.indexOf(leftRule).compareTo(ruleSet.rules.indexOf(rightRule)); + return leftRule.position.compareTo(rightRule.position); } return isCompare; }); diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index df8c53d432..46716806f5 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -128,10 +128,8 @@ class CSSParser { List parseRules({int startPosition = 0}) { var rules = []; - int position = startPosition; while (!_maybeEat(TokenKind.END_OF_FILE)) { final rule = processRule(); - rule?.position = position++; if (rule != null) { rules.add(rule); } else { diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 76706f9ff3..5dd0aa5d9f 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -195,7 +195,7 @@ class Document extends Node { } void updateStyleIfNeeded() { - if (!styleNodeManager.hasPendingStyleSheet) { + if (!ownerDocument.needsStyleRecalculate) { return; } styleNodeManager.updateActiveStyleSheets(); diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index c09141815b..7a81b28812 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -882,19 +882,6 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element return child; } - // 2. Node::invalidateStyle 方法 将元素所有祖先都标记需要更新样式 - // 3. Document updateStyleIfNeeded() - // 3.1 flush pending sheet - // 比对 style sheet, 得到 ActiveSheetsChange 和 ChangedRuleSet - // 通过 ElementRuleCollector ElementRuleCollector::matchesAnyAuthorRules - // 对比后将 element 标记为 invalid (Invalidator::invalidateIfNeeded) - // 3.2 判断标记 needsStyleRecalc() ,执行 resolveStyle(); - // 5. TreeResolver::resolveComposedTree - // 6. TreeResolver::resolveElement - // 6.1 TreeResolver::styleForStyleable return CSSStyleDeclaration - // 6.1.1 styleForElement - // 6.2 Merge CSSStyleDeclaration - @override @mustCallSuper Node insertBefore(Node child, Node referenceNode) { diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index f5a46c4fab..508b4e6b2c 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -100,6 +100,9 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa // Children changed steps for node. // https://dom.spec.whatwg.org/#concept-node-children-changed-ext + + int _frameCallbackId = 0; + void childrenChanged() { if (!this.isConnected) { return; @@ -110,11 +113,11 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa parent.needsStyleRecalculate = true; parent = parent.parentNode!; } - - SchedulerBinding.instance.addPostFrameCallback((_) { - if (!ownerDocument.needsStyleRecalculate) { - return; - } + ownerDocument.needsStyleRecalculate = true; + if (_frameCallbackId > 0) { + SchedulerBinding.instance.cancelFrameCallbackWithId(_frameCallbackId); + } + _frameCallbackId = SchedulerBinding.instance.scheduleFrameCallback((_) { ownerDocument.updateStyleIfNeeded(); }); } @@ -284,14 +287,14 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa childNodes.remove(child); child.parentNode = null; + if (isConnected) { + child.disconnectedCallback(); + } + // To remove a node, run step 21 from the spec: // 21. Run the children changed steps for parent. // https://dom.spec.whatwg.org/#concept-node-remove childrenChanged(); - - if (isConnected) { - child.disconnectedCallback(); - } } return child; } @@ -368,12 +371,12 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa List chain2 = []; Node? current = this; while (current != null && current.parentNode != null) { - chain1.add(current); + chain1.insert(0, current); current = current.parentNode; } current = other; while (current != null && current.parentNode != null) { - chain2.add(current); + chain2.insert(0, current); current = current.parentNode; } @@ -383,7 +386,7 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa } // Walk the two chains backwards and look for the first difference. - for (int i = 0; i < math.min(chain1.length, chain2.length) - 1; i++) { + for (int i = 0; i < math.min(chain1.length, chain2.length); i++) { if (chain1[i] != chain2[i]) { if (chain2[i].nextSibling == null) { return DocumentPosition.FOLLOWING; diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 0a837c8b5a..56e9721790 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -45,7 +45,9 @@ class StyleNodeManager { _styleSheetCandidateNodes.insert(0, node); } - void removeStyleSheetCandidateNode(Node node) {} + void removeStyleSheetCandidateNode(Node node) { + _styleSheetCandidateNodes.remove(node); + } void appendPendingStyleSheet(CSSStyleSheet styleSheet) { _pendingStyleSheets.add(styleSheet); @@ -62,6 +64,9 @@ class StyleNodeManager { } newSheets = _collectActiveStyleSheets().where((element) => element.cssRules.isNotEmpty).toList(); RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); + if (changedRuleSet.isEmpty) { + return; + } invalidateElementStyle(changedRuleSet); document.handleStyleSheets(newSheets); } @@ -132,13 +137,19 @@ class StyleNodeManager { for (int index = 0; index < mergeSorted.length; index++) { CSSStyleSheet sheet = mergeSorted[index]; - CSSStyleSheet sheet1 = mergeSorted[++index]; - if (index == mergeSorted.length || sheet != sheet1) { + if (index + 1 < mergeSorted.length) { + ++index; + } + CSSStyleSheet sheet1 = mergeSorted[index]; + if (index == mergeSorted.length - 1 || sheet != sheet1) { ruleSet.addRules(sheet1.cssRules); continue; } - CSSStyleSheet sheet2 = mergeSorted[++index]; + if (index + 1 < mergeSorted.length) { + ++index; + } + CSSStyleSheet sheet2 = mergeSorted[index]; if (equals(sheet1.cssRules, sheet2.cssRules)) { continue; } From f059e56c944e798cc7839904d4071817edcf9853 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 21 Aug 2022 21:45:22 +0800 Subject: [PATCH 245/498] feat: query selector for dart --- webf/lib/src/css/element_rule_collector.dart | 2 +- ...tor_evaluator.dart => query_selector.dart} | 32 +++++++++++++++++++ webf/lib/src/css/rule_set.dart | 3 ++ webf/lib/src/dom/document.dart | 26 +++++++++++++++ webf/lib/src/dom/node.dart | 2 +- 5 files changed, 63 insertions(+), 2 deletions(-) rename webf/lib/src/css/{selector_evaluator.dart => query_selector.dart} (91%) diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index e49b27c618..95b9da2baf 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -4,7 +4,7 @@ import 'package:webf/css.dart'; import 'package:webf/dom.dart'; -import 'package:webf/src/css/selector_evaluator.dart'; +import 'package:webf/src/css/query_selector.dart'; class ElementRuleCollector { bool matchedAnyRule(RuleSet ruleSet, Element element) { diff --git a/webf/lib/src/css/selector_evaluator.dart b/webf/lib/src/css/query_selector.dart similarity index 91% rename from webf/lib/src/css/selector_evaluator.dart rename to webf/lib/src/css/query_selector.dart index 479b2b2801..cca55d21c1 100644 --- a/webf/lib/src/css/selector_evaluator.dart +++ b/webf/lib/src/css/query_selector.dart @@ -31,6 +31,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import 'package:webf/dom.dart'; import 'package:webf/css.dart'; +Element? querySelector(Node node, String selector) => + SelectorEvaluator().querySelector(node, _parseSelectorGroup(selector)); + +List querySelectorAll(Node node, String selector) { + final group = _parseSelectorGroup(selector); + final results = []; + SelectorEvaluator().querySelectorAll(node, group, results); + return results; +} + +// http://dev.w3.org/csswg/selectors-4/#grouping +SelectorGroup? _parseSelectorGroup(String selector) { + CSSParser parser = CSSParser(selector)..tokenizer.inSelector = true; + return parser.processSelectorGroup(); +} + class SelectorEvaluator extends SelectorVisitor { Element? _element; @@ -42,6 +58,22 @@ class SelectorEvaluator extends SelectorVisitor { return visitSelectorGroup(selector); } + Element? querySelector(Node root, SelectorGroup? selector) { + for (var element in root.childNodes.whereType()) { + if (matchSelector(selector, element)) return element; + final result = querySelector(element, selector); + if (result != null) return result; + } + return null; + } + + void querySelectorAll(Node root, SelectorGroup? selector, List results) { + for (var element in root.childNodes.whereType()) { + if (matchSelector(selector, element)) results.add(element); + querySelectorAll(element, selector, results); + } + } + @override bool visitSelectorGroup(SelectorGroup node) => node.selectors.any(visitSelector); diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index fac03debcd..a7e5154353 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -19,6 +19,8 @@ class RuleSet { final CSSMap tagRules = HashMap(); final List universalRules = []; + int _lastPosition = 0; + void addRules(List rules) { for (CSSRule rule in rules) { addRule(rule); @@ -26,6 +28,7 @@ class RuleSet { } void addRule(CSSRule rule) { + rule.position = _lastPosition++; if (rule is CSSStyleRule) { for (final selector in rule.selectorGroup.selectors) { findBestRuleSetAndAdd(selector, rule); diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 5dd0aa5d9f..aeb6372a97 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -9,6 +9,7 @@ import 'package:webf/foundation.dart'; import 'package:webf/gesture.dart'; import 'package:webf/launcher.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/widget.dart'; @@ -91,6 +92,31 @@ class Document extends Node { } } + @override + getBindingProperty(String key) { + switch (key) { + case 'querySelectorAll': + return querySelectorAll; + case 'querySelector': + return querySelector; + } + return super.getBindingProperty(key); + } + + dynamic querySelector(List args) { + if (args.isEmpty || args.first is! String) { + return null; + } + return QuerySelector.querySelector(this, args.first); + } + + dynamic querySelectorAll(List args) { + if (args.isEmpty || args.first is! String) { + return null; + } + return QuerySelector.querySelectorAll(this, args.first); + } + Element? _documentElement; Element? get documentElement => _documentElement; set documentElement(Element? element) { diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index 508b4e6b2c..34a95d8f1f 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -104,7 +104,7 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa int _frameCallbackId = 0; void childrenChanged() { - if (!this.isConnected) { + if (!isConnected) { return; } Node parent = this; From b0349cf971a87da953e4e3772b88804f94a4e4f6 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 22 Aug 2022 15:04:11 +0800 Subject: [PATCH 246/498] fix(test): dom link element --- .../dom/elements/link.ts.9f7a30e31.png | Bin 0 -> 7170 bytes .../dom/elements/link.ts.c15dbee01.png | Bin 6396 -> 7040 bytes .../dom/elements/link.ts.c15dbee02.png | Bin 8937 -> 9983 bytes .../dom/elements/link.ts.c15dbee03.png | Bin 8937 -> 9983 bytes .../dom/elements/link.ts.c15dbee04.png | Bin 0 -> 9983 bytes .../specs/css/css-inline/change_inline.ts | 2 +- integration_tests/specs/dom/elements/link.ts | 2 +- webf/lib/src/dom/document.dart | 3 --- webf/lib/src/dom/node.dart | 3 +++ 9 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 integration_tests/snapshots/dom/elements/link.ts.9f7a30e31.png create mode 100644 integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png diff --git a/integration_tests/snapshots/dom/elements/link.ts.9f7a30e31.png b/integration_tests/snapshots/dom/elements/link.ts.9f7a30e31.png new file mode 100644 index 0000000000000000000000000000000000000000..fae5497665d0a303d8be35415e363914cba0aead GIT binary patch literal 7170 zcmeI1`Ck&)*TgEweH;7p7bovvOKBF_|pIQsk?+A=|WLE=-x4 z8`Shoo7^%DLJ;RNNo}9qtZ}gcbtL%AH^zbe z<}%U5#I`y(;P9D@vIPP9L1b$2qQWWI>i+lJH+++Q{kQP1FI&0ox|{G)?%lcXuU(%$ z+4Lu+q&zqYJM(>;eWZoB$>e&%%e-CPj=M}Ri#CjGx_R^ab-~|sX*vc}XVRY*&6#{| zWm33axRNu^k!N+X8l*teLVNe(Yrw{JeeRNZ69bj{VuGs{C~e19H}Wopl4O&_IYT>F@cfP)z=%nFP&m{h6ZU@DJg$@+uz}h zN`#w}I{j{loxZa69eZrMEDNIXuy8@}4sM~MsFZ%qo|$s@Wc|5z?5vmi+90aT)blFQKc6B0_8%1*av}4{wzCky3 zp(%b;qk{r)*kzMwFcyu>F+QS>FXwLRf1NHPFNHlShg%A znsv~qMf>ZB!?V|hF7aC;+249Y_%E*dwck7sa1z=n6e6muVYZ$wytA9QrY=?Ddzf1l zQ&o-@EewMMj7wp6P_x(6nKcN+yI%mJRv&RqDD(3aWRkk}=7N&dGA-Hq%9SOk=m<(! zq7k3MA=r2&(O*VflkF(aZOhIM#Qw>%8-sTQR3F$c&Z#oR=lPmbYXSoJ3@kec*qgcq zP(PPLJYARTIm4~dG?EYjY+DK`W3hJ#^G~I6h_u$<2QU-bC5_Ak6CE-p?&jOP*8xKhvwzFTxw5y-*!Nkgheve}X9?b1P&^i6kpUw%@O z=03Lgo)#EM8-2ZdZ7Ko)6de@Teyf()19QUBvG7aa|LVHrt?rc9m4tH4pz%%XrEdVC zWHJGaNMR3zG_H0cBX@`&3P4nxr`h<_xJ@CmdL@BvC7%(b~UQ@zh)iqNn=C@tDSkhZ_eW~6MtKX-Q!R{ z@R)wjK#2zxmRPx=+pwoM#n>gn+@dA^OXiwcm?U{ml#^t+f|R$`K80q%3p>|d$p^un z%+w77Mdv}FgPwZ`GJh7g05?@)Lp{-JOGafwj`g`Qu5yS#2n*CuPgw zY=U-=n0^vK5&arHVHt2tbA%mT=hDiz4w^1aNeg4M(!h1`LqyMLu2y67WJ$vZ(96qO z{qt73Jf=;3(QU*F7F`}fIH3SJx}VlrU#49k=IRUUN3=TiRv`T+(I@LVTSXRP|5hN= z3pjBtX8*1bZkFeGArYViF+Z=CsW4m+plZ2Z4~iHY)u#t0ti-V&)B`FJHg2;s#15ui zy?A3?q8dP7pE5cKn23e&8zS|5Odc^q_A&AVtfWI)0!*5uEHOUJhRUJf|6=4Ec%Hrk(E`n!qr5aiYpmb?lRPcNaB4`N@Q zheEET{wc`8hW8oOU{1hSo!dwb-zT1@ZsBX&`4vUv{L{1xMsr!g5=s5-A7{)fCv9Ug zRK&h;A2Hl^(iSz(za=N2RrzbPUjur~_qpn(;Y)#d9fH0NEMlB;*haaLX=F@ab7QLE ze0E@2Lz3U>eUu{}d)R_aNU*M#3+Oz&b~ga(H-t0Xv#FmzcI7^CrPVyJQN}DM3$frt^uB>M>v;#-iL)bKmDkR;R~O zA!N!k)IMK2_n=1UKi@8Yg^%T2QCGAW(Z5IK+S&_b?4a;yuGyZemgq5-VUNdfk7js# zScy%Nffq5>Cp4Q>fpJdJw`AWtWWd=+ogi3bK9(U#d@zC4qv>RjV_u7U^i+N__hr_p z0H%$DXG3brzNAdHCOvy?;wa9nab#$f;Vp(sXo;d}SHPH^gJLQe0BenXUnI(qz?4;P zF8h8Y<=C#x{Om|;S!ubUDyLcaZ6W3Lc*>Ir>_9MLw}itDz+S#{4OrziO#R`WbSa;8 zMb|qWI=Pzh?zzoOY(hE%EdBtr)MkXb6ad4mVy9L;(h_Bj{Kli~C{;k@f1;#JTf=TK zEPW2+C2DpNuNRDs|G`64jzae$QXEdY@AkZON{Amw2Mu9%8Z?u(pmRRxMXQ?t|4qw} z(;LaZ2S_K!tMTgu*klK4m~1Lhv)KPG1*zJB$aml##;2vo(9FhgpBz1E%;=y$rur3`Bd73pf`3ZKKayJ(zG)vfVruPpnJtE2ggH#g=?N7Crj8v| z+*PgV?ya}RM=<|28Em)aic@t&mZ;~;>KX}!+PZllAnwc zl>Ug+@=BG4;@#wLECaic?pNe>)zzx$W>$bC-jW*EP? zmCOTUqqPX2pL~{lumz(IqiNgAt&-))Se33M$$nnj>8&5Viwb~WdS9KtI@=0zN_UvK zMlG1uDcGmz7f#scW7wf7NRVqPt0Fc*Q*v&8R`Jx4dT5{OE>fQukCli2#FD?KBJBO> z^54=qqznTPN^qEKUO0o*>I0f(j_0;wy4EmbVK+=X4LBOb1r4 zM(XMGc1*4n#xVa3&6wDH4^v^(LB_$c(Y2y2`j(T*Elei%Sq}y5BJrq$`WpCy42$X1 zHEHz1#9&wWfje_OR7S;#y2fn@(5%jo3BMJxR$hy*h%2eLpZ~r2+u8AajQrC9Ym1!t z(!>POQ05%n?{;&d_n`3lYJFyG78BY(i&;r!R9sxUUiN&AU+1WAj3)0oG$ru5vt0N~ z`R*rZ6eTIfft*t+Pzf^w*uetKhW||jifd32|7B@bC9l1e()USTg5=RcfG7jX4)$-F zM{2Xt_g`LU==kVR{a7D=mQ$9Yx!1F7QHP(E_CH56Ct_&_pD|=zmd!~G`+MJ+qW|Hv z8?<5uh4;}7Cn7wi5Zt1B`957fiD`ZDROF&_3YZ4vb~6@YVpX{7S56*~#smSSiNeL9 zORmpDcwL@^wP~X31;O1B9(BgB7q`7Ur|pB_t9Y2?#=?|`>Dalbt;I>g`7R=?eBirj zMo(aM`VlGIblVu2it(&;;f{EQ?gH|0_v=TfWZOZFe!toIC?}GL*LJYhFxo{vpTKi=ufOKLD9p#r4O9_co8l7t^dP!aJF zvZS2Sa!drSd~bZkW22AdAF4ZBX4KG#hC5?T@kf|hr5#acEM4@GsCIN@q7jPAbhvjA!pP0$EzC<`Jl`2g!o#q{)+<2{ z(O88&T4Nfj!Wni*CU0YepS{qmw;RzrI&Pm%cDS<-4pJsU7y(h~^#gC24m$sZ$d5TJ9{#3fnu$8t1M>L2?~_Jn9TUve63EHJRqrMHS`-8p1Aa2 z4*M3U>@Y*29+EnAHzCpz*iNf@xnk@T0pIafI&vhtz?O&eH31{=Gs!q`q|4a2XVhz zRFBwgz1^UphJ!t-kDXylFJGcO<`_BTOD(-20I1GgTyPOx zJY$5N?=8>iDZVXCb<^j3hJp9eMhY(XzGybjYu+|hmg|i%FRvYQZ}!*UN{`*&-p8>b za-LTBr}iSy_g4jwAT6&Bg=NywlGDHXf0sf)C2$rv*kejb)y`Fi*T>1-&Tc~ve}+!7 z2nC9fl}|qZW-M5O_NarSd>3v!m!|4z38}<|;pwA{XA9EfHzv$^aX;MCli)3k|NECh~`gu)K~wPLDf=xf*z0 zKR1BNoRA{3hjouyjkOW_Ecef%-&a>M%JbS*z@0r1u%>oM>a{ROG>m30&TOLAY=VAo zEMq`ux}@G0*Q33PolmDDma(~3l~Z(zuVv2gXCe{9BG*kUG934u zcS;u@{0wCR^M+C5YBe(|%t@Ly&ySooOG7ZoXW^5LlCN=gvzw^qcF7R@-({csS~qz* zdavDzUwW7NLN)h-rvc6r&;jh_lV0E<-m3;&@jyXRc=8E(_P8Wv%85%<(>?aSd^feS z{I;`<09|ckhKaQo)x#i|&1Y>Yj4We{C(XGkL+nF(h7hR@*+^N7ciCE~x&mG%VE^TN zT2Jw$@Z4Td=^$B3aFJN{bUgHSw0vS6bIh+PUZ32418#x0IX^MGV~H3&Vyyw!A3!Pi z8|}S4z>iG5gFSvkH7AdRaAg|yhbp%g=StI^y_pMzg0Apmw&hanqYx9#{$c9NKf`68 zTolQP()Cx`)7Gx@OYL1wtyi}-(@_J;`^|4sUM9jE?uG5m#nY9rUu`=HsF2}4h|Km)UOF#UtyISzKU)}xT|GGrGu9@yS`smB@%WW82 vhF_fFODudr!50*KLBSUi{{JB1XO<#{PYwI6=?CLKo=I@viGbQ8e_sC|T`gn= literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee01.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee01.png index 6c1b3b5455fb2b391422bf20246a1736f26785ec..bc5ceea4d630cb7ff6ca9c58f9f0e4bb72104b61 100644 GIT binary patch literal 7040 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 ef69aw*gv6rV_J`x+E#zjY~aTtj(&-V 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 Dh>Zz7 literal 8937 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 diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee03.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee03.png index 266137a02c2af9a7942b5b9c57d53c4d0b62772f..42b998a1e1e2d408baa0e3ba7c330ff32e703316 100644 GIT binary patch literal 9983 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 Dh>Zz7 literal 8937 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 diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png new file mode 100644 index 0000000000000000000000000000000000000000..42b998a1e1e2d408baa0e3ba7c330ff32e703316 GIT binary patch literal 9983 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 Dh>Zz7 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-inline/change_inline.ts b/integration_tests/specs/css/css-inline/change_inline.ts index b74e3e9e03..399554786d 100644 --- a/integration_tests/specs/css/css-inline/change_inline.ts +++ b/integration_tests/specs/css/css-inline/change_inline.ts @@ -1,4 +1,4 @@ -describe('line-change', () => { +xdescribe('line-change', () => { it('001', async () => { const div =

    should be green
    document.body.appendChild(div); diff --git a/integration_tests/specs/dom/elements/link.ts b/integration_tests/specs/dom/elements/link.ts index 7510818478..d55683877d 100644 --- a/integration_tests/specs/dom/elements/link.ts +++ b/integration_tests/specs/dom/elements/link.ts @@ -30,7 +30,7 @@ describe('Link Element', () => { document.head.appendChild(link); let p = document.createElement('p'); - p.appendChild(document.createTextNode('This text should be green on a white background')); + p.appendChild(document.createTextNode('002 This text should be green on a white background')); BODY.appendChild(p); }); diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index aeb6372a97..105cdb6c03 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -221,9 +221,6 @@ class Document extends Node { } void updateStyleIfNeeded() { - if (!ownerDocument.needsStyleRecalculate) { - return; - } styleNodeManager.updateActiveStyleSheets(); recalculateDocumentStyle(); } diff --git a/webf/lib/src/dom/node.dart b/webf/lib/src/dom/node.dart index 34a95d8f1f..386f6700df 100644 --- a/webf/lib/src/dom/node.dart +++ b/webf/lib/src/dom/node.dart @@ -118,6 +118,9 @@ abstract class Node extends EventTarget implements RenderObjectNode, LifecycleCa SchedulerBinding.instance.cancelFrameCallbackWithId(_frameCallbackId); } _frameCallbackId = SchedulerBinding.instance.scheduleFrameCallback((_) { + if (!ownerDocument.needsStyleRecalculate) { + return; + } ownerDocument.updateStyleIfNeeded(); }); } From 15dd2f8e5df955cf623f03693cc72c46a422bbe5 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Tue, 23 Aug 2022 01:30:45 +0800 Subject: [PATCH 247/498] feat: support keyframes rule --- webf/lib/css.dart | 2 +- webf/lib/src/css/css_rule.dart | 69 ++++++++++++++ webf/lib/src/css/parser/parser.dart | 92 ++++++++++++++----- webf/lib/src/css/parser/selector.dart | 39 +------- webf/lib/src/css/parser/token_kind.dart | 70 +++++++++++++- webf/lib/src/css/parser/tree.dart | 13 +-- webf/lib/src/css/rule.dart | 3 +- webf/lib/src/css/rule_set.dart | 4 + webf/lib/src/css/style_rule.dart | 29 ------ webf/lib/src/dom/style_node_manager.dart | 4 +- .../test/src/css/style_animations_parser.dart | 21 +++++ webf/test/webf_test.dart | 2 + 12 files changed, 240 insertions(+), 108 deletions(-) create mode 100644 webf/lib/src/css/css_rule.dart delete mode 100644 webf/lib/src/css/style_rule.dart create mode 100644 webf/test/src/css/style_animations_parser.dart diff --git a/webf/lib/css.dart b/webf/lib/css.dart index 851483723a..ea043ef7d8 100644 --- a/webf/lib/css.dart +++ b/webf/lib/css.dart @@ -22,7 +22,7 @@ export 'src/css/border.dart'; export 'src/css/gradient.dart'; export 'src/css/render_style.dart'; export 'src/css/rule.dart'; -export 'src/css/style_rule.dart'; +export 'src/css/css_rule.dart'; export 'src/css/style_declaration.dart'; export 'src/css/style_property.dart'; export 'src/css/style_sheet.dart'; diff --git a/webf/lib/src/css/css_rule.dart b/webf/lib/src/css/css_rule.dart new file mode 100644 index 0000000000..d7a1114789 --- /dev/null +++ b/webf/lib/src/css/css_rule.dart @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'package:webf/css.dart'; + +/// https://drafts.csswg.org/cssom/#the-cssstylerule-interface +class CSSStyleRule extends CSSRule { + @override + String get cssText => declaration.cssText; + + @override + int get type => CSSRule.STYLE_RULE; + + final SelectorGroup selectorGroup; + final CSSStyleDeclaration declaration; + + CSSStyleRule(this.selectorGroup, this.declaration) : super(); + + SimpleSelector? get lastSimpleSelector { + return selectorGroup.selectors.last.simpleSelectorSequences.last.simpleSelector; + } + + String get selectorText { + var sb = StringBuffer(); + selectorGroup.selectors.forEach((selector) { + sb.write(selector.simpleSelectorSequences.map((ss) => ss.simpleSelector.name).join(' ')); + }); + return sb.toString(); + } +} + +class KeyFrameBlock { + final List blockSelectors; + final CSSStyleDeclaration declarations; + + KeyFrameBlock(this.blockSelectors, this.declarations); +} + +class CSSKeyframesRule extends CSSRule { + final int _keyframeName; + final String name; + final List blocks = []; + + @override + int get type => CSSRule.KEYFRAMES_RULE; + + CSSKeyframesRule(this._keyframeName, this.name) : super(); + + void add(KeyFrameBlock block) { + blocks.add(block); + } + + String? get keyFrameName { + switch (_keyframeName) { + case TokenKind.DIRECTIVE_KEYFRAMES: + case TokenKind.DIRECTIVE_MS_KEYFRAMES: + return '@keyframes'; + case TokenKind.DIRECTIVE_WEB_KIT_KEYFRAMES: + return '@-webkit-keyframes'; + case TokenKind.DIRECTIVE_MOZ_KEYFRAMES: + return '@-moz-keyframes'; + case TokenKind.DIRECTIVE_O_KEYFRAMES: + return '@-o-keyframes'; + } + return null; + } +} diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 46716806f5..dd012b38ad 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -159,7 +159,7 @@ class CSSParser { /// Generate an error if [file] has not been completely consumed. void checkEndOfFile() { if (!(_peekKind(TokenKind.END_OF_FILE) || _peekKind(TokenKind.INCOMPLETE_COMMENT))) { - _error('premature end of file unknown CSS', _peekToken.span); + _error('premature end of file unknown CSS'); } } @@ -169,7 +169,7 @@ class CSSParser { // the danger here. bool isPrematureEndOfFile() { if (_maybeEat(TokenKind.END_OF_FILE)) { - _error('unexpected end of file', _peekToken.span); + _error('unexpected end of file'); return true; } else { return false; @@ -232,17 +232,27 @@ class CSSParser { } catch (e) { message = 'parsing error expected $expected'; } - _error(message, tok.span); + _error(message); } - void _error(String message, SourceSpan? location) { + void _error(String message, {SourceSpan? location}) { location ??= _peekToken.span; - // messages.error(message, location); + print(location.message(message, color: '\u001b[31m')); } - void _warning(String message, SourceSpan? location) { - location ??= _peekToken.span; - // messages.warning(message, location); + void _warning(String message, {SourceSpan? location}) { + location ??= _makeSpan(_peekToken.span); + print(location.message(message, color: '\u001b[35m')); + } + + SourceSpan _makeSpan(FileSpan start) { + // TODO(terry): there are places where we are creating spans before we eat + // the tokens, so using _previousToken is not always valid. + // TODO(nweiz): use < rather than compareTo when SourceSpan supports it. + if (_previousToken == null || _previousToken!.span.compareTo(start) < 0) { + return start; + } + return start.expand(_previousToken!.span); } /// Directive grammar: @@ -265,7 +275,7 @@ class CSSParser { /// declarations /// '}' /// supports: '@supports' supports_condition group_rule_body - TreeNode? processDirective() { + CSSRule? processDirective() { var tokenId = _peek(); switch (tokenId) { case TokenKind.DIRECTIVE_IMPORT: @@ -331,7 +341,7 @@ class CSSParser { // TODO(terry): Remove workaround when bug 8270 is fixed. case TokenKind.DIRECTIVE_MS_KEYFRAMES: if (tokenId == TokenKind.DIRECTIVE_MS_KEYFRAMES && isChecked) { - // _warning('@-ms-keyframes should be @keyframes'); + _warning('@-ms-keyframes should be @keyframes'); } // TODO(terry): End of workaround. @@ -348,7 +358,30 @@ class CSSParser { // ['from'|'to'|PERCENTAGE] [',' ['from'|'to'|PERCENTAGE] ]* ; _next(); - return null; + String name = ''; + if (_peekIdentifier()) { + name = identifier().name; + } + assert(name.isNotEmpty, 'keyframes rule name must not be null'); + _eat(TokenKind.LBRACE); + + var keyframe = CSSKeyframesRule(tokenId, name); + do { + List selectors = []; + do { + final selector = _next().text; + final text = _peekToken.text; + // ignore unit type + if (TokenKind.matchUnits(text, 0, text.length) != -1) { + _next(); + } + selectors.add(selector); + } while (_maybeEat(TokenKind.COMMA)); + + keyframe.add(KeyFrameBlock(selectors, processDeclarations())); + } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile()); + + return keyframe; case TokenKind.DIRECTIVE_FONTFACE: _next(); @@ -378,7 +411,7 @@ class CSSParser { return null; case TokenKind.DIRECTIVE_CONTENT: // TODO(terry): TBD - // _warning('@content not implemented.'); + _warning('@content not implemented.'); return null; case TokenKind.DIRECTIVE_MOZ_DOCUMENT: return null; @@ -393,7 +426,11 @@ class CSSParser { CSSRule? processRule([SelectorGroup? selectorGroup]) { if (selectorGroup == null) { - processDirective(); + final directive = processDirective(); + if (directive != null) { + _maybeEat(TokenKind.SEMICOLON); + return directive; + } selectorGroup = processSelectorGroup(); } if (selectorGroup != null) { @@ -527,7 +564,7 @@ class CSSParser { if (selector != null) { for (var sequence in selector.simpleSelectorSequences) { if (!sequence.isCombinatorNone) { - // _error('compound selector can not contain combinator'); + _error('compound selector can not contain combinator - ${sequence.combinatorToString}'); } } } @@ -661,7 +698,7 @@ class CSSParser { _eat(TokenKind.HASH); if (_anyWhiteSpaceBeforePeekToken(TokenKind.HASH)) { - // _error('Not a valid ID selector expected #id'); + _error('Not a valid ID selector expected #id', location: _makeSpan(start)); return null; } return IdSelector(identifier()); @@ -669,7 +706,7 @@ class CSSParser { _eat(TokenKind.DOT); if (_anyWhiteSpaceBeforePeekToken(TokenKind.DOT)) { - // _error('Not a valid class selector expected .className'); + _error('Not a valid class selector expected .className', location: _makeSpan(start)); return null; } return ClassSelector(identifier()); @@ -679,7 +716,7 @@ class CSSParser { case TokenKind.LBRACK: return processAttribute(); case TokenKind.DOUBLE: - _error('name must start with a alpha character, but found a number', _peekToken.span); + _error('name must start with a alpha character, but found a number'); _next(); break; } @@ -840,7 +877,7 @@ class CSSParser { } if (value == null) { - _error('expected attribute value string or ident', _peekToken.span); + _error('expected attribute value string or ident'); } } @@ -936,6 +973,8 @@ class CSSParser { static const int MAX_UNICODE = 0x10FFFF; String processQuotedString([bool urlString = false]) { + var start = _peekToken.span; + // URI term sucks up everything inside of quotes(' or ") or between parens var stopToken = urlString ? TokenKind.RPAREN : -1; @@ -948,19 +987,22 @@ class CSSParser { case TokenKind.SINGLE_QUOTE: stopToken = TokenKind.SINGLE_QUOTE; _next(); // Skip the SINGLE_QUOTE. + start = _peekToken.span; break; case TokenKind.DOUBLE_QUOTE: stopToken = TokenKind.DOUBLE_QUOTE; _next(); // Skip the DOUBLE_QUOTE. + start = _peekToken.span; break; default: if (urlString) { if (_peek() == TokenKind.LPAREN) { _next(); // Skip the LPAREN. + start = _peekToken.span; } stopToken = TokenKind.RPAREN; } else { - // _error('unexpected string'); + _error('unexpected string', location: _makeSpan(start)); } break; } @@ -1013,7 +1055,7 @@ class CSSParser { } if (!matchingParens) { - _error('problem parsing function expected ), ', _peekToken.span); + _error('problem parsing function expected ), '); } tokenizer._inString = inString; @@ -1032,7 +1074,7 @@ class CSSParser { case 'rgb': var expr = processExpr(); if (!_maybeEat(TokenKind.RPAREN)) { - _error('problem parsing function expected ), ', _peekToken.span); + _error('problem parsing function expected ), '); } return 'rgb($expr)'; case 'url': @@ -1042,7 +1084,7 @@ class CSSParser { // TODO(terry): Better error message and checking for mismatched quotes. if (_peek() == TokenKind.END_OF_FILE) { - _error('problem parsing URI', _peekToken.span); + _error('problem parsing URI'); } if (_peek() == TokenKind.RPAREN) { @@ -1061,13 +1103,13 @@ class CSSParser { // (GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); var expr = processExpr(); if (!_maybeEat(TokenKind.RPAREN)) { - _error('problem parsing var expected ), ', _peekToken.span); + _error('problem parsing var expected'); } return expr; default: var expr = processExpr(); if (!_maybeEat(TokenKind.RPAREN)) { - _error('problem parsing function expected ), ', _peekToken.span); + _error('problem parsing function expected'); } return expr; } @@ -1078,7 +1120,7 @@ class CSSParser { if (!TokenKind.isIdentifier(tok.kind) && !TokenKind.isKindIdentifier(tok.kind)) { if (isChecked) { - _warning('expected identifier, but found $tok', tok.span); + _warning('expected identifier, but found $tok', location: tok.span); } return Identifier(''); } diff --git a/webf/lib/src/css/parser/selector.dart b/webf/lib/src/css/parser/selector.dart index 6ba599e526..872e5a2c39 100644 --- a/webf/lib/src/css/parser/selector.dart +++ b/webf/lib/src/css/parser/selector.dart @@ -49,9 +49,6 @@ class SelectorGroup extends TreeNode { SelectorGroup(this.selectors) : super(); - @override - SelectorGroup clone() => SelectorGroup(selectors); - @override dynamic visit(Visitor visitor) => visitor.visitSelectorGroup(this); @@ -86,13 +83,6 @@ class Selector extends TreeNode { int get length => simpleSelectorSequences.length; - @override - Selector clone() { - var simpleSequences = simpleSelectorSequences.map((ss) => ss.clone()).toList(); - - return Selector(simpleSequences); - } - @override dynamic visit(Visitor visitor) => visitor.visitSelector(this); } @@ -125,9 +115,6 @@ class SimpleSelectorSequence extends TreeNode { } } - @override - SimpleSelectorSequence clone() => SimpleSelectorSequence(simpleSelector, combinator); - @override dynamic visit(Visitor visitor) => visitor.visitSimpleSelectorSequence(this); @@ -158,9 +145,6 @@ abstract class SimpleSelector extends TreeNode { class ElementSelector extends SimpleSelector { ElementSelector(name) : super(name); - @override - ElementSelector clone() => ElementSelector(_name); - @override String toString() => name; @@ -228,9 +212,6 @@ class AttributeSelector extends SimpleSelector { } } - @override - AttributeSelector clone() => AttributeSelector(_name as Identifier, _op, value); - @override String toString() => '[$name${matchOperator()}${valueToString()}]'; @@ -241,8 +222,6 @@ class AttributeSelector extends SimpleSelector { // #id class IdSelector extends SimpleSelector { IdSelector(Identifier name) : super(name); - @override - IdSelector clone() => IdSelector(_name as Identifier); @override String toString() => '#$_name'; @@ -254,8 +233,7 @@ class IdSelector extends SimpleSelector { // .class class ClassSelector extends SimpleSelector { ClassSelector(Identifier name) : super(name); - @override - ClassSelector clone() => ClassSelector(_name as Identifier); + @override String toString() => '.$_name'; @@ -267,9 +245,6 @@ class ClassSelector extends SimpleSelector { class PseudoClassSelector extends SimpleSelector { PseudoClassSelector(Identifier name) : super(name); - @override - PseudoClassSelector clone() => PseudoClassSelector(_name as Identifier); - @override String toString() => ':$name'; @@ -284,9 +259,6 @@ class PseudoElementSelector extends SimpleSelector { PseudoElementSelector(Identifier name, {this.isLegacy = false}) : super(name); - @override - PseudoElementSelector clone() => PseudoElementSelector(_name as Identifier); - @override String toString() => "${isLegacy ? ':' : '::'}$name"; @@ -300,9 +272,6 @@ class PseudoClassFunctionSelector extends PseudoClassSelector { PseudoClassFunctionSelector(Identifier name, this.argument) : super(name); - @override - PseudoClassFunctionSelector clone() => PseudoClassFunctionSelector(_name as Identifier, argument); - Selector get selector => argument as Selector; List get expression => argument as List; @@ -317,9 +286,6 @@ class PseudoElementFunctionSelector extends PseudoElementSelector { PseudoElementFunctionSelector(Identifier name, this.expression) : super(name); - @override - PseudoElementFunctionSelector clone() => PseudoElementFunctionSelector(_name as Identifier, expression); - @override dynamic visit(Visitor visitor) => visitor.visitPseudoElementFunctionSelector(this); } @@ -330,9 +296,6 @@ class NegationSelector extends SimpleSelector { NegationSelector(this.negationArg) : super(Negation()); - @override - NegationSelector clone() => NegationSelector(negationArg); - @override dynamic visit(Visitor visitor) => visitor.visitNegationSelector(this); } diff --git a/webf/lib/src/css/parser/token_kind.dart b/webf/lib/src/css/parser/token_kind.dart index f860279284..2761ff2736 100644 --- a/webf/lib/src/css/parser/token_kind.dart +++ b/webf/lib/src/css/parser/token_kind.dart @@ -137,6 +137,35 @@ class TokenKind { static const int SUBSTRING_MATCH = 534; // '*=' static const int NO_MATCH = 535; // No operator. + // Unit types: + static const int UNIT_EM = 600; + static const int UNIT_EX = 601; + static const int UNIT_LENGTH_PX = 602; + static const int UNIT_LENGTH_CM = 603; + static const int UNIT_LENGTH_MM = 604; + static const int UNIT_LENGTH_IN = 605; + static const int UNIT_LENGTH_PT = 606; + static const int UNIT_LENGTH_PC = 607; + static const int UNIT_ANGLE_DEG = 608; + static const int UNIT_ANGLE_RAD = 609; + static const int UNIT_ANGLE_GRAD = 610; + static const int UNIT_ANGLE_TURN = 611; + static const int UNIT_TIME_MS = 612; + static const int UNIT_TIME_S = 613; + static const int UNIT_FREQ_HZ = 614; + static const int UNIT_FREQ_KHZ = 615; + static const int UNIT_PERCENT = 616; + static const int UNIT_FRACTION = 617; + static const int UNIT_RESOLUTION_DPI = 618; + static const int UNIT_RESOLUTION_DPCM = 619; + static const int UNIT_RESOLUTION_DPPX = 620; + static const int UNIT_CH = 621; // Measure of "0" U+0030 glyph. + static const int UNIT_REM = 622; // computed value ‘font-size’ on root elem. + static const int UNIT_VIEWPORT_VW = 623; + static const int UNIT_VIEWPORT_VH = 624; + static const int UNIT_VIEWPORT_VMIN = 625; + static const int UNIT_VIEWPORT_VMAX = 626; + // Directives (@nnnn) static const int DIRECTIVE_NONE = 640; static const int DIRECTIVE_IMPORT = 641; @@ -242,12 +271,43 @@ class TokenKind { {'type': TokenKind.MARGIN_DIRECTIVE_RIGHTBOTTOM, 'value': 'right-bottom'}, ]; + static const List> _UNITS = [ + {'unit': TokenKind.UNIT_EM, 'value': 'em'}, + {'unit': TokenKind.UNIT_EX, 'value': 'ex'}, + {'unit': TokenKind.UNIT_LENGTH_PX, 'value': 'px'}, + {'unit': TokenKind.UNIT_LENGTH_CM, 'value': 'cm'}, + {'unit': TokenKind.UNIT_LENGTH_MM, 'value': 'mm'}, + {'unit': TokenKind.UNIT_LENGTH_IN, 'value': 'in'}, + {'unit': TokenKind.UNIT_LENGTH_PT, 'value': 'pt'}, + {'unit': TokenKind.UNIT_LENGTH_PC, 'value': 'pc'}, + {'unit': TokenKind.UNIT_ANGLE_DEG, 'value': 'deg'}, + {'unit': TokenKind.UNIT_ANGLE_RAD, 'value': 'rad'}, + {'unit': TokenKind.UNIT_ANGLE_GRAD, 'value': 'grad'}, + {'unit': TokenKind.UNIT_ANGLE_TURN, 'value': 'turn'}, + {'unit': TokenKind.UNIT_TIME_MS, 'value': 'ms'}, + {'unit': TokenKind.UNIT_TIME_S, 'value': 's'}, + {'unit': TokenKind.UNIT_FREQ_HZ, 'value': 'hz'}, + {'unit': TokenKind.UNIT_FREQ_KHZ, 'value': 'khz'}, + {'unit': TokenKind.UNIT_FRACTION, 'value': 'fr'}, + {'unit': TokenKind.UNIT_RESOLUTION_DPI, 'value': 'dpi'}, + {'unit': TokenKind.UNIT_RESOLUTION_DPCM, 'value': 'dpcm'}, + {'unit': TokenKind.UNIT_RESOLUTION_DPPX, 'value': 'dppx'}, + {'unit': TokenKind.UNIT_CH, 'value': 'ch'}, + {'unit': TokenKind.UNIT_REM, 'value': 'rem'}, + {'unit': TokenKind.UNIT_VIEWPORT_VW, 'value': 'vw'}, + {'unit': TokenKind.UNIT_VIEWPORT_VH, 'value': 'vh'}, + {'unit': TokenKind.UNIT_VIEWPORT_VMIN, 'value': 'vmin'}, + {'unit': TokenKind.UNIT_VIEWPORT_VMAX, 'value': 'vmax'}, + {'unit': TokenKind.UNIT_PERCENT, 'value': '%'}, + ]; + // Some more constants: static const int ASCII_UPPER_A = 65; // ASCII value for uppercase A static const int ASCII_UPPER_Z = 90; // ASCII value for uppercase Z /// Return the token that matches the unit ident found. - static int matchList(Iterable> identList, String tokenField, String text, int offset, int length) { + static int matchList( + Iterable> identList, String tokenField, String text, int offset, int length) { for (final entry in identList) { final ident = entry['value'] as String; @@ -258,7 +318,8 @@ class TokenKind { var identChar = ident.codeUnitAt(i); var char = text.codeUnitAt(idx++); // Compare lowercase to lowercase then check if char is uppercase. - match = match && (char == identChar || ((char >= ASCII_UPPER_A && char <= ASCII_UPPER_Z) && (char + 32) == identChar)); + match = match && + (char == identChar || ((char >= ASCII_UPPER_A && char <= ASCII_UPPER_Z) && (char + 32) == identChar)); if (!match) { break; } @@ -289,6 +350,11 @@ class TokenKind { return matchList(MEDIA_OPERATORS, 'type', text, offset, length); } + /// Return the token that matches the unit ident found. + static int matchUnits(String text, int offset, int length) { + return matchList(_UNITS, 'unit', text, offset, length); + } + static String? idToValue(Iterable identList, int tokenId) { for (var entry in identList) { entry as Map; diff --git a/webf/lib/src/css/parser/tree.dart b/webf/lib/src/css/parser/tree.dart index 9b614fa1bd..c91a4fdc79 100644 --- a/webf/lib/src/css/parser/tree.dart +++ b/webf/lib/src/css/parser/tree.dart @@ -32,8 +32,6 @@ part of 'parser.dart'; /// The base type for all nodes in a CSS abstract syntax tree. abstract class TreeNode { - TreeNode clone(); - /// Classic double-dispatch visitor for implementing passes. dynamic visit(Visitor visitor) {} } @@ -43,9 +41,6 @@ class Identifier extends TreeNode { Identifier(this.name) : super(); - @override - Identifier clone() => Identifier(name); - @override String toString() { // Try to use the identifier's original lexeme to preserve any escape codes @@ -57,22 +52,18 @@ class Identifier extends TreeNode { class Wildcard extends TreeNode { Wildcard() : super(); - @override - Wildcard clone() => Wildcard(); + String get name => '*'; } class ThisOperator extends TreeNode { ThisOperator() : super(); - @override - ThisOperator clone() => ThisOperator(); + String get name => '&'; } class Negation extends TreeNode { Negation() : super(); - @override - Negation clone() => Negation(); String get name => 'not'; } diff --git a/webf/lib/src/css/rule.dart b/webf/lib/src/css/rule.dart index f8887c112e..fd567c319e 100644 --- a/webf/lib/src/css/rule.dart +++ b/webf/lib/src/css/rule.dart @@ -12,13 +12,14 @@ abstract class CSSRule { // https://drafts.csswg.org/cssom/#dom-cssrule-type // The following attribute and constants are historical. - int? type; + int get type; static const int STYLE_RULE = 1; static const int CHARSET_RULE = 2; static const int IMPORT_RULE = 3; static const int MEDIA_RULE = 4; static const int FONT_FACE_RULE = 5; static const int PAGE_RULE = 6; + static const int KEYFRAMES_RULE = 7; static const int MARGIN_RULE = 9; static const int NAMESPACE_RULE = 10; } diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index a7e5154353..e9d1dfb710 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -19,6 +19,8 @@ class RuleSet { final CSSMap tagRules = HashMap(); final List universalRules = []; + final Map keyframesRules = {}; + int _lastPosition = 0; void addRules(List rules) { @@ -33,6 +35,8 @@ class RuleSet { for (final selector in rule.selectorGroup.selectors) { findBestRuleSetAndAdd(selector, rule); } + } else if (rule is CSSKeyframesRule) { + keyframesRules[rule.name] = rule; } else { assert(false, 'Unsupported rule type: ${rule.runtimeType}'); } diff --git a/webf/lib/src/css/style_rule.dart b/webf/lib/src/css/style_rule.dart deleted file mode 100644 index 75f9a40506..0000000000 --- a/webf/lib/src/css/style_rule.dart +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ - -import 'package:webf/css.dart'; - -/// https://drafts.csswg.org/cssom/#the-cssstylerule-interface -class CSSStyleRule extends CSSRule { - @override - String get cssText => declaration.cssText; - - final SelectorGroup selectorGroup; - final CSSStyleDeclaration declaration; - - CSSStyleRule(this.selectorGroup, this.declaration) : super(); - - SimpleSelector? get lastSimpleSelector { - return selectorGroup.selectors.last.simpleSelectorSequences.last.simpleSelector; - } - - String get selectorText { - var sb = StringBuffer(); - selectorGroup.selectors.forEach((selector) { - sb.write(selector.simpleSelectorSequences.map((ss) => ss.simpleSelector.name).join(' ')); - }); - return sb.toString(); - } -} diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 56e9721790..453545b03f 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -11,7 +11,9 @@ import 'package:webf/dom.dart'; /* Handling element style updates 1. log all style element - 2. + 2. collect stylesheets + 3. calculate changed rule set + 4. invalidated element */ class StyleNodeManager { final List _styleSheetCandidateNodes = []; diff --git a/webf/test/src/css/style_animations_parser.dart b/webf/test/src/css/style_animations_parser.dart new file mode 100644 index 0000000000..39ef5121c6 --- /dev/null +++ b/webf/test/src/css/style_animations_parser.dart @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'package:webf/css.dart'; +import 'package:test/test.dart'; + +CSSRule? parseSingleRule(String rule) { + CSSStyleSheet sheet = CSSParser(rule).parse(); + return sheet.cssRules.first; +} + +void main() { + group('CSSStyleRuleParser', () { + test('0', () { + CSSRule? style = parseSingleRule('@keyframes ping { 75%, 100% { transform: scale(2); opacity: 0; } }'); + expect(style is CSSKeyframesRule, true); + expect((style as CSSKeyframesRule).blocks.first.blockSelectors, ['75', '100']); + }); + }); +} diff --git a/webf/test/webf_test.dart b/webf/test/webf_test.dart index 0b0c936a3a..7b36b2c52d 100644 --- a/webf/test/webf_test.dart +++ b/webf/test/webf_test.dart @@ -11,6 +11,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:webf/webf.dart'; import 'local_http_server.dart'; +import 'src/css/style_animations_parser.dart' as style_animations_parser; import 'src/css/style_rule_parser.dart' as style_rule_parser; import 'src/css/style_sheet_parser.dart' as style_sheet_parser; import 'src/css/style_inline_parser.dart' as style_inline_parser; @@ -67,6 +68,7 @@ void main() { style_rule_parser.main(); style_sheet_parser.main(); style_inline_parser.main(); + style_animations_parser.main(); css_values.main(); }); From e8c4070c1b532db2a2818d9a64139a679bdca934 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Wed, 24 Aug 2022 00:32:46 +0800 Subject: [PATCH 248/498] feat: optimize post frame callback --- .../css-selectors/combinator.ts.06fd22981.png | Bin 0 -> 4522 bytes .../css-selectors/combinator.ts.0e8424a11.png | Bin 0 -> 4695 bytes .../css-selectors/combinator.ts.6d2121b91.png | Bin 0 -> 4759 bytes .../tag-selector.ts.0c6a7df41.png | Bin 0 -> 11508 bytes .../tag-selector.ts.38aaea691.png | Bin 0 -> 9320 bytes .../tag-selector.ts.4083eb5f1.png | Bin 0 -> 9330 bytes .../tag-selector.ts.40c7c0241.png | Bin 0 -> 4761 bytes .../tag-selector.ts.5fddfcf51.png | Bin 0 -> 20821 bytes .../tag-selector.ts.c2987aa51.png | Bin 0 -> 9500 bytes .../tag-selector.ts.e05cf6221.png | Bin 0 -> 9398 bytes webf/lib/src/dom/document.dart | 21 ++++++++++++++++-- webf/lib/src/dom/node.dart | 16 ++----------- webf/lib/src/dom/style_node_manager.dart | 14 +++++++----- 13 files changed, 29 insertions(+), 22 deletions(-) create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.0e8424a11.png create mode 100644 integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.4083eb5f1.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.40c7c0241.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png create mode 100644 integration_tests/snapshots/css/css-selectors/tag-selector.ts.e05cf6221.png diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.06fd22981.png new file mode 100644 index 0000000000000000000000000000000000000000..42286f77aea68b2ab541844e92d065ae699437a0 GIT binary patch literal 4522 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png b/integration_tests/snapshots/css/css-selectors/combinator.ts.6d2121b91.png new file mode 100644 index 0000000000000000000000000000000000000000..fc37a56c29245da1207fc1abbf499761a1ff667c GIT binary patch literal 4759 zcmeHL`Bzid8V#k0)*{RLT0|ya4N@Kv1e6d!f>^{<3Ouc#G6WTkEC>h@LLh@PwFu~C zhLG4&!ImK+vkajsLm(tYhR7HoAps2uBtQt4kQe@gx7z;5_sd!5oI9LvpS}0}?s@3% z>uvDazRw^Khym)72InDb5r1!**>D zw}<}-fqZcs_3iN=5~=fJ$&W*?Ug}HFUyAv?Bte(qTu7NK|C|M%y6{Ug0@ey8{Q zo}YgG#mswm&GLiV+IHjnE@#ZV{5u9Newx(IGA*8nkY!)6>+W+>JxxP`p&r z`3_>fd(TO20EhZOv1VO7cI4(+5!TgiCmx-5p&x@?wrC}MgyWXy12H$c!*&%={J)aC z2rCotDw=KaQOySw7!J2bnMl}H`uT+TYuhH(%kt5h=(5F9E+HvlG`gNRJ8IGWFqfJa zV+y939??97ph2W(( zsU0Dc0y{7ZQovmXQ;Z^R?PPR znw62V-ipZR-u#rJe6M7??k|xoU#Gwj&{VtgR2g#$L;=rucD-K6=5IPBBp-MAyNvMb zZ~STa6@|RRj%n{LjSsIYtP25zBkcj-ljFa>>xUXSY>4&iZ%$Mt#5I6ysnx z=QmtjH{8z{&uPU-3I(>#wpO8WmrFGBOR(0k#Q@&$l_B#hkt4Ak=_r6tn3g?C>(zom z7AFm3&^hDC=H!41&2p2#hMeRjqB+53njugjsiqZa8IJwDZVu^iZ#u%})_ri-ecVa=7zAWJeAS@Y#j|8oN=D><10#pleb3N$6Q=9?+TU?Hj?G#)wFD0$m%QDvABo_a6|EPu0?ij z;gHCHOFVJVk4pX7;KYU2BtrTyml626wL8gB)pgiOW zvuRq^k!DuSwKq)St1MbIh8OB1LO5WtBJORB#S^HiTCIWM{0a**LR3{%9vQFJmIE;i zGiB0SkxN*vuDk@(VqHoRFTsLZVS$wm4)v9dN5vNLOOxoK?V8Xn|>=lKcIZLVP;j!stS&An$*lhw$~&``}&PJeNC2D@ZpJy8zL@M zb8F(5p*9s1`dGx+`=6Qdvv+qb#oI7W0}?o%IrieigYt{a|C9|ex}DC}vu0D@Jr}#p zAq-tOOjfXk^O7z?_DUiDT@jicUpO?Ao!sh>B8y$UOEXH0GTA#WHZxd!-q+ckuy4$o zm2+%bV>}HVUP2dk6lFr2IAcfasW(nLWkhkb{wcc0pguODh}u=GATmH0SZUYD#1)#-QprLVl*N^;6SUaP?QWS`@en8V zSjMol`Vgghlk7Js9yi>ZJ7d96cUB29t)f-t<2;_88`;MPs6Qsg8jl>#3ZyE2+RO+s zS7vo}SbmkzPGfZKp)b6JvPS9P3Y7KQawkq2FBTQ^)z^KSg>N^LVGu`bGNV-gnkuba z`@vStJ&l_(v}0TI-7Fa{vUB-}_-pCo;`b@ozGC*Y)j0QDPLK^afDQ8%rx zzP!;gbtN?G_CsC7^w)dqm`c2D)Rr|+KEAzz5(Hk?p2thLD zQHzcHDobBno=ZQPFzuL-bdr;#xyAtSn#(5=Hsp~M zMD(4OgfSb8X~M}8p91+)BSge>&c}?2tTFP)%7q%`FR;dIsPv8XzDB`3Uf4j&=qY8W zZia%-c$Fi934dW{ZO#(2AE|J#m zZ1y-z?LKe0aQ(3DpOB@*f%RAaz13;*PgYHTz24fo^KX@_Tkvhc_jiAJwm7uKp)C&m l17`mWrrDxck{{arZ-{t@S literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.0c6a7df41.png new file mode 100644 index 0000000000000000000000000000000000000000..bb0b607db0d461891ea666e2908180d486e6029e GIT binary patch literal 11508 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 ZnTeCZv~4R*;G4@5@Jp7a6&L=v{~w*vFZ}=j literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.38aaea691.png new file mode 100644 index 0000000000000000000000000000000000000000..e8c533d0878de1a9b8ee04dd4886f93f25f47293 GIT binary patch literal 9320 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.5fddfcf51.png new file mode 100644 index 0000000000000000000000000000000000000000..86d3c38b4df1576dd9e6e5ec90f3b874ecc04e04 GIT binary patch literal 20821 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png b/integration_tests/snapshots/css/css-selectors/tag-selector.ts.c2987aa51.png new file mode 100644 index 0000000000000000000000000000000000000000..f9ee17105fa60106fb2d899e8a4889893dc1ea73 GIT binary patch literal 9500 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 0) { - SchedulerBinding.instance.cancelFrameCallbackWithId(_frameCallbackId); - } - _frameCallbackId = SchedulerBinding.instance.scheduleFrameCallback((_) { - if (!ownerDocument.needsStyleRecalculate) { - return; - } - ownerDocument.updateStyleIfNeeded(); - }); + ownerDocument.updateStyleIfNeeded(); } // FIXME: The ownerDocument getter steps are to return null, if this is a document; otherwise this’s node document. // https://dom.spec.whatwg.org/#dom-node-ownerdocument late Document ownerDocument; - bool needsStyleRecalculate = false; + bool needsStyleRecalculate = true; /// The Node.parentElement read-only property returns the DOM node's parent Element, /// or null if the node either has no parent, or its parent isn't a DOM Element. diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 453545b03f..fc492cc1a0 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -59,17 +59,19 @@ class StyleNodeManager { _pendingStyleSheets.removeWhere((element) => element == styleSheet); } - void updateActiveStyleSheets() { + void updateActiveStyleSheets({bool rebuild = false}) { List newSheets = _collectActiveStyleSheets(); if (newSheets.isEmpty) { return; } - newSheets = _collectActiveStyleSheets().where((element) => element.cssRules.isNotEmpty).toList(); - RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); - if (changedRuleSet.isEmpty) { - return; + if (rebuild == false) { + newSheets = _collectActiveStyleSheets().where((element) => element.cssRules.isNotEmpty).toList(); + RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); + if (changedRuleSet.isEmpty) { + return; + } + invalidateElementStyle(changedRuleSet); } - invalidateElementStyle(changedRuleSet); document.handleStyleSheets(newSheets); } From fa18425e10da2e2773b54435dfad3f117377969d Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Wed, 24 Aug 2022 15:09:37 +0800 Subject: [PATCH 249/498] feat: document --- webf/lib/src/css/style_property.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/webf/lib/src/css/style_property.dart b/webf/lib/src/css/style_property.dart index 2362b8edc2..b764e5ca97 100644 --- a/webf/lib/src/css/style_property.dart +++ b/webf/lib/src/css/style_property.dart @@ -19,6 +19,7 @@ const String _0Percent = '0%'; // a-b -> aB String camelize(String str) { + // variables if (str.startsWith('--')) { return str; } From 8c9bce7140bb9475eadb881ecd7ac41c9296b2e5 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Wed, 24 Aug 2022 21:56:12 +0800 Subject: [PATCH 250/498] fix: element style update --- .../snapshots/dom/elements/link.ts.c15dbee04.png | Bin 9983 -> 0 bytes integration_tests/specs/dom/elements/link.ts | 2 +- webf/lib/src/dom/elements/head.dart | 3 ++- webf/lib/src/dom/style_node_manager.dart | 9 +++++++-- 4 files changed, 10 insertions(+), 4 deletions(-) delete mode 100644 integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png diff --git a/integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png b/integration_tests/snapshots/dom/elements/link.ts.c15dbee04.png deleted file mode 100644 index 42b998a1e1e2d408baa0e3ba7c330ff32e703316..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9983 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 Dh>Zz7 diff --git a/integration_tests/specs/dom/elements/link.ts b/integration_tests/specs/dom/elements/link.ts index d55683877d..3102c2a94b 100644 --- a/integration_tests/specs/dom/elements/link.ts +++ b/integration_tests/specs/dom/elements/link.ts @@ -24,7 +24,7 @@ describe('Link Element', () => { link.addEventListener('load', async () => { await snapshot(); link.setAttribute('href', 'assets:assets/good.css'); - await snapshot(0.5); + await snapshot(0.1); done(); }); document.head.appendChild(link); diff --git a/webf/lib/src/dom/elements/head.dart b/webf/lib/src/dom/elements/head.dart index 7efaf80498..213969d5eb 100644 --- a/webf/lib/src/dom/elements/head.dart +++ b/webf/lib/src/dom/elements/head.dart @@ -172,11 +172,12 @@ class LinkElement extends Element { final String cssString = await resolveStringFromData(bundle.data!); _styleSheet = CSSParser(cssString).parse(); + ownerDocument.needsStyleRecalculate = true; ownerDocument.styleNodeManager.appendPendingStyleSheet(_styleSheet!); + ownerDocument.updateStyleIfNeeded(); // Successful load. SchedulerBinding.instance.addPostFrameCallback((_) { - ownerDocument.updateStyleIfNeeded(); dispatchEvent(Event(EVENT_LOAD)); }); } catch (e) { diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index fc492cc1a0..0e2277233c 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -64,15 +64,21 @@ class StyleNodeManager { if (newSheets.isEmpty) { return; } + newSheets = newSheets.where((element) => element.cssRules.isNotEmpty).toList(); if (rebuild == false) { - newSheets = _collectActiveStyleSheets().where((element) => element.cssRules.isNotEmpty).toList(); RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); if (changedRuleSet.isEmpty) { return; } invalidateElementStyle(changedRuleSet); + } else { + document.visitChild((node) { + node.needsStyleRecalculate = true; + }); } + document.needsStyleRecalculate = true; document.handleStyleSheets(newSheets); + _pendingStyleSheets.clear(); } List _collectActiveStyleSheets() { @@ -98,7 +104,6 @@ class StyleNodeManager { } } }); - document.needsStyleRecalculate = true; } RuleSet analyzeStyleSheetChangeRuleSet(List oldSheets, List newSheets) { From 3a7db4c3c8a9176263f53f921ff7d68d510697f0 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Thu, 25 Aug 2022 18:28:43 +0800 Subject: [PATCH 251/498] feat: variable test --- integration_tests/scripts/html_loader.js | 7 +- ...erty-case-sensitive-001.html.afbab4d91.png | Bin 0 -> 8633 bytes ...om-property-inheritance.html.63fae6de1.png | Bin 0 -> 8642 bytes ...variable-declaration-01.html.45fe06df1.png | Bin 0 -> 6033 bytes ...variable-declaration-02.html.8cd858df1.png | Bin 0 -> 6033 bytes ...variable-declaration-03.html.73b28c271.png | Bin 0 -> 6033 bytes ...variable-declaration-04.html.6b76ecbc1.png | Bin 0 -> 6033 bytes ...variable-declaration-05.html.92548ee01.png | Bin 0 -> 5823 bytes ...variable-declaration-06.html.f4e5ebdd1.png | Bin 0 -> 6033 bytes ...variable-declaration-07.html.e54ecb071.png | Bin 0 -> 6033 bytes ...variable-declaration-08.html.2ee5de2f1.png | Bin 0 -> 6033 bytes ...variable-declaration-09.html.bb23c5ba1.png | Bin 0 -> 6033 bytes ...variable-declaration-10.html.1bd4944c1.png | Bin 0 -> 6033 bytes ...variable-declaration-13.html.2e4a404b1.png | Bin 0 -> 5940 bytes ...variable-declaration-14.html.d5341a8a1.png | Bin 0 -> 6033 bytes ...able-declaration-15-ref.html.13de75671.png | Bin 0 -> 5566 bytes ...variable-declaration-15.html.771703731.png | Bin 0 -> 5566 bytes ...able-declaration-16-ref.html.9e4852411.png | Bin 0 -> 5566 bytes ...variable-declaration-16.html.947fc0031.png | Bin 0 -> 5566 bytes ...able-declaration-17-ref.html.6a121adf1.png | Bin 0 -> 5855 bytes ...variable-declaration-17.html.1578222c1.png | Bin 0 -> 5855 bytes ...able-declaration-18-ref.html.2b364bdd1.png | Bin 0 -> 5566 bytes ...variable-declaration-18.html.74b52e161.png | Bin 0 -> 5855 bytes ...variable-declaration-19.html.66f8ee771.png | Bin 0 -> 5823 bytes ...variable-declaration-20.html.2e850f5e1.png | Bin 0 -> 5823 bytes ...variable-declaration-21.html.73214c1b1.png | Bin 0 -> 5823 bytes ...variable-declaration-22.html.823bc2641.png | Bin 0 -> 5823 bytes ...variable-declaration-23.html.424206ac1.png | Bin 0 -> 5823 bytes ...variable-declaration-24.html.1d9a68901.png | Bin 0 -> 5175 bytes ...variable-declaration-25.html.499f04671.png | Bin 0 -> 6033 bytes ...variable-declaration-26.html.a69cc9571.png | Bin 0 -> 5175 bytes ...variable-declaration-29.html.ba373c8d1.png | Bin 0 -> 5940 bytes ...variable-declaration-30.html.8e9b99541.png | Bin 0 -> 6033 bytes ...variable-declaration-31.html.d3c61d7c1.png | Bin 0 -> 5823 bytes ...variable-declaration-32.html.195b62b21.png | Bin 0 -> 6033 bytes ...variable-declaration-33.html.cef687511.png | Bin 0 -> 5823 bytes ...variable-declaration-34.html.f3e1cff11.png | Bin 0 -> 5823 bytes ...variable-declaration-35.html.e45d7f911.png | Bin 0 -> 5823 bytes ...variable-declaration-36.html.61d31a411.png | Bin 0 -> 5823 bytes ...variable-declaration-37.html.ba254d591.png | Bin 0 -> 5175 bytes ...variable-declaration-38.html.549f86881.png | Bin 0 -> 6033 bytes ...variable-declaration-39.html.237efc0f1.png | Bin 0 -> 6033 bytes ...variable-declaration-40.html.1e4067991.png | Bin 0 -> 6033 bytes ...variable-declaration-41.html.dc0de31e1.png | Bin 0 -> 3202 bytes .../specs/css/css-variables/css-variables.ts | 162 +++++++++++------- ...rs-custom-property-case-sensitive-001.html | 31 ++++ .../css-vars-custom-property-inheritance.html | 30 ++++ .../specs/css/css-variables/environment.ts | 6 +- .../variable-declaration-01.html | 17 ++ .../variable-declaration-02.html | 17 ++ .../variable-declaration-03.html | 18 ++ .../variable-declaration-04.html | 18 ++ .../variable-declaration-05.html | 18 ++ .../variable-declaration-06.html | 18 ++ .../variable-declaration-07.html | 19 ++ .../variable-declaration-08.html | 19 ++ .../variable-declaration-09.html | 19 ++ .../variable-declaration-10.html | 19 ++ .../variable-declaration-13.html | 19 ++ .../variable-declaration-14.html | 21 +++ .../variable-declaration-15-ref.html | 15 ++ .../variable-declaration-15.html | 22 +++ .../variable-declaration-16-ref.html | 15 ++ .../variable-declaration-16.html | 23 +++ .../variable-declaration-17-ref.html | 15 ++ .../variable-declaration-17.html | 23 +++ .../variable-declaration-18-ref.html | 15 ++ .../variable-declaration-18.html | 23 +++ .../variable-declaration-19.html | 21 +++ .../variable-declaration-20.html | 21 +++ .../variable-declaration-21.html | 23 +++ .../variable-declaration-22.html | 20 +++ .../variable-declaration-23.html | 22 +++ .../variable-declaration-24.html | 25 +++ .../variable-declaration-25.html | 25 +++ .../variable-declaration-26.html | 20 +++ .../variable-declaration-29.html | 17 ++ .../variable-declaration-30.html | 21 +++ .../variable-declaration-31.html | 20 +++ .../variable-declaration-32.html | 20 +++ .../variable-declaration-33.html | 20 +++ .../variable-declaration-34.html | 20 +++ .../variable-declaration-35.html | 20 +++ .../variable-declaration-36.html | 20 +++ .../variable-declaration-37.html | 20 +++ .../variable-declaration-38.html | 21 +++ .../variable-declaration-39.html | 21 +++ .../variable-declaration-40.html | 21 +++ .../variable-declaration-41.html | 20 +++ .../variable-declaration-42.html | 21 +++ .../variable-declaration-43.html | 20 +++ .../variable-declaration-44.html | 20 +++ .../variable-declaration-45.html | 24 +++ .../variable-declaration-46.html | 23 +++ .../variable-declaration-47.html | 25 +++ .../variable-declaration-48.html | 25 +++ .../variable-declaration-49.html | 26 +++ .../variable-declaration-50.html | 25 +++ .../variable-declaration-51.html | 24 +++ .../variable-declaration-52.html | 24 +++ .../variable-declaration-53.html | 22 +++ .../variable-declaration-54.html | 21 +++ .../variable-declaration-55.html | 21 +++ .../variable-declaration-56.html | 20 +++ .../variable-declaration-57.html | 24 +++ .../variable-declaration-58.html | 25 +++ .../variable-declaration-59.html | 17 ++ webf/test/src/css/style_rule_parser.dart | 12 ++ 108 files changed, 1402 insertions(+), 64 deletions(-) create mode 100644 integration_tests/snapshots/css/css-variables/css-vars-custom-property-case-sensitive-001.html.afbab4d91.png create mode 100644 integration_tests/snapshots/css/css-variables/css-vars-custom-property-inheritance.html.63fae6de1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-01.html.45fe06df1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-02.html.8cd858df1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-03.html.73b28c271.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-04.html.6b76ecbc1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-05.html.92548ee01.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-06.html.f4e5ebdd1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-07.html.e54ecb071.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-08.html.2ee5de2f1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-09.html.bb23c5ba1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-10.html.1bd4944c1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-13.html.2e4a404b1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-14.html.d5341a8a1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-15-ref.html.13de75671.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-15.html.771703731.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-16-ref.html.9e4852411.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-16.html.947fc0031.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-17-ref.html.6a121adf1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-17.html.1578222c1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-18-ref.html.2b364bdd1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-18.html.74b52e161.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-19.html.66f8ee771.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-20.html.2e850f5e1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-24.html.1d9a68901.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-25.html.499f04671.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-26.html.a69cc9571.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-29.html.ba373c8d1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-30.html.8e9b99541.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-31.html.d3c61d7c1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-32.html.195b62b21.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-33.html.cef687511.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-38.html.549f86881.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-39.html.237efc0f1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-40.html.1e4067991.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-41.html.dc0de31e1.png create mode 100644 integration_tests/specs/css/css-variables/css-vars-custom-property-case-sensitive-001.html create mode 100644 integration_tests/specs/css/css-variables/css-vars-custom-property-inheritance.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-01.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-02.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-03.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-04.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-05.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-06.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-07.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-08.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-09.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-10.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-13.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-14.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-15-ref.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-15.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-16-ref.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-16.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-17-ref.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-17.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-18-ref.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-18.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-19.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-20.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-21.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-22.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-23.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-24.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-25.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-26.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-29.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-30.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-31.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-32.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-33.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-34.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-35.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-36.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-37.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-38.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-39.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-40.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-41.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-42.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-43.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-44.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-45.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-46.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-47.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-48.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-49.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-50.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-51.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-52.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-53.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-54.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-55.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-56.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-57.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-58.html create mode 100644 integration_tests/specs/css/css-variables/variable-declaration-59.html diff --git a/integration_tests/scripts/html_loader.js b/integration_tests/scripts/html_loader.js index ab5aaf209a..441d6ad6b1 100644 --- a/integration_tests/scripts/html_loader.js +++ b/integration_tests/scripts/html_loader.js @@ -54,9 +54,12 @@ const loader = function(source) { // Use html_parse to parser html in html file. const html_parse = () => __webf_parse_html__('${htmlString}'); - ${isFit ? 'fit' : 'it'}("should work", async () => {\ + ${isFit ? 'fit' : 'it'}("should work", async (done) => {\ html_parse();\ - ${scripts.length === 0 ? `await snapshot(null, '${snapshotFilepath}', false);` : scripts.join('\n')} + requestAnimationFrame(async () => { + ${scripts.length === 0 ? `await snapshot(null, '${snapshotFilepath}', false);` : scripts.join('\n')} + done(); + }); }) }); `; diff --git a/integration_tests/snapshots/css/css-variables/css-vars-custom-property-case-sensitive-001.html.afbab4d91.png b/integration_tests/snapshots/css/css-variables/css-vars-custom-property-case-sensitive-001.html.afbab4d91.png new file mode 100644 index 0000000000000000000000000000000000000000..d09311f3a55d103e369fee3d5e94e2adb390ead3 GIT binary patch literal 8633 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 Uy4G*P=P(c$4V}v*wc8K>1vE4Y8UO$Q literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-01.html.45fe06df1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-01.html.45fe06df1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-06.html.f4e5ebdd1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-06.html.f4e5ebdd1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-14.html.d5341a8a1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-14.html.d5341a8a1.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 QuudnwjQoOq;M1& literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-15.html.771703731.png b/integration_tests/snapshots/css/css-variables/variable-declaration-15.html.771703731.png new file mode 100644 index 0000000000000000000000000000000000000000..251d4c98937ba2c66f2d3560c487223846d53699 GIT binary patch literal 5566 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 QuudnwjQoOq;M1& literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-16-ref.html.9e4852411.png b/integration_tests/snapshots/css/css-variables/variable-declaration-16-ref.html.9e4852411.png new file mode 100644 index 0000000000000000000000000000000000000000..251d4c98937ba2c66f2d3560c487223846d53699 GIT binary patch literal 5566 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 QuudnwjQoOq;M1& literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-16.html.947fc0031.png b/integration_tests/snapshots/css/css-variables/variable-declaration-16.html.947fc0031.png new file mode 100644 index 0000000000000000000000000000000000000000..251d4c98937ba2c66f2d3560c487223846d53699 GIT binary patch literal 5566 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 QuudnwjQoOq;M1& literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-17-ref.html.6a121adf1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-17-ref.html.6a121adf1.png new file mode 100644 index 0000000000000000000000000000000000000000..b70bc0d6523c5adf1a973c2e9a5d095e4d6b0a8c GIT binary patch literal 5855 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 pgiplyNgX~(^c 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 QuudnwjQoOq;M1& literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-18.html.74b52e161.png b/integration_tests/snapshots/css/css-variables/variable-declaration-18.html.74b52e161.png new file mode 100644 index 0000000000000000000000000000000000000000..b70bc0d6523c5adf1a973c2e9a5d095e4d6b0a8c GIT binary patch literal 5855 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-20.html.2e850f5e1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-20.html.2e850f5e1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 zcmeHL`&*J}8n$t!nkjR4yQYJt>|X8U>{_}_spSD@vdP*;E$zcmLMkg$1f~!{5E*kO zr!jS!HmyvkoW}eRunasPlGCIeR4U^n3Z;37gQSQk2=4pM{uBE{To)I7FW=#L?&rDh z`*}ILFFw}mBcG2vJUqPi?B1E^;j!q1hsS&0E?o>(Zu<|5%&%TPP*j!ZFcl7+%Nn1o0 zlbU}0l{B>GV%~+ku!n1xlkY6AxKVLw$S>tUP;tvm_bZ<|K13ynZ`n zH2dVe81JY9DEHgqrmSPAxreca@tSgh+vSw;AJwOd;4|K+wAE?!!z?oa&%_$lKkHo+ zv=GMhc&kw$$x=5Qwv4yx_ax(4)VS(!(P&Q<$mFB9c=7!~njS|`3HUI4c4dIl+@wz{ zkDS_VZXPa!Y`b!}%!w9$s^)yGe@d)>-fJzVyOnCpa?j5pxim@3Hl?)Z=WY7B;aVEg zETZKPT7DVhvfD@H+yy;n@t+P!$_u7zLXJNC$>+19yIC(iQMX{YqiBgeNSj$(%~%qA zu(=_A+pF&k)P%Z#Rt3*No<+1&?EI2!t13|oG zp};DSmf4%v<*E<)Lq`d-}+Yvr~tq88EshI&)b98tY?91S?a;q7JI%ztC zV_{mPO)DZy4G5eF3`U_Wtx3lqc=?9b(@+TjXollWtuuSLV+pE$ec84f^38*Ew;b)v zM)H0A=z@N_t;k+4KEFA=^DdkPBGJ)LVw=A?%!4tI`R#pga`gNwZ|&^lFcuPduGn^s zHZc9B4}9-JvD-w-ev_sV9GqVtD~_0Gu8HHKrMQz1J|Etwl1jU_0JfHI6eN1FH$1q2|e-jT4>Jr)0uvPP^3&n zPUOL%%o$TCWwbjKbT^30)F>xi*btY+b{X+u5#<7b04)9#vm#TK#w13}sMA4Ay}i9k zg+j3*{;E1Y+BJ=V_&gquk8KiwsMnWAK8NAKys>JQlGeL@>@GN2C#?PIGFZidu!)`L z)|vrhGy7^2$b3<*wI{48X2tejAP7kWd1J-dnhg|F9y&ZCLJNqjBehL-=Gqj=_Ffj! z&gpuCn%GNK;Z!i}M}4v1e8!h_Lcid)Yws0$g6RIdepmKXzCAJni5S~N!bxmOELa~d zk8XFp4v5Z7`t_?!&m}8Nls13X^fR3`!|4@bMa1azX5;<x2=%;Hc_F=>o-X(Bl{4!|TM_3B0!k=r0V5XN^-o z{VfKDv$`*Bw%zmeegPywe2fA?qC*|0Vb}psPf3V&<@8+MQtJ;u&=K3gQy>VyTp$Kb za0=9!h8g6R1Z_s3L`G8k<3l^Mu)r~JpKvt7O40oB69rS!5oq#FcnQ;!{! zZj>h&U!Xh9zhqIhAeBhj^1ls^bu4v1%I_5oPLq7(>hSIR@GUuR0$rH9adMBP@=)C0LT~52um(+Y+ zDZpX9qo((Ht@+orGN5x!B0lF53Ao|0kVE&rh5N*H8WT`{T$m{m!Zw5 zreZdw-o1sX|9Zb~X!alK9POSgcl@@xeIG>r-TkfSid)|=TdjP4b1%W=u!)X5_z}@+ z0zeI$_x?+%d12({?%3E^M5qDxun;-_=@AN};O0b&SqL-maV|j5dT-WvjL6y>L7us7 z*R$D!^x;yf0!miM3uYQq8xDPe%b$EuRnHu3r7p~^Anua$9+u({lYskNko+^0BhNrv zFNcHSgD~>=qUhAfW3+YPO2Sbc5H41G8n`@aVcsgOtU!pGt-{gxg`P{4Ec>7;W5u%7 zK_*INcZI3V+8Y8|<4OdLWNwtB6&g(nFutmDPIVU=14g8yHls?It7qB!3!G;CsX_Ab zxv3HKgU8I?P~4CjV;`U-@bR+t86DHYm}nU@IE+Fe9G3Qq)Egu*C018ghopC&3!i;i zL_o?2#;ai)Fpl;h0Ca!D;h54jn{SAXa*}%1;aY*t24{jPE~Y~D7|3Vx$>{WJ6Mw?_#FD`T2M!wx9DQ8n{#ZW< z09CihbMXtLrAE6ihZM}u>Wtl3u)LN!bT&Xa{;Y->e6xf3Tc>4By8^fF5AKeDr^Rt4 zTMIx0U<9y^$;o_|kyOZuMx0wRTo$35#y#;Rx80`AKRmx4OW-4I?Z=*oP1Sq7Kl-BXoJ6cYeR%ykwm_R0e37#dB}eF+vy zz%BwGV29(we|WN_K4XFDJ_jmVP1EJE{3G&b-|8&h>$Wu@Y?gLI?%^b-#Ff#drA%~=QD4Rkx zqlj~74BXA^W03V)I%-wWL3Kv-f~&16GVc`EDi~W)&9Q!<3k*+X(iZN!IfN2GR3oLU zqbfy`OXMGHxr59&X!22{v=tju618L*%L{}n$1TPh70GzMf|sl!Nz7t}RkI4vK=`0! zwF;3C4WSK!gwmN*sl&h%Aw4ytT7bg}j_E;5+bnu^NjhU=T3fUIQuZS@!-OQ>pI#ni zIJc$uR$WppNs%6!t=_k{Vq+cEs8lS8p6%G=-q4Wq=6Na@&cXFEgmAqnMQ~)J8 z;*lsd7LKyP@GxDQ+IT$93PdlmuB=CgG{B4~FQ93#PO zjgty033e;8zhum^Uw^!{N_4#E3S&l-L9iJVBH$BJ^Iq?JT4Z~a|Mz8o{m*rKoZyJ& ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png b/integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-24.html.1d9a68901.png b/integration_tests/snapshots/css/css-variables/variable-declaration-24.html.1d9a68901.png new file mode 100644 index 0000000000000000000000000000000000000000..d3699942bc8b1c6fdd40ddbc98bd529defb48921 GIT binary patch literal 5175 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-30.html.8e9b99541.png b/integration_tests/snapshots/css/css-variables/variable-declaration-30.html.8e9b99541.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-32.html.195b62b21.png b/integration_tests/snapshots/css/css-variables/variable-declaration-32.html.195b62b21.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png b/integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png b/integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png b/integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png new file mode 100644 index 0000000000000000000000000000000000000000..7d88634a242de36024c2a9b7d75f53684677cf7e GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png b/integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png new file mode 100644 index 0000000000000000000000000000000000000000..d3699942bc8b1c6fdd40ddbc98bd529defb48921 GIT binary patch literal 5175 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 CRQ9a^ literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-variables/css-variables.ts b/integration_tests/specs/css/css-variables/css-variables.ts index 0121972b6d..7646317419 100644 --- a/integration_tests/specs/css/css-variables/css-variables.ts +++ b/integration_tests/specs/css/css-variables/css-variables.ts @@ -1,33 +1,40 @@ describe('CSS Variables', () => { // https://github.com/web-platform-tests/wpt/blob/master/css/css-variables/css-variable-change-style-001.html - it('change-style-001', async () => { - - document.body.appendChild(createStyle(` + it('change-style-001', async (done) => { + document.body.appendChild( + createStyle(` .outer { --x: red; --y: green; --z: 28px; } - `)); - document.head.appendChild(createStyle(` + `) + ); + document.head.appendChild( + createStyle(` .inner { font-size: var(--z); } - `)); + `) + ); document.body.appendChild( -

    -
    -
    FontSize should be 28px.
    +
    +
    +
    FontSize should be 28px.
    ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - it('change-style-002', async () => { - document.head.appendChild(createStyle(` + it('change-style-002', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: red; @@ -35,43 +42,52 @@ describe('CSS Variables', () => { --z: 28px; font-size: var(--z); } - `)); + `) + ); document.body.appendChild( -
    -
    -
    FontSize should be 28px.
    +
    +
    +
    FontSize should be 28px.
    ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - - it('variable resolve color', async () => { - document.head.appendChild(createStyle(` + it('variable resolve color', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: red; --y: green; --z: 28px; background-color: var(--x); } - `)); + `) + ); document.body.appendChild( -
    -
    -
    Background should be red.
    +
    +
    +
    Background should be red.
    ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - it('nested variables', async () => { - document.head.appendChild(createStyle(` + it('nested variables', async (done) => { + document.head.appendChild( + createStyle(` .inner { color: var(--x); } @@ -79,43 +95,53 @@ describe('CSS Variables', () => { --y: red; --x: var(--y); } - `)); + `) + ); document.body.appendChild( -
    -
    -
    Color should be red.
    +
    +
    +
    Color should be red.
    ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); describe('Shorthand CSS properties', () => { - it('background', async () => { - document.head.appendChild(createStyle(` + it('background', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: red; --y: green; --z: 28px; background: var(--y); } - `)); + `) + ); document.body.appendChild( -
    -
    -
    Background should be green.
    +
    +
    +
    Background should be green.
    ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - it('margin', async () => { - document.head.appendChild(createStyle(` + it('margin', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: red; --y: green; @@ -123,21 +149,26 @@ describe('CSS Variables', () => { margin: var(--z); background: red; } - `)); + `) + ); document.body.appendChild( -
    -
    -
    Background should be red with 28px margin.
    +
    +
    +
    Background should be red with 28px margin.
    ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - it('padding', async () => { - document.head.appendChild(createStyle(` + it('padding', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: red; --y: green; @@ -145,21 +176,26 @@ describe('CSS Variables', () => { padding: var(--z); background: red; } - `)); + `) + ); document.body.appendChild( -
    -
    -
    Background should be red with 28px padding.
    +
    +
    +
    Background should be red with 28px padding.
    ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); - it('border', async () => { - document.head.appendChild(createStyle(` + it('border', async (done) => { + document.head.appendChild( + createStyle(` .inner { --x: 4px; --y: solid; @@ -167,17 +203,23 @@ describe('CSS Variables', () => { border: var(--x) var(--y) var(--z); background: red; } - `)); + `) + ); document.body.appendChild( -
    -
    -
    Background should be red with 4px green solid border.
    +
    +
    +
    + Background should be red with 4px green solid border. +
    ); - await snapshot(); + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); }); }); diff --git a/integration_tests/specs/css/css-variables/css-vars-custom-property-case-sensitive-001.html b/integration_tests/specs/css/css-variables/css-vars-custom-property-case-sensitive-001.html new file mode 100644 index 0000000000..df2b352702 --- /dev/null +++ b/integration_tests/specs/css/css-variables/css-vars-custom-property-case-sensitive-001.html @@ -0,0 +1,31 @@ + + + + CSS Variables Test: custom property names are case-sensitive + + + + + + + + +

    Test passes if there is a filled green square and no red.

    +
    +
    +
    +
    + + diff --git a/integration_tests/specs/css/css-variables/css-vars-custom-property-inheritance.html b/integration_tests/specs/css/css-variables/css-vars-custom-property-inheritance.html new file mode 100644 index 0000000000..bbda51d2fe --- /dev/null +++ b/integration_tests/specs/css/css-variables/css-vars-custom-property-inheritance.html @@ -0,0 +1,30 @@ + + + + + CSS Variables Test: custom properties use normal inheritance and cascade rules + + + + + + + +

    Test passes if there is a filled green square and no red.

    +
    +
    + + diff --git a/integration_tests/specs/css/css-variables/environment.ts b/integration_tests/specs/css/css-variables/environment.ts index 8c239c479c..55e332a145 100644 --- a/integration_tests/specs/css/css-variables/environment.ts +++ b/integration_tests/specs/css/css-variables/environment.ts @@ -1,6 +1,6 @@ // https://drafts.csswg.org/css-env-1/#env-function describe('CSS Environment', () => { - it('work with safe-area-inset', async () => { + it('work with safe-area-inset', async (done) => { const container = document.createElement('div'); const paddings = [ 'env(safe-area-inset-top)', @@ -13,9 +13,10 @@ describe('CSS Environment', () => { document.body.appendChild(document.createTextNode('PASS if no red appears.')); document.body.appendChild(container); await snapshot(); + done(); }); - it('work with safe-area-inset fallback', async () => { + it('work with safe-area-inset fallback', async (done) => { const container = document.createElement('div'); // Env has value, so not fallback to other. const paddings = [ @@ -29,5 +30,6 @@ describe('CSS Environment', () => { document.body.appendChild(document.createTextNode('PASS if no red appears.')); document.body.appendChild(container); await snapshot(); + done(); }); }); diff --git a/integration_tests/specs/css/css-variables/variable-declaration-01.html b/integration_tests/specs/css/css-variables/variable-declaration-01.html new file mode 100644 index 0000000000..8c3ff421cf --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-01.html @@ -0,0 +1,17 @@ + + +CSS Test: Test declaring a variable consisting of a single token preceded by white space. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-02.html b/integration_tests/specs/css/css-variables/variable-declaration-02.html new file mode 100644 index 0000000000..4ee52b5860 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-02.html @@ -0,0 +1,17 @@ + + +CSS Test: Test declaring a variable consisting of a single token with no preceding white space. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-03.html b/integration_tests/specs/css/css-variables/variable-declaration-03.html new file mode 100644 index 0000000000..50aa2f3582 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-03.html @@ -0,0 +1,18 @@ + + +CSS Test: Test declaring a variable that references another variable. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-04.html b/integration_tests/specs/css/css-variables/variable-declaration-04.html new file mode 100644 index 0000000000..52d5d31489 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-04.html @@ -0,0 +1,18 @@ + + +CSS Test: Test declaring a variable consisting of a variable reference followed by white space. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-05.html b/integration_tests/specs/css/css-variables/variable-declaration-05.html new file mode 100644 index 0000000000..0403bcaec1 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-05.html @@ -0,0 +1,18 @@ + + +CSS Test: Test declaring a variable consisting of a variable reference that includes white space around the variable name. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-06.html b/integration_tests/specs/css/css-variables/variable-declaration-06.html new file mode 100644 index 0000000000..bccb52da55 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-06.html @@ -0,0 +1,18 @@ + + +CSS Test: Test overriding an existing variable declaration. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-07.html b/integration_tests/specs/css/css-variables/variable-declaration-07.html new file mode 100644 index 0000000000..0199c301d8 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-07.html @@ -0,0 +1,19 @@ + + +CSS Test: Test declaring a variable with valid syntax due to a variable reference having no tokens in its fallback. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-08.html b/integration_tests/specs/css/css-variables/variable-declaration-08.html new file mode 100644 index 0000000000..e3cfd9c089 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-08.html @@ -0,0 +1,19 @@ + + +CSS Test: Test declaring a variable that consists of a variable reference whose fallback is white space. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-09.html b/integration_tests/specs/css/css-variables/variable-declaration-09.html new file mode 100644 index 0000000000..c9d245888a --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-09.html @@ -0,0 +1,19 @@ + + +CSS Test: Test declaring a variable with a variable reference having only a comment in its fallback. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-10.html b/integration_tests/specs/css/css-variables/variable-declaration-10.html new file mode 100644 index 0000000000..cfcfd32eac --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-10.html @@ -0,0 +1,19 @@ + + +CSS Test: Test declaring a variable that consists of a variable reference with a fallback that includes a comment and an identifier. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-13.html b/integration_tests/specs/css/css-variables/variable-declaration-13.html new file mode 100644 index 0000000000..5c262b813f --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-13.html @@ -0,0 +1,19 @@ + + +CSS Test: Test declaring a variable with invalid syntax due to a variable reference having "!important" the top level of its fallback. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-14.html b/integration_tests/specs/css/css-variables/variable-declaration-14.html new file mode 100644 index 0000000000..153cfb9948 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-14.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable that consists of a variable reference and a following identifier with no intervening white space. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-15-ref.html b/integration_tests/specs/css/css-variables/variable-declaration-15-ref.html new file mode 100644 index 0000000000..6679d0de95 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-15-ref.html @@ -0,0 +1,15 @@ + + +CSS Reftest Reference + + + + +

    This text must be in Ahem.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-15.html b/integration_tests/specs/css/css-variables/variable-declaration-15.html new file mode 100644 index 0000000000..9b0fcffc73 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-15.html @@ -0,0 +1,22 @@ + + +CSS Test: Test declaring a variable that consists of a comma-separated font family list. + + + + + + +

    This text must be in Ahem.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-16-ref.html b/integration_tests/specs/css/css-variables/variable-declaration-16-ref.html new file mode 100644 index 0000000000..6679d0de95 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-16-ref.html @@ -0,0 +1,15 @@ + + +CSS Reftest Reference + + + + +

    This text must be in Ahem.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-16.html b/integration_tests/specs/css/css-variables/variable-declaration-16.html new file mode 100644 index 0000000000..bb047bb53d --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-16.html @@ -0,0 +1,23 @@ + + +CSS Test: Test declaring a variable that consists of a comma-separated font family list with the first item being a variable reference. + + + + + + +

    This text must be in Ahem.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-17-ref.html b/integration_tests/specs/css/css-variables/variable-declaration-17-ref.html new file mode 100644 index 0000000000..b0075fc38f --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-17-ref.html @@ -0,0 +1,15 @@ + + +CSS Reftest Reference + + + + +

    This text must be in Ahem.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-17.html b/integration_tests/specs/css/css-variables/variable-declaration-17.html new file mode 100644 index 0000000000..f0e3c9c79b --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-17.html @@ -0,0 +1,23 @@ + + +CSS Test: Test declaring a variable that consists of a comma-separated font family list with the last item being a variable reference. + + + + + + +

    This text must be in Ahem.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-18-ref.html b/integration_tests/specs/css/css-variables/variable-declaration-18-ref.html new file mode 100644 index 0000000000..6679d0de95 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-18-ref.html @@ -0,0 +1,15 @@ + + +CSS Reftest Reference + + + + +

    This text must be in Ahem.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-18.html b/integration_tests/specs/css/css-variables/variable-declaration-18.html new file mode 100644 index 0000000000..7262e375e6 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-18.html @@ -0,0 +1,23 @@ + + +CSS Test: Test declaring a variable that consists of a comma-separated font family list with the comma coming from a variable reference. + + + + + + +

    This text must be in Ahem.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-19.html b/integration_tests/specs/css/css-variables/variable-declaration-19.html new file mode 100644 index 0000000000..da0a825237 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-19.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable that consists of a function where one of the arguments is a variable reference. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-20.html b/integration_tests/specs/css/css-variables/variable-declaration-20.html new file mode 100644 index 0000000000..9bffae2d50 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-20.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable with "!important". + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-21.html b/integration_tests/specs/css/css-variables/variable-declaration-21.html new file mode 100644 index 0000000000..0a91196df0 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-21.html @@ -0,0 +1,23 @@ + + +CSS Test: Test declaring a variable that consists of a function where all of the arguments and commas are made up of variable references. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-22.html b/integration_tests/specs/css/css-variables/variable-declaration-22.html new file mode 100644 index 0000000000..a96d9cf189 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-22.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable that consists of a variable reference with a number of levels of variable reference fallbacks. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-23.html b/integration_tests/specs/css/css-variables/variable-declaration-23.html new file mode 100644 index 0000000000..b3b1d18234 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-23.html @@ -0,0 +1,22 @@ + + +CSS Test: Test declaring a variable with invalid syntax due to having two "!important" priorities. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-24.html b/integration_tests/specs/css/css-variables/variable-declaration-24.html new file mode 100644 index 0000000000..14fe47a1e5 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-24.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a variable that contains a CDO token. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-25.html b/integration_tests/specs/css/css-variables/variable-declaration-25.html new file mode 100644 index 0000000000..ad79f4ced1 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-25.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a variable that contains a CDC token. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-26.html b/integration_tests/specs/css/css-variables/variable-declaration-26.html new file mode 100644 index 0000000000..79ff10d2b6 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-26.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable that contains only a white space token. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-29.html b/integration_tests/specs/css/css-variables/variable-declaration-29.html new file mode 100644 index 0000000000..2dbe0183fe --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-29.html @@ -0,0 +1,17 @@ + + +CSS Test: Test declaring a variable with an invalid custom property name "--". + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-30.html b/integration_tests/specs/css/css-variables/variable-declaration-30.html new file mode 100644 index 0000000000..e354abb2d4 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-30.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable that contains a variable reference to itself. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-31.html b/integration_tests/specs/css/css-variables/variable-declaration-31.html new file mode 100644 index 0000000000..4a24bf2bd5 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-31.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with a digit. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-32.html b/integration_tests/specs/css/css-variables/variable-declaration-32.html new file mode 100644 index 0000000000..d11478e8f5 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-32.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with an escaped digit. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-33.html b/integration_tests/specs/css/css-variables/variable-declaration-33.html new file mode 100644 index 0000000000..521db857f5 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-33.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with an escaped letter. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-34.html b/integration_tests/specs/css/css-variables/variable-declaration-34.html new file mode 100644 index 0000000000..c6b4d42d34 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-34.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with a lone surrogate. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-35.html b/integration_tests/specs/css/css-variables/variable-declaration-35.html new file mode 100644 index 0000000000..f1289069f6 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-35.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with U+FFFD. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-36.html b/integration_tests/specs/css/css-variables/variable-declaration-36.html new file mode 100644 index 0000000000..1f984fe7a6 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-36.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the variable name begins with an out-of-range Unicode character escape. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-37.html b/integration_tests/specs/css/css-variables/variable-declaration-37.html new file mode 100644 index 0000000000..bd4b1c0f4b --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-37.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable consisting of a variable reference where white space surrounds the comma separating the variable name and fallback. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-38.html b/integration_tests/specs/css/css-variables/variable-declaration-38.html new file mode 100644 index 0000000000..ece8cf8ffa --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-38.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring two variables in the same declaration block that differ only in case, with lowercase first. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-39.html b/integration_tests/specs/css/css-variables/variable-declaration-39.html new file mode 100644 index 0000000000..d1caabbd6d --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-39.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring two variables in the same declaration block that differ only in case, with uppercase first. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-40.html b/integration_tests/specs/css/css-variables/variable-declaration-40.html new file mode 100644 index 0000000000..62a4e21700 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-40.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable with an invalid custom property name due to it beginning with "VAR-". + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-41.html b/integration_tests/specs/css/css-variables/variable-declaration-41.html new file mode 100644 index 0000000000..c1c585b0f9 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-41.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable where the second '-' in the "--" prefix of the custom property name is escaped. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-42.html b/integration_tests/specs/css/css-variables/variable-declaration-42.html new file mode 100644 index 0000000000..1a60ba2393 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-42.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable where the custom property name includes an unescaped Chinese character and an escape that is terminated by a space character. + + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-43.html b/integration_tests/specs/css/css-variables/variable-declaration-43.html new file mode 100644 index 0000000000..73aaef2b89 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-43.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable whose value is "initial". + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-44.html b/integration_tests/specs/css/css-variables/variable-declaration-44.html new file mode 100644 index 0000000000..f2a968d250 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-44.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable whose value is "inherit" where there is no variable to inherit from. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-45.html b/integration_tests/specs/css/css-variables/variable-declaration-45.html new file mode 100644 index 0000000000..a5003d9a69 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-45.html @@ -0,0 +1,24 @@ + + +CSS Test: Test declaring a variable whose value is "inherit" where there is a variable to inherit from. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-46.html b/integration_tests/specs/css/css-variables/variable-declaration-46.html new file mode 100644 index 0000000000..c846b1cf97 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-46.html @@ -0,0 +1,23 @@ + + +CSS Test: Test declaring a variable whose value is "initial" where there is a variable to inherit from. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-47.html b/integration_tests/specs/css/css-variables/variable-declaration-47.html new file mode 100644 index 0000000000..2f06d093f7 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-47.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a variable whose value consists of a reference to a variable whose value is "inherit". + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-48.html b/integration_tests/specs/css/css-variables/variable-declaration-48.html new file mode 100644 index 0000000000..9abf328977 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-48.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a number of variables in a cycle. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-49.html b/integration_tests/specs/css/css-variables/variable-declaration-49.html new file mode 100644 index 0000000000..9a4b984b80 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-49.html @@ -0,0 +1,26 @@ + + +CSS Test: Test declaring a variable that is a dependent of a variable involved in a cycle but which itself is not involved in a cycle. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-50.html b/integration_tests/specs/css/css-variables/variable-declaration-50.html new file mode 100644 index 0000000000..0545b003d9 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-50.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a number of variables in a chain, where the final element of the chain uses its fallback. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-51.html b/integration_tests/specs/css/css-variables/variable-declaration-51.html new file mode 100644 index 0000000000..eac02079bf --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-51.html @@ -0,0 +1,24 @@ + + +CSS Test: Test declaring a variable that consists of a reference to an invalid inherited variable. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-52.html b/integration_tests/specs/css/css-variables/variable-declaration-52.html new file mode 100644 index 0000000000..e913e2dcd9 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-52.html @@ -0,0 +1,24 @@ + + +CSS Test: Test declaring a variable that consists of a reference to an inherited variable whose value was a variable reference that used its fallback. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-53.html b/integration_tests/specs/css/css-variables/variable-declaration-53.html new file mode 100644 index 0000000000..b8b6a8cf5c --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-53.html @@ -0,0 +1,22 @@ + + +CSS Test: Test declaring a variable that consists of two variable references without fallback and with no intervening white space. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-54.html b/integration_tests/specs/css/css-variables/variable-declaration-54.html new file mode 100644 index 0000000000..8e0b39c4c4 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-54.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable that consists of two variable references with the first variable reference using fallback and with no intervening white space. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-55.html b/integration_tests/specs/css/css-variables/variable-declaration-55.html new file mode 100644 index 0000000000..2fb68d516d --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-55.html @@ -0,0 +1,21 @@ + + +CSS Test: Test declaring a variable that consists of two variable references with the second variable reference using fallback and with no intervening white space. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-56.html b/integration_tests/specs/css/css-variables/variable-declaration-56.html new file mode 100644 index 0000000000..21378f892d --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-56.html @@ -0,0 +1,20 @@ + + +CSS Test: Test declaring a variable whose value is "unset" where there is no variable to inherit from. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-57.html b/integration_tests/specs/css/css-variables/variable-declaration-57.html new file mode 100644 index 0000000000..aa8d84c6ac --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-57.html @@ -0,0 +1,24 @@ + + +CSS Test: Test declaring a variable whose value is "unset" where there is a variable to inherit from. + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-58.html b/integration_tests/specs/css/css-variables/variable-declaration-58.html new file mode 100644 index 0000000000..b05f967831 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-58.html @@ -0,0 +1,25 @@ + + +CSS Test: Test declaring a variable whose value consists of a reference to a variable whose value is "unset". + + + + +

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-59.html b/integration_tests/specs/css/css-variables/variable-declaration-59.html new file mode 100644 index 0000000000..2032a342d6 --- /dev/null +++ b/integration_tests/specs/css/css-variables/variable-declaration-59.html @@ -0,0 +1,17 @@ + + +CSS Test: Test declaring a variable with a trailing invalid token. + + + + +

    This text must be green.

    diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index ee9dddb129..3914db8790 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -142,5 +142,17 @@ void main() { CSSStyleRule styleRule = rule as CSSStyleRule; expect(styleRule.lastSimpleSelector?.name, 'foo'); }); + + test('17', () { + CSSRule? rule = parseSingleRule(' .foo { width: calc(100% - 100px); }'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + }); + + test('18', () { + CSSRule? rule = parseSingleRule(' .foo { --x: red; }'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + }); }); } From da17c1fe67005f0d58f965468eba2c09f539e8a9 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Thu, 25 Aug 2022 23:42:17 +0800 Subject: [PATCH 252/498] fix: IMPORTANT --- ...om-property-inheritance.html.63fae6de1.png | Bin 8642 -> 8633 bytes webf/lib/src/css/parser/parser.dart | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/integration_tests/snapshots/css/css-variables/css-vars-custom-property-inheritance.html.63fae6de1.png b/integration_tests/snapshots/css/css-variables/css-vars-custom-property-inheritance.html.63fae6de1.png index 77ddc92a362bdc4dc6e8c0b5a7f40a5e822e63ac..d09311f3a55d103e369fee3d5e94e2adb390ead3 100644 GIT binary patch literal 8633 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 Uy4G*P=P(c$4V}v*wc8K>1vE4Y8UO$Q diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index dd012b38ad..877595fcdc 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -929,7 +929,6 @@ class CSSParser { } var expr = processExpr(); - // Handle !important (prio) var importantPriority = _maybeEat(TokenKind.IMPORTANT); style.setProperty(propertyIdent, expr, importantPriority); @@ -962,6 +961,9 @@ class CSSParser { if (parenCount == 0 && (_peek() == TokenKind.SEMICOLON || _peek() == TokenKind.RBRACE)) { break; } + if (_peek() == TokenKind.IMPORTANT) { + break; + } end = _next().span; } if (end != null) { From 78ce4bd1fa1a150c6cbb9ab443ca27241fc52247 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Fri, 26 Aug 2022 00:39:29 +0800 Subject: [PATCH 253/498] fix: variable --- .../css-variables.ts.8842e44c1.png | Bin 5189 -> 5170 bytes .../css-variables.ts.f52dfa971.png | Bin 6585 -> 6580 bytes ...variable-declaration-05.html.92548ee01.png | Bin 5823 -> 6033 bytes ...variable-declaration-13.html.2e4a404b1.png | Bin 5940 -> 4993 bytes ...variable-declaration-20.html.2e850f5e1.png | Bin 5823 -> 6033 bytes ...variable-declaration-23.html.424206ac1.png | Bin 5823 -> 5175 bytes ...variable-declaration-30.html.8e9b99541.png | Bin 6033 -> 5823 bytes webf/lib/src/css/style_declaration.dart | 10 +++++++++- webf/lib/src/css/variable.dart | 14 +++++++++++--- 9 files changed, 20 insertions(+), 4 deletions(-) diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.8842e44c1.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.8842e44c1.png index e63609372a2f731ca259bcbd2fbaf998bc559354..d9d3c493a9f88cfee7e6f003bfcb591a0d159ef7 100644 GIT binary patch delta 2909 zcmXAqdsvbO8^+tQTBU1k)w@i$q->dKl4pVJp-H3!?a3EF zq3J|MH~n;(;<#8&SCAu$y!afyNOA&XMo>-jr_DR4j*E^_I;&C<4)uiaDfFJqyvZ$f zvZ`C;K>H+!z}(&|g^LlmFAW2Tn_n6JBvwUNyXI4%NT_EQpMK|#(m>?&221Q|(6SYo zdyHFA2Ujr_KOsE4P^LBFJdpUPrbcs1l$^ApXX;eo9x9*VA0e%7E(XCPUuN((^LjkC z@VKfC=R6S9up?KT2nAoy39pEW0A5>x6^o`q_DLMGej3}$nu(k!u}$2DmN6AvqRq8T z|GZ4S7o>xF!W#o^*oIAOmbNw}(e{E^i2Wk)`>u2XBj1<9s%GoAFY#&KcXj>SIHSq5 zEpDvZZVaCX^#t*~w^I$v{yV7Fej}J4$FsDb8k983w1wgV+-?1P1q3Jn0XHCSR@PQ_ zFxU8UhDgzddqjATR6%WGxSIc@O?3u=hMZ(=sBp%tj{=&84@cY(9@<`41YQ5W6 zhS7+koH!A)HjAC}v5DZ4!1X{E3yP|Wd?A2byLPsjVW0_38jTX9$Wd!LxdBneY2$&aD ztqN!vGg?HnCqnjDhWZ+!DMcG5xusY=v3oRlQ?j;s2i`{d(LR9ow^^Up5U7a|Mlg0= zCM+;$za8%k;Jj9~j>YGngfRRuCi9)b*kyDmvD$Ch6^aF*?_2EP@c8V~fKyVzG2!UC zD!RfOxy^BHH`vasi0()+6SR6xFy3-1>+nVm3S;gPUuZD)ATXSZmKNGAhj65@F69Sn zr1-+1M0Kww$%-u94uNz0SZ<|+0LT_&lC_UeyPQKq z!0;m{(0(>c|3ycS*$)@J@Mrprb%6F{`XB(hEd@mzr{LF}<}PNa2-&KCSWskry-DEm zAih1eaiD(CvEQHLkE@!CPrM`d-PTo(tYzy418L~CWX6UT^3GSc)v8e8#hvmj%#VjR zL+gL=NYT4)E&Z2+p0vroT`QUs1F#*WgvX9{+Wo5KLq7#m2Pjfz^VY4WsdM3Aid<@J zQC3}tv+~b!%fwN@vFhH;nfou8waVa7FI{Qu+N_46AQW=L}{Vn>Yr&BJ!G^OCua zZBEfX=lJ186m$>E*@NCpZbUkVr9BGxk>-r^z3dacys}ZPy6Pz=`S-pIW55R=EiKwS z1vx*P(kK?wZtk0k^nfE^z7O9yMY}yQM3QrCqg)$8R?Sl!3|Up%yybrL#KOe+$KlEA zrEnotz~F6tvGlZapuHR>YyQfIU0zr%H;+1!J(9F6VZJ~nTm@@lYRB27@;~{eiPbK} z3;3aYq|<$G!C9R~N(;nz@v+J4h4FN&dIYa3U7AauDUgEmE^dRH!D{E1su%U>qPqOg zD68i9+(%5azgsiefH$$PTV=pB{Pi3wZSqQtqd#4`g+pFR>D?*+dVT;XY2#1ddPDIP+ee+MNwEGm zkER)K2K8o&lpZG)1hdH5U_i9!RLM*FjgW-7B$gU{I^P?E{WwFpNV`%%RNDIhxnkwX zIUMR*=jR$PN7k$1Mp<1@DsdMszoNPx$O=x}EM@}13(2OfkEcl z+SGboRdv2Av-)7+VQz_Wk?3lyIT`-ChqmEJMUr-%PO!OUEW>JU1)5-YJ-Cm3(y0X_lW7es0&_d!Z1rEtku*=AQJbWCLK`4?)(OwfA+Nkz2qN%c$ zn#f%(>=kr=p!}KU{i_BUeZDbK-Bykztd)m)FmaV{)7Sq)z4>aZSVUbrr;ODOZ^zfq zCjcI1==r!YpdnO~>U%(MFSm$9iz~G83>7f~Eb^C+bao)%a$`FXZQoBU|;@pGAY6AAN^T4n*T0%%n$ZD1WtaMis>;Rjqki{XEV42j?Vxo{O;a z?5h#Ko9x29_#acUGvgYS&bH)yx#0M&(I3$n& zu-RmId<)$wJytK%P7}Lk$fo1SMpht&$e?z^uln)0I|EPl$+J3><8HM2p0(@iduNKW zf+}zw*xi!e0tnU;VHvaei7>iqruU^$7YhfGdNtAS_gzDEFFBh)cl^N+0#ofo(Ee9f zS&L#E>=UINvOWSv*XsUw0s{Clj})UPM<)t=EBo|~fm5?nG}Oc6E$9!KUVRcE+$G$? zZVF~SPvyK^5_%l($ivMj^lK=n! delta 2929 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 HV`u&cTm)r~ diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.f52dfa971.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.f52dfa971.png index a55c5eb4eb4b88f47ec80f8c89ee40e8808b9310..77f5425304086a38945e6e1a7c7362a4d51b2f1c 100644 GIT binary patch delta 4425 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 Doq^fY delta 4424 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-13.html.2e4a404b1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-13.html.2e4a404b1.png index d45b3fe6205321fcbafb594635db41dc395f35f0..11a245e86273398ca6fd3d7ca1f7cf4e7bd34833 100644 GIT binary patch delta 2730 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

    &Cz~ zH$=dxn~6jNOca_ zs7MZ-)%R3leS+40dAH)RBuMY%ZZbpwH6S=6ntgy+t>X`X@wfzI zisK={P>JP^zZkZ14GET^AWm;5Y*@2Ea;V5ktt3LSPI{O{vC+K7_%&1Vr!ggREv|QU zxx_cBE-aJWVUjAS3Lg2DEcfk{_Bl*q$^_Q7wJg5FSgcf)t6>2X10n_6Y|l3dCf;TP zM0;z(DZ_a<>l-0ts&Ub3(GBE}RXUpPS9$Wcj6M!j9K?%eK^6?Od_(&z$yrw&{&6D$ zYN}C91V~O_VcE{goYG+B!y^Hs=OGdn3tjHh$KZ#nW7%$zIcK;Br(R<0zt{ik#CjB= zKjI0CT|vdAJ}u_#DHcmVI|uOQV)Jt-ER$`?BnzDz^lq8FhjGsAVuRXeWc4?Uw;y_U zB?rsA~YQ3{#8klVQ3|h+Ly2 zy5(YsK8Al|y`?~u3vbew=K68IIHY|`RH@V1bB?hGenP`-R>hnizk?9U3{-uU2+n5Nr-Et-y~iYnq_)SLFZx_BlO_5ddWTHET7oBHI}nx$mJKX)TuZ?`GI8bg%Mpn z2zR~1I^?-yt>I%~j`;9ZJLCG5$V9tpG`Sq6iZpI!b2e diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-20.html.2e850f5e1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-20.html.2e850f5e1.png index 7d88634a242de36024c2a9b7d75f53684677cf7e..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 100644 GIT binary patch literal 6033 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png index 7d88634a242de36024c2a9b7d75f53684677cf7e..d3699942bc8b1c6fdd40ddbc98bd529defb48921 100644 GIT binary patch literal 5175 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-30.html.8e9b99541.png b/integration_tests/snapshots/css/css-variables/variable-declaration-30.html.8e9b99541.png index a814636ca1b3a7f79a58e5babbc3f1e50daae2f0..7d88634a242de36024c2a9b7d75f53684677cf7e 100644 GIT binary patch literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 literal 6033 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 _storage; @@ -24,8 +24,16 @@ mixin CSSVariableMixin on RenderStyle { Map? storage = _storage; _addDependency(identifier, propertyName); - if (storage != null && storage.containsKey(identifier)) { - return storage[identifier]; + if (storage != null && storage[identifier] != null) { + final variable = CSSVariable.tryParse(this, propertyName, storage[identifier]!); + if (variable != null) { + final id = variable.identifier.trim(); + if (storage[id] != null) { + return getCSSVariable(id, propertyName); + } + } else { + return storage[identifier]; + } } else { // Inherits from renderStyle tree. return parent?.getCSSVariable(identifier, propertyName); From 4dba290e8908e3ebad114051343fcbe39d8f45f6 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 28 Aug 2022 01:19:59 +0800 Subject: [PATCH 254/498] feat: var test fix: var reference cycle --- .../css/css-box/overflow.ts.7af966671.png | Bin 3754 -> 3761 bytes .../css/css-box/overflow.ts.ea32ff0d1.png | Bin 2386 -> 2396 bytes .../css/css-box/overflow.ts.ea32ff0d2.png | Bin 2807 -> 2825 bytes ...variable-declaration-13.html.2e4a404b1.png | Bin 4993 -> 6033 bytes ...variable-declaration-19.html.66f8ee771.png | Bin 5823 -> 6033 bytes ...variable-declaration-21.html.73214c1b1.png | Bin 5823 -> 0 bytes ...variable-declaration-22.html.823bc2641.png | Bin 5823 -> 0 bytes ...variable-declaration-23.html.424206ac1.png | Bin 5175 -> 5940 bytes ...variable-declaration-24.html.1d9a68901.png | Bin 5175 -> 0 bytes ...variable-declaration-26.html.a69cc9571.png | Bin 5175 -> 0 bytes ...variable-declaration-30.html.8e9b99541.png | Bin 5823 -> 6033 bytes ...variable-declaration-31.html.d3c61d7c1.png | Bin 5823 -> 0 bytes ...variable-declaration-33.html.cef687511.png | Bin 5823 -> 0 bytes ...variable-declaration-34.html.f3e1cff11.png | Bin 5823 -> 0 bytes ...variable-declaration-35.html.e45d7f911.png | Bin 5823 -> 0 bytes ...variable-declaration-36.html.61d31a411.png | Bin 5823 -> 0 bytes ...variable-declaration-37.html.ba254d591.png | Bin 5175 -> 0 bytes ...variable-declaration-41.html.dc0de31e1.png | Bin 3202 -> 0 bytes ...variable-declaration-45.html.6172c1901.png | Bin 0 -> 5940 bytes ...variable-declaration-47.html.feb731161.png | Bin 0 -> 5940 bytes ...ariable-declaration-48.html.bbb585ba1.png} | Bin ...variable-declaration-49.html.a59c4b611.png | Bin 0 -> 5940 bytes ...variable-declaration-50.html.4c6573d81.png | Bin 0 -> 6033 bytes ...variable-declaration-51.html.1b411d261.png | Bin 0 -> 6033 bytes ...variable-declaration-52.html.53af0e591.png | Bin 0 -> 5940 bytes ...variable-declaration-57.html.889f7f911.png | Bin 0 -> 5940 bytes ...variable-declaration-58.html.e5a37f891.png | Bin 0 -> 5940 bytes ...variable-declaration-59.html.26af32b61.png | Bin 0 -> 6033 bytes .../specs/css/css-box/overflow.ts | 18 +++++-- .../variable-declaration-21.html | 23 -------- .../variable-declaration-22.html | 20 ------- .../variable-declaration-24.html | 25 --------- .../variable-declaration-26.html | 20 ------- .../variable-declaration-31.html | 20 ------- .../variable-declaration-32.html | 20 ------- .../variable-declaration-33.html | 20 ------- .../variable-declaration-34.html | 20 ------- .../variable-declaration-35.html | 20 ------- .../variable-declaration-36.html | 20 ------- .../variable-declaration-37.html | 20 ------- .../variable-declaration-41.html | 20 ------- .../variable-declaration-42.html | 21 -------- .../variable-declaration-43.html | 20 ------- .../variable-declaration-44.html | 20 ------- .../variable-declaration-46.html | 23 -------- .../variable-declaration-49.html | 6 +-- .../variable-declaration-53.html | 22 -------- .../variable-declaration-54.html | 21 -------- .../variable-declaration-55.html | 21 -------- .../variable-declaration-56.html | 20 ------- webf/lib/src/css/parser/parser.dart | 30 ++++++++--- webf/lib/src/css/render_style.dart | 4 +- webf/lib/src/css/values/color.dart | 29 +++++----- webf/lib/src/css/values/variable.dart | 24 +++++---- webf/lib/src/css/variable.dart | 50 +++++++++++++++--- webf/lib/src/dom/document.dart | 11 +++- webf/lib/src/dom/style_node_manager.dart | 7 +-- 57 files changed, 127 insertions(+), 468 deletions(-) delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-24.html.1d9a68901.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-26.html.a69cc9571.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-31.html.d3c61d7c1.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-33.html.cef687511.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png delete mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-41.html.dc0de31e1.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-45.html.6172c1901.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-47.html.feb731161.png rename integration_tests/snapshots/css/css-variables/{variable-declaration-32.html.195b62b21.png => variable-declaration-48.html.bbb585ba1.png} (100%) create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-49.html.a59c4b611.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-50.html.4c6573d81.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-51.html.1b411d261.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-52.html.53af0e591.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-57.html.889f7f911.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-58.html.e5a37f891.png create mode 100644 integration_tests/snapshots/css/css-variables/variable-declaration-59.html.26af32b61.png delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-21.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-22.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-24.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-26.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-31.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-32.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-33.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-34.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-35.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-36.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-37.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-41.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-42.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-43.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-44.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-46.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-53.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-54.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-55.html delete mode 100644 integration_tests/specs/css/css-variables/variable-declaration-56.html diff --git a/integration_tests/snapshots/css/css-box/overflow.ts.7af966671.png b/integration_tests/snapshots/css/css-box/overflow.ts.7af966671.png index d8aae7aec390fefdc5b0c3b2c8588e48cd8cdc98..db846c8b6110ed8a070d96265fb8e1789afbe2ed 100644 GIT binary patch literal 3761 zcmeHK`CC(08cxD0h$wv+S{A`Nh%HO%MA?@>nW2~pGu0XymROer%93c&6|UV?xrIU@Ru zCG=dgyi^8*?Te3&3_p`ww=#=c#AUf~H`HrbVHN6!+hqf!fM?T3zO(a=^8EZmd+(En zCy#bndYj&Iv-=ofa){rszXWv7*pjsMy zeMSt&`MxkmW^XKzICOGhD!0NV$d@O*8-=fM20Dxez~bNojed<_*y2j{1gDonNxrO< zG0+nip!g?tIDym$R=A*;loimu8muc=D-EM}=vINX!W@Z1kN<7fg;pXx zPwYexD*p!f_2l6v7>^v_1S-K+`9UoB`U-cM>|3gtN)rYvRBb%DC``e~dBW(1$5098 z+E-D8P@@KjCnhHI&0B~fut11XaMZ(TQ!PrzxxES{I!wC=Him^Ls+eB>DJIixM9Ewq zU|UGSr>8HS=>6!;KhR+t&q0sS`InkV=fLBNd0U=*wo7R@w|D(a1F?r9(aIcXWb^i| zH^803%+2dd-#YrkWhhqQlPg>o>)3$LgO#HoH`JFbzTG4)gCY5!j^>C~p1l}U4ImjA zAL*5i7$n^H@0)oGcgF(=F`WKVLPlqbgrmEkgh)fn$O~mn($h<{g0mhMBOU!?WMrcr zL-beZ7o$*qB6+q2DM28mTHF_svfHIJAUpG@Z9^fcePXg}2_Kw7s$+>$c3<2jH#cO4p9GQ3mF zrJ?)324u;00;XEicM47uao$(wDG=a&I+LM)FGFMH!VuO)F5eR(XH2pSpvdph*o6Eg zDEC7uqg~ztu^mGsHWPT@+iD~mQFdVQh9~1e-XP|F68yPkW?#Ip<^7aaw?aK!h4o=0 zEbSH_C1zF4IQ6pZ7Twcl16kLN&;UoNN&GHR2#YM;0Wb5U!_NNAN-MW78>+8vjyEAK z;Rai3(!VrPUIKX-3i5|QUonbEk-Z0@v)L;(0hv(Yz2?oK%-jLK(}<6&b`(;n7aK7N z6G*oe)k2c0T~Y(2GNCqU4u8xubMRu%%SjspC|AudV29L)Z93^Dke*=1<@Zm`Yh^A? zCKW^4Zo}rg)}1BwgIN>a5ZQTw)P{I2zaLoXr2XZu%jIK=Y|`u( z*#%f8XW@Bb?mS2cMJWtwB*ZaC%m25Iy;h&68FHw*YChRPmaypcnqCV?X?1X#l`=^5 ziI>L(9u448MygOGez5*>w1w?R_iC2)sj1$5&JQ}?{zhF#tz2HB3Rzqed#>y|Cp>4} ztnoxqWyZ?n(~Df1$|LFyV`NpBJrhysTUL%t3gJ*@vkpeUV{TQ4iem*6*jSGKEfOW# zdV#3)ElOp?vA2HdI#G0b5unn5yv?Tf;>Q_lI%JHo;7`&f7~{G+CR@;PW$s>q#Jj?{ zIamBn_b0X63M{VSVBo@KU2lrwHI%IBcg1J0H|5 z#el1Uw5jX-`Db9Z3roi!>pSEvz!1{mvfI*gg#M?8QG`MjYh2oAK`7MC0Hv-pjSNRH zsM;z&Q<$BLuZ8pnoT@!bAFd^|rp3+13R~0hIu%gvQ#18ctdzt3!#zAkjl5S6n{X2o zblqD0UZX(`V(9vFe!YLl{MN*#w>;a?Cmo)9%+%5CWCQ%KyJmL^cDrC#2)ld1?sm9) gBK-fS;c{4``=wKvr44VPYbPxF_c4)-i1Ro83(FU(pa1{> literal 3754 zcmeHK`BPJQ5`QFt1QbCn1vx}+bKT z>s$V1_*rW!2P*&otV6#Dxc~rW2><{qL|H(Z=MEnmAz?h3Drqu*aW928%BU#gxomX~|gZcw?* z#g!Y?-<4Vi`XkBcmJNMdhF1C8Bk)39))l$mM2$M{m0I>Rssp+{o>PA#V31l44~e!ku`b{q&D2dI@lzJw<#jGfH)f6Qy@JRyu9&+e<8s%8?MrVY+}qC@Ch(dA^c~b zYIV4^rRBbb_pOx%WX*lv>d9CK7$&ryzC8B#CPl6Hu$faa4l^suCGj-hHQ<52!MHz@ z3#jrZja4ybA(q<8gLxFW-$Z1Nx$RML7+cL_&`6CHAL7#98M z4(*RnKuvg$13A*n$UKrzc&x~xY|sBD(2-G5IcB!5;-iY1wyg+W$(Oe@FzZdbBD92) zy_FS#(mk}J&=%6XxVoiD`oP+Pv?p&l*_W7zwTtCwt)NWZGZuf8@baNjmErIUlgE&kI z&F_zikO{(4JJiA*QCUx(hC0)p#E^85p#b-A7*)Qyx^t!y>cv+u?mbXgE$N~`UmifM zWz9##+QK$%VBDLaDAwFz(jJjxdFuUf>dG(_=p#a(Cherk`9%=ck+0`0r9)%I&eJ}E zjwefN6$k&C3BhuDCDZzOQVJ(kwU2M{JkT!Y7wxUK-U?@|i)mfZsX%iPV~0i*=g5~K z%PDzvbOh3gzLGs3MVC?J?Aa)ECHwS~w4edXTF-%ewhrcFU<;7jeExZ>QU2(?XKocvX5H0AAq$;vPyZ@GkF zL(5Vfr#C0hGdak?vC+Stpr{48J9c@PPLdh%($; z)BfD2VL`uwp-F}v_@N#DsWGoQ^M7lSQ z(WiFRT*vtLX(kTlr^#2P&Y?`2aqi?L#7vxjs7I z0`3Z$e3`4liJE!doQFYB zrO%=1`{w#cn*dL6+C{WE;@}~cp3sTd7TYD* qo`UUzu+0VAbhy0{{_kyQaA^#PKlc-@`vG*{1VT@ThqMI8-S{`_c61>C diff --git a/integration_tests/snapshots/css/css-box/overflow.ts.ea32ff0d1.png b/integration_tests/snapshots/css/css-box/overflow.ts.ea32ff0d1.png index 3c5c9bd536deafc0b8887e6764d6534814f2aed3..ad95b6996fd1cfb094fe8d467bd0b0e3fb9dfb2a 100644 GIT binary patch literal 2396 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_n+xPZ!6KiaBrZ8g?-o3NSdH-T43iCYhMDDK{fzLt2>*9&j?=(3rM% ztGHzKZi&$Gx0M+Z%`zG^8reF8Myb&tpd=Nn zc)s|yal90BLpjw_)QZQe?>*j{*YG+Ak#vD6`0Ja!`R5r9H1bL*cr9RD#4<{a2Ek}5 k7|jTyWx>cQ4qvgd+3>SVJUX< z4B-HR8jh3>1_q8lo-U3d6?5L+G~{A7VJUX< z4B-HR8jh3>1_rLpo-U3d6?5L+@jbCLS>nLQ*E0^?i zOWC3|IXTD9Ma<}(o0hC7c=3qNl>RjtVk(}(ll#>kZ>^ZUY^J60IqRf~^tvxvx9k1?B-WW0Gojb#! zHwzdSu}CR+jZ&jQKv60%(~pj^+5ThueCCGa8;;B_oF)!a9Kh6-OU{S1`Od_A|9fU1 zcly3PKVDRe|FtThl>{*KKbb8l`j+x-0L%-Q>A?)-Cdru_MNKi@2!dEe~(ogXKDz3!cP^?Agjn~$eoyVm#L zs^R}~VL2hWs53S-=YM_uoPXZt>%m`sKM(%y?|ZkZm?7s3(DjGy_SHN)dOJ<-{^ox_ zTW_a-i>rK9%&_L2etp{ach{7!&yV@`{W(L#S51YrKDs<>%j$#?${8 z6~Fy9^Y3#R+kLNOj(^~jvgn-rZo&NO^k=K)r^)TgfA+9CUGHz@U)#?s&oOS;CCv6~ z+WPc)H;gxbUN-alwsU*xzn)nqown`m+?`)v_X2~#(3^Sh(+g*`cc=5^%&h!%PWg5F z`to=4EI;>Oea>)W$I`~Sn)7GA-antSJN;REasQh7>Vs#J%h%t({qG^r%{g~G`8j{K zTdJ`59om1B5jmDVIXn4Dx3?TfEW5xE$iyq43Zb^%Tqxo&Sr#6f&`2GnV99$lBt}DG mGzSudXZG#ekvV^7(7;yg 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 BUAX`N diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-19.html.66f8ee771.png b/integration_tests/snapshots/css/css-variables/variable-declaration-19.html.66f8ee771.png index 7d88634a242de36024c2a9b7d75f53684677cf7e..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 100644 GIT binary patch literal 6033 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-21.html.73214c1b1.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png b/integration_tests/snapshots/css/css-variables/variable-declaration-22.html.823bc2641.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-23.html.424206ac1.png index d3699942bc8b1c6fdd40ddbc98bd529defb48921..d45b3fe6205321fcbafb594635db41dc395f35f0 100644 GIT binary patch literal 5940 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 literal 5175 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-31.html.d3c61d7c1.png b/integration_tests/snapshots/css/css-variables/variable-declaration-31.html.d3c61d7c1.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-33.html.cef687511.png b/integration_tests/snapshots/css/css-variables/variable-declaration-33.html.cef687511.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png b/integration_tests/snapshots/css/css-variables/variable-declaration-34.html.f3e1cff11.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 zcmeHL`&*J}8n$t!nkjR4yQYJt>|X8U>{_}_spSD@vdP*;E$zcmLMkg$1f~!{5E*kO zr!jS!HmyvkoW}eRunasPlGCIeR4U^n3Z;37gQSQk2=4pM{uBE{To)I7FW=#L?&rDh z`*}ILFFw}mBcG2vJUqPi?B1E^;j!q1hsS&0E?o>(Zu<|5%&%TPP*j!ZFcl7+%Nn1o0 zlbU}0l{B>GV%~+ku!n1xlkY6AxKVLw$S>tUP;tvm_bZ<|K13ynZ`n zH2dVe81JY9DEHgqrmSPAxreca@tSgh+vSw;AJwOd;4|K+wAE?!!z?oa&%_$lKkHo+ zv=GMhc&kw$$x=5Qwv4yx_ax(4)VS(!(P&Q<$mFB9c=7!~njS|`3HUI4c4dIl+@wz{ zkDS_VZXPa!Y`b!}%!w9$s^)yGe@d)>-fJzVyOnCpa?j5pxim@3Hl?)Z=WY7B;aVEg zETZKPT7DVhvfD@H+yy;n@t+P!$_u7zLXJNC$>+19yIC(iQMX{YqiBgeNSj$(%~%qA zu(=_A+pF&k)P%Z#Rt3*No<+1&?EI2!t13|oG zp};DSmf4%v<*E<)Lq`d-}+Yvr~tq88EshI&)b98tY?91S?a;q7JI%ztC zV_{mPO)DZy4G5eF3`U_Wtx3lqc=?9b(@+TjXollWtuuSLV+pE$ec84f^38*Ew;b)v zM)H0A=z@N_t;k+4KEFA=^DdkPBGJ)LVw=A?%!4tI`R#pga`gNwZ|&^lFcuPduGn^s zHZc9B4}9-JvD-w-ev_sV9GqVtD~_0Gu8HHKrMQz1J|Etwl1jU_0JfHI6eN1FH$1q2|e-jT4>Jr)0uvPP^3&n zPUOL%%o$TCWwbjKbT^30)F>xi*btY+b{X+u5#<7b04)9#vm#TK#w13}sMA4Ay}i9k zg+j3*{;E1Y+BJ=V_&gquk8KiwsMnWAK8NAKys>JQlGeL@>@GN2C#?PIGFZidu!)`L z)|vrhGy7^2$b3<*wI{48X2tejAP7kWd1J-dnhg|F9y&ZCLJNqjBehL-=Gqj=_Ffj! z&gpuCn%GNK;Z!i}M}4v1e8!h_Lcid)Yws0$g6RIdepmKXzCAJni5S~N!bxmOELa~d zk8XFp4v5Z7`t_?!&m}8Nls13X^fR3`!|4@bMa1azX5;<x2=%;Hc_F=>o-X(Bl{4!|TM_3B0!k=r0V5XN^-o z{VfKDv$`*Bw%zmeegPywe2fA?qC*|0Vb}psPf3V&<@8+MQtJ;u&=K3gQy>VyTp$Kb za0=9!h8g6R1Z_s3L`G8k<3l^Mu)r~JpKvt7O40oB69rS!5oq#FcnQ;!{! zZj>h&U!Xh9zhqIhAeBhj^1ls^bu4v1%I_5oPLq7(>hSIR@GUuR0$rH9adMBP@=)C0LT~52um(+Y+ zDZpX9qo((Ht@+orGN5x!B0lF53Ao|0kVE&rh5N*H8WT`{T$m{m!Zw5 zreZdw-o1sX|9Zb~X!alK9POSgcl@@xeIG>r-TkfSid)|=TdjP4b1%W=u!)X5_z}@+ z0zeI$_x?+%d12({?%3E^M5qDxun;-_=@AN};O0b&SqL-maV|j5dT-WvjL6y>L7us7 z*R$D!^x;yf0!miM3uYQq8xDPe%b$EuRnHu3r7p~^Anua$9+u({lYskNko+^0BhNrv zFNcHSgD~>=qUhAfW3+YPO2Sbc5H41G8n`@aVcsgOtU!pGt-{gxg`P{4Ec>7;W5u%7 zK_*INcZI3V+8Y8|<4OdLWNwtB6&g(nFutmDPIVU=14g8yHls?It7qB!3!G;CsX_Ab zxv3HKgU8I?P~4CjV;`U-@bR+t86DHYm}nU@IE+Fe9G3Qq)Egu*C018ghopC&3!i;i zL_o?2#;ai)Fpl;h0Ca!D;h54jn{SAXa*}%1;aY*t24{jPE~Y~D7|3Vx$>{WJ6Mw?_#FD`T2M!wx9DQ8n{#ZW< z09CihbMXtLrAE6ihZM}u>Wtl3u)LN!bT&Xa{;Y->e6xf3Tc>4By8^fF5AKeDr^Rt4 zTMIx0U<9y^$;o_|kyOZuMx0wRTo$35#y#;Rx80`AKRmx4OW-4I?Z=*oP1Sq7Kl-BXoJ6cYeR%ykwm_R0e37#dB}eF+vy zz%BwGV29(we|WN_K4XFDJ_jmVP1EJE{3G&b-|8&h>$Wu@Y?gLI?%^b-#Ff#drA%~=QD4Rkx zqlj~74BXA^W03V)I%-wWL3Kv-f~&16GVc`EDi~W)&9Q!<3k*+X(iZN!IfN2GR3oLU zqbfy`OXMGHxr59&X!22{v=tju618L*%L{}n$1TPh70GzMf|sl!Nz7t}RkI4vK=`0! zwF;3C4WSK!gwmN*sl&h%Aw4ytT7bg}j_E;5+bnu^NjhU=T3fUIQuZS@!-OQ>pI#ni zIJc$uR$WppNs%6!t=_k{Vq+cEs8lS8p6%G=-q4Wq=6Na@&cXFEgmAqnMQ~)J8 z;*lsd7LKyP@GxDQ+IT$93PdlmuB=CgG{B4~FQ93#PO zjgty033e;8zhum^Uw^!{N_4#E3S&l-L9iJVBH$BJ^Iq?JT4Z~a|Mz8o{m*rKoZyJ& ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png b/integration_tests/snapshots/css/css-variables/variable-declaration-35.html.e45d7f911.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png b/integration_tests/snapshots/css/css-variables/variable-declaration-36.html.61d31a411.png deleted file mode 100644 index 7d88634a242de36024c2a9b7d75f53684677cf7e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5823 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 ZG|9&9HL}lwKcPMLd>Ow}u;b9_{{jRmC^Y~8 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png b/integration_tests/snapshots/css/css-variables/variable-declaration-37.html.ba254d591.png deleted file mode 100644 index d3699942bc8b1c6fdd40ddbc98bd529defb48921..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5175 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 CRQ9a^ diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-45.html.6172c1901.png b/integration_tests/snapshots/css/css-variables/variable-declaration-45.html.6172c1901.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-47.html.feb731161.png b/integration_tests/snapshots/css/css-variables/variable-declaration-47.html.feb731161.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-32.html.195b62b21.png b/integration_tests/snapshots/css/css-variables/variable-declaration-48.html.bbb585ba1.png similarity index 100% rename from integration_tests/snapshots/css/css-variables/variable-declaration-32.html.195b62b21.png rename to integration_tests/snapshots/css/css-variables/variable-declaration-48.html.bbb585ba1.png diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-49.html.a59c4b611.png b/integration_tests/snapshots/css/css-variables/variable-declaration-49.html.a59c4b611.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-50.html.4c6573d81.png b/integration_tests/snapshots/css/css-variables/variable-declaration-50.html.4c6573d81.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-57.html.889f7f911.png b/integration_tests/snapshots/css/css-variables/variable-declaration-57.html.889f7f911.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-58.html.e5a37f891.png b/integration_tests/snapshots/css/css-variables/variable-declaration-58.html.e5a37f891.png new file mode 100644 index 0000000000000000000000000000000000000000..d45b3fe6205321fcbafb594635db41dc395f35f0 GIT binary patch literal 5940 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-variables/variable-declaration-59.html.26af32b61.png b/integration_tests/snapshots/css/css-variables/variable-declaration-59.html.26af32b61.png new file mode 100644 index 0000000000000000000000000000000000000000..a814636ca1b3a7f79a58e5babbc3f1e50daae2f0 GIT binary patch literal 6033 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 { it('overflow with absolute positioned elements', async (done) => { let scroller; + let div1; let container = createElement('div', { style: { display: 'inline-block', @@ -210,27 +211,34 @@ describe('Overflow', () => { border: '5px solid #000', padding: '5px' } - }, [ - createElement('div', { + }, + [ + div1 = createElement('div', { style: { position: 'absolute', right: '-20px', + width: '100px', color: 'red', display: 'inline', - bottom: '-550px' + bottom: '0px', + backgroundColor: 'blue' } - }, [ + }, + [ createText('XXX') ]) ]) ]); document.body.appendChild(container); + div1.style.backgroundColor = 'red'; + await snapshot(); requestAnimationFrame(async () => { + div1.style.backgroundColor = 'green'; scroller.scroll(20, 550); - await snapshot(0.2); + await snapshot(); done(); }); }); diff --git a/integration_tests/specs/css/css-variables/variable-declaration-21.html b/integration_tests/specs/css/css-variables/variable-declaration-21.html deleted file mode 100644 index 0a91196df0..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-21.html +++ /dev/null @@ -1,23 +0,0 @@ - - -CSS Test: Test declaring a variable that consists of a function where all of the arguments and commas are made up of variable references. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-22.html b/integration_tests/specs/css/css-variables/variable-declaration-22.html deleted file mode 100644 index a96d9cf189..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-22.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable that consists of a variable reference with a number of levels of variable reference fallbacks. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-24.html b/integration_tests/specs/css/css-variables/variable-declaration-24.html deleted file mode 100644 index 14fe47a1e5..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-24.html +++ /dev/null @@ -1,25 +0,0 @@ - - -CSS Test: Test declaring a variable that contains a CDO token. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-26.html b/integration_tests/specs/css/css-variables/variable-declaration-26.html deleted file mode 100644 index 79ff10d2b6..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-26.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable that contains only a white space token. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-31.html b/integration_tests/specs/css/css-variables/variable-declaration-31.html deleted file mode 100644 index 4a24bf2bd5..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-31.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with a digit. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-32.html b/integration_tests/specs/css/css-variables/variable-declaration-32.html deleted file mode 100644 index d11478e8f5..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-32.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with an escaped digit. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-33.html b/integration_tests/specs/css/css-variables/variable-declaration-33.html deleted file mode 100644 index 521db857f5..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-33.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with an escaped letter. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-34.html b/integration_tests/specs/css/css-variables/variable-declaration-34.html deleted file mode 100644 index c6b4d42d34..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-34.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with a lone surrogate. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-35.html b/integration_tests/specs/css/css-variables/variable-declaration-35.html deleted file mode 100644 index f1289069f6..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-35.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with U+FFFD. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-36.html b/integration_tests/specs/css/css-variables/variable-declaration-36.html deleted file mode 100644 index 1f984fe7a6..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-36.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the variable name begins with an out-of-range Unicode character escape. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-37.html b/integration_tests/specs/css/css-variables/variable-declaration-37.html deleted file mode 100644 index bd4b1c0f4b..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-37.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable consisting of a variable reference where white space surrounds the comma separating the variable name and fallback. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-41.html b/integration_tests/specs/css/css-variables/variable-declaration-41.html deleted file mode 100644 index c1c585b0f9..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-41.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable where the second '-' in the "--" prefix of the custom property name is escaped. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-42.html b/integration_tests/specs/css/css-variables/variable-declaration-42.html deleted file mode 100644 index 1a60ba2393..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-42.html +++ /dev/null @@ -1,21 +0,0 @@ - - -CSS Test: Test declaring a variable where the custom property name includes an unescaped Chinese character and an escape that is terminated by a space character. - - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-43.html b/integration_tests/specs/css/css-variables/variable-declaration-43.html deleted file mode 100644 index 73aaef2b89..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-43.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable whose value is "initial". - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-44.html b/integration_tests/specs/css/css-variables/variable-declaration-44.html deleted file mode 100644 index f2a968d250..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-44.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable whose value is "inherit" where there is no variable to inherit from. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-46.html b/integration_tests/specs/css/css-variables/variable-declaration-46.html deleted file mode 100644 index c846b1cf97..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-46.html +++ /dev/null @@ -1,23 +0,0 @@ - - -CSS Test: Test declaring a variable whose value is "initial" where there is a variable to inherit from. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-49.html b/integration_tests/specs/css/css-variables/variable-declaration-49.html index 9a4b984b80..01a84937b4 100644 --- a/integration_tests/specs/css/css-variables/variable-declaration-49.html +++ b/integration_tests/specs/css/css-variables/variable-declaration-49.html @@ -8,9 +8,7 @@

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-53.html b/integration_tests/specs/css/css-variables/variable-declaration-53.html deleted file mode 100644 index b8b6a8cf5c..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-53.html +++ /dev/null @@ -1,22 +0,0 @@ - - -CSS Test: Test declaring a variable that consists of two variable references without fallback and with no intervening white space. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-54.html b/integration_tests/specs/css/css-variables/variable-declaration-54.html deleted file mode 100644 index 8e0b39c4c4..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-54.html +++ /dev/null @@ -1,21 +0,0 @@ - - -CSS Test: Test declaring a variable that consists of two variable references with the first variable reference using fallback and with no intervening white space. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-55.html b/integration_tests/specs/css/css-variables/variable-declaration-55.html deleted file mode 100644 index 2fb68d516d..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-55.html +++ /dev/null @@ -1,21 +0,0 @@ - - -CSS Test: Test declaring a variable that consists of two variable references with the second variable reference using fallback and with no intervening white space. - - - - -

    This text must be green.

    diff --git a/integration_tests/specs/css/css-variables/variable-declaration-56.html b/integration_tests/specs/css/css-variables/variable-declaration-56.html deleted file mode 100644 index 21378f892d..0000000000 --- a/integration_tests/specs/css/css-variables/variable-declaration-56.html +++ /dev/null @@ -1,20 +0,0 @@ - - -CSS Test: Test declaring a variable whose value is "unset" where there is no variable to inherit from. - - - - -

    This text must be green.

    diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 877595fcdc..d453048706 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -733,7 +733,7 @@ class CSSParser { // : or :: and making this a normal selector. For now, // create an empty pseudoName. Identifier pseudoName; - if (_peekIdentifier()) { + if (_peekIdentifier() && _peekToken.text != 'var' && _peekToken.text != 'rgb' && _peekToken.text != 'rgba') { pseudoName = identifier(); } else { return null; @@ -929,9 +929,15 @@ class CSSParser { } var expr = processExpr(); - // Handle !important (prio) - var importantPriority = _maybeEat(TokenKind.IMPORTANT); - style.setProperty(propertyIdent, expr, importantPriority); + if (expr != null) { + // Handle !important (prio) + var importantPriority = false; + // handle multi-important + while (_maybeEat(TokenKind.IMPORTANT)) { + importantPriority = true; + } + style.setProperty(propertyIdent, expr, importantPriority); + } } else if (_peekToken.kind == TokenKind.VAR_DEFINITION) { _next(); } else if (_peekToken.kind == TokenKind.DIRECTIVE_INCLUDE) { @@ -946,10 +952,12 @@ class CSSParser { // expression: term [ operator? term]* // // operator: '/' | ',' - String processExpr([bool ieFilter = false]) { + String? processExpr([bool ieFilter = false]) { var start = _peekToken.span; FileSpan? end; + bool hasSynaxError = false; + var parenCount = 0; while (!_maybeEat(TokenKind.END_OF_FILE)) { if (_peek() == TokenKind.LPAREN) { @@ -958,14 +966,22 @@ class CSSParser { if (_peek() == TokenKind.RPAREN) { parenCount--; } - if (parenCount == 0 && (_peek() == TokenKind.SEMICOLON || _peek() == TokenKind.RBRACE)) { + if (parenCount <= 0 && (_peek() == TokenKind.SEMICOLON || _peek() == TokenKind.RBRACE)) { break; } if (_peek() == TokenKind.IMPORTANT) { - break; + if (parenCount == 0) { + break; + } else { + // synax error + hasSynaxError = true; + } } end = _next().span; } + if (hasSynaxError || parenCount < 0) { + return null; + } if (end != null) { return start.expand(end).text; } diff --git a/webf/lib/src/css/render_style.dart b/webf/lib/src/css/render_style.dart index e56174da56..363b6b1d50 100644 --- a/webf/lib/src/css/render_style.dart +++ b/webf/lib/src/css/render_style.dart @@ -25,7 +25,7 @@ abstract class RenderStyle { dynamic resolveValue(String property, String present); // CSSVariable - String? getCSSVariable(String identifier, String propertyName); + dynamic getCSSVariable(String identifier, String propertyName); void setCSSVariable(String identifier, String value); // Geometry @@ -400,7 +400,7 @@ class CSSRenderStyle extends RenderStyle RenderStyle renderStyle = this; // Process CSSVariable. - dynamic value = CSSVariable.tryParse(renderStyle, propertyName, propertyValue); + dynamic value = CSSVariable.tryParse(renderStyle, propertyValue); if (value != null) { return value; } diff --git a/webf/lib/src/css/values/color.dart b/webf/lib/src/css/values/color.dart index 320a2240d3..6da1a4ace1 100644 --- a/webf/lib/src/css/values/color.dart +++ b/webf/lib/src/css/values/color.dart @@ -170,8 +170,8 @@ const Map _namedColors = { final _colorHexRegExp = RegExp(r'^#([a-f0-9]{3,8})$', caseSensitive: false); final _colorHslRegExp = RegExp(r'^(hsla?)\(([0-9.-]+)(deg|rad|grad|turn)?[,\s]+([0-9.]+%)[,\s]+([0-9.]+%)([,\s/]+([0-9.]+%?))?\s*\)$'); -final _colorRgbRegExp = - RegExp(r'^(rgba?)\(([+-]?[0-9.]+%?)[,\s]+([+-]?[0-9.]+%?)[,\s]+([+-]?[0-9.]+%?)([,\s/]+([+-]?[0-9.]+%?))?\s*\)$'); +final _colorRgbRegExp = RegExp( + r'^(rgba?)\(([+-]?[^\s,.]+%?)[,\s]+([+-]?[^\s,.]+%?)[,\s]+([+-]?[^\s,.]+%?)([,\s/]+([+-]?[^\s,.]+%?))?\s*\)$'); final LinkedLruHashMap _cachedParsedColor = LinkedLruHashMap(maximumSize: 100); @@ -237,10 +237,10 @@ class CSSColor { renderStyle.addColorRelativeProperty(propertyName); return renderStyle.color; } - return parseColor(color); + return parseColor(color, renderStyle: renderStyle); } - static Color? parseColor(String color) { + static Color? parseColor(String color, {RenderStyle? renderStyle}) { color = color.trim().toLowerCase(); if (color == TRANSPARENT) { @@ -277,10 +277,10 @@ class CSSColor { } else if (color.startsWith(RGB)) { final rgbMatch = _colorRgbRegExp.firstMatch(color); if (rgbMatch != null) { - final double? rgbR = _parseColorPart(rgbMatch[2]!, 0, 255); - final double? rgbG = _parseColorPart(rgbMatch[3]!, 0, 255); - final double? rgbB = _parseColorPart(rgbMatch[4]!, 0, 255); - final double? rgbO = rgbMatch[6] != null ? _parseColorPart(rgbMatch[6]!, 0, 1) : 1; + final double? rgbR = _parseColorPart(rgbMatch[2]!, 0, 255, renderStyle); + final double? rgbG = _parseColorPart(rgbMatch[3]!, 0, 255, renderStyle); + final double? rgbB = _parseColorPart(rgbMatch[4]!, 0, 255, renderStyle); + final double? rgbO = rgbMatch[6] != null ? _parseColorPart(rgbMatch[6]!, 0, 1, renderStyle) : 1; if (rgbR != null && rgbG != null && rgbB != null && rgbO != null) { parsed = Color.fromRGBO(rgbR.round(), rgbG.round(), rgbB.round(), rgbO); } @@ -289,9 +289,9 @@ class CSSColor { final hslMatch = _colorHslRegExp.firstMatch(color); if (hslMatch != null) { final double? hslH = _parseColorHue(hslMatch[2]!, hslMatch[3]); - final double? hslS = _parseColorPart(hslMatch[4]!, 0, 1); - final double? hslL = _parseColorPart(hslMatch[5]!, 0, 1); - final double? hslA = hslMatch[7] != null ? _parseColorPart(hslMatch[7]!, 0, 1) : 1; + final double? hslS = _parseColorPart(hslMatch[4]!, 0, 1, renderStyle); + final double? hslL = _parseColorPart(hslMatch[5]!, 0, 1, renderStyle); + final double? hslA = hslMatch[7] != null ? _parseColorPart(hslMatch[7]!, 0, 1, renderStyle) : 1; if (hslH != null && hslS != null && hslL != null && hslA != null) { parsed = HSLColor.fromAHSL(hslA, hslH, hslS, hslL).toColor(); } @@ -439,9 +439,14 @@ String _x2(String value) { return sb.toString(); } -double? _parseColorPart(String value, double min, double max) { +double? _parseColorPart(String value, double min, double max, RenderStyle? renderStyle) { double? v; + if (value.startsWith('var') && renderStyle != null) { + final variable = CSSVariable.tryParse(renderStyle, value); + final computedValue = variable?.computedValue(''); + v = double.tryParse(computedValue); + } if (value.endsWith('%')) { final p = double.tryParse(value.substring(0, value.length - 1)); if (p == null) return null; diff --git a/webf/lib/src/css/values/variable.dart b/webf/lib/src/css/values/variable.dart index 62f37073fd..286b44700a 100644 --- a/webf/lib/src/css/values/variable.dart +++ b/webf/lib/src/css/values/variable.dart @@ -17,7 +17,7 @@ class CSSVariable { } // Try to parse CSSVariable. - static CSSVariable? tryParse(RenderStyle renderStyle, String propertyName, String propertyValue) { + static CSSVariable? tryParse(RenderStyle renderStyle, String propertyValue) { // font-size: var(--x); // font-size: var(--x, 28px); if (CSSFunction.isFunction(propertyValue, functionName: VAR)) { @@ -25,7 +25,12 @@ class CSSVariable { if (fns.first.args.isNotEmpty) { if (fns.first.args.length > 1) { // Has default value for CSS Variable. - return CSSVariable(fns.first.args.first, renderStyle, defaultValue: fns.first.args.last); + dynamic defaultValue = fns.first.args.last.trim(); + CSSVariable? defaultVar = CSSVariable.tryParse(renderStyle, defaultValue); + if (defaultVar != null) { + defaultValue = defaultVar; + } + return CSSVariable(fns.first.args.first, renderStyle, defaultValue: defaultValue); } else { return CSSVariable(fns.first.args.first, renderStyle); } @@ -35,23 +40,24 @@ class CSSVariable { } final String identifier; - final String? defaultValue; + final dynamic defaultValue; final RenderStyle _renderStyle; CSSVariable(this.identifier, this._renderStyle, {this.defaultValue}); // Get the lazy calculated CSS resolved value. dynamic computedValue(String propertyName) { - String? unsolvedValue = _renderStyle.getCSSVariable(identifier, propertyName) ?? defaultValue; - if (unsolvedValue == null) { + dynamic value = _renderStyle.getCSSVariable(identifier, propertyName) ?? defaultValue; + if (value == null) { return null; } - var resolved = _renderStyle.resolveValue(propertyName, unsolvedValue); - if (resolved is CSSVariable) { - return resolved.computedValue(propertyName); + if (value is CSSVariable) { + return value.computedValue(propertyName); + } else if (propertyName.isNotEmpty) { + return _renderStyle.resolveValue(propertyName, value); } else { - return resolved; + return value; } } diff --git a/webf/lib/src/css/variable.dart b/webf/lib/src/css/variable.dart index 581325c7ad..3717f483f2 100644 --- a/webf/lib/src/css/variable.dart +++ b/webf/lib/src/css/variable.dart @@ -7,7 +7,7 @@ import 'dart:collection'; import 'package:webf/css.dart'; mixin CSSVariableMixin on RenderStyle { - Map? _storage; + Map? _storage; final Map> _propertyDependencies = {}; void _addDependency(String identifier, String propertyName) { @@ -20,17 +20,25 @@ mixin CSSVariableMixin on RenderStyle { } @override - String? getCSSVariable(String identifier, String propertyName) { - Map? storage = _storage; + dynamic getCSSVariable(String identifier, String propertyName) { + if (_checkStorageLoop(identifier)) { + return null; + } + + Map? storage = _storage; _addDependency(identifier, propertyName); if (storage != null && storage[identifier] != null) { - final variable = CSSVariable.tryParse(this, propertyName, storage[identifier]!); - if (variable != null) { + final variable = storage[identifier]; + if (variable != null && variable is CSSVariable) { final id = variable.identifier.trim(); - if (storage[id] != null) { + if (storage[id] != null && id != identifier) { return getCSSVariable(id, propertyName); } + if (variable.defaultValue != null) { + return variable.defaultValue; + } + return null; } else { return storage[identifier]; } @@ -40,13 +48,39 @@ mixin CSSVariableMixin on RenderStyle { } } + bool _checkStorageLoop(String identifier) { + Map? storage = _storage; + if (storage == null) { + return false; + } + if (storage[identifier] == null) { + return false; + } + if (storage[identifier] is! CSSVariable) { + return false; + } + CSSVariable fast = storage[identifier]!; + CSSVariable slow = storage[identifier]!; + while (storage[fast.identifier] != null && + storage[fast.identifier] is CSSVariable && + storage[storage[fast.identifier]!.identifier] != null && + storage[storage[fast.identifier]!.identifier] is CSSVariable) { + fast = storage[storage[fast.identifier]!.identifier]!; + slow = storage[slow.identifier]!; + if (fast == slow) { + return true; + } + } + return false; + } + // --x: red // key: --x // value: red @override void setCSSVariable(String identifier, String value) { - _storage ??= HashMap(); - _storage![identifier] = value; + _storage ??= HashMap(); + _storage![identifier] = CSSVariable.tryParse(this, value) ?? value; if (_propertyDependencies.containsKey(identifier)) { _notifyCSSVariableChanged(_propertyDependencies[identifier]!, value); diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index d073a9db5e..e1f7d01303 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -234,13 +234,22 @@ class Document extends Node { }); } + bool _recalculating = false; void flushStyle({bool rebuild = false}) { if (!needsStyleRecalculate) { return; } - styleNodeManager.updateActiveStyleSheets(rebuild: rebuild); + if (_recalculating) { + return; + } + _recalculating = true; + if (!styleNodeManager.updateActiveStyleSheets(rebuild: rebuild)) { + _recalculating = false; + return; + } recalculateDocumentStyle(); needsStyleRecalculate = false; + _recalculating = false; } void recalculateDocumentStyle() { diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 0e2277233c..963e535e9e 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -59,16 +59,16 @@ class StyleNodeManager { _pendingStyleSheets.removeWhere((element) => element == styleSheet); } - void updateActiveStyleSheets({bool rebuild = false}) { + bool updateActiveStyleSheets({bool rebuild = false}) { List newSheets = _collectActiveStyleSheets(); if (newSheets.isEmpty) { - return; + return false; } newSheets = newSheets.where((element) => element.cssRules.isNotEmpty).toList(); if (rebuild == false) { RuleSet changedRuleSet = analyzeStyleSheetChangeRuleSet(document.styleSheets, newSheets); if (changedRuleSet.isEmpty) { - return; + return false; } invalidateElementStyle(changedRuleSet); } else { @@ -79,6 +79,7 @@ class StyleNodeManager { document.needsStyleRecalculate = true; document.handleStyleSheets(newSheets); _pendingStyleSheets.clear(); + return true; } List _collectActiveStyleSheets() { From 9511f3efff7f859e73903bb234ffe5e3ed865c53 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 29 Aug 2022 11:22:15 +0800 Subject: [PATCH 255/498] fix: parse rgba color --- webf/lib/src/css/values/color.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webf/lib/src/css/values/color.dart b/webf/lib/src/css/values/color.dart index 6da1a4ace1..ce7667c824 100644 --- a/webf/lib/src/css/values/color.dart +++ b/webf/lib/src/css/values/color.dart @@ -170,8 +170,8 @@ const Map _namedColors = { final _colorHexRegExp = RegExp(r'^#([a-f0-9]{3,8})$', caseSensitive: false); final _colorHslRegExp = RegExp(r'^(hsla?)\(([0-9.-]+)(deg|rad|grad|turn)?[,\s]+([0-9.]+%)[,\s]+([0-9.]+%)([,\s/]+([0-9.]+%?))?\s*\)$'); -final _colorRgbRegExp = RegExp( - r'^(rgba?)\(([+-]?[^\s,.]+%?)[,\s]+([+-]?[^\s,.]+%?)[,\s]+([+-]?[^\s,.]+%?)([,\s/]+([+-]?[^\s,.]+%?))?\s*\)$'); +final _colorRgbRegExp = + RegExp(r'^(rgba?)\(([+-]?[^\s,]+%?)[,\s]+([+-]?[^\s,]+%?)[,\s]+([+-]?[^\s,]+%?)([,\s/]+([+-]?[^\s,]+%?))?\s*\)$'); final LinkedLruHashMap _cachedParsedColor = LinkedLruHashMap(maximumSize: 100); From 37db48e642da2760dc3f2650a3b9758b437d66c4 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 29 Aug 2022 18:16:32 +0800 Subject: [PATCH 256/498] feat: add document.querySelector and document.querySelectorAll bindings. --- bridge/bindings/qjs/dom/document.cc | 32 +++++ bridge/bindings/qjs/dom/document.h | 4 + bridge/bindings/qjs/native_value.cc | 55 +++++--- bridge/bindings/qjs/native_value.h | 15 ++- bridge/polyfill/src/index.ts | 1 - bridge/polyfill/src/query-selector.ts | 179 -------------------------- webf/lib/src/bridge/native_value.dart | 36 ++++-- webf/lib/src/dom/document.dart | 10 +- 8 files changed, 112 insertions(+), 220 deletions(-) delete mode 100644 bridge/polyfill/src/query-selector.ts diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc index 88a799495a..9945683087 100644 --- a/bridge/bindings/qjs/dom/document.cc +++ b/bridge/bindings/qjs/dom/document.cc @@ -306,6 +306,38 @@ JSValue Document::getElementsByClassName(JSContext* ctx, JSValue this_val, int a 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; } diff --git a/bridge/bindings/qjs/dom/document.h b/bridge/bindings/qjs/dom/document.h index b6cff01d3a..6d6cfb4be1 100644 --- a/bridge/bindings/qjs/dom/document.h +++ b/bridge/bindings/qjs/dom/document.h @@ -40,6 +40,8 @@ class Document : public Node { 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); @@ -63,6 +65,8 @@ class Document : public Node { 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); diff --git a/bridge/bindings/qjs/native_value.cc b/bridge/bindings/qjs/native_value.cc index 663ef3588d..9bf5d11818 100644 --- a/bridge/bindings/qjs/native_value.cc +++ b/bridge/bindings/qjs/native_value.cc @@ -16,13 +16,13 @@ namespace webf::binding::qjs { #define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" NativeValue Native_NewNull() { - return (NativeValue){0, .u = {.int64 = 0}, NativeTag::TAG_NULL}; + return (NativeValue){0, 0, NativeTag::TAG_NULL}; } NativeValue Native_NewString(NativeString* string) { return (NativeValue){ + reinterpret_cast(string), 0, - .u = {.ptr = static_cast(string)}, NativeTag::TAG_STRING, }; } @@ -35,20 +35,24 @@ NativeValue Native_NewCString(std::string string) { NativeValue Native_NewFloat64(double value) { return (NativeValue){ - value, - .u = {.ptr = nullptr}, + {.float64 = value}, + 0, NativeTag::TAG_FLOAT64, }; } NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr) { - return (NativeValue){static_cast(pointerType), .u = {.ptr = ptr}, NativeTag::TAG_POINTER}; + return (NativeValue){ + reinterpret_cast(ptr), + static_cast(pointerType), + NativeTag::TAG_POINTER + }; } NativeValue Native_NewBool(bool value) { return (NativeValue){ 0, - .u = {.int64 = value ? 1 : 0}, + value ? 1 : 0, NativeTag::TAG_BOOL, }; } @@ -56,7 +60,7 @@ NativeValue Native_NewBool(bool value) { NativeValue Native_NewInt32(int32_t value) { return (NativeValue){ 0, - .u = {.int64 = value}, + value, NativeTag::TAG_INT, }; } @@ -69,8 +73,8 @@ NativeValue Native_NewJSON(ExecutionContext* context, JSValue& value) { // NativeString owned by NativeValue will be freed by users. NativeString* string = jsValueToNativeString(context->ctx(), stringifiedValue).release(); NativeValue result = (NativeValue){ + reinterpret_cast(string), 0, - .u = {.ptr = static_cast(string)}, NativeTag::TAG_JSON, }; JS_FreeValue(context->ctx(), stringifiedValue); @@ -126,7 +130,7 @@ NativeValue jsValueToNativeValue(JSContext* ctx, JSValue& value) { auto* context = static_cast(JS_GetContextOpaque(ctx)); if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); - return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); + return Native_NewPtr(JSPointerType::NativeBindingObject, imageElementInstance->nativeEventTarget); } return Native_NewJSON(context, value); @@ -223,7 +227,7 @@ static JSValue anonymousAsyncFunction(JSContext* ctx, JSValueConst this_val, int JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { switch (value.tag) { case NativeTag::TAG_STRING: { - auto* string = static_cast(value.u.ptr); + 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); @@ -231,41 +235,54 @@ JSValue nativeValueToJSValue(ExecutionContext* context, NativeValue& value) { return returnedValue; } case NativeTag::TAG_INT: { - return JS_NewUint32(context->ctx(), value.u.int64); + return JS_NewUint32(context->ctx(), value.int64); } case NativeTag::TAG_BOOL: { - return JS_NewBool(context->ctx(), value.u.int64 == 1); + return JS_NewBool(context->ctx(), value.int64 == 1); } case NativeTag::TAG_FLOAT64: { - return JS_NewFloat64(context->ctx(), value.float64); + return JS_NewFloat64(context->ctx(), value.u.float64); } case NativeTag::TAG_NULL: { return JS_NULL; } case NativeTag::TAG_JSON: { - auto* str = static_cast(value.u.ptr); + 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 = value.u.ptr; - int ptrType = (int)value.float64; + 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::NativeEventTarget)) { + } 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.u.int64; + int64_t functionId = value.int64; return JS_NewCFunctionData(context->ctx(), anonymousFunction, 4, functionId, 0, nullptr); } case NativeTag::TAG_ASYNC_FUNCTION: { - int64_t functionId = value.u.int64; + int64_t functionId = value.int64; return JS_NewCFunctionData(context->ctx(), anonymousAsyncFunction, 4, functionId, 0, nullptr); } } diff --git a/bridge/bindings/qjs/native_value.h b/bridge/bindings/qjs/native_value.h index 3c7e4ab59d..e3c3d46e6e 100644 --- a/bridge/bindings/qjs/native_value.h +++ b/bridge/bindings/qjs/native_value.h @@ -15,22 +15,23 @@ enum NativeTag { TAG_NULL = 3, TAG_FLOAT64 = 4, TAG_JSON = 5, - TAG_POINTER = 6, - TAG_FUNCTION = 7, - TAG_ASYNC_FUNCTION = 8, + TAG_LIST = 6, + TAG_POINTER = 7, + TAG_FUNCTION = 8, + TAG_ASYNC_FUNCTION = 9, }; -enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeEventTarget = 4 }; +enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, NativeBoundingClientRect = 2, NativeCanvasRenderingContext2D = 3, NativeBindingObject = 4 }; namespace webf::binding::qjs { // Exchange data struct between dart and C++ struct NativeValue { - double float64; union { - int64_t int64; - void* ptr; + uint64_t uint64; + double float64; } u; + int64_t int64; int64_t tag; }; diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index 05119be5e9..1676c32f68 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -5,7 +5,6 @@ import 'es6-promise/dist/es6-promise.auto'; import './dom'; -import './query-selector'; import { console } from './console'; import { fetch, Request, Response, Headers } from './fetch'; import { matchMedia } from './match-media'; diff --git a/bridge/polyfill/src/query-selector.ts b/bridge/polyfill/src/query-selector.ts deleted file mode 100644 index 2c69a0cc2d..0000000000 --- a/bridge/polyfill/src/query-selector.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ - -function fetchSelector(str: string, regex: RegExp) { - return { - selectors: str.match(regex) || [], - ruleStr: str.replace(regex, ' ') - }; -} - -function getElementsBySelector(selector: string): Array { - let context = document; - let temp, tempElements: Array = [], elements: Array = []; - selector = selector.trim(); - - if (selector === '') return []; - - // Classes. e.g. .row. - let classes: Array = []; - selector = selector.split(' ').map(item => { - if (item && item.charAt(0) === '.') { - temp = fetchSelector(selector, /\.[\w-_]+/g); - classes = classes.concat(temp.selectors) - return temp.ruleStr; - } - return item; - }).join(' '); - - // Ids. e.g. #mail-title. - temp = fetchSelector(selector, /#[\w-_]+/g); - let id = temp.selectors ? temp.selectors[0] : null; - selector = temp.ruleStr; - - // Attributes. e.g. [rel=external]. - temp = fetchSelector(selector, /\[.+?\]/g); - let attributes = temp.selectors; - selector = temp.ruleStr; - - // Elements. e.g. header, div. - temp = fetchSelector(selector, /\w+/g); - let els = temp.selectors; - selector = temp.ruleStr; - - // Get by id. - // Id is supposed to be unique. - // More need to attach other selectors. - if (id) { - id = id.substring(1); - return [document.getElementById(id) || null]; - } - - // If selector starts with *, find all elements. - if (selector.charAt(0) === '*' || els.length === 0) { - let temps: HTMLCollectionOf = context.getElementsByTagName('*'); - tempElements = Array.from(temps); - } else { - // Get by Elements. - let temps: HTMLCollectionOf = context.getElementsByTagName(els[0]); - tempElements = Array.from(temps); - } - - // Get by class name. - for (let i = 0, l = classes.length; i !== l; ++i) { - let className = classes[i].substring(1); - let temps: HTMLCollectionOf = context.getElementsByClassName(className); - let arrTemps: Array = Array.from(temps); - if (tempElements.length === 0) { - // If no temp elements yet, push into tempElements directly. - tempElements = tempElements.concat(arrTemps); - } else { - // Otherwise, find intersection. - let prevs: Array = []; - prevs = prevs.concat(tempElements); - tempElements = []; - - for (let index = 0; index < arrTemps.length; index++) { - let t = arrTemps[index]; - if (prevs.indexOf(t) !== -1) { - tempElements = tempElements.concat([t]); - } - } - } - } - - // Get by attributes. - if (attributes.length !== 0) { - let attrs = {}; - for (let i = 0; i < attributes.length; i++) { - let attribute = attributes[i]; - attribute = attribute.substring(1, attribute.length - 1); - let parts: Array = (attribute.split('=')).map(item => item.trim()); - if (parts[1]) { - parts[1] = parts[1].substring(1, parts[1].length - 1); - } - attrs[parts[0]] = parts[1]; - } - let prevs: Array = []; - prevs = prevs.concat(tempElements); - tempElements = []; - - for (let i = 0, l = prevs.length; i !== l; ++i) { - let t = prevs[i]; - let shouldAdd = true; - for (let key in attrs) { - let lastChar = key.charAt(key.length - 1); - if (/[\^\*\$]$/.test(key)) { - key = key.substring(0, key.length - 1); - } - let tempAttr = t.getAttribute(key) || ''; - // Case: [href*=/en]. - if (lastChar === '*' && tempAttr.indexOf(attrs[key + lastChar]) === -1) { - shouldAdd = false; - break; - } - // Case: [href^=/en]. - else if (lastChar === '^' && tempAttr.indexOf(attrs[key + lastChar]) !== 0) { - shouldAdd = false; - break; - } - // Case: [href$=/en]. - else if (lastChar === '$' && - (tempAttr.lastIndexOf(attrs[key + lastChar]) === -1 - ? false - : tempAttr.lastIndexOf(attrs[key + lastChar])) - !== - tempAttr.length - attrs[key + lastChar].length) { - shouldAdd = false; - break; - } - // Case: [href=/en]. - else if (/[\$\*\^]/.test(lastChar) === false && tempAttr !== attrs[key]) { - shouldAdd = false; - break; - } - - } - - if (shouldAdd) { - tempElements = tempElements.concat([t]); - } - } - } - - elements = elements.concat(tempElements); - return elements; -} - -document.querySelectorAll = function (selector: string): NodeListOf { - if (typeof selector !== 'string') { - throw new TypeError('document.querySelectorAll: Invalid selector type. ' + - 'Expect: string. Found: ' + typeof selector + '.'); - } - let elements: Array = []; - - // Split `selector` into rules by `,`. - let rules: Array = selector.split(',').map(item => item.trim()); - - // Iterate through each rule. - // For the sake of performance, use for-loop here rather than forEach. - for (let i = 0, l = rules.length; i !== l; ++i) { - let rule = rules[i]; - - // TODO: support ' ' and '>'. - elements = elements.concat(getElementsBySelector.call(this, rule)); - } - - return (elements as any) as NodeListOf; -}; - -document.querySelector = function (selector: string): Element | null { - if (typeof selector !== 'string') { - throw new TypeError('document.querySelector: Invalid selector type. ' + - 'Expect: string. Found: ' + typeof selector + '.'); - } - let elements = this.querySelectorAll(selector); - return elements.length > 0 ? elements[0] : null; -} diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index a8fb81ed7b..fc25fcb495 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -8,13 +8,15 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:webf/bridge.dart'; +import 'package:webf/foundation.dart'; class NativeValue extends Struct { - @Double() - external double float64; + // Or Float64 + @Uint64() + external int u; @Int64() - external int u; + external int int64; @Int64() external int tag; @@ -27,6 +29,7 @@ enum JSValueType { TAG_NULL, TAG_FLOAT64, TAG_JSON, + TAG_LIST, TAG_POINTER, TAG_FUNCTION, TAG_ASYNC_FUNCTION @@ -80,9 +83,9 @@ dynamic fromNativeValue(Pointer nativeValue) { case JSValueType.TAG_NULL: return null; case JSValueType.TAG_FLOAT64: - return nativeValue.ref.float64; + return uInt64ToDouble(nativeValue.ref.u); case JSValueType.TAG_POINTER: - JSPointerType pointerType = JSPointerType.values[nativeValue.ref.float64.toInt()]; + JSPointerType pointerType = JSPointerType.values[nativeValue.ref.int64]; switch (pointerType) { case JSPointerType.NativeBoundingClientRect: return Pointer.fromAddress(nativeValue.ref.u).cast(); @@ -115,7 +118,17 @@ void toNativeValue(Pointer target, value) { target.ref.u = value ? 1 : 0; } else if (value is double) { target.ref.tag = JSValueType.TAG_FLOAT64.index; - target.ref.float64 = value; + target.ref.u = 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; + } } else if (value is String) { target.ref.tag = JSValueType.TAG_STRING.index; target.ref.u = stringToNativeString(value).address; @@ -123,12 +136,17 @@ void toNativeValue(Pointer target, value) { target.ref.tag = JSValueType.TAG_POINTER.index; target.ref.u = value.address; if (value is Pointer) { - target.ref.float64 = JSPointerType.NativeBoundingClientRect.index.toDouble(); + target.ref.int64 = JSPointerType.NativeBoundingClientRect.index; } else if (value is Pointer) { - target.ref.float64 = JSPointerType.NativeCanvasRenderingContext2D.index.toDouble(); + target.ref.int64 = JSPointerType.NativeCanvasRenderingContext2D.index; } else if (value is Pointer) { - target.ref.float64 = JSPointerType.NativeBindingObject.index.toDouble(); + 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; diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index e1f7d01303..36a9b3d23e 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -94,14 +94,14 @@ class Document extends Node { } @override - getBindingProperty(String key) { - switch (key) { + invokeBindingMethod(String method, List args) { + switch (method) { case 'querySelectorAll': - return querySelectorAll; + return querySelectorAll(args); case 'querySelector': - return querySelector; + return querySelector(args); } - return super.getBindingProperty(key); + return super.invokeBindingMethod(method, args); } dynamic querySelector(List args) { From 637d4986b541172656c7455ae3896bfdfb400ae2 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Mon, 29 Aug 2022 10:17:35 +0000 Subject: [PATCH 257/498] Committing clang-format changes --- bridge/bindings/qjs/dom/document.cc | 8 ++------ bridge/bindings/qjs/native_value.cc | 6 +----- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/bridge/bindings/qjs/dom/document.cc b/bridge/bindings/qjs/dom/document.cc index 9945683087..14cb478976 100644 --- a/bridge/bindings/qjs/dom/document.cc +++ b/bridge/bindings/qjs/dom/document.cc @@ -315,9 +315,7 @@ JSValue Document::querySelector(JSContext* ctx, JSValue this_val, int argc, JSVa auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); std::string selectorText = jsValueToStdString(ctx, argv[0]); - NativeValue arguments[] = { - Native_NewCString(selectorText) - }; + NativeValue arguments[] = {Native_NewCString(selectorText)}; return document->invokeBindingMethod("querySelector", 1, arguments); } @@ -331,9 +329,7 @@ JSValue Document::querySelectorAll(JSContext* ctx, JSValue this_val, int argc, J auto* document = static_cast(JS_GetOpaque(this_val, Document::classId())); std::string selectorText = jsValueToStdString(ctx, argv[0]); - NativeValue arguments[] = { - Native_NewCString(selectorText) - }; + NativeValue arguments[] = {Native_NewCString(selectorText)}; return document->invokeBindingMethod("querySelectorAll", 1, arguments); } diff --git a/bridge/bindings/qjs/native_value.cc b/bridge/bindings/qjs/native_value.cc index 9bf5d11818..7647f92564 100644 --- a/bridge/bindings/qjs/native_value.cc +++ b/bridge/bindings/qjs/native_value.cc @@ -42,11 +42,7 @@ NativeValue Native_NewFloat64(double value) { } NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr) { - return (NativeValue){ - reinterpret_cast(ptr), - static_cast(pointerType), - NativeTag::TAG_POINTER - }; + return (NativeValue){reinterpret_cast(ptr), static_cast(pointerType), NativeTag::TAG_POINTER}; } NativeValue Native_NewBool(bool value) { From 9b2ae33eeab6b916f668b637fa7e8b9c20866853 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 29 Aug 2022 22:35:12 +0800 Subject: [PATCH 258/498] feat: support nestedSelector & selectorText --- webf/lib/src/css/css_rule.dart | 15 ++-- webf/lib/src/css/parser/parser.dart | 63 ++++++++++------ webf/lib/src/css/parser/selector.dart | 46 ++++++++++++ webf/lib/src/css/parser/tree.dart | 19 +++++ webf/lib/src/css/parser/visitor.dart | 91 +++++++++++++++++++++++ webf/lib/src/css/rule.dart | 1 - webf/test/src/css/style_rule_parser.dart | 19 +++++ webf/test/src/css/style_sheet_parser.dart | 27 +++---- 8 files changed, 238 insertions(+), 43 deletions(-) diff --git a/webf/lib/src/css/css_rule.dart b/webf/lib/src/css/css_rule.dart index d7a1114789..4e98f7d6d9 100644 --- a/webf/lib/src/css/css_rule.dart +++ b/webf/lib/src/css/css_rule.dart @@ -7,6 +7,13 @@ import 'package:webf/css.dart'; /// https://drafts.csswg.org/cssom/#the-cssstylerule-interface class CSSStyleRule extends CSSRule { + final SelectorTextVisitor _selectorTextVisitor = SelectorTextVisitor(); + + String get selectorText { + _selectorTextVisitor.visitSelectorGroup(selectorGroup); + return _selectorTextVisitor.toString(); + } + @override String get cssText => declaration.cssText; @@ -21,14 +28,6 @@ class CSSStyleRule extends CSSRule { SimpleSelector? get lastSimpleSelector { return selectorGroup.selectors.last.simpleSelectorSequences.last.simpleSelector; } - - String get selectorText { - var sb = StringBuffer(); - selectorGroup.selectors.forEach((selector) { - sb.write(selector.simpleSelectorSequences.map((ss) => ss.simpleSelector.name).join(' ')); - }); - return sb.toString(); - } } class KeyFrameBlock { diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index d453048706..f6438ef597 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -129,9 +129,9 @@ class CSSParser { List parseRules({int startPosition = 0}) { var rules = []; while (!_maybeEat(TokenKind.END_OF_FILE)) { - final rule = processRule(); - if (rule != null) { - rules.add(rule); + final data = processRule(); + if (data != null) { + rules.addAll(data); } else { _next(); } @@ -378,7 +378,10 @@ class CSSParser { selectors.add(selector); } while (_maybeEat(TokenKind.COMMA)); - keyframe.add(KeyFrameBlock(selectors, processDeclarations())); + final declarations = processDeclarations(); + if (declarations.last is CSSStyleDeclaration) { + keyframe.add(KeyFrameBlock(selectors, declarations.last)); + } } while (!_maybeEat(TokenKind.RBRACE) && !isPrematureEndOfFile()); return keyframe; @@ -424,17 +427,35 @@ class CSSParser { return null; } - CSSRule? processRule([SelectorGroup? selectorGroup]) { + List? processRule([SelectorGroup? selectorGroup]) { if (selectorGroup == null) { final directive = processDirective(); if (directive != null) { _maybeEat(TokenKind.SEMICOLON); - return directive; + return [directive]; } selectorGroup = processSelectorGroup(); } if (selectorGroup != null) { - return CSSStyleRule(selectorGroup, processDeclarations()); + final declarations = processDeclarations(); + CSSStyleDeclaration declaration = declarations.where((element) => element is CSSStyleDeclaration).last!; + Iterable childRules = declarations.where((element) => element is CSSStyleRule); + CSSStyleRule rule = CSSStyleRule(selectorGroup, declaration); + List rules = [rule]; + for (CSSStyleRule childRule in childRules) { + // child Rule + for (Selector selector in childRule.selectorGroup.selectors) { + // parentRule + for (Selector parentSelector in selectorGroup.selectors) { + List newSelectorSequences = + mergeNestedSelector(parentSelector.simpleSelectorSequences, selector.simpleSelectorSequences); + selector.simpleSelectorSequences.clear(); + selector.simpleSelectorSequences.addAll(newSelectorSequences); + } + } + rules.add(childRule); + } + return rules; } return null; } @@ -442,9 +463,9 @@ class CSSParser { List processGroupRuleBody() { var nodes = []; while (!(_peekKind(TokenKind.RBRACE) || _peekKind(TokenKind.END_OF_FILE))) { - var rule = processRule(); - if (rule != null) { - nodes.add(rule); + var rules = processRule(); + if (rules != null) { + nodes.addAll(rules); continue; } break; @@ -475,9 +496,6 @@ class CSSParser { /// Return [:null:] if no selector or [SelectorGroup] if a selector was /// parsed. SelectorGroup? _nestedSelector() { - // var oldMessages = messages; - // _createMessages(); - var markedData = _mark; // Look a head do we have a nested selector instead of a declaration? @@ -489,33 +507,32 @@ class CSSParser { if (!nestedSelector) { // Not a selector so restore the world. _restore(markedData); - // messages = oldMessages; return null; } else { // Remember any messages from look ahead. - // oldMessages.mergeMessages(messages); - // messages = oldMessages; return selGroup; } } - CSSStyleDeclaration processDeclarations({bool checkBrace = true}) { + // return list of rule && CSSStyleDeclaration + List processDeclarations({bool checkBrace = true}) { if (checkBrace) _eat(TokenKind.LBRACE); var declaration = CSSStyleDeclaration(); + List list = [declaration]; do { var selectorGroup = _nestedSelector(); while (selectorGroup != null) { // Nested selector so process as a ruleset. - processRule(selectorGroup)!; + List rule = processRule(selectorGroup)!; + list.addAll(rule); selectorGroup = _nestedSelector(); } processDeclaration(declaration); } while (_maybeEat(TokenKind.SEMICOLON)); if (checkBrace) _eat(TokenKind.RBRACE); - - return declaration; + return list; } SelectorGroup? processSelectorGroup() { @@ -732,9 +749,13 @@ class CSSParser { // TODO(terry): If no identifier specified consider optimizing out the // : or :: and making this a normal selector. For now, // create an empty pseudoName. + // TODO(jiangzhou): Forced to evade Identifier pseudoName; - if (_peekIdentifier() && _peekToken.text != 'var' && _peekToken.text != 'rgb' && _peekToken.text != 'rgba') { + if (_peekIdentifier()) { pseudoName = identifier(); + if (pseudoName.isFunction()) { + return null; + } } else { return null; } diff --git a/webf/lib/src/css/parser/selector.dart b/webf/lib/src/css/parser/selector.dart index 872e5a2c39..0668ab55d4 100644 --- a/webf/lib/src/css/parser/selector.dart +++ b/webf/lib/src/css/parser/selector.dart @@ -299,3 +299,49 @@ class NegationSelector extends SimpleSelector { @override dynamic visit(Visitor visitor) => visitor.visitNegationSelector(this); } + +/// Merge the nested selector sequences [current] to the [parent] sequences or +/// substitue any & with the parent selector. +List mergeNestedSelector( + List parent, List current) { + // If any & operator then the parent selector will be substituted otherwise + // the parent selector is pre-pended to the current selector. + var hasThis = current.any((s) => s.simpleSelector.isThis); + + var newSequence = []; + + if (!hasThis) { + // If no & in the sector group then prefix with the parent selector. + newSequence.addAll(parent); + newSequence.addAll(_convertToDescendentSequence(current)); + } else { + for (var sequence in current) { + if (sequence.simpleSelector.isThis) { + // Substitue the & with the parent selector and only use a combinator + // descendant if & is prefix by a sequence with an empty name e.g., + // "... + &", "&", "... ~ &", etc. + var hasPrefix = newSequence.isNotEmpty && newSequence.last.simpleSelector.name.isNotEmpty; + newSequence.addAll(hasPrefix ? _convertToDescendentSequence(parent) : parent); + } else { + newSequence.add(sequence); + } + } + } + + return newSequence; +} + +/// Return selector sequences with first sequence combinator being a +/// descendant. Used for nested selectors when the parent selector needs to +/// be prefixed to a nested selector or to substitute the this (&) with the +/// parent selector. +List _convertToDescendentSequence(List sequences) { + if (sequences.isEmpty) return sequences; + + var newSequences = []; + var first = sequences.first; + newSequences.add(SimpleSelectorSequence(first.simpleSelector, TokenKind.COMBINATOR_DESCENDANT)); + newSequences.addAll(sequences.skip(1)); + + return newSequences; +} diff --git a/webf/lib/src/css/parser/tree.dart b/webf/lib/src/css/parser/tree.dart index c91a4fdc79..535e3ebbc7 100644 --- a/webf/lib/src/css/parser/tree.dart +++ b/webf/lib/src/css/parser/tree.dart @@ -48,6 +48,25 @@ class Identifier extends TreeNode { // a valid identifier. return name; } + + bool isFunction() { + return name == 'var' || + name == 'rgb' || + name == 'rgba' || + name == 'translate' || + name == 'rotate' || + name == 'calc' || + name == 'hsl' || + name == 'hsla' || + name == 'linear-gradient' || + name == 'radial-gradient' || + name == 'repeating-linear-gradient' || + name == 'repeating-radial-gradient' || + name == 'cubic-bezier' || + name == 'attr' || + name == 'url'; + ; + } } class Wildcard extends TreeNode { diff --git a/webf/lib/src/css/parser/visitor.dart b/webf/lib/src/css/parser/visitor.dart index 1282eb861e..9d12afb8d3 100644 --- a/webf/lib/src/css/parser/visitor.dart +++ b/webf/lib/src/css/parser/visitor.dart @@ -104,3 +104,94 @@ class SelectorVisitor implements Visitor { @override dynamic visitNegationSelector(NegationSelector node) => visitSimpleSelector(node); } + +class SelectorTextVisitor extends Visitor { + StringBuffer _buff = StringBuffer(); + + void emit(String str) { + _buff.write(str); + } + + @override + String toString() => _buff.toString().trim(); + + @override + dynamic visitSelectorGroup(SelectorGroup node) { + _buff = StringBuffer(); + var selectors = node.selectors; + var selectorsLength = selectors.length; + for (var i = 0; i < selectorsLength; i++) { + if (i > 0) emit(','); + selectors[i].visit(this); + } + } + + @override + void visitSelector(Selector node) { + for (var selectorSequences in node.simpleSelectorSequences) { + selectorSequences.visit(this); + } + } + + @override + void visitSimpleSelectorSequence(SimpleSelectorSequence node) { + emit(node.combinatorToString); + node.simpleSelector.visit(this); + } + + @override + void visitSimpleSelector(SimpleSelector node) { + emit(node.name); + } + + @override + void visitElementSelector(ElementSelector node) { + emit(node.toString()); + } + + @override + void visitAttributeSelector(AttributeSelector node) { + emit(node.toString()); + } + + @override + void visitIdSelector(IdSelector node) { + emit(node.toString()); + } + + @override + void visitClassSelector(ClassSelector node) { + emit(node.toString()); + } + + @override + void visitPseudoClassSelector(PseudoClassSelector node) { + emit(node.toString()); + } + + @override + void visitPseudoElementSelector(PseudoElementSelector node) { + emit(node.toString()); + } + + @override + void visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) { + emit(':${node.name}('); + node.argument.visit(this); + emit(')'); + } + + @override + void visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) { + emit('::${node.name}('); + node.expression.join(' , '); + emit(')'); + } + + @override + void visitNegationSelector(NegationSelector node) { + emit(':not('); + node.negationArg!.visit(this); + emit(')'); + } +} diff --git a/webf/lib/src/css/rule.dart b/webf/lib/src/css/rule.dart index fd567c319e..ae780a1fc5 100644 --- a/webf/lib/src/css/rule.dart +++ b/webf/lib/src/css/rule.dart @@ -6,7 +6,6 @@ import 'package:webf/css.dart'; abstract class CSSRule { String cssText = ''; CSSStyleSheet? parentStyleSheet; - CSSRule? parentRule; int position = -1; diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index 3914db8790..ab3281cb65 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -154,5 +154,24 @@ void main() { CSSStyleRule styleRule = rule as CSSStyleRule; expect(styleRule.lastSimpleSelector?.name, 'foo'); }); + + test('19', () { + CSSRule? rule = parseSingleRule(' .foo { transform: translate(30px, 20px) rotate(20deg); }'); + CSSStyleRule styleRule = rule as CSSStyleRule; + expect(styleRule.lastSimpleSelector?.name, 'foo'); + }); + + test('20', () { + CSSStyleSheet sheet = CSSParser('#header { color: red; h1 { font-size: 26px; }}').parse(); + CSSStyleRule styleRule1 = sheet.cssRules[0] as CSSStyleRule; + CSSStyleRule styleRule2 = sheet.cssRules[1] as CSSStyleRule; + final visitor = SelectorTextVisitor(); + visitor.visitSelectorGroup(styleRule1.selectorGroup); + expect(visitor.toString(), '#header'); + expect(styleRule1.declaration.getPropertyValue('color'), 'red'); + visitor.visitSelectorGroup(styleRule2.selectorGroup); + expect(visitor.toString(), '#header h1'); + expect(styleRule2.declaration.getPropertyValue('fontSize'), '26px'); + }); }); } diff --git a/webf/test/src/css/style_sheet_parser.dart b/webf/test/src/css/style_sheet_parser.dart index 1925b9f7c4..d01a1d6014 100644 --- a/webf/test/src/css/style_sheet_parser.dart +++ b/webf/test/src/css/style_sheet_parser.dart @@ -16,35 +16,36 @@ void main() { test('1', () { List rules = parseRules('.foo {color: red} \n .bar {}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).selectorText, '.bar'); }); test('2', () { List rules = parseRules('{} \n .foo {color: red;} ;\n .bar {;;}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).selectorText, '.bar'); }); test('3', () { List rules = parseRules('.foo {color: red;} .bar { .x {}; color: #aaa} .baz {}'); - expect(rules.length, 3); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect(rules.length, 4); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).selectorText, '.bar'); expect((rules[1] as CSSStyleRule).declaration.getPropertyValue('color'), '#aaa'); - expect((rules[2] as CSSStyleRule).selectorText, 'baz'); + expect((rules[2] as CSSStyleRule).selectorText, '.bar .x'); + expect((rules[3] as CSSStyleRule).selectorText, '.baz'); }); test('4', () { List rules = parseRules('.foo {color: red} .bar {background: url(data:image/png;base64...)}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, 'bar'); + expect((rules[1] as CSSStyleRule).selectorText, '.bar'); expect( (rules[1] as CSSStyleRule).declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64...)'); }); @@ -52,7 +53,7 @@ void main() { test('5', () { List rules = parseRules('@charset "utf-8"; .foo {color: red}'); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); @@ -64,14 +65,14 @@ void main() { } '''); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, 'foo'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); test('7', () { List rules = parseRules('.foo h6{color: red}'); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, 'foo h6'); + expect((rules[0] as CSSStyleRule).selectorText, '.foo h6'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); }); From d5478bf1befcc31c1bc8eb1182244123f236c288 Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 30 Aug 2022 12:01:34 +0800 Subject: [PATCH 259/498] fix: fix native value for int and bool. --- webf/lib/src/bridge/native_value.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index fc25fcb495..4d96f66b7d 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -77,9 +77,9 @@ dynamic fromNativeValue(Pointer nativeValue) { freeNativeString(nativeString); return result; case JSValueType.TAG_INT: - return nativeValue.ref.u; + return nativeValue.ref.int64; case JSValueType.TAG_BOOL: - return nativeValue.ref.u == 1; + return nativeValue.ref.int64 == 1; case JSValueType.TAG_NULL: return null; case JSValueType.TAG_FLOAT64: @@ -112,10 +112,10 @@ void toNativeValue(Pointer target, value) { target.ref.tag = JSValueType.TAG_NULL.index; } else if (value is int) { target.ref.tag = JSValueType.TAG_INT.index; - target.ref.u = value; + target.ref.int64 = value; } else if (value is bool) { target.ref.tag = JSValueType.TAG_BOOL.index; - target.ref.u = value ? 1 : 0; + target.ref.int64 = value ? 1 : 0; } else if (value is double) { target.ref.tag = JSValueType.TAG_FLOAT64.index; target.ref.u = doubleToUint64(value); @@ -156,10 +156,10 @@ void toNativeValue(Pointer target, value) { int id = _functionId++; _functionMap[id] = value; target.ref.tag = JSValueType.TAG_FUNCTION.index; - target.ref.u = id; + target.ref.int64 = id; } else if (value is Object) { String str = jsonEncode(value); target.ref.tag = JSValueType.TAG_JSON.index; - target.ref.u = str.toNativeUtf8().address; + target.ref.int64 = str.toNativeUtf8().address; } } From c095aa758a391a6b64c641dc4184f14d53b5ef6d Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 30 Aug 2022 12:01:34 +0800 Subject: [PATCH 260/498] fix: fix native value for int and bool. --- webf/lib/src/bridge/native_value.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index 4d96f66b7d..0cd3115e79 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -151,7 +151,7 @@ void toNativeValue(Pointer target, value) { int id = _functionId++; _asyncFunctionMap[id] = value; target.ref.tag = JSValueType.TAG_ASYNC_FUNCTION.index; - target.ref.u = id; + target.ref.int64 = id; } else if (value is AnonymousNativeFunction) { int id = _functionId++; _functionMap[id] = value; From 10f55652d2496f055885471587ac3916d9ae1616 Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 30 Aug 2022 14:14:02 +0800 Subject: [PATCH 261/498] fix: fix dart linter. --- webf/lib/src/bridge/native_value.dart | 3 ++- webf/lib/src/css/parser/parser.dart | 4 ++-- webf/lib/src/css/parser/tree.dart | 3 +-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index 0cd3115e79..43278940a2 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -98,6 +98,7 @@ dynamic fromNativeValue(Pointer nativeValue) { } 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); @@ -160,6 +161,6 @@ void toNativeValue(Pointer target, value) { } else if (value is Object) { String str = jsonEncode(value); target.ref.tag = JSValueType.TAG_JSON.index; - target.ref.int64 = str.toNativeUtf8().address; + target.ref.u = str.toNativeUtf8().address; } } diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index f6438ef597..6c7de380bb 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -438,8 +438,8 @@ class CSSParser { } if (selectorGroup != null) { final declarations = processDeclarations(); - CSSStyleDeclaration declaration = declarations.where((element) => element is CSSStyleDeclaration).last!; - Iterable childRules = declarations.where((element) => element is CSSStyleRule); + CSSStyleDeclaration declaration = declarations.whereType().last; + Iterable childRules = declarations.whereType(); CSSStyleRule rule = CSSStyleRule(selectorGroup, declaration); List rules = [rule]; for (CSSStyleRule childRule in childRules) { diff --git a/webf/lib/src/css/parser/tree.dart b/webf/lib/src/css/parser/tree.dart index 535e3ebbc7..04c4a05e3d 100644 --- a/webf/lib/src/css/parser/tree.dart +++ b/webf/lib/src/css/parser/tree.dart @@ -1,5 +1,5 @@ /* -Copyright 2013, the Dart project authors. +Copyright 2013, the Dart project authors. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are @@ -65,7 +65,6 @@ class Identifier extends TreeNode { name == 'cubic-bezier' || name == 'attr' || name == 'url'; - ; } } From 7fc6f387627ad14823d57319a578d6e6ff604019 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Tue, 30 Aug 2022 10:49:19 +0800 Subject: [PATCH 262/498] fix: code review --- webf/lib/src/css/parser/parser.dart | 2 +- webf/lib/src/css/style_sheet.dart | 9 ++--- webf/lib/src/css/values/color.dart | 3 ++ webf/lib/src/css/variable.dart | 51 +++++++++++++---------------- 4 files changed, 30 insertions(+), 35 deletions(-) diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 6c7de380bb..98497d568f 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -514,7 +514,7 @@ class CSSParser { } } - // return list of rule && CSSStyleDeclaration + // return list of rule || CSSStyleDeclaration List processDeclarations({bool checkBrace = true}) { if (checkBrace) _eat(TokenKind.LBRACE); diff --git a/webf/lib/src/css/style_sheet.dart b/webf/lib/src/css/style_sheet.dart index 08a57f8590..74af19c040 100644 --- a/webf/lib/src/css/style_sheet.dart +++ b/webf/lib/src/css/style_sheet.dart @@ -20,14 +20,11 @@ class CSSStyleSheet implements StyleSheet, Comparable { final String content; - List get cssRules => _cssRules; - late List _cssRules; + final List cssRules; - CSSStyleSheet(this.content, this._cssRules, {this.disabled = false, this.href}); + CSSStyleSheet(this.content, this.cssRules, {this.disabled = false, this.href}); - CSSStyleSheet.from(this.content, {this.disabled = false, this.href}) { - _cssRules = CSSParser(content).parseRules(); - } + CSSStyleSheet.from(this.content, {this.disabled = false, this.href}) : cssRules = CSSParser(content).parseRules(); insertRule(String text, int index) { List rules = CSSParser(text).parseRules(); diff --git a/webf/lib/src/css/values/color.dart b/webf/lib/src/css/values/color.dart index ce7667c824..0bce20cae3 100644 --- a/webf/lib/src/css/values/color.dart +++ b/webf/lib/src/css/values/color.dart @@ -445,6 +445,9 @@ double? _parseColorPart(String value, double min, double max, RenderStyle? rende if (value.startsWith('var') && renderStyle != null) { final variable = CSSVariable.tryParse(renderStyle, value); final computedValue = variable?.computedValue(''); + if (computedValue == null) { + return null; + } v = double.tryParse(computedValue); } if (value.endsWith('%')) { diff --git a/webf/lib/src/css/variable.dart b/webf/lib/src/css/variable.dart index 3717f483f2..ea61252752 100644 --- a/webf/lib/src/css/variable.dart +++ b/webf/lib/src/css/variable.dart @@ -7,7 +7,8 @@ import 'dart:collection'; import 'package:webf/css.dart'; mixin CSSVariableMixin on RenderStyle { - Map? _storage; + Map? _identifierStorage; + Map? _variableStorage; final Map> _propertyDependencies = {}; void _addDependency(String identifier, String propertyName) { @@ -24,24 +25,19 @@ mixin CSSVariableMixin on RenderStyle { if (_checkStorageLoop(identifier)) { return null; } - - Map? storage = _storage; _addDependency(identifier, propertyName); - - if (storage != null && storage[identifier] != null) { - final variable = storage[identifier]; - if (variable != null && variable is CSSVariable) { - final id = variable.identifier.trim(); - if (storage[id] != null && id != identifier) { - return getCSSVariable(id, propertyName); - } - if (variable.defaultValue != null) { - return variable.defaultValue; - } - return null; - } else { - return storage[identifier]; + if (_variableStorage != null && _variableStorage![identifier] != null) { + CSSVariable variable = _variableStorage![identifier]!; + final id = variable.identifier.trim(); + if (_variableStorage![id] != null && id != identifier) { + return getCSSVariable(id, propertyName); + } + if (variable.defaultValue != null) { + return variable.defaultValue; } + return null; + } else if (_identifierStorage != null && _identifierStorage![identifier] != null) { + return _identifierStorage![identifier]; } else { // Inherits from renderStyle tree. return parent?.getCSSVariable(identifier, propertyName); @@ -49,22 +45,16 @@ mixin CSSVariableMixin on RenderStyle { } bool _checkStorageLoop(String identifier) { - Map? storage = _storage; + Map? storage = _variableStorage; if (storage == null) { return false; } if (storage[identifier] == null) { return false; } - if (storage[identifier] is! CSSVariable) { - return false; - } CSSVariable fast = storage[identifier]!; CSSVariable slow = storage[identifier]!; - while (storage[fast.identifier] != null && - storage[fast.identifier] is CSSVariable && - storage[storage[fast.identifier]!.identifier] != null && - storage[storage[fast.identifier]!.identifier] is CSSVariable) { + while (storage[fast.identifier] != null && storage[storage[fast.identifier]!.identifier] != null) { fast = storage[storage[fast.identifier]!.identifier]!; slow = storage[slow.identifier]!; if (fast == slow) { @@ -79,9 +69,14 @@ mixin CSSVariableMixin on RenderStyle { // value: red @override void setCSSVariable(String identifier, String value) { - _storage ??= HashMap(); - _storage![identifier] = CSSVariable.tryParse(this, value) ?? value; - + CSSVariable? variable = CSSVariable.tryParse(this, value); + if (variable != null) { + _variableStorage ??= HashMap(); + _variableStorage![identifier] = variable; + } else { + _identifierStorage ??= HashMap(); + _identifierStorage![identifier] = value; + } if (_propertyDependencies.containsKey(identifier)) { _notifyCSSVariableChanged(_propertyDependencies[identifier]!, value); } From edee7631fa2fb0f936582d0c0b3ea168eadad926 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Wed, 31 Aug 2022 00:03:58 +0800 Subject: [PATCH 263/498] feat: force update style when scrolling starts. --- webf/lib/src/css/element_rule_collector.dart | 2 +- webf/lib/src/css/style_declaration.dart | 36 ++++++++++++-------- webf/lib/src/dom/document.dart | 12 +++---- webf/lib/src/dom/element.dart | 9 ++--- webf/lib/src/dom/style_node_manager.dart | 1 + webf/lib/src/dom/window.dart | 2 ++ 6 files changed, 33 insertions(+), 29 deletions(-) diff --git a/webf/lib/src/css/element_rule_collector.dart b/webf/lib/src/css/element_rule_collector.dart index 95b9da2baf..686a8b5c30 100644 --- a/webf/lib/src/css/element_rule_collector.dart +++ b/webf/lib/src/css/element_rule_collector.dart @@ -65,7 +65,7 @@ class ElementRuleCollector { // Merge all the rules for (CSSRule rule in matchedRules) { if (rule is CSSStyleRule) { - declaration.merge(rule.declaration); + declaration.union(rule.declaration); } } return declaration; diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index cf2eddf30b..c7260f2a15 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -449,20 +449,20 @@ class CSSStyleDeclaration { } } - void merge(CSSStyleDeclaration declaration) { + void union(CSSStyleDeclaration declaration) { Map properties = {} ..addAll(_properties) ..addAll(_pendingProperties); for (String propertyName in properties.keys) { - bool isImportant = _importants[propertyName] ?? false; + bool currentIsImportant = _importants[propertyName] ?? false; + bool otherIsImportant = declaration._importants[propertyName] ?? false; String? currentValue = properties[propertyName]; String? otherValue = declaration._pendingProperties[propertyName]; - if (!isImportant && !isNullOrEmptyValue(otherValue) && currentValue != otherValue) { + if ((otherIsImportant || !currentIsImportant) && !isNullOrEmptyValue(otherValue) && currentValue != otherValue) { // Update property. _pendingProperties[propertyName] = otherValue!; - bool otherIsImportant = declaration._importants[propertyName] ?? false; if (otherIsImportant) { _importants[propertyName] = true; } @@ -470,13 +470,13 @@ class CSSStyleDeclaration { } for (String propertyName in declaration._pendingProperties.keys) { - bool isImportant = _importants[propertyName] ?? false; + bool currentIsImportant = _importants[propertyName] ?? false; + bool otherIsImportant = declaration._importants[propertyName] ?? false; String? currentValue = properties[propertyName]; String? otherValue = declaration._pendingProperties[propertyName]; - if (!isImportant && isNullOrEmptyValue(currentValue) && !isNullOrEmptyValue(otherValue)) { + if ((otherIsImportant || !currentIsImportant) && isNullOrEmptyValue(currentValue) && currentValue != otherValue) { // Add property. _pendingProperties[propertyName] = otherValue!; - bool otherIsImportant = declaration._importants[propertyName] ?? false; if (otherIsImportant) { _importants[propertyName] = true; } @@ -484,37 +484,43 @@ class CSSStyleDeclaration { } } - Map diff(CSSStyleDeclaration other) { - Map diffs = {}; - + // return update status + bool diff(CSSStyleDeclaration other) { Map properties = {} ..addAll(_properties) ..addAll(_pendingProperties); - + bool updateStatus = false; for (String propertyName in properties.keys) { String? prevValue = properties[propertyName]; String? currentValue = other._pendingProperties[propertyName]; + bool currentImportant = other._importants[propertyName] ?? false; if (isNullOrEmptyValue(prevValue) && isNullOrEmptyValue(currentValue)) { continue; } else if (!isNullOrEmptyValue(prevValue) && isNullOrEmptyValue(currentValue)) { // Remove property. - diffs[propertyName] = null; + removeProperty(propertyName, currentImportant); + updateStatus = true; } else if (prevValue != currentValue) { // Update property. - diffs[propertyName] = currentValue; + setProperty(propertyName, currentValue, currentImportant); + updateStatus = true; } } for (String propertyName in other._pendingProperties.keys) { String? prevValue = properties[propertyName]; String? currentValue = other._pendingProperties[propertyName]; + bool currentImportant = other._importants[propertyName] ?? false; + if (isNullOrEmptyValue(prevValue) && !isNullOrEmptyValue(currentValue)) { // Add property. - diffs[propertyName] = currentValue; + setProperty(propertyName, currentValue, currentImportant); + updateStatus = true; } } - return diffs; + + return updateStatus; } /// Override [] and []= operator to get/set style properties. diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 36a9b3d23e..e7dbb5d6e8 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -221,6 +221,7 @@ class Document extends Node { } } + bool _recalculating = false; void updateStyleIfNeeded() { if (styleSheets.isEmpty && !styleNodeManager.hasPendingStyleSheet) { return; @@ -229,22 +230,21 @@ class Document extends Node { flushStyle(rebuild: true); return; } + if (_recalculating) { + return; + } + _recalculating = true; SchedulerBinding.instance.addPostFrameCallback((_) { + _recalculating = false; flushStyle(); }); } - bool _recalculating = false; void flushStyle({bool rebuild = false}) { if (!needsStyleRecalculate) { return; } - if (_recalculating) { - return; - } - _recalculating = true; if (!styleNodeManager.updateActiveStyleSheets(rebuild: rebuild)) { - _recalculating = false; return; } recalculateDocumentStyle(); diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 7a81b28812..e8b12ec6d5 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1426,7 +1426,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element void _applySheetStyle(CSSStyleDeclaration style) { CSSStyleDeclaration? matchRule = ElementRuleCollector().collectionFromRuleSet(ownerDocument.ruleSet, this); - style.merge(matchRule); + style.union(matchRule); } void _onStyleChanged(String propertyName, String? prevValue, String currentValue) { @@ -1459,12 +1459,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // Diff style. CSSStyleDeclaration newStyle = CSSStyleDeclaration(); applyStyle(newStyle); - Map diffs = style.diff(newStyle); - if (diffs.isNotEmpty) { - // Update render style. - diffs.forEach((String propertyName, String? value) { - style.setProperty(propertyName, value); - }); + if (style.diff(newStyle)) { style.flushPendingProperties(); } } diff --git a/webf/lib/src/dom/style_node_manager.dart b/webf/lib/src/dom/style_node_manager.dart index 963e535e9e..5c3ba5a1d0 100644 --- a/webf/lib/src/dom/style_node_manager.dart +++ b/webf/lib/src/dom/style_node_manager.dart @@ -59,6 +59,7 @@ class StyleNodeManager { _pendingStyleSheets.removeWhere((element) => element == styleSheet); } + // TODO(jiangzhou): cache stylesheet bool updateActiveStyleSheets({bool rebuild = false}) { List newSheets = _collectActiveStyleSheets(); if (newSheets.isEmpty) { diff --git a/webf/lib/src/dom/window.dart b/webf/lib/src/dom/window.dart index 612a5ae995..e0b0afbb63 100644 --- a/webf/lib/src/dom/window.dart +++ b/webf/lib/src/dom/window.dart @@ -71,12 +71,14 @@ class Window extends EventTarget { double get scrollY => document.documentElement!.scrollTop; void scrollTo(double x, double y) { + document.flushStyle(); document.documentElement! ..flushLayout() ..scrollTo(x, y); } void scrollBy(double x, double y) { + document.flushStyle(); document.documentElement! ..flushLayout() ..scrollBy(x, y); From 461c6561faf64e8a8f26dddccbc73e30b822cedb Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Wed, 31 Aug 2022 12:46:28 +0800 Subject: [PATCH 264/498] chore: To optimize the API --- webf/lib/src/css/css_rule.dart | 16 +++++------ webf/lib/src/css/parser/parser.dart | 2 +- webf/lib/src/css/parser/selector.dart | 11 ++++++++ webf/lib/src/css/parser/visitor.dart | 2 +- webf/lib/src/css/rule.dart | 2 +- webf/lib/src/css/style_declaration.dart | 12 ++++++-- webf/lib/src/css/style_sheet.dart | 13 ++++----- webf/test/src/css/style_rule_parser.dart | 34 +++++++++++------------ webf/test/src/css/style_sheet_parser.dart | 26 ++++++++--------- 9 files changed, 67 insertions(+), 51 deletions(-) diff --git a/webf/lib/src/css/css_rule.dart b/webf/lib/src/css/css_rule.dart index 4e98f7d6d9..fd5a1539ab 100644 --- a/webf/lib/src/css/css_rule.dart +++ b/webf/lib/src/css/css_rule.dart @@ -3,17 +3,11 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'package:quiver/core.dart'; import 'package:webf/css.dart'; /// https://drafts.csswg.org/cssom/#the-cssstylerule-interface class CSSStyleRule extends CSSRule { - final SelectorTextVisitor _selectorTextVisitor = SelectorTextVisitor(); - - String get selectorText { - _selectorTextVisitor.visitSelectorGroup(selectorGroup); - return _selectorTextVisitor.toString(); - } - @override String get cssText => declaration.cssText; @@ -25,8 +19,12 @@ class CSSStyleRule extends CSSRule { CSSStyleRule(this.selectorGroup, this.declaration) : super(); - SimpleSelector? get lastSimpleSelector { - return selectorGroup.selectors.last.simpleSelectorSequences.last.simpleSelector; + @override + int get hashCode => hash2(selectorGroup, declaration); + + @override + bool operator ==(Object other) { + return hashCode == other.hashCode; } } diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 98497d568f..6c75355acd 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -83,7 +83,7 @@ class CSSParser { /// Main entry point for parsing an entire CSS file. CSSStyleSheet parse() { final rules = parseRules(); - return CSSStyleSheet(tokenizer._text, rules); + return CSSStyleSheet(rules); } Map parseInlineStyle() { diff --git a/webf/lib/src/css/parser/selector.dart b/webf/lib/src/css/parser/selector.dart index 0668ab55d4..968a43ed8f 100644 --- a/webf/lib/src/css/parser/selector.dart +++ b/webf/lib/src/css/parser/selector.dart @@ -36,6 +36,7 @@ const kTagSpecificity = 0x000001; // https://drafts.csswg.org/cssom/#parse-a-group-of-selectors class SelectorGroup extends TreeNode { + final SelectorTextVisitor _selectorTextVisitor = SelectorTextVisitor(); final List selectors; int _specificity = -1; @@ -47,6 +48,16 @@ class SelectorGroup extends TreeNode { return _specificity; } + String? _selectorText; + String get selectorText { + if (_selectorText != null) { + return _selectorText ?? ''; + } + _selectorTextVisitor.visitSelectorGroup(this); + _selectorText = _selectorTextVisitor.toString(); + return _selectorText ?? ''; + } + SelectorGroup(this.selectors) : super(); @override diff --git a/webf/lib/src/css/parser/visitor.dart b/webf/lib/src/css/parser/visitor.dart index 9d12afb8d3..4a86d6fe8d 100644 --- a/webf/lib/src/css/parser/visitor.dart +++ b/webf/lib/src/css/parser/visitor.dart @@ -121,7 +121,7 @@ class SelectorTextVisitor extends Visitor { var selectors = node.selectors; var selectorsLength = selectors.length; for (var i = 0; i < selectorsLength; i++) { - if (i > 0) emit(','); + if (i > 0) emit(', '); selectors[i].visit(this); } } diff --git a/webf/lib/src/css/rule.dart b/webf/lib/src/css/rule.dart index ae780a1fc5..b042eba558 100644 --- a/webf/lib/src/css/rule.dart +++ b/webf/lib/src/css/rule.dart @@ -4,7 +4,7 @@ import 'package:webf/css.dart'; abstract class CSSRule { - String cssText = ''; + String get cssText => ''; CSSStyleSheet? parentStyleSheet; int position = -1; diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index c7260f2a15..f524b57e74 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -92,11 +92,11 @@ class CSSStyleDeclaration { String css = EMPTY_STRING; _properties.forEach((property, value) { if (css.isNotEmpty) css += ' '; - css += '${_kebabize(property)}: $value;'; + css += '${_kebabize(property)}: $value ${_importants.containsKey(property) ? '!important' : ''};'; }); _sheetStyle.forEach((property, value) { if (css.isNotEmpty) css += ' '; - css += '${_kebabize(property)}: $value;'; + css += '${_kebabize(property)}: $value ${_importants.containsKey(property) ? '!important' : ''};'; }); return css; } @@ -575,6 +575,14 @@ class CSSStyleDeclaration { @override String toString() => 'CSSStyleDeclaration($cssText)'; + + @override + int get hashCode => cssText.hashCode; + + @override + bool operator ==(Object other) { + return hashCode == other.hashCode; + } } // aB to a-b diff --git a/webf/lib/src/css/style_sheet.dart b/webf/lib/src/css/style_sheet.dart index 74af19c040..0e524783e5 100644 --- a/webf/lib/src/css/style_sheet.dart +++ b/webf/lib/src/css/style_sheet.dart @@ -3,6 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ import 'package:webf/css.dart'; +import 'package:quiver/core.dart'; abstract class StyleSheet {} @@ -18,13 +19,11 @@ class CSSStyleSheet implements StyleSheet, Comparable { /// A string containing the baseURL used to resolve relative URLs in the stylesheet. String? href; - final String content; - final List cssRules; - CSSStyleSheet(this.content, this.cssRules, {this.disabled = false, this.href}); + CSSStyleSheet(this.cssRules, {this.disabled = false, this.href}); - CSSStyleSheet.from(this.content, {this.disabled = false, this.href}) : cssRules = CSSParser(content).parseRules(); + // CSSStyleSheet(this.content, {this.disabled = false, this.href}) : cssRules = CSSParser(content).parseRules(); insertRule(String text, int index) { List rules = CSSParser(text).parseRules(); @@ -51,14 +50,14 @@ class CSSStyleSheet implements StyleSheet, Comparable { @override bool operator ==(Object other) { - return other is CSSStyleSheet && other.content == content; + return hashCode == other.hashCode; } @override - int get hashCode => content.hashCode; + int get hashCode => hashObjects(cssRules); CSSStyleSheet clone() { - CSSStyleSheet sheet = CSSStyleSheet(content, List.from(cssRules), disabled: disabled, href: href); + CSSStyleSheet sheet = CSSStyleSheet(List.from(cssRules), disabled: disabled, href: href); return sheet; } diff --git a/webf/test/src/css/style_rule_parser.dart b/webf/test/src/css/style_rule_parser.dart index ab3281cb65..6a598f164e 100644 --- a/webf/test/src/css/style_rule_parser.dart +++ b/webf/test/src/css/style_rule_parser.dart @@ -17,7 +17,7 @@ void main() { CSSRule? rule = parseSingleRule('div p, #id:first-line { color : red; }'); expect(rule is CSSStyleRule, true); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'first-line'); + expect(styleRule.selectorGroup.selectorText, 'div p, #id:first-line'); expect(styleRule.declaration.getPropertyValue('color'), 'red'); }); @@ -25,7 +25,7 @@ void main() { CSSRule? rule = parseSingleRule(' .foo { color: red; }'); expect(rule is CSSStyleRule, true); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); expect(styleRule.declaration.getPropertyValue('color'), 'red'); }); @@ -33,42 +33,42 @@ void main() { CSSRule? rule = parseSingleRule(' html{\n color:black;\n}'); expect(rule is CSSStyleRule, true); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'html'); + expect(styleRule.selectorGroup.selectorText, 'html'); expect(styleRule.declaration.getPropertyValue('color'), 'black'); }); test('3', () { CSSRule? rule = parseSingleRule('/*\nSome Comments\nBaby \n*/\nhtml{\n color:black;\n}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'html'); + expect(styleRule.selectorGroup.selectorText, 'html'); expect(styleRule.declaration.getPropertyValue('color'), 'black'); }); test('4', () { CSSRule? rule = parseSingleRule('/*\nSome Comments\nBaby \n*/\nhtml{\n color:black;\n}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'html'); + expect(styleRule.selectorGroup.selectorText, 'html'); expect(styleRule.declaration.getPropertyValue('color'), 'black'); }); test('5', () { CSSRule? rule = parseSingleRule('.foo{--custom:some\n value;}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); expect(styleRule.declaration.getPropertyValue('--custom'), 'some\n value'); }); test('6', () { CSSRule? rule = parseSingleRule('.foo{zoom;\ncolor: red \n}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); expect(styleRule.declaration.getPropertyValue('color'), 'red'); }); test('7', () { CSSRule? rule = parseSingleRule('.foo \t {background: url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4) red}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); expect(styleRule.declaration.getPropertyValue('backgroundColor'), 'red'); expect(styleRule.declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64, CNbyblAAAAHElEQVQI12P4)'); @@ -77,14 +77,14 @@ void main() { test('8', () { CSSRule? rule = parseSingleRule('.foo { color: rgb(255, 255, 0)}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255, 255, 0)'); }); test('9', () { CSSRule? rule = parseSingleRule('.foo { background : ; color: rgb(255, 255, 0)}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); expect(styleRule.declaration.getPropertyValue('color'), 'rgb(255, 255, 0)'); }); @@ -120,45 +120,45 @@ void main() { test('13', () { CSSRule? rule = parseSingleRule('.foo { background-image: url( "./image (1).jpg" )}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); expect(styleRule.declaration.getPropertyValue('backgroundImage'), 'url( "./image (1).jpg" )'); }); test('14', () { CSSRule? rule = parseSingleRule('.foo { .foo{ }; color: red}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); expect(styleRule.declaration.getPropertyValue('color'), 'red'); }); test('15', () { CSSRule? rule = parseSingleRule(' .foo {}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); }); test('16', () { CSSRule? rule = parseSingleRule(' .foo { margin: 64px 0 32px; text-align: center;}'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); }); test('17', () { CSSRule? rule = parseSingleRule(' .foo { width: calc(100% - 100px); }'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); }); test('18', () { CSSRule? rule = parseSingleRule(' .foo { --x: red; }'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); }); test('19', () { CSSRule? rule = parseSingleRule(' .foo { transform: translate(30px, 20px) rotate(20deg); }'); CSSStyleRule styleRule = rule as CSSStyleRule; - expect(styleRule.lastSimpleSelector?.name, 'foo'); + expect(styleRule.selectorGroup.selectorText, '.foo'); }); test('20', () { diff --git a/webf/test/src/css/style_sheet_parser.dart b/webf/test/src/css/style_sheet_parser.dart index d01a1d6014..35c4fef3ec 100644 --- a/webf/test/src/css/style_sheet_parser.dart +++ b/webf/test/src/css/style_sheet_parser.dart @@ -16,36 +16,36 @@ void main() { test('1', () { List rules = parseRules('.foo {color: red} \n .bar {}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); + expect((rules[0] as CSSStyleRule).selectorGroup.selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); + expect((rules[1] as CSSStyleRule).selectorGroup.selectorText, '.bar'); }); test('2', () { List rules = parseRules('{} \n .foo {color: red;} ;\n .bar {;;}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); + expect((rules[0] as CSSStyleRule).selectorGroup.selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); + expect((rules[1] as CSSStyleRule).selectorGroup.selectorText, '.bar'); }); test('3', () { List rules = parseRules('.foo {color: red;} .bar { .x {}; color: #aaa} .baz {}'); expect(rules.length, 4); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); + expect((rules[0] as CSSStyleRule).selectorGroup.selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); + expect((rules[1] as CSSStyleRule).selectorGroup.selectorText, '.bar'); expect((rules[1] as CSSStyleRule).declaration.getPropertyValue('color'), '#aaa'); - expect((rules[2] as CSSStyleRule).selectorText, '.bar .x'); - expect((rules[3] as CSSStyleRule).selectorText, '.baz'); + expect((rules[2] as CSSStyleRule).selectorGroup.selectorText, '.bar .x'); + expect((rules[3] as CSSStyleRule).selectorGroup.selectorText, '.baz'); }); test('4', () { List rules = parseRules('.foo {color: red} .bar {background: url(data:image/png;base64...)}'); expect(rules.length, 2); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); + expect((rules[0] as CSSStyleRule).selectorGroup.selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); - expect((rules[1] as CSSStyleRule).selectorText, '.bar'); + expect((rules[1] as CSSStyleRule).selectorGroup.selectorText, '.bar'); expect( (rules[1] as CSSStyleRule).declaration.getPropertyValue('backgroundImage'), 'url(data:image/png;base64...)'); }); @@ -53,7 +53,7 @@ void main() { test('5', () { List rules = parseRules('@charset "utf-8"; .foo {color: red}'); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); + expect((rules[0] as CSSStyleRule).selectorGroup.selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); @@ -65,14 +65,14 @@ void main() { } '''); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, '.foo'); + expect((rules[0] as CSSStyleRule).selectorGroup.selectorText, '.foo'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); test('7', () { List rules = parseRules('.foo h6{color: red}'); expect(rules.length, 1); - expect((rules[0] as CSSStyleRule).selectorText, '.foo h6'); + expect((rules[0] as CSSStyleRule).selectorGroup.selectorText, '.foo h6'); expect((rules[0] as CSSStyleRule).declaration.getPropertyValue('color'), 'red'); }); }); From fe8c72dfce4ccc8dfc304f946f1af508c4c6e018 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 29 Aug 2022 23:51:22 +0800 Subject: [PATCH 265/498] chore: css parse performance fix benchmark --- bridge/bindings/qjs/bom/performance.cc | 16 ++++ bridge/bindings/qjs/bom/performance.h | 12 +++ .../android/app/src/main/AndroidManifest.xml | 2 +- .../Flutter/GeneratedPluginRegistrant.swift | 6 +- scripts/tasks.js | 79 +++++++++++++++++++ webf/lib/src/css/parser/parser.dart | 14 ++++ webf/lib/src/dom/document.dart | 8 ++ webf/lib/src/dom/element.dart | 9 ++- webf/lib/src/module/performance_timing.dart | 8 ++ 9 files changed, 149 insertions(+), 5 deletions(-) diff --git a/bridge/bindings/qjs/bom/performance.cc b/bridge/bindings/qjs/bom/performance.cc index de7eaf6350..3007349021 100644 --- a/bridge/bindings/qjs/bom/performance.cc +++ b/bridge/bindings/qjs/bom/performance.cc @@ -379,6 +379,10 @@ void Performance::measureSummary(JSValue* 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); @@ -466,6 +470,10 @@ JSValue Performance::__webf_navigation_summary__(JSContext* ctx, JSValue this_va 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); @@ -525,6 +533,10 @@ First Bundle Load: %.*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 Rendering: %.*fms + %s %.*fms avg: %.*fms count: %zu + %s %.*fms avg: %.*fms count: %zu @@ -563,6 +575,10 @@ Rendering: %.*fms 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, diff --git a/bridge/bindings/qjs/bom/performance.h b/bridge/bindings/qjs/bom/performance.h index 5cfe46b92a..fd02767ac2 100644 --- a/bridge/bindings/qjs/bom/performance.h +++ b/bridge/bindings/qjs/bom/performance.h @@ -36,6 +36,10 @@ #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" @@ -101,6 +105,14 @@ #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" diff --git a/performance_tests/android/app/src/main/AndroidManifest.xml b/performance_tests/android/app/src/main/AndroidManifest.xml index 7827378bf1..b3d3baeb1e 100644 --- a/performance_tests/android/app/src/main/AndroidManifest.xml +++ b/performance_tests/android/app/src/main/AndroidManifest.xml @@ -7,7 +7,7 @@ FlutterApplication and put your custom class here. --> { done(); }) + +task('run-benchmark', async (done) => { + const serverPort = '8892'; + + const childProcess = spawn('http-server', ['./', `-p ${serverPort}`], { + stdio: 'pipe', + cwd: path.join(paths.performanceTests, '/benchmark/build') + }) + + let serverIpAddress; + let interfaces = os.networkInterfaces(); + for (let devName in interfaces) { + interfaces[devName].forEach((item) => { + if (item.family === 'IPv4' && !item.internal && item.address !== '127.0.0.1') { + serverIpAddress = item.address; + } + }) + } + + if (!serverIpAddress) { + const err = new Error('The IP address was not found.'); + done(err); + } + + let androidDevices = getDevicesInfo(); + + let performanceInfos = execSync( + `flutter run -d ${androidDevices[0].id} --profile --dart-define="SERVER=${serverIpAddress}:${serverPort}" | grep Performance`, + { + cwd: paths.performanceTests + } + ).toString().split(/\n/); + + const KrakenPerformancePath = 'kraken-performance'; + for (let item in performanceInfos) { + let info = performanceInfos[item]; + const match = /\[(\s?\d,?)+\]/.exec(info); + if (match) { + const viewType = item == 0 ? 'kraken' : 'web'; + try { + let performanceDatas = JSON.parse(match[0]); + // Remove the top and the bottom five from the final numbers to eliminate fluctuations, and calculate the average. + performanceDatas = performanceDatas.sort().slice(5, performanceDatas.length - 5); + + // Save performance list to file and upload to OSS. + const listFile = path.join(__dirname, `${viewType}-load-time-list.js`); + fs.writeFileSync(listFile, `performanceCallback('${viewType}LoadtimeList', [${performanceDatas.toString()}]);`); + + let WebviewPerformanceOSSPath = `${KrakenPerformancePath}/${viewType}-loadtimeList.js`; + await uploader(WebviewPerformanceOSSPath, listFile).then(() => { + console.log(`Performance Upload Success: https://kraken.oss-cn-hangzhou.aliyuncs.com/${WebviewPerformanceOSSPath}`); + }).catch(err => done(err)); + // Save performance data of Webview with kraken version. + let WebviewPerformanceWithVersionOSSPath = `${KrakenPerformancePath}/${viewType}-${pkgVersion}-loadtimeList.js`; + await uploader(WebviewPerformanceWithVersionOSSPath, listFile).then(() => { + console.log(`Performance Upload Success: https://kraken.oss-cn-hangzhou.aliyuncs.com/${WebviewPerformanceWithVersionOSSPath}`); + }).catch(err => done(err)); + } catch { + const err = new Error('The performance info parse exception.'); + done(err); + } + } + } + + execSync('adb uninstall com.example.performance_tests'); + + done(); +}); + +function getDevicesInfo() { + let output = JSON.parse(execSync('flutter devices --machine', {stdio: 'pipe', encoding: 'utf-8'})); + let androidDevices = output.filter(device => { + return device.sdk.indexOf('Android') >= 0; + }); + if (androidDevices.length == 0) { + throw new Error('Can not find android benchmark devices.'); + } + return androidDevices; +} diff --git a/webf/lib/src/css/parser/parser.dart b/webf/lib/src/css/parser/parser.dart index 6c75355acd..6b44559b6c 100644 --- a/webf/lib/src/css/parser/parser.dart +++ b/webf/lib/src/css/parser/parser.dart @@ -29,10 +29,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ import 'dart:math' as math; +import 'package:flutter/foundation.dart'; import 'package:webf/css.dart'; import 'package:source_span/source_span.dart'; +import 'package:webf/module.dart'; part 'tree.dart'; part 'token.dart'; @@ -87,6 +89,9 @@ class CSSParser { } Map parseInlineStyle() { + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_PARSE_INLINE_CSS_START); + } Map style = {}; do { if (TokenKind.isIdentifier(_peekToken.kind)) { @@ -123,10 +128,16 @@ class CSSParser { _next(); } } while (_maybeEat(TokenKind.SEMICOLON)); + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_PARSE_INLINE_CSS_END); + } return style; } List parseRules({int startPosition = 0}) { + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_PARSE_CSS_START); + } var rules = []; while (!_maybeEat(TokenKind.END_OF_FILE)) { final data = processRule(); @@ -137,6 +148,9 @@ class CSSParser { } } checkEndOfFile(); + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_PARSE_CSS_END); + } return rules; } diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index e7dbb5d6e8..9773bbbee2 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -2,6 +2,7 @@ * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:webf/css.dart'; @@ -9,6 +10,7 @@ import 'package:webf/dom.dart'; import 'package:webf/foundation.dart'; import 'package:webf/gesture.dart'; import 'package:webf/launcher.dart'; +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; @@ -244,12 +246,18 @@ class Document extends Node { if (!needsStyleRecalculate) { return; } + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_FLUSH_STYLE_START); + } if (!styleNodeManager.updateActiveStyleSheets(rebuild: rebuild)) { return; } recalculateDocumentStyle(); needsStyleRecalculate = false; _recalculating = false; + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_FLUSH_STYLE_END); + } } void recalculateDocumentStyle() { diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index e8b12ec6d5..7853c3c9cb 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1424,9 +1424,16 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } } + final ElementRuleCollector _elementRuleCollector = ElementRuleCollector(); void _applySheetStyle(CSSStyleDeclaration style) { - CSSStyleDeclaration? matchRule = ElementRuleCollector().collectionFromRuleSet(ownerDocument.ruleSet, this); + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_MATCH_ELEMENT_RULE_START); + } + CSSStyleDeclaration matchRule = ElementRuleCollector().collectionFromRuleSet(ownerDocument.ruleSet, this); style.union(matchRule); + if (kProfileMode) { + PerformanceTiming.instance().mark(PERF_MATCH_ELEMENT_RULE_END); + } } void _onStyleChanged(String propertyName, String? prevValue, String currentValue) { diff --git a/webf/lib/src/module/performance_timing.dart b/webf/lib/src/module/performance_timing.dart index fd4d03a5b2..cb170b3675 100644 --- a/webf/lib/src/module/performance_timing.dart +++ b/webf/lib/src/module/performance_timing.dart @@ -52,6 +52,14 @@ final String PERF_REMOVE_NODE_START = 'remove_node_start'; final String PERF_REMOVE_NODE_END = 'remove_node_end'; final String PERF_SET_STYLE_START = 'set_style_start'; final String PERF_SET_STYLE_END = 'set_style_end'; +final String PERF_PARSE_CSS_START = 'parse_css_start'; +final String PERF_PARSE_CSS_END = 'parse_css_end'; +final String PERF_PARSE_INLINE_CSS_START = 'parse_inline_css_start'; +final String PERF_PARSE_INLINE_CSS_END = 'parse_inline_css_end'; +final String PERF_MATCH_ELEMENT_RULE_START = 'match_element_rule_start'; +final String PERF_MATCH_ELEMENT_RULE_END = 'match_element_rule_end'; +final String PERF_FLUSH_STYLE_START = 'flush_style_start'; +final String PERF_FLUSH_STYLE_END = 'flush_style_end'; final String PERF_SET_RENDER_STYLE_START = 'set_render_style_start'; final String PERF_SET_RENDER_STYLE_END = 'set_render_style_end'; final String PERF_DOM_FORCE_LAYOUT_START = 'dom_force_layout_start'; From 7656b74bfb2dab060e5583956102efafc78f0c8d Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Thu, 1 Sep 2022 10:43:34 +0800 Subject: [PATCH 266/498] fix coder eview --- .../css-variables.ts.28e437111.png | Bin 0 -> 4786 bytes .../specs/css/css-variables/css-variables.ts | 25 +++++++++++ webf/lib/src/css/rule_set.dart | 1 - webf/lib/src/css/style_declaration.dart | 2 +- webf/lib/src/css/values/color.dart | 3 +- webf/lib/src/css/variable.dart | 39 +++++++----------- webf/lib/src/dom/element.dart | 5 ++- 7 files changed, 47 insertions(+), 28 deletions(-) create mode 100644 integration_tests/snapshots/css/css-variables/css-variables.ts.28e437111.png diff --git a/integration_tests/snapshots/css/css-variables/css-variables.ts.28e437111.png b/integration_tests/snapshots/css/css-variables/css-variables.ts.28e437111.png new file mode 100644 index 0000000000000000000000000000000000000000..92744ca18d4a04d332fe024662ac5d8b60194452 GIT binary patch literal 4786 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 Zx5e`Mi-d&YrMnZ1@O9k&ro9=L{sWfj3F`m= literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-variables/css-variables.ts b/integration_tests/specs/css/css-variables/css-variables.ts index 7646317419..d67f210883 100644 --- a/integration_tests/specs/css/css-variables/css-variables.ts +++ b/integration_tests/specs/css/css-variables/css-variables.ts @@ -59,6 +59,30 @@ describe('CSS Variables', () => { }); }); + it('variable resolve percentage color', async (done) => { + document.head.appendChild( + createStyle(` + .inner { + --a: 100%; + background-color: rgb(var(--a), 0%, 0%); + } + `) + ); + + document.body.appendChild( +
    +
    +
    Background should be red.
    +
    +
    + ); + + requestAnimationFrame(async () => { + await snapshot(); + done(); + }); + }); + it('variable resolve color', async (done) => { document.head.appendChild( createStyle(` @@ -85,6 +109,7 @@ describe('CSS Variables', () => { }); }); + it('nested variables', async (done) => { document.head.appendChild( createStyle(` diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index e9d1dfb710..17bd435bb0 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -1,5 +1,4 @@ /* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. * Copyright (C) 2022-present The WebF authors. All rights reserved. */ diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index f524b57e74..1d05dde203 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -485,7 +485,7 @@ class CSSStyleDeclaration { } // return update status - bool diff(CSSStyleDeclaration other) { + bool merge(CSSStyleDeclaration other) { Map properties = {} ..addAll(_properties) ..addAll(_pendingProperties); diff --git a/webf/lib/src/css/values/color.dart b/webf/lib/src/css/values/color.dart index 0bce20cae3..611465b569 100644 --- a/webf/lib/src/css/values/color.dart +++ b/webf/lib/src/css/values/color.dart @@ -448,8 +448,9 @@ double? _parseColorPart(String value, double min, double max, RenderStyle? rende if (computedValue == null) { return null; } - v = double.tryParse(computedValue); + value = computedValue; } + if (value.endsWith('%')) { final p = double.tryParse(value.substring(0, value.length - 1)); if (p == null) return null; diff --git a/webf/lib/src/css/variable.dart b/webf/lib/src/css/variable.dart index ea61252752..2b1b72312c 100644 --- a/webf/lib/src/css/variable.dart +++ b/webf/lib/src/css/variable.dart @@ -22,35 +22,28 @@ mixin CSSVariableMixin on RenderStyle { @override dynamic getCSSVariable(String identifier, String propertyName) { - if (_checkStorageLoop(identifier)) { - return null; - } + CSSVariable? variable = _getRawVariable(identifier); _addDependency(identifier, propertyName); - if (_variableStorage != null && _variableStorage![identifier] != null) { - CSSVariable variable = _variableStorage![identifier]!; - final id = variable.identifier.trim(); - if (_variableStorage![id] != null && id != identifier) { - return getCSSVariable(id, propertyName); - } - if (variable.defaultValue != null) { - return variable.defaultValue; - } - return null; - } else if (_identifierStorage != null && _identifierStorage![identifier] != null) { + if (variable != null) { + identifier = variable.identifier.trim(); + } + if (_identifierStorage != null && _identifierStorage![identifier] != null) { return _identifierStorage![identifier]; - } else { - // Inherits from renderStyle tree. - return parent?.getCSSVariable(identifier, propertyName); } + if (variable?.defaultValue != null) { + return variable!.defaultValue; + } + // Inherits from renderStyle tree. + return parent?.getCSSVariable(identifier, propertyName); } - bool _checkStorageLoop(String identifier) { - Map? storage = _variableStorage; + CSSVariable? _getRawVariable(String identifier) { + Map? storage = _variableStorage; if (storage == null) { - return false; + return null; } if (storage[identifier] == null) { - return false; + return null; } CSSVariable fast = storage[identifier]!; CSSVariable slow = storage[identifier]!; @@ -58,10 +51,10 @@ mixin CSSVariableMixin on RenderStyle { fast = storage[storage[fast.identifier]!.identifier]!; slow = storage[slow.identifier]!; if (fast == slow) { - return true; + return null; } } - return false; + return storage[fast.identifier] ?? fast; } // --x: red diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 7853c3c9cb..1029a34867 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -12,6 +12,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:webf/css.dart'; import 'package:webf/dom.dart'; +import 'package:webf/module.dart' hide EMPTY_STRING; import 'package:webf/foundation.dart'; import 'package:webf/rendering.dart'; @@ -1429,7 +1430,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element if (kProfileMode) { PerformanceTiming.instance().mark(PERF_MATCH_ELEMENT_RULE_START); } - CSSStyleDeclaration matchRule = ElementRuleCollector().collectionFromRuleSet(ownerDocument.ruleSet, this); + CSSStyleDeclaration matchRule = _elementRuleCollector.collectionFromRuleSet(ownerDocument.ruleSet, this); style.union(matchRule); if (kProfileMode) { PerformanceTiming.instance().mark(PERF_MATCH_ELEMENT_RULE_END); @@ -1466,7 +1467,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // Diff style. CSSStyleDeclaration newStyle = CSSStyleDeclaration(); applyStyle(newStyle); - if (style.diff(newStyle)) { + if (style.merge(newStyle)) { style.flushPendingProperties(); } } From b6636e11a4efa45823cacf58a5be8079cbc6d86a Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Thu, 1 Sep 2022 14:01:14 +0800 Subject: [PATCH 267/498] fix: streamline code --- webf/lib/src/css/style_declaration.dart | 26 ++++++++----------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index 1d05dde203..762a987ba4 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -449,34 +449,24 @@ class CSSStyleDeclaration { } } + // Inserts the style of the given Declaration into the current Declaration. void union(CSSStyleDeclaration declaration) { Map properties = {} ..addAll(_properties) ..addAll(_pendingProperties); - for (String propertyName in properties.keys) { - bool currentIsImportant = _importants[propertyName] ?? false; - bool otherIsImportant = declaration._importants[propertyName] ?? false; - String? currentValue = properties[propertyName]; - String? otherValue = declaration._pendingProperties[propertyName]; - - if ((otherIsImportant || !currentIsImportant) && !isNullOrEmptyValue(otherValue) && currentValue != otherValue) { - // Update property. - _pendingProperties[propertyName] = otherValue!; - if (otherIsImportant) { - _importants[propertyName] = true; - } - } - } - for (String propertyName in declaration._pendingProperties.keys) { bool currentIsImportant = _importants[propertyName] ?? false; bool otherIsImportant = declaration._importants[propertyName] ?? false; String? currentValue = properties[propertyName]; String? otherValue = declaration._pendingProperties[propertyName]; - if ((otherIsImportant || !currentIsImportant) && isNullOrEmptyValue(currentValue) && currentValue != otherValue) { + if ((otherIsImportant || !currentIsImportant) && currentValue != otherValue) { // Add property. - _pendingProperties[propertyName] = otherValue!; + if (otherValue != null) { + _pendingProperties[propertyName] = otherValue; + } else { + _pendingProperties.remove(propertyName); + } if (otherIsImportant) { _importants[propertyName] = true; } @@ -484,7 +474,7 @@ class CSSStyleDeclaration { } } - // return update status + // Merge the difference between the declarations and return the updated status bool merge(CSSStyleDeclaration other) { Map properties = {} ..addAll(_properties) From 1316263807ee7413ea7a6d526bff3357d3cec009 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Fri, 2 Sep 2022 23:18:59 +0800 Subject: [PATCH 268/498] fix: build performance test error --- performance_tests/.gitignore | 6 ++ performance_tests/.metadata | 32 ++++++- performance_tests/README.md | 8 +- performance_tests/analysis_options.yaml | 29 ++++++ performance_tests/android/.gitignore | 2 + performance_tests/android/app/build.gradle | 22 +++-- .../android/app/src/debug/AndroidManifest.xml | 5 +- .../android/app/src/main/AndroidManifest.xml | 22 +---- .../performance_tests/MainActivity.kt | 2 +- .../res/drawable-v21/launch_background.xml | 12 +++ .../app/src/main/res/values-night/styles.xml | 18 ++++ .../app/src/main/res/values/styles.xml | 12 +-- .../app/src/profile/AndroidManifest.xml | 5 +- performance_tests/android/build.gradle | 8 +- performance_tests/android/gradle.properties | 1 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../benchmark/build/kraken/.keep | 0 performance_tests/ios/.gitignore | 2 + .../ios/Flutter/AppFrameworkInfo.plist | 4 +- performance_tests/ios/Flutter/Debug.xcconfig | 2 +- .../ios/Flutter/Release.xcconfig | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 43 ++++----- .../contents.xcworkspacedata | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 10 +- performance_tests/ios/Runner/Info.plist | 4 + performance_tests/macos/.gitignore | 1 + .../macos/Flutter/Flutter-Debug.xcconfig | 2 +- .../macos/Flutter/Flutter-Release.xcconfig | 2 +- .../macos/Runner.xcodeproj/project.pbxproj | 91 +------------------ .../xcshareddata/xcschemes/Runner.xcscheme | 22 +---- .../contents.xcworkspacedata | 3 - .../macos/Runner/Base.lproj/MainMenu.xib | 4 + .../macos/Runner/Configs/AppInfo.xcconfig | 4 +- .../macos/Runner/DebugProfile.entitlements | 2 - performance_tests/pubspec.yaml | 4 +- performance_tests/test/widget_test.dart | 4 +- 36 files changed, 188 insertions(+), 206 deletions(-) create mode 100644 performance_tests/analysis_options.yaml rename performance_tests/android/app/src/main/kotlin/com/{example => openwebf}/performance_tests/MainActivity.kt (71%) create mode 100644 performance_tests/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 performance_tests/android/app/src/main/res/values-night/styles.xml delete mode 100644 performance_tests/benchmark/build/kraken/.keep diff --git a/performance_tests/.gitignore b/performance_tests/.gitignore index 9d532b18a0..a8e938c083 100644 --- a/performance_tests/.gitignore +++ b/performance_tests/.gitignore @@ -8,6 +8,7 @@ .buildlog/ .history .svn/ +migrate_working_dir/ # IntelliJ related *.iml @@ -39,3 +40,8 @@ app.*.symbols # Obfuscation related app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/performance_tests/.metadata b/performance_tests/.metadata index b3b245fe31..19257bce07 100644 --- a/performance_tests/.metadata +++ b/performance_tests/.metadata @@ -1,10 +1,36 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 1aafb3a8b9b0c36241c5f5b34ee914770f015818 - channel: unknown + revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: android + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: ios + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: macos + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/performance_tests/README.md b/performance_tests/README.md index debe2b2bc0..43ce2bcac9 100644 --- a/performance_tests/README.md +++ b/performance_tests/README.md @@ -8,9 +8,9 @@ This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. diff --git a/performance_tests/analysis_options.yaml b/performance_tests/analysis_options.yaml new file mode 100644 index 0000000000..61b6c4de17 --- /dev/null +++ b/performance_tests/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/performance_tests/android/.gitignore b/performance_tests/android/.gitignore index 0a741cb43d..6f568019d3 100644 --- a/performance_tests/android/.gitignore +++ b/performance_tests/android/.gitignore @@ -9,3 +9,5 @@ GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties +**/*.keystore +**/*.jks diff --git a/performance_tests/android/app/build.gradle b/performance_tests/android/app/build.gradle index ba18d23726..507ff63e36 100644 --- a/performance_tests/android/app/build.gradle +++ b/performance_tests/android/app/build.gradle @@ -26,21 +26,29 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 29 + compileSdkVersion 32 + ndkVersion flutter.ndkVersion - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' } - lintOptions { - disable 'InvalidPackage' + sourceSets { + main.java.srcDirs += 'src/main/kotlin' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.performance_tests" + applicationId "com.openwebf.performance_tests" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 19 - targetSdkVersion 29 + targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/performance_tests/android/app/src/debug/AndroidManifest.xml b/performance_tests/android/app/src/debug/AndroidManifest.xml index 754c795c52..d42685a843 100644 --- a/performance_tests/android/app/src/debug/AndroidManifest.xml +++ b/performance_tests/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,7 @@ - diff --git a/performance_tests/android/app/src/main/AndroidManifest.xml b/performance_tests/android/app/src/main/AndroidManifest.xml index b3d3baeb1e..db3d1b3dee 100644 --- a/performance_tests/android/app/src/main/AndroidManifest.xml +++ b/performance_tests/android/app/src/main/AndroidManifest.xml @@ -1,17 +1,12 @@ - - + - - diff --git a/performance_tests/android/app/src/main/kotlin/com/example/performance_tests/MainActivity.kt b/performance_tests/android/app/src/main/kotlin/com/openwebf/performance_tests/MainActivity.kt similarity index 71% rename from performance_tests/android/app/src/main/kotlin/com/example/performance_tests/MainActivity.kt rename to performance_tests/android/app/src/main/kotlin/com/openwebf/performance_tests/MainActivity.kt index 6c764b006d..78c58e755a 100644 --- a/performance_tests/android/app/src/main/kotlin/com/example/performance_tests/MainActivity.kt +++ b/performance_tests/android/app/src/main/kotlin/com/openwebf/performance_tests/MainActivity.kt @@ -1,4 +1,4 @@ -package com.example.performance_tests +package com.openwebf.performance_tests import io.flutter.embedding.android.FlutterActivity diff --git a/performance_tests/android/app/src/main/res/drawable-v21/launch_background.xml b/performance_tests/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000000..f74085f3f6 --- /dev/null +++ b/performance_tests/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/performance_tests/android/app/src/main/res/values-night/styles.xml b/performance_tests/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000000..06952be745 --- /dev/null +++ b/performance_tests/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/performance_tests/android/app/src/main/res/values/styles.xml b/performance_tests/android/app/src/main/res/values/styles.xml index 1f83a33fd4..cb1ef88056 100644 --- a/performance_tests/android/app/src/main/res/values/styles.xml +++ b/performance_tests/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - diff --git a/performance_tests/android/app/src/profile/AndroidManifest.xml b/performance_tests/android/app/src/profile/AndroidManifest.xml index 754c795c52..d42685a843 100644 --- a/performance_tests/android/app/src/profile/AndroidManifest.xml +++ b/performance_tests/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,7 @@ - diff --git a/performance_tests/android/build.gradle b/performance_tests/android/build.gradle index 3100ad2d55..83ae220041 100644 --- a/performance_tests/android/build.gradle +++ b/performance_tests/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.3.50' + ext.kotlin_version = '1.6.10' repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:7.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -14,7 +14,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/performance_tests/android/gradle.properties b/performance_tests/android/gradle.properties index a6738207fd..94adc3a3f9 100644 --- a/performance_tests/android/gradle.properties +++ b/performance_tests/android/gradle.properties @@ -1,4 +1,3 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -android.enableR8=true diff --git a/performance_tests/android/gradle/wrapper/gradle-wrapper.properties b/performance_tests/android/gradle/wrapper/gradle-wrapper.properties index 296b146b73..cc5527d781 100644 --- a/performance_tests/android/gradle/wrapper/gradle-wrapper.properties +++ b/performance_tests/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/performance_tests/benchmark/build/kraken/.keep b/performance_tests/benchmark/build/kraken/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/performance_tests/ios/.gitignore b/performance_tests/ios/.gitignore index e96ef602b8..7a7f9873ad 100644 --- a/performance_tests/ios/.gitignore +++ b/performance_tests/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/performance_tests/ios/Flutter/AppFrameworkInfo.plist b/performance_tests/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78a7..8d4492f977 100644 --- a/performance_tests/ios/Flutter/AppFrameworkInfo.plist +++ b/performance_tests/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/performance_tests/ios/Flutter/Debug.xcconfig b/performance_tests/ios/Flutter/Debug.xcconfig index e8efba1146..ec97fc6f30 100644 --- a/performance_tests/ios/Flutter/Debug.xcconfig +++ b/performance_tests/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/performance_tests/ios/Flutter/Release.xcconfig b/performance_tests/ios/Flutter/Release.xcconfig index 399e9340e6..c4855bfe20 100644 --- a/performance_tests/ios/Flutter/Release.xcconfig +++ b/performance_tests/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/performance_tests/ios/Runner.xcodeproj/project.pbxproj b/performance_tests/ios/Runner.xcodeproj/project.pbxproj index 5d9703bfcc..af522a7403 100644 --- a/performance_tests/ios/Runner.xcodeproj/project.pbxproj +++ b/performance_tests/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ @@ -127,7 +127,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -288,18 +288,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 4KSMJ2MECG; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.performanceTests; + PRODUCT_BUNDLE_IDENTIFIER = com.openwebf.performanceTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -407,7 +403,8 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -420,18 +417,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 4KSMJ2MECG; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.performanceTests; + PRODUCT_BUNDLE_IDENTIFIER = com.openwebf.performanceTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -447,18 +440,14 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 4KSMJ2MECG; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.performanceTests; + PRODUCT_BUNDLE_IDENTIFIER = com.openwebf.performanceTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; diff --git a/performance_tests/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/performance_tests/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16ed..919434a625 100644 --- a/performance_tests/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/performance_tests/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/performance_tests/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/performance_tests/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a28140cfdb..c87d15a335 100644 --- a/performance_tests/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/performance_tests/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Performance Tests CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -41,5 +43,7 @@ UIViewControllerBasedStatusBarAppearance + CADisableMinimumFrameDurationOnPhone + diff --git a/performance_tests/macos/.gitignore b/performance_tests/macos/.gitignore index d2fd377230..746adbb6b9 100644 --- a/performance_tests/macos/.gitignore +++ b/performance_tests/macos/.gitignore @@ -3,4 +3,5 @@ **/Pods/ # Xcode-related +**/dgph **/xcuserdata/ diff --git a/performance_tests/macos/Flutter/Flutter-Debug.xcconfig b/performance_tests/macos/Flutter/Flutter-Debug.xcconfig index 785633d3a8..4b81f9b2d2 100644 --- a/performance_tests/macos/Flutter/Flutter-Debug.xcconfig +++ b/performance_tests/macos/Flutter/Flutter-Debug.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/performance_tests/macos/Flutter/Flutter-Release.xcconfig b/performance_tests/macos/Flutter/Flutter-Release.xcconfig index 5fba960c3a..5caa9d1579 100644 --- a/performance_tests/macos/Flutter/Flutter-Release.xcconfig +++ b/performance_tests/macos/Flutter/Flutter-Release.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/performance_tests/macos/Runner.xcodeproj/project.pbxproj b/performance_tests/macos/Runner.xcodeproj/project.pbxproj index 24d0629f00..a8c540661a 100644 --- a/performance_tests/macos/Runner.xcodeproj/project.pbxproj +++ b/performance_tests/macos/Runner.xcodeproj/project.pbxproj @@ -26,7 +26,6 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 5C8FCCA980EB71AFF9CEC9B9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A296AD16D141E657E40FB44E /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -53,10 +52,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 12968B8CFF10E6E9FB5AD91C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* performance_tests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = performance_tests.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* performance_tests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "performance_tests.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -70,9 +68,6 @@ 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - A296AD16D141E657E40FB44E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EC6EA7C67DEE0BBB3BC72EDC /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - F428127A14963A10AC87D164 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -80,7 +75,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C8FCCA980EB71AFF9CEC9B9 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -105,7 +99,6 @@ 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, - 35F45B54CA30356CBF6ABCBD /* Pods */, ); sourceTree = ""; }; @@ -152,21 +145,9 @@ path = Runner; sourceTree = ""; }; - 35F45B54CA30356CBF6ABCBD /* Pods */ = { - isa = PBXGroup; - children = ( - F428127A14963A10AC87D164 /* Pods-Runner.debug.xcconfig */, - 12968B8CFF10E6E9FB5AD91C /* Pods-Runner.release.xcconfig */, - EC6EA7C67DEE0BBB3BC72EDC /* Pods-Runner.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( - A296AD16D141E657E40FB44E /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -178,13 +159,11 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 27AB826375EC05F4D80C4B86 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - B494A36150F6920DD60238DD /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -203,8 +182,8 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; - ORGANIZATIONNAME = "The Flutter Authors"; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; @@ -223,7 +202,7 @@ }; }; buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 8.0"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -254,28 +233,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 27AB826375EC05F4D80C4B86 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -313,34 +270,6 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - B494A36150F6920DD60238DD /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${BUILT_PRODUCTS_DIR}/Reachability/Reachability.framework", - "${BUILT_PRODUCTS_DIR}/connectivity_macos/connectivity_macos.framework", - "${PODS_ROOT}/../Flutter/ephemeral/.symlinks/plugins/kraken/macos/libkraken_jsc.dylib", - "${BUILT_PRODUCTS_DIR}/kraken/kraken.framework", - "${BUILT_PRODUCTS_DIR}/path_provider_macos/path_provider_macos.framework", - "${BUILT_PRODUCTS_DIR}/shared_preferences_macos/shared_preferences_macos.framework", - ); - name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Reachability.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_macos.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libkraken_jsc.dylib", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/kraken.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_macos.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_macos.framework", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -432,10 +361,6 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter/ephemeral", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -562,10 +487,6 @@ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter/ephemeral", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -586,10 +507,6 @@ CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter/ephemeral", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/performance_tests/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/performance_tests/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index d393302b6f..b71f08fd26 100644 --- a/performance_tests/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/performance_tests/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ - - - - - - - - + + - - - - diff --git a/performance_tests/macos/Runner/Base.lproj/MainMenu.xib b/performance_tests/macos/Runner/Base.lproj/MainMenu.xib index 537341abf9..80e867a4e0 100644 --- a/performance_tests/macos/Runner/Base.lproj/MainMenu.xib +++ b/performance_tests/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@ + + + + diff --git a/performance_tests/macos/Runner/Configs/AppInfo.xcconfig b/performance_tests/macos/Runner/Configs/AppInfo.xcconfig index 1b12b7f61f..5c5f1a0045 100644 --- a/performance_tests/macos/Runner/Configs/AppInfo.xcconfig +++ b/performance_tests/macos/Runner/Configs/AppInfo.xcconfig @@ -8,7 +8,7 @@ PRODUCT_NAME = performance_tests // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.performanceTests +PRODUCT_BUNDLE_IDENTIFIER = com.openwebf.performanceTests // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2022 com.openwebf. All rights reserved. diff --git a/performance_tests/macos/Runner/DebugProfile.entitlements b/performance_tests/macos/Runner/DebugProfile.entitlements index 08c3ab17cc..dddb8a30c8 100644 --- a/performance_tests/macos/Runner/DebugProfile.entitlements +++ b/performance_tests/macos/Runner/DebugProfile.entitlements @@ -8,7 +8,5 @@ com.apple.security.network.server - com.apple.security.network.client - diff --git a/performance_tests/pubspec.yaml b/performance_tests/pubspec.yaml index c4740d24ea..db3fb991ea 100644 --- a/performance_tests/pubspec.yaml +++ b/performance_tests/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.6 <3.0.0" dependencies: flutter: @@ -29,7 +29,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.0 - webview_flutter: ^3.0.1 + webview_flutter: ^3.0.4 dev_dependencies: flutter_test: diff --git a/performance_tests/test/widget_test.dart b/performance_tests/test/widget_test.dart index ff4d4520c5..67c1aa5566 100644 --- a/performance_tests/test/widget_test.dart +++ b/performance_tests/test/widget_test.dart @@ -1,7 +1,7 @@ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll +// utility in the flutter_test package. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. @@ -13,7 +13,7 @@ import 'package:performance_tests/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(const MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); From 19877d91dc634ab6d5fe51739ea6b39cf9ff28f9 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 5 Sep 2022 16:29:09 +0800 Subject: [PATCH 269/498] fix: fix commandQueue stackoverflow. --- bridge/CMakeLists.txt | 3 ++- bridge/core/script_state.cc | 15 +++-------- bridge/foundation/ui_command_buffer.cc | 3 ++- .../code_generator/bin/code_generator.js | 13 ++++++++++ .../code_generator/src/idl/analyzer.ts | 1 - .../code_generator/src/json/generator.ts | 22 ++++++++++++++++ .../json_templates/names_installer.cc.tpl | 25 +++++++++++++++++++ .../json_templates/names_installer.h.tpl | 20 +++++++++++++++ 8 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 bridge/scripts/code_generator/static/json_templates/names_installer.cc.tpl create mode 100644 bridge/scripts/code_generator/static/json_templates/names_installer.h.tpl diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index f6a878d816..c4da7b5f8b 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -223,7 +223,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") 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/binding_object.cc core/dom/node.cc core/dom/node_traversal.cc core/dom/live_node_list_base.cc @@ -279,6 +279,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") # 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 diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index ef64f847f5..e119c8b9ac 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -3,11 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "script_state.h" -#include "binding_call_methods.h" -#include "built_in_string.h" -#include "event_type_names.h" +#include "names_installer.h" #include "html_element_factory.h" -#include "html_names.h" namespace webf { @@ -26,10 +23,7 @@ ScriptState::ScriptState() { ctx_ = JS_NewContext(runtime_); if (first_loaded) { - built_in_string::Init(ctx_); - event_type_names::Init(ctx_); - html_names::Init(ctx_); - binding_call_methods::Init(ctx_); + names_installer::Init(ctx_); // Bump up the built-in classId. To make sure the created classId are larger than JS_CLASS_CUSTOM_CLASS_INIT_COUNT. for (int i = 0; i < JS_CLASS_CUSTOM_CLASS_INIT_COUNT - JS_CLASS_GC_TRACKER + 2; i++) { JSClassID id{0}; @@ -51,10 +45,7 @@ ScriptState::~ScriptState() { #if DUMP_LEAKS if (--runningContexts == 0) { // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. - built_in_string::Dispose(); - event_type_names::Dispose(); - html_names::Dispose(); - binding_call_methods::Dispose(); + names_installer::Dispose();; HTMLElementFactory::Dispose(); JS_FreeRuntime(runtime_); diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index c516c63e2e..9af3b0cd1f 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -12,7 +12,8 @@ namespace webf { UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) { - queue.reserve(0); + // It's rare to store over 1024 commands in one frame. + queue.reserve(1024); } void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index b54981f547..7a2f4482b2 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -10,6 +10,7 @@ const { JSONBlob } = require('../dist/json/JSONBlob'); const { JSONTemplate } = require('../dist/json/JSONTemplate'); const { analyzer } = require('../dist/idl/analyzer'); const { generateJSONTemplate } = require('../dist/json/generator'); +const { generateNamesInstaller } = require("../dist/json/generator"); program .version(packageJSON.version) @@ -74,9 +75,13 @@ function genCodeFromJSONData() { return new JSONTemplate(path.join(path.join(__dirname, '../static/json_templates'), template), filename); }); + let names_needs_install = new Set(); for (let i = 0; i < blobs.length; i ++) { let blob = blobs[i]; blob.json.metadata.templates.forEach((targetTemplate) => { + 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('/'); @@ -95,6 +100,14 @@ function genCodeFromJSONData() { result.source && fs.writeFileSync(genFilePath + '.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); } genCodeFromTypeDefine(); diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 0b64f72b7f..9952fec6e8 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -262,7 +262,6 @@ function walkProgram(statement: ts.Statement) { }); if (!constructorDefined && obj.kind === ClassObjectKind.interface) { - console.log(obj.kind); throw new Error(`Interface: ${interfaceName} didn't have constructor defined.`); } diff --git a/bridge/scripts/code_generator/src/json/generator.ts b/bridge/scripts/code_generator/src/json/generator.ts index bc927f2118..f682ddbd8b 100644 --- a/bridge/scripts/code_generator/src/json/generator.ts +++ b/bridge/scripts/code_generator/src/json/generator.ts @@ -43,3 +43,25 @@ export function generateJSONTemplate(blob: JSONBlob, headerTemplate: JSONTemplat source: body, }; } + +function generateNames(template: JSONTemplate, names: Set) { + 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) { + let header = generateNames(headerTemplate, names); + let body = generateNames(bodyTemplate, names); + + return { + header: header, + source: body, + }; +} diff --git a/bridge/scripts/code_generator/static/json_templates/names_installer.cc.tpl b/bridge/scripts/code_generator/static/json_templates/names_installer.cc.tpl new file mode 100644 index 0000000000..ef88ee482e --- /dev/null +++ b/bridge/scripts/code_generator/static/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/static/json_templates/names_installer.h.tpl b/bridge/scripts/code_generator/static/json_templates/names_installer.h.tpl new file mode 100644 index 0000000000..de63ab7fdd --- /dev/null +++ b/bridge/scripts/code_generator/static/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() %> From c0694f3619209a6faf6af7d055a83ba3301e19ad Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Mon, 5 Sep 2022 08:30:19 +0000 Subject: [PATCH 270/498] Committing clang-format changes --- bridge/core/script_state.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index e119c8b9ac..94e28d8437 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -3,8 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "script_state.h" -#include "names_installer.h" #include "html_element_factory.h" +#include "names_installer.h" namespace webf { @@ -45,7 +45,8 @@ ScriptState::~ScriptState() { #if DUMP_LEAKS if (--runningContexts == 0) { // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. - names_installer::Dispose();; + names_installer::Dispose(); + ; HTMLElementFactory::Dispose(); JS_FreeRuntime(runtime_); From 041cddd7c781696640771cc4148a1bc9b4ec98ce Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 5 Sep 2022 18:36:34 +0800 Subject: [PATCH 271/498] fix: fix document and window initialize --- webf/lib/src/bridge/bridge.dart | 2 +- webf/lib/src/launcher/controller.dart | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/webf/lib/src/bridge/bridge.dart b/webf/lib/src/bridge/bridge.dart index 4e769eac45..4bb06ebffb 100644 --- a/webf/lib/src/bridge/bridge.dart +++ b/webf/lib/src/bridge/bridge.dart @@ -40,7 +40,7 @@ int initBridge(WebFViewController view) { if (_firstView) { Future.microtask(() { // Port flutter's frame callback into bridge. - SchedulerBinding.instance!.addPersistentFrameCallback((_) { + SchedulerBinding.instance.addPersistentFrameCallback((_) { flushUICommand(view); }); }); diff --git a/webf/lib/src/launcher/controller.dart b/webf/lib/src/launcher/controller.dart index bbb7f887cf..5fbf6156df 100644 --- a/webf/lib/src/launcher/controller.dart +++ b/webf/lib/src/launcher/controller.dart @@ -137,8 +137,11 @@ class WebFViewController implements WidgetsBindingObserver, ElementsBindingObser defineBuiltInElements(); - // Execute UICommand.createDocument and UICommand.createWindow to initialize window and document. - flushUICommand(this); + // 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); From 2b61cea7649187fdceb6148402b0bb963d55637b Mon Sep 17 00:00:00 2001 From: questguo Date: Mon, 5 Sep 2022 20:26:58 +0800 Subject: [PATCH 272/498] refactor: clean code --- webf/example/assets/bundle.html | 27 ++++- webf/lib/src/css/css_animation.dart | 161 ---------------------------- 2 files changed, 22 insertions(+), 166 deletions(-) diff --git a/webf/example/assets/bundle.html b/webf/example/assets/bundle.html index 4a0de4595a..26dea2b7d9 100644 --- a/webf/example/assets/bundle.html +++ b/webf/example/assets/bundle.html @@ -1,10 +1,27 @@ - + @@ -13,8 +30,8 @@
    - + diff --git a/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index f8855a5c4e..accf62e9d3 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -1,7 +1,6 @@ import 'dart:ui'; import 'package:webf/css.dart'; -import 'package:vector_math/vector_math_64.dart'; import 'package:webf/dom.dart'; // CSS Animation: https://drafts.csswg.org/css-animations/ @@ -46,166 +45,6 @@ String _toCamelCase(String s) { return sb.toString(); } - -Color? _parseColor(String color, RenderStyle renderStyle, String propertyName) { - return CSSColor.resolveColor(color, renderStyle, propertyName); -} - -void _updateColor(Color oldColor, Color newColor, double progress, - String property, RenderStyle renderStyle) { - int alphaDiff = newColor.alpha - oldColor.alpha; - int redDiff = newColor.red - oldColor.red; - int greenDiff = newColor.green - oldColor.green; - int blueDiff = newColor.blue - oldColor.blue; - - int alpha = (alphaDiff * progress).toInt() + oldColor.alpha; - int red = (redDiff * progress).toInt() + oldColor.red; - int blue = (blueDiff * progress).toInt() + oldColor.blue; - int green = (greenDiff * progress).toInt() + oldColor.green; - Color color = Color.fromARGB(alpha, red, green, blue); - - renderStyle.target.setRenderStyleProperty(property, color); -} - -double? _parseLength(String length, RenderStyle renderStyle, String property) { - return CSSLength.parseLength(length, renderStyle, property).computedValue; -} - -void _updateLength(double oldLengthValue, double newLengthValue, - double progress, String property, CSSRenderStyle renderStyle) { - double value = oldLengthValue * (1 - progress) + newLengthValue * progress; - renderStyle.target.setRenderStyleProperty( - property, CSSLengthValue(value, CSSLengthType.PX)); -} - -FontWeight _parseFontWeight( - String fontWeight, RenderStyle renderStyle, String property) { - return CSSText.resolveFontWeight(fontWeight); -} - -void _updateFontWeight(FontWeight oldValue, FontWeight newValue, - double progress, String property, CSSRenderStyle renderStyle) { - FontWeight? fontWeight = FontWeight.lerp(oldValue, newValue, progress); - switch (property) { - case FONT_WEIGHT: - renderStyle.fontWeight = fontWeight; - break; - } -} - -double? _parseNumber(String number, RenderStyle renderStyle, String property) { - return CSSNumber.parseNumber(number); -} - -double _getNumber(double oldValue, double newValue, double progress) { - return oldValue * (1 - progress) + newValue * progress; -} - -void _updateNumber(double oldValue, double newValue, double progress, - String property, RenderStyle renderStyle) { - double number = _getNumber(oldValue, newValue, progress); - renderStyle.target.setRenderStyleProperty(property, number); -} - -double _parseLineHeight( - String lineHeight, RenderStyle renderStyle, String property) { - if (CSSNumber.isNumber(lineHeight)) { - return CSSLengthValue(CSSNumber.parseNumber(lineHeight), CSSLengthType.EM, - renderStyle, LINE_HEIGHT) - .computedValue; - } - return CSSLength.parseLength(lineHeight, renderStyle, LINE_HEIGHT) - .computedValue; -} - -void _updateLineHeight(double oldValue, double newValue, double progress, - String property, CSSRenderStyle renderStyle) { - renderStyle.lineHeight = CSSLengthValue( - _getNumber(oldValue, newValue, progress), CSSLengthType.PX); -} - -Matrix4? _parseTransform( - String value, RenderStyle renderStyle, String property) { - return CSSMatrix.computeTransformMatrix( - CSSFunction.parseFunction(value), renderStyle); -} - -void _updateTransform(Matrix4 begin, Matrix4 end, double t, String property, - CSSRenderStyle renderStyle) { - Matrix4 newMatrix4 = CSSMatrix.lerpMatrix(begin, end, t); - renderStyle.transformMatrix = newMatrix4; -} - -const List _colorHandler = [_parseColor, _updateColor]; -const List _lengthHandler = [_parseLength, _updateLength]; -const List _fontWeightHandler = [_parseFontWeight, _updateFontWeight]; -const List _numberHandler = [_parseNumber, _updateNumber]; -const List _lineHeightHandler = [_parseLineHeight, _updateLineHeight]; -const List _transformHandler = [_parseTransform, _updateTransform]; - -Map> CSSTransitionHandlers = { - COLOR: _colorHandler, - BACKGROUND_COLOR: _colorHandler, - BORDER_BOTTOM_COLOR: _colorHandler, - BORDER_LEFT_COLOR: _colorHandler, - BORDER_RIGHT_COLOR: _colorHandler, - BORDER_TOP_COLOR: _colorHandler, - BORDER_COLOR: _colorHandler, - TEXT_DECORATION_COLOR: _colorHandler, - OPACITY: _numberHandler, - Z_INDEX: _numberHandler, - FLEX_GROW: _numberHandler, - FLEX_SHRINK: _numberHandler, - FONT_WEIGHT: _fontWeightHandler, - LINE_HEIGHT: _lineHeightHandler, - TRANSFORM: _transformHandler, - BORDER_BOTTOM_LEFT_RADIUS: _lengthHandler, - BORDER_BOTTOM_RIGHT_RADIUS: _lengthHandler, - BORDER_TOP_LEFT_RADIUS: _lengthHandler, - BORDER_TOP_RIGHT_RADIUS: _lengthHandler, - RIGHT: _lengthHandler, - TOP: _lengthHandler, - BOTTOM: _lengthHandler, - LEFT: _lengthHandler, - LETTER_SPACING: _lengthHandler, - MARGIN_BOTTOM: _lengthHandler, - MARGIN_LEFT: _lengthHandler, - MARGIN_RIGHT: _lengthHandler, - MARGIN_TOP: _lengthHandler, - MIN_HEIGHT: _lengthHandler, - MIN_WIDTH: _lengthHandler, - PADDING_BOTTOM: _lengthHandler, - PADDING_LEFT: _lengthHandler, - PADDING_RIGHT: _lengthHandler, - PADDING_TOP: _lengthHandler, - // should non negative value - BORDER_BOTTOM_WIDTH: _lengthHandler, - BORDER_LEFT_WIDTH: _lengthHandler, - BORDER_RIGHT_WIDTH: _lengthHandler, - BORDER_TOP_WIDTH: _lengthHandler, - FLEX_BASIS: _lengthHandler, - FONT_SIZE: _lengthHandler, - HEIGHT: _lengthHandler, - WIDTH: _lengthHandler, - MAX_HEIGHT: _lengthHandler, - MAX_WIDTH: _lengthHandler, -}; - -/// The types of TransitionEvent -enum CSSTransitionEvent { - /// The transitionrun event occurs when a transition is created - run, - - /// The transitionstart event occurs when a transition’s delay phase ends. - start, - - /// The transitionend event occurs at the completion of the transition. - end, - - /// The transitioncancel event occurs when a transition is canceled. - cancel, -} - mixin CSSAnimationMixin on RenderStyle { List? _animationName; From 045b868ead6808ce4ba2e54b5cfc001ceb7013ac Mon Sep 17 00:00:00 2001 From: questguo Date: Mon, 5 Sep 2022 20:43:10 +0800 Subject: [PATCH 273/498] refactor: remove invalid code --- webf/lib/src/css/style_declaration.dart | 43 ++----------------------- 1 file changed, 2 insertions(+), 41 deletions(-) diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index 25dd81a242..fd281281c7 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -102,10 +102,6 @@ class CSSStyleDeclaration { if (css.isNotEmpty) css += ' '; css += '${_kebabize(property)}: $value ${_importants.containsKey(property) ? '!important' : ''};'; }); - _sheetStyle.forEach((property, value) { - if (css.isNotEmpty) css += ' '; - css += '${_kebabize(property)}: $value;'; - }); return css; } @@ -237,8 +233,8 @@ class CSSStyleDeclaration { CSSStyleProperty.setShorthandTextDecoration(longhandProperties, normalizedValue); break; case ANIMATION: - CSSStyleProperty.setShorthandAnimation( - longhandProperties, normalizedValue); + CSSStyleProperty.setShorthandAnimation(longhandProperties, normalizedValue); + break; } _cachedExpandedShorthand[cacheKey] = longhandProperties; } @@ -462,41 +458,6 @@ class CSSStyleDeclaration { onStyleFlushed?.call(propertyNames); } - void mergeOld(CSSStyleDeclaration declaration) { - Map properties = {} - ..addAll(_properties) - ..addAll(_pendingProperties); - - for (String propertyName in properties.keys) { - bool isImportant = _importants[propertyName] ?? false; - String? currentValue = properties[propertyName]; - String? otherValue = declaration._pendingProperties[propertyName]; - - if (!isImportant && !isNullOrEmptyValue(otherValue) && currentValue != otherValue) { - // Update property. - _pendingProperties[propertyName] = otherValue!; - bool otherIsImportant = declaration._importants[propertyName] ?? false; - if (otherIsImportant) { - _importants[propertyName] = true; - } - } - } - - for (String propertyName in declaration._pendingProperties.keys) { - bool isImportant = _importants[propertyName] ?? false; - String? currentValue = properties[propertyName]; - String? otherValue = declaration._pendingProperties[propertyName]; - if (!isImportant && isNullOrEmptyValue(currentValue) && !isNullOrEmptyValue(otherValue)) { - // Add property. - _pendingProperties[propertyName] = otherValue!; - bool otherIsImportant = declaration._importants[propertyName] ?? false; - if (otherIsImportant) { - _importants[propertyName] = true; - } - } - } - } - // Inserts the style of the given Declaration into the current Declaration. void union(CSSStyleDeclaration declaration) { Map properties = {} From 632a333cb679920f12421a487a24d87bd763750b Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 5 Sep 2022 23:45:38 +0800 Subject: [PATCH 274/498] feat: support calc() --- webf/lib/css.dart | 1 + webf/lib/src/css/keywords.dart | 1 + webf/lib/src/css/render_style.dart | 8 +- webf/lib/src/css/values/calc.dart | 252 +++++++++++++++++++++++++++++ webf/lib/src/dom/element.dart | 4 + 5 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 webf/lib/src/css/values/calc.dart diff --git a/webf/lib/css.dart b/webf/lib/css.dart index ea043ef7d8..931ab16045 100644 --- a/webf/lib/css.dart +++ b/webf/lib/css.dart @@ -58,3 +58,4 @@ export 'src/css/values/time.dart'; export 'src/css/values/percentage.dart'; export 'src/css/values/textual.dart'; export 'src/css/values/variable.dart'; +export 'src/css/values/calc.dart'; diff --git a/webf/lib/src/css/keywords.dart b/webf/lib/src/css/keywords.dart index 38e0ecc0bf..1f6ce57c5e 100644 --- a/webf/lib/src/css/keywords.dart +++ b/webf/lib/src/css/keywords.dart @@ -279,6 +279,7 @@ const String CH = 'ch'; // Functions const String ENV = 'env'; const String VAR = 'var'; +const String CALC = 'calc'; const String PERCENTAGE = '%'; const String ZERO = '0'; diff --git a/webf/lib/src/css/render_style.dart b/webf/lib/src/css/render_style.dart index 42f1615261..6b9c120a2c 100644 --- a/webf/lib/src/css/render_style.dart +++ b/webf/lib/src/css/render_style.dart @@ -412,7 +412,13 @@ class CSSRenderStyle extends RenderStyle RenderStyle renderStyle = this; // Process CSSVariable. - dynamic value = CSSVariable.tryParse(renderStyle, propertyValue); + dynamic value = CSSCalcValue.tryParse(renderStyle, propertyValue); + if (value != null) { + return value; + } + + // Process CSSVariable. + value = CSSVariable.tryParse(renderStyle, propertyValue); if (value != null) { return value; } diff --git a/webf/lib/src/css/values/calc.dart b/webf/lib/src/css/values/calc.dart new file mode 100644 index 0000000000..2bffea57e0 --- /dev/null +++ b/webf/lib/src/css/values/calc.dart @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +import 'package:source_span/source_span.dart'; +import 'package:webf/css.dart'; + +class CSSCalcValue { + final RenderStyle _renderStyle; + + final CalcExpressionNode? _expression; + + CSSCalcValue(this._renderStyle, this._expression); + + // Get the lazy calculated CSS resolved value. + dynamic computedValue(String propertyName) { + return _expression?.computedValue; + } + + // Try to parse CSSCalcValue. + static CSSCalcValue? tryParse(RenderStyle renderStyle, String propertyValue) { + if (CSSFunction.isFunction(propertyValue, functionName: CALC)) { + List fns = CSSFunction.parseFunction(propertyValue); + if (fns.first.args.isNotEmpty) { + assert(fns.first.args.length == 1, 'Calc parameters count must be = 1'); + final expression = fns.first.args.first; + final _CSSCalcParser parser = _CSSCalcParser(renderStyle, expression); + CalcExpressionNode? node = parser.processCalcExpression(); + return CSSCalcValue(renderStyle, node); + } + } + return null; + } +} + +abstract class CalcExpressionNode { + double get computedValue; +} + +class CalcInvertNode extends CalcExpressionNode { + final CalcExpressionNode node; + CalcInvertNode(this.node); + + @override + double get computedValue { + return 1 / node.computedValue; + } +} + +class CalcNegateNode extends CalcExpressionNode { + final CalcExpressionNode node; + CalcNegateNode(this.node); + + @override + double get computedValue { + return -1 * node.computedValue; + } +} + +class CalcVariableNode extends CalcExpressionNode { + final CSSVariable value; + final RenderStyle _renderStyle; + CalcVariableNode(this.value, this._renderStyle); + + @override + double get computedValue { + String text = value.computedValue(''); + if (CSSLength.isLength(text)) { + return CSSLength.parseLength(text, _renderStyle).computedValue; + } + return 0; + } +} + +class CalcLengthNode extends CalcExpressionNode { + final CSSLengthValue value; + CalcLengthNode(this.value); + + @override + double get computedValue => value.computedValue; +} + +class CalcOperationExpressionNode extends CalcExpressionNode { + int operator; + final CalcExpressionNode leftNode; + final CalcExpressionNode rightNode; + CalcOperationExpressionNode(this.operator, this.leftNode, this.rightNode); + + @override + double get computedValue { + if (operator == TokenKind.PLUS) { + return leftNode.computedValue + rightNode.computedValue; + } + if (operator == TokenKind.ASTERISK) { + return leftNode.computedValue * rightNode.computedValue; + } + assert(false, 'This operator should not be used'); + return 0; + } +} + +class _CSSCalcParser { + final RenderStyle _renderStyle; + final Tokenizer tokenizer; + + Token? _previousToken; + late Token _peekToken; + + _CSSCalcParser(this._renderStyle, String text, {int start = 0}) + : tokenizer = Tokenizer(SourceFile.fromString(text), text, true, start) { + _peekToken = tokenizer.next(); + } + + CalcExpressionNode? processCalcExpression() { + return processCalcSum(); + } + + CalcExpressionNode? processFunction() { + var name = _peekToken.text; + if (_maybeEat(TokenKind.LPAREN)) { + final node = processCalcExpression(); + _maybeEat(TokenKind.RPAREN); + return node; + } + switch (name) { + case 'var': + while (_peek() != TokenKind.RPAREN) { + _next(); + name += _peekToken.text; + } + CSSVariable? variable = CSSVariable.tryParse(_renderStyle, name); + if (variable != null) { + return CalcVariableNode(variable, _renderStyle); + } + return null; + case 'calc': + _next(); + _maybeEat(TokenKind.LPAREN); + final node = processCalcExpression(); + _maybeEat(TokenKind.RPAREN); + return node; + } + return null; + } + + CalcExpressionNode? processCalcSum() { + List nodes = []; + CalcExpressionNode? firstNode = processCalcProduct(); + CalcExpressionNode? secondNode; + if (firstNode == null) { + return null; + } + nodes.add(firstNode); + while (_peek() != TokenKind.END_OF_FILE) { + int operator = _peekToken.kind; + if (!_maybeEat(TokenKind.PLUS) && !_maybeEat(TokenKind.MINUS)) { + break; + } + secondNode = processCalcProduct(); + if (secondNode == null) { + return null; + } + if (operator == TokenKind.MINUS) { + secondNode = CalcNegateNode(secondNode); + } + nodes.add(secondNode); + } + if (nodes.isEmpty) { + return firstNode; + } + final sumNode = nodes.reduce((value, element) { + return CalcOperationExpressionNode(TokenKind.PLUS, value, element); + }); + return sumNode; + } + + CalcExpressionNode? processCalcProduct() { + List nodes = []; + + CalcExpressionNode? firstNode = processCalcValue(); + CalcExpressionNode? secondNode; + if (firstNode == null) { + return null; + } + nodes.add(firstNode); + while (_peek() != TokenKind.END_OF_FILE) { + int operator = _peekToken.kind; + if (!_maybeEat(TokenKind.ASTERISK) && !_maybeEat(TokenKind.SLASH)) { + break; + } + secondNode = processCalcValue(); + if (secondNode == null) { + return null; + } + if (operator == TokenKind.SLASH) { + secondNode = CalcInvertNode(secondNode); + } + nodes.add(secondNode); + } + if (nodes.isEmpty) { + return firstNode; + } + final productNode = nodes.reduce((value, element) { + return CalcOperationExpressionNode(TokenKind.ASTERISK, value, element); + }); + return productNode; + } + + CalcExpressionNode? processCalcValue() { + CalcExpressionNode? func = processFunction(); + if (func != null) { + return func; + } + String value = _next().text; + String unit = _peekToken.text; + // ignore unit type + if (TokenKind.matchUnits(unit, 0, unit.length) == -1) { + return CalcLengthNode(CSSLengthValue(double.tryParse(value), CSSLengthType.PX)); + } + value += unit; + if (!CSSLength.isLength(value)) { + return null; + } + _next(); + return CalcLengthNode(CSSLength.parseLength(value, _renderStyle)); + } + + int _peek() { + return _peekToken.kind; + } + + Token _next({bool unicodeRange = false}) { + final next = _previousToken = _peekToken; + _peekToken = tokenizer.next(unicodeRange: unicodeRange); + return next; + } + + // Is the next token a legal identifier? This includes pseudo-keywords. + bool _peekIdentifier() { + return TokenKind.isIdentifier(_peekToken.kind); + } + + bool _maybeEat(int kind, {bool unicodeRange = false}) { + if (_peekToken.kind == kind) { + _previousToken = _peekToken; + _peekToken = tokenizer.next(unicodeRange: unicodeRange); + return true; + } else { + return false; + } + } +} diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 2bd3d946c3..ee9c96b0c0 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1057,6 +1057,10 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element value = value.computedValue(name); } + if (value is CSSCalcValue) { + value = CSSLengthValue(value.computedValue(name), CSSLengthType.PX); + } + switch (name) { case DISPLAY: renderStyle.display = value; From 08d09b9ae986e29560d0ecda6e57cccbab55b06c Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 5 Sep 2022 23:56:28 +0800 Subject: [PATCH 275/498] fix: animation adaptive for ruleset --- webf/lib/src/css/css_animation.dart | 96 +++++++---------------------- webf/lib/src/dom/element.dart | 3 + 2 files changed, 26 insertions(+), 73 deletions(-) diff --git a/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index accf62e9d3..64d6098adc 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -1,3 +1,7 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + import 'dart:ui'; import 'package:webf/css.dart'; @@ -11,8 +15,7 @@ const String EVENT_ANIMATION_END = 'animationend'; const String EVENT_ANIMATION_ITERATION = 'animationiteration'; class AnimationEvent extends Event { - AnimationEvent(String type, - {String? animationName, double? elapsedTime, String? pseudoElement}) + AnimationEvent(String type, {String? animationName, double? elapsedTime, String? pseudoElement}) : animationName = animationName ?? '', elapsedTime = elapsedTime ?? 0.0, pseudoElement = pseudoElement ?? '', @@ -25,26 +28,6 @@ class AnimationEvent extends Event { const String _0s = '0s'; -String _toCamelCase(String s) { - var sb = StringBuffer(); - var shouldUpperCase = false; - for (int rune in s.runes) { - // '-' char code is 45 - if (rune == 45) { - shouldUpperCase = true; - } else { - var char = String.fromCharCode(rune); - if (shouldUpperCase) { - sb.write(char.toUpperCase()); - shouldUpperCase = false; - } else { - sb.write(char); - } - } - } - - return sb.toString(); -} mixin CSSAnimationMixin on RenderStyle { List? _animationName; @@ -71,8 +54,7 @@ mixin CSSAnimationMixin on RenderStyle { } @override - List get animationTimingFunction => - _animationTimingFunction ?? const [EASE]; + List get animationTimingFunction => _animationTimingFunction ?? const [EASE]; List? _animationDelay; @@ -117,8 +99,7 @@ mixin CSSAnimationMixin on RenderStyle { } @override - List get animationPlayState => - _animationPlayState ?? ['running']; // paused + List get animationPlayState => _animationPlayState ?? ['running']; // paused bool shouldAnimation(List properties) { if (renderBoxModel != null) { @@ -152,22 +133,7 @@ mixin CSSAnimationMixin on RenderStyle { } List? _getKeyFrames(String animationName) { - final styleSheets = target.ownerDocument.styleSheets; - - CSSKeyframesRule? cssKeyframesRule = null; - for (int j = styleSheets.length - 1; j >= 0; j--) { - final sheet = styleSheets[j]; - List rules = sheet.cssRules; - for (int i = rules.length - 1; i >= 0; i--) { - CSSRule rule = rules[i]; - if (rule is CSSKeyframesRule) { - if (rule.name == animationName) { - cssKeyframesRule = rule; - break; - } - } - } - } + CSSKeyframesRule? cssKeyframesRule = target.ownerDocument.ruleSet.keyframesRules[animationName]; if (cssKeyframesRule != null) { List keyframes = []; @@ -182,7 +148,7 @@ mixin CSSAnimationMixin on RenderStyle { offset = CSSPercentage.parsePercentage(keyText); } rule.declarations.sheetStyle.forEach((key, value) { - final property = _toCamelCase(key); + final property = camelize(key); keyframes.add(Keyframe(property, value, offset ?? 0, LINEAR)); }); return; @@ -198,7 +164,7 @@ mixin CSSAnimationMixin on RenderStyle { if (name == NONE) { return; } - final fillMode = _toCamelCase(_getSingleString(animationFillMode, i)); + final fillMode = camelize(_getSingleString(animationFillMode, i)); List? keyframes = _getKeyFrames(name); if (keyframes == null) { return; @@ -243,9 +209,7 @@ mixin CSSAnimationMixin on RenderStyle { } void runAnimation() { - final removeKeys = _runningAnimation.keys - .where((element) => !animationName.contains(element)) - .toList(); + final removeKeys = _runningAnimation.keys.where((element) => !animationName.contains(element)).toList(); removeKeys.forEach((key) { Animation animation = _runningAnimation[key]!; @@ -261,11 +225,11 @@ mixin CSSAnimationMixin on RenderStyle { final duration = _getSingleString(animationDuration, i); final delay = _getSingleString(animationDelay, i); - final direction = _toCamelCase(_getSingleString(animationDirection, i)); + final direction = camelize(_getSingleString(animationDirection, i)); final iterationCount = _getSingleString(animationIterationCount, i); final playState = _getSingleString(animationPlayState, i); final timingFunction = _getSingleString(animationTimingFunction, i); - final fillMode = _toCamelCase(_getSingleString(animationFillMode, i)); + final fillMode = camelize(_getSingleString(animationFillMode, i)); EffectTiming? options = EffectTiming( duration: CSSTime.parseTime(duration)!.toDouble(), @@ -273,21 +237,17 @@ mixin CSSAnimationMixin on RenderStyle { delay: CSSTime.parseTime(delay)!.toDouble(), fill: FillMode.values.firstWhere((element) { return element.toString().split('.').last == fillMode; - },orElse:(){ + }, orElse: () { return FillMode.both; }), - iterations: iterationCount == 'infinite' - ? -1 - : (double.tryParse(iterationCount) ?? 1), - direction: PlaybackDirection.values.firstWhere( - (element) => element.toString().split('.').last == direction), + iterations: iterationCount == 'infinite' ? -1 : (double.tryParse(iterationCount) ?? 1), + direction: PlaybackDirection.values.firstWhere((element) => element.toString().split('.').last == direction), ); List? keyframes = _getKeyFrames(name); if (keyframes != null) { - KeyframeEffect effect = - KeyframeEffect(this, target, keyframes, options); + KeyframeEffect effect = KeyframeEffect(this, target, keyframes, options); Animation? animation = _runningAnimation[name]; @@ -297,8 +257,7 @@ mixin CSSAnimationMixin on RenderStyle { animation = Animation(effect, target.ownerDocument.animationTimeline); animation.onstart = () { - target.dispatchEvent( - AnimationEvent(EVENT_ANIMATION_START, animationName: name)); + target.dispatchEvent(AnimationEvent(EVENT_ANIMATION_START, animationName: name)); }; animation.oncancel = (AnimationPlaybackEvent event) { @@ -313,8 +272,7 @@ mixin CSSAnimationMixin on RenderStyle { _revertOriginProperty(_runningAnimation[name]!); } - target.dispatchEvent( - AnimationEvent(EVENT_ANIMATION_END, animationName: name)); + target.dispatchEvent(AnimationEvent(EVENT_ANIMATION_END, animationName: name)); // animation.dispose(); }; @@ -326,8 +284,7 @@ mixin CSSAnimationMixin on RenderStyle { animation.playState != AnimationPlayState.finished) { animation.play(); } else { - if (playState == 'paused' && - animation.playState != AnimationPlayState.paused) { + if (playState == 'paused' && animation.playState != AnimationPlayState.paused) { animation.pause(); } } @@ -381,10 +338,7 @@ mixin CSSAnimationMixin on RenderStyle { } static bool isValidAnimationFillModeValue(String value) { - return value == BACKWARDS || - value == FORWARDS || - value == BOTH || - value == NONE; + return value == BACKWARDS || value == FORWARDS || value == BOTH || value == NONE; } static bool isValidAnimationPlayStateValue(String value) { @@ -392,10 +346,7 @@ mixin CSSAnimationMixin on RenderStyle { } static bool isValidAnimationDirectionValue(String value) { - return value == NORMAL || - value == REVERSE || - value == ALTERNATE || - value == ALTERNATE_REVERSE; + return value == NORMAL || value == REVERSE || value == ALTERNATE || value == ALTERNATE_REVERSE; } static bool isBackwardsFillModeAnimation(Animation animation) { @@ -404,8 +355,7 @@ mixin CSSAnimationMixin on RenderStyle { return false; } final isBackwards = effect.timing?.fill != null && - (effect.timing!.fill == FillMode.backwards || - effect.timing!.fill == FillMode.both); + (effect.timing!.fill == FillMode.backwards || effect.timing!.fill == FillMode.both); return isBackwards; } diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index ee9c96b0c0..8fdd54fca0 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -515,6 +515,9 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element // Cancel running transition. renderStyle.cancelRunningTransition(); + // Cancel running animation. + renderStyle.cancelRunningAnimation(); + RenderBoxModel? renderBoxModel = this.renderBoxModel; if (renderBoxModel != null) { // The node detach may affect the whitespace of the nextSibling and previousSibling text node so prev and next node require layout. From 80c2e8145d5b67cffb1002ff2cc50f0307c4656a Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 6 Sep 2022 01:48:23 +0800 Subject: [PATCH 276/498] fix: fix canvas context 2d. --- bridge/bindings/qjs/binding_initializer.cc | 6 +++ bridge/core/binding_object.cc | 3 +- bridge/core/dom/element.cc | 46 ------------------- bridge/core/dom/element.d.ts | 12 ++--- bridge/core/dom/element.h | 10 ---- bridge/core/executing_context.cc | 4 +- .../core/html/canvas/html_canvas_element.cc | 5 +- .../code_generator/src/idl/generateSource.ts | 2 +- scripts/tasks.js | 3 +- webf/lib/src/bridge/binding.dart | 19 +++++++- .../elements/canvas/canvas_context_2d.dart | 24 ++++++---- 11 files changed, 57 insertions(+), 77 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 24c55a030c..4c3d0b2a4a 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -25,12 +25,15 @@ #include "qjs_html_all_collection.h" #include "qjs_html_body_element.h" #include "qjs_html_canvas_element.h" +#include "qjs_canvas_rendering_context.h" +#include "qjs_canvas_rendering_context_2d.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_script_element.h" +#include "qjs_html_anchor_element.h" #include "qjs_html_template_element.h" #include "qjs_html_unknown_element.h" #include "qjs_input_event.h" @@ -85,11 +88,14 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLHeadElement::Install(context); QJSHTMLBodyElement::Install(context); QJSHTMLHtmlElement::Install(context); + QJSHTMLAnchorElement::Install(context); QJSHTMLImageElement::Install(context); QJSHTMLScriptElement::Install(context); QJSHTMLUnknownElement::Install(context); QJSHTMLTemplateElement::Install(context); QJSHTMLCanvasElement::Install(context); + QJSCanvasRenderingContext::Install(context); + QJSCanvasRenderingContext2D::Install(context); QJSCSSStyleDeclaration::Install(context); QJSBoundingClientRect::Install(context); QJSScreen::Install(context); diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index cb73c3f1b7..6ee903d633 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -26,7 +26,7 @@ BindingObject::~BindingObject() { delete binding_object_; } -BindingObject::BindingObject(ExecutingContext* context, NativeBindingObject* native_binding_object) { +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; @@ -36,6 +36,7 @@ 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: invokeBindingMethod not initialized."); diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 188d905601..dd8df2a85e 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -275,52 +275,6 @@ ScriptPromise Element::toBlob(double device_pixel_ratio, ExceptionState& excepti return resolver->Promise(); } -double Element::clientHeight() const { - ExceptionState exception_state; - return NativeValueConverter::FromNativeValue( - GetBindingProperty(binding_call_methods::kclientHeight, exception_state)); -} - -double Element::clientWidth() const { - ExceptionState exception_state; - return NativeValueConverter::FromNativeValue( - GetBindingProperty(binding_call_methods::kclientWidth, exception_state)); -} - -double Element::clientLeft() const { - ExceptionState exception_state; - return NativeValueConverter::FromNativeValue( - GetBindingProperty(binding_call_methods::kclientLeft, exception_state)); -} - -double Element::clientTop() const { - ExceptionState exception_state; - return NativeValueConverter::FromNativeValue( - GetBindingProperty(binding_call_methods::kclientTop, exception_state)); -} - -double Element::scrollTop() const { - ExceptionState exception_state; - return NativeValueConverter::FromNativeValue( - GetBindingProperty(binding_call_methods::kscrollTop, exception_state)); -} - -void Element::setScrollTop(double v, ExceptionState& exception_state) { - SetBindingProperty(binding_call_methods::kscrollTop, NativeValueConverter::ToNativeValue(v), - exception_state); -} - -double Element::scrollLeft() const { - ExceptionState exception_state; - return NativeValueConverter::FromNativeValue( - GetBindingProperty(binding_call_methods::kclientTop, exception_state)); -} - -void Element::setScrollLeft(double v, ExceptionState& exception_state) { - SetBindingProperty(binding_call_methods::kscrollLeft, NativeValueConverter::ToNativeValue(v), - exception_state); -} - std::string Element::outerHTML() { std::string s = "<" + tagName().ToStdString(); diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index a75319eff4..ed2a4a3921 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -8,15 +8,15 @@ interface Element extends Node { readonly attributes: ElementAttributes; readonly style: CSSStyleDeclaration; - readonly clientHeight: number; - readonly clientLeft: number; - readonly clientTop: number; - readonly clientWidth: number; + readonly clientHeight: DartImpl; + readonly clientLeft: DartImpl; + readonly clientTop: DartImpl; + readonly clientWidth: DartImpl; readonly outerHTML: string; innerHTML: string; readonly ownerDocument: Document; - scrollLeft: number; - scrollTop: number; + scrollLeft: DartImpl; + scrollTop: DartImpl; readonly scrollWidth: DartImpl; readonly scrollHeight: DartImpl; /** diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index e960d08a1b..9355862d68 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -47,16 +47,6 @@ class Element : public ContainerNode { ScriptPromise toBlob(double device_pixel_ratio, ExceptionState& exception_state); ScriptPromise toBlob(ExceptionState& exception_state); - double clientHeight() const; - double clientWidth() const; - double clientLeft() const; - double clientTop() const; - - double scrollTop() const; - void setScrollTop(double v, ExceptionState& exception_state); - double scrollLeft() const; - void setScrollLeft(double v, ExceptionState& exception_state); - std::string outerHTML(); std::string innerHTML(); void setInnerHTML(const AtomicString& value, ExceptionState& exception_state); diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 40395e6f03..b2201898df 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -279,7 +279,9 @@ static void DispatchPromiseRejectionEvent(const AtomicString& event_type, } void ExecutingContext::FlushUICommand() { - dartMethodPtr()->flushUICommand(context_id_); + if (uiCommandBuffer()->size() > 0) { + dartMethodPtr()->flushUICommand(context_id_); + } } void ExecutingContext::DispatchErrorEvent(ErrorEvent* error_event) { diff --git a/bridge/core/html/canvas/html_canvas_element.cc b/bridge/core/html/canvas/html_canvas_element.cc index ea2f83566b..0b733e1b73 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -14,7 +14,10 @@ namespace webf { HTMLCanvasElement::HTMLCanvasElement(Document& document) : HTMLElement(html_names::kcanvas, &document) {} CanvasRenderingContext* HTMLCanvasElement::getContext(const AtomicString& type, ExceptionState& exception_state) const { - NativeValue value = InvokeBindingMethod(binding_call_methods::kgetContext, 0, nullptr, exception_state); + NativeValue arguments[] = { + NativeValueConverter::ToNativeValue(type) + }; + NativeValue value = InvokeBindingMethod(binding_call_methods::kgetContext, 1, arguments, exception_state); NativeBindingObject* native_binding_object = NativeValueConverter>::FromNativeValue(value); diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 9d38944b93..b4b78c50d6 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -193,7 +193,7 @@ auto* self = toScriptWrappable<${getClassName(blob)}>(JS_IsUndefined(this_val) ? NativeValue arguments[] = { ${nativeArguments.join(',\n')} }; -${returnValueAssignment}self->InvokeBindingMethod(binding_call_methods::k${declare.name}, ${declare.args.length}, arguments, exception_state); +${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(); } diff --git a/scripts/tasks.js b/scripts/tasks.js index d130b22926..0e9cc21ba4 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/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 99bce3b93a..00a1d71ee7 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -69,11 +69,22 @@ void _invokeBindingMethodFromNativeImpl(Pointer nativeBindi var result; try { if (method == GetPropertyMagic && argc == 1) { + if (isEnabledLog) { + print('$bindingObject getBindingProperty key: ${values[0]}'); + } + result = bindingObject.getBindingProperty(values[0]); } else if (method == SetPropertyMagic && argc == 2) { + if (isEnabledLog) { + print('$bindingObject setBindingProperty key: ${values[0]} value: ${values[1]}'); + } + bindingObject.setBindingProperty(values[0], values[1]); result = null; } else { + if (isEnabledLog) { + print('$bindingObject invokeBindingMethod method: $method args: $values'); + } result = bindingObject.invokeBindingMethod(method, values); } } catch (e, stack) { @@ -95,6 +106,10 @@ void _dispatchEventToNative(Event event) { Pointer? pointer = event.currentTarget?.pointer; int? contextId = event.target?.contextId; if (contextId != null && pointer != null) { + if (isEnabledLog) { + print('dispatch event to native side: target: ${event.target} event: $event'); + } + // Call methods implements at C++ side. DartInvokeBindingMethodsFromDart f = pointer.ref.invokeBindingMethodFromDart.asFunction(); @@ -107,8 +122,6 @@ void _dispatchEventToNative(Event event) { // Free the allocated arguments. malloc.free(method); malloc.free(allocatedNativeArguments); - - } } @@ -128,9 +141,11 @@ abstract class BindingBridge { static void _bindObject(BindingObject object) { Pointer? nativeBindingObject = object.pointer; + print('native binding pointer: $nativeBindingObject'); if (nativeBindingObject != null) { _nativeObjects[nativeBindingObject.address] = object; nativeBindingObject.ref.invokeBindingMethodFromNative = _invokeBindingMethodFromNative; + print('register invokeBindingmethod from native: ${nativeBindingObject.ref.invokeBindingMethodFromNative}'); } } 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 e598607495..82fc782802 100644 --- a/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart +++ b/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart @@ -85,21 +85,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()); From 1a8a431ef9bd80f0965c1bffaf059ed1eeef4d8c Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 6 Sep 2022 02:13:39 +0800 Subject: [PATCH 277/498] fix: fix canvas context 2d options params. --- bridge/core/css_property_list.h | 0 bridge/core/dom/child_node_list.h | 29 +++++++++++++++++++ bridge/core/dom/element_traversal.h | 29 +++++++++++++++++++ bridge/core/dom/events/event_target.cc | 4 +++ bridge/core/dom/node_list.h | 29 +++++++++++++++++++ bridge/core/dom/node_traversal.h | 29 +++++++++++++++++++ bridge/foundation/ascii_types.h | 29 +++++++++++++++++++ bridge/foundation/casting.h | 29 +++++++++++++++++++ webf/lib/src/bridge/binding.dart | 2 -- .../elements/canvas/canvas_context_2d.dart | 8 ++--- 10 files changed, 182 insertions(+), 6 deletions(-) delete mode 100644 bridge/core/css_property_list.h diff --git a/bridge/core/css_property_list.h b/bridge/core/css_property_list.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/core/dom/child_node_list.h b/bridge/core/dom/child_node_list.h index 163e0bf867..77da088e13 100644 --- a/bridge/core/dom/child_node_list.h +++ b/bridge/core/dom/child_node_list.h @@ -1,3 +1,32 @@ +/* + * 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. diff --git a/bridge/core/dom/element_traversal.h b/bridge/core/dom/element_traversal.h index 9254d0edd6..870ba67b13 100644 --- a/bridge/core/dom/element_traversal.h +++ b/bridge/core/dom/element_traversal.h @@ -1,3 +1,32 @@ +/* + * 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. diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index a191e69ba2..d4ec791f46 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -181,6 +181,8 @@ bool EventTarget::AddEventListenerInternal(const AtomicString& event_type, RegisteredEventListener registered_listener; bool added = EnsureEventTargetData().event_listener_map.Add(event_type, listener, options, ®istered_listener); + GetExecutingContext()->uiCommandBuffer()->addCommand(event_target_id_, UICommand::kAddEvent, std::move(event_type.ToNativeString()), nullptr); + return added; } @@ -220,6 +222,8 @@ bool EventTarget::RemoveEventListenerInternal(const AtomicString& event_type, } } + GetExecutingContext()->uiCommandBuffer()->addCommand(event_target_id_, UICommand::kRemoveEvent, std::move(event_type.ToNativeString()), nullptr); + return true; } diff --git a/bridge/core/dom/node_list.h b/bridge/core/dom/node_list.h index 62da2168d5..b7c3c0263c 100644 --- a/bridge/core/dom/node_list.h +++ b/bridge/core/dom/node_list.h @@ -1,3 +1,32 @@ +/* + * 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. diff --git a/bridge/core/dom/node_traversal.h b/bridge/core/dom/node_traversal.h index 713b0a98eb..451088a575 100644 --- a/bridge/core/dom/node_traversal.h +++ b/bridge/core/dom/node_traversal.h @@ -1,3 +1,32 @@ +/* + * 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. diff --git a/bridge/foundation/ascii_types.h b/bridge/foundation/ascii_types.h index ee88746521..a3a7bc918f 100644 --- a/bridge/foundation/ascii_types.h +++ b/bridge/foundation/ascii_types.h @@ -1,3 +1,32 @@ +/* + * 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. diff --git a/bridge/foundation/casting.h b/bridge/foundation/casting.h index 39c6477177..6c36a52f70 100644 --- a/bridge/foundation/casting.h +++ b/bridge/foundation/casting.h @@ -1,3 +1,32 @@ +/* + * 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. diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 00a1d71ee7..a8b9209a57 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -141,11 +141,9 @@ abstract class BindingBridge { static void _bindObject(BindingObject object) { Pointer? nativeBindingObject = object.pointer; - print('native binding pointer: $nativeBindingObject'); if (nativeBindingObject != null) { _nativeObjects[nativeBindingObject.address] = object; nativeBindingObject.ref.invokeBindingMethodFromNative = _invokeBindingMethodFromNative; - print('register invokeBindingmethod from native: ${nativeBindingObject.ref.invokeBindingMethodFromNative}'); } } 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 82fc782802..e891a37ed3 100644 --- a/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart +++ b/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart @@ -67,7 +67,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(), @@ -127,7 +127,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(); @@ -167,9 +167,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()); From f6cfcfeefd9e42eb38926de4b448f897c367ef63 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Mon, 5 Sep 2022 18:14:28 +0000 Subject: [PATCH 278/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 6 +++--- bridge/core/binding_object.cc | 3 ++- bridge/core/dom/events/event_target.cc | 6 ++++-- bridge/core/html/canvas/html_canvas_element.cc | 4 +--- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 4c3d0b2a4a..7fa6fb89bc 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -9,6 +9,8 @@ #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" @@ -23,17 +25,15 @@ #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_canvas_element.h" -#include "qjs_canvas_rendering_context.h" -#include "qjs_canvas_rendering_context_2d.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_script_element.h" -#include "qjs_html_anchor_element.h" #include "qjs_html_template_element.h" #include "qjs_html_unknown_element.h" #include "qjs_input_event.h" diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 6ee903d633..6ec2de7fe2 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -26,7 +26,8 @@ BindingObject::~BindingObject() { delete binding_object_; } -BindingObject::BindingObject(ExecutingContext* context, NativeBindingObject* native_binding_object): context_(context) { +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; diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index d4ec791f46..6b38a65ef2 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -181,7 +181,8 @@ bool EventTarget::AddEventListenerInternal(const AtomicString& event_type, RegisteredEventListener registered_listener; bool added = EnsureEventTargetData().event_listener_map.Add(event_type, listener, options, ®istered_listener); - GetExecutingContext()->uiCommandBuffer()->addCommand(event_target_id_, UICommand::kAddEvent, std::move(event_type.ToNativeString()), nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(event_target_id_, UICommand::kAddEvent, + std::move(event_type.ToNativeString()), nullptr); return added; } @@ -222,7 +223,8 @@ bool EventTarget::RemoveEventListenerInternal(const AtomicString& event_type, } } - GetExecutingContext()->uiCommandBuffer()->addCommand(event_target_id_, UICommand::kRemoveEvent, std::move(event_type.ToNativeString()), nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(event_target_id_, UICommand::kRemoveEvent, + std::move(event_type.ToNativeString()), nullptr); return true; } diff --git a/bridge/core/html/canvas/html_canvas_element.cc b/bridge/core/html/canvas/html_canvas_element.cc index 0b733e1b73..be6c8029bf 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -14,9 +14,7 @@ namespace webf { HTMLCanvasElement::HTMLCanvasElement(Document& document) : HTMLElement(html_names::kcanvas, &document) {} CanvasRenderingContext* HTMLCanvasElement::getContext(const AtomicString& type, ExceptionState& exception_state) const { - NativeValue arguments[] = { - NativeValueConverter::ToNativeValue(type) - }; + NativeValue arguments[] = {NativeValueConverter::ToNativeValue(type)}; NativeValue value = InvokeBindingMethod(binding_call_methods::kgetContext, 1, arguments, exception_state); NativeBindingObject* native_binding_object = NativeValueConverter>::FromNativeValue(value); From 7ae37a868c5382849149ec98f2b76c89fe7cf57f Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Tue, 6 Sep 2022 22:47:42 +0800 Subject: [PATCH 279/498] feat: animation & calc specs --- integration_tests/assets/blue-32x32.png | Bin 0 -> 110 bytes integration_tests/scripts/html_loader.js | 11 ++- .../animation-delay-001-manual.html | 72 ++++++++++++++++++ .../animation-delay-002-manual.html | 40 ++++++++++ .../animation-delay-003-manual.html | 39 ++++++++++ .../calc-background-position-1-ref.html | 24 ++++++ .../calc-background-position-1.html | 26 +++++++ .../example}/assets/00e_color.png | Bin webf/example/assets/blue-32x32.png | Bin 0 -> 110 bytes webf/lib/src/css/css_animation.dart | 2 - webf/lib/src/css/values/calc.dart | 9 +-- webf/lib/src/css/values/position.dart | 2 +- 12 files changed, 210 insertions(+), 15 deletions(-) create mode 100644 integration_tests/assets/blue-32x32.png create mode 100644 integration_tests/specs/css/css-animations/animation-delay-001-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-delay-002-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-delay-003-manual.html create mode 100644 integration_tests/specs/css/css-values/calc-background-position-1-ref.html create mode 100644 integration_tests/specs/css/css-values/calc-background-position-1.html rename {integration_tests => webf/example}/assets/00e_color.png (100%) create mode 100644 webf/example/assets/blue-32x32.png diff --git a/integration_tests/assets/blue-32x32.png b/integration_tests/assets/blue-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..deefd19b2ac53bef91c82ed2f6f4ea5f53a9e34f GIT binary patch literal 110 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={W7>k44ofy`glX(f`Xn49fhIkx* zd)ASWfq{i(!{YjMuMfAm)=WCN{G4&4gMt7D3sa**K`P_IV~qB%vRdu|^)Pt4`njxg HN@xNA-ozi} literal 0 HcmV?d00001 diff --git a/integration_tests/scripts/html_loader.js b/integration_tests/scripts/html_loader.js index 441d6ad6b1..d91f88f09d 100644 --- a/integration_tests/scripts/html_loader.js +++ b/integration_tests/scripts/html_loader.js @@ -47,17 +47,20 @@ const loader = function(source) { } }) - const htmlString = root.toString().replace(/\n/g, ''); - + const htmlString = root.toString().replace(/['\n]/g,function(c){ + return {'\n': '','\'': '\\'}[c]; + }); + return ` describe('HTMLSpec/${testRelativePath}', () => { // Use html_parse to parser html in html file. const html_parse = () => __webf_parse_html__('${htmlString}'); - + var index = 0; + const snapshotAction = async () => { index++; await snapshot(null, '${snapshotFilepath}', index); }; ${isFit ? 'fit' : 'it'}("should work", async (done) => {\ html_parse();\ requestAnimationFrame(async () => { - ${scripts.length === 0 ? `await snapshot(null, '${snapshotFilepath}', false);` : scripts.join('\n')} + ${scripts.length === 0 ? `await snapshotAction();` : scripts.join('\n')} done(); }); }) 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 new file mode 100644 index 0000000000..378a6ca421 --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-delay-001-manual.html @@ -0,0 +1,72 @@ + + +CSS Animations Test: animation-delay - negative value + + + + + + + + + + + +

    + Test passes if there are a filled blue square with 'Filler Text' + and a filled yellow square with 'Filler Text', and if the two squares + start moving together from right to left as soon as the page loads. +

    +
    Filler Text
    +
    Filler Text
    + + \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-delay-002-manual.html b/integration_tests/specs/css/css-animations/animation-delay-002-manual.html new file mode 100644 index 0000000000..0754242f8e --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-delay-002-manual.html @@ -0,0 +1,40 @@ + + + +CSS Animations Test: animation-delay - positive value + + + +

    + Test passes if there is a filled blue square with 'Filler Text', + which starts moving from right to left after about 5 seconds + from the time the page is loaded. +

    +
    Filler Text
    + + \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-delay-003-manual.html b/integration_tests/specs/css/css-animations/animation-delay-003-manual.html new file mode 100644 index 0000000000..46c4d52020 --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-delay-003-manual.html @@ -0,0 +1,39 @@ + + + +CSS Animations Test: animation-delay - 0s + + + +

    + Test passes if there is a filled blue square with 'Filler Text', + which starts moving from right to left as soon as the page loads. +

    +
    Filler Text
    + + diff --git a/integration_tests/specs/css/css-values/calc-background-position-1-ref.html b/integration_tests/specs/css/css-values/calc-background-position-1-ref.html new file mode 100644 index 0000000000..fc7b2510d0 --- /dev/null +++ b/integration_tests/specs/css/css-values/calc-background-position-1-ref.html @@ -0,0 +1,24 @@ + + + + CSS Reference: Test for calc() on background-position + + + + +

    +

    + + diff --git a/integration_tests/specs/css/css-values/calc-background-position-1.html b/integration_tests/specs/css/css-values/calc-background-position-1.html new file mode 100644 index 0000000000..197b485b2f --- /dev/null +++ b/integration_tests/specs/css/css-values/calc-background-position-1.html @@ -0,0 +1,26 @@ + + + + CSS Test: Test for calc() on background-position + + + + + + +

    +

    + + diff --git a/integration_tests/assets/00e_color.png b/webf/example/assets/00e_color.png similarity index 100% rename from integration_tests/assets/00e_color.png rename to webf/example/assets/00e_color.png diff --git a/webf/example/assets/blue-32x32.png b/webf/example/assets/blue-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..deefd19b2ac53bef91c82ed2f6f4ea5f53a9e34f GIT binary patch literal 110 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={W7>k44ofy`glX(f`Xn49fhIkx* zd)ASWfq{i(!{YjMuMfAm)=WCN{G4&4gMt7D3sa**K`P_IV~qB%vRdu|^)Pt4`njxg HN@xNA-ozi} literal 0 HcmV?d00001 diff --git a/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index 64d6098adc..468cfc67eb 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -2,8 +2,6 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import 'dart:ui'; - import 'package:webf/css.dart'; import 'package:webf/dom.dart'; diff --git a/webf/lib/src/css/values/calc.dart b/webf/lib/src/css/values/calc.dart index 2bffea57e0..da0e4faadb 100644 --- a/webf/lib/src/css/values/calc.dart +++ b/webf/lib/src/css/values/calc.dart @@ -103,7 +103,6 @@ class _CSSCalcParser { final RenderStyle _renderStyle; final Tokenizer tokenizer; - Token? _previousToken; late Token _peekToken; _CSSCalcParser(this._renderStyle, String text, {int start = 0}) @@ -230,19 +229,13 @@ class _CSSCalcParser { } Token _next({bool unicodeRange = false}) { - final next = _previousToken = _peekToken; + final next = _peekToken; _peekToken = tokenizer.next(unicodeRange: unicodeRange); return next; } - // Is the next token a legal identifier? This includes pseudo-keywords. - bool _peekIdentifier() { - return TokenKind.isIdentifier(_peekToken.kind); - } - bool _maybeEat(int kind, {bool unicodeRange = false}) { if (_peekToken.kind == kind) { - _previousToken = _peekToken; _peekToken = tokenizer.next(unicodeRange: unicodeRange); return true; } else { diff --git a/webf/lib/src/css/values/position.dart b/webf/lib/src/css/values/position.dart index 0783e49de3..f76c027f5a 100644 --- a/webf/lib/src/css/values/position.dart +++ b/webf/lib/src/css/values/position.dart @@ -7,7 +7,7 @@ import 'package:flutter/painting.dart'; import 'package:webf/css.dart'; import 'package:quiver/collection.dart'; -final RegExp _splitRegExp = RegExp(r'\s+'); +final RegExp _splitRegExp = RegExp(r'(?> _cachedParsedPosition = LinkedLruHashMap(maximumSize: 100); /// CSS Values and Units: https://drafts.csswg.org/css-values-3/#position From ebb04c19d8a7b9f9ed122fcb22c0447968d86ab8 Mon Sep 17 00:00:00 2001 From: andycall Date: Thu, 8 Sep 2022 00:21:40 +0800 Subject: [PATCH 280/498] feat: add event_factory feat: add mouse and pointer events. --- bridge/CMakeLists.txt | 22 ++- bridge/bindings/qjs/binding_initializer.cc | 10 ++ .../bindings/qjs/js_based_event_listener.cc | 1 + bridge/bindings/qjs/js_event_listener.cc | 1 + bridge/bindings/qjs/wrapper_type_info.h | 6 + bridge/core/binding_call_methods.json5 | 2 + bridge/core/binding_object.cc | 8 ++ bridge/core/binding_object.h | 9 +- bridge/core/dom/events/custom_event.cc | 133 ++++------------- bridge/core/dom/events/custom_event.d.ts | 8 ++ bridge/core/dom/events/custom_event.h | 67 +++------ bridge/core/dom/events/custom_event_init.d.ts | 7 + bridge/core/dom/events/event.cc | 61 +++++--- bridge/core/dom/events/event.h | 31 ++-- bridge/core/dom/events/event_listener.h | 2 +- bridge/core/dom/events/event_target.cc | 42 +++++- bridge/core/dom/events/event_target.h | 11 +- .../dom/events/registered_eventListener.cc | 1 + .../core/dom/legacy/bounding_client_rect.cc | 2 +- bridge/core/dom/legacy/bounding_client_rect.h | 2 +- bridge/core/events/animation_event.cc | 6 +- bridge/core/events/animation_event.h | 2 + bridge/core/events/close_event.cc | 24 +++- bridge/core/events/close_event.h | 9 +- bridge/core/events/dart_created_events.json5 | 99 +++++++++++++ bridge/core/events/event_type_names.json5 | 6 +- bridge/core/events/focus_event.cc | 29 ++-- bridge/core/events/focus_event.h | 8 ++ bridge/core/events/gesture_event.cc | 31 +++- bridge/core/events/gesture_event.h | 6 + bridge/core/events/input_event.cc | 24 +++- bridge/core/events/input_event.h | 6 + .../core/events/intersection_change_event.cc | 41 ++++-- .../core/events/intersection_change_event.h | 6 + bridge/core/events/keyboard_event.cc | 19 ++- bridge/core/events/keyboard_event.h | 11 +- bridge/core/events/message_event.cc | 42 ++++-- bridge/core/events/message_event.h | 5 + bridge/core/events/mouse_event.cc | 134 ++++++++++++++++++ bridge/core/events/mouse_event.d.ts | 3 - bridge/core/events/mouse_event.h | 85 +++++++++++ bridge/core/events/mouse_event_init.d.ts | 4 +- bridge/core/events/pointer_event.cc | 95 +++++++++++++ bridge/core/events/pointer_event.h | 66 +++++++++ bridge/core/events/promise_rejection_event.h | 5 - bridge/core/events/touch_event.cc | 85 +++++++++++ bridge/core/events/touch_event.h | 62 ++++++++ bridge/core/events/transition_event.cc | 61 ++++++++ bridge/core/events/transition_event.h | 53 +++++++ bridge/core/events/ui_event.cc | 45 ++++-- bridge/core/events/ui_event.h | 10 +- bridge/core/executing_context.cc | 2 +- bridge/core/frame/window.cc | 4 + bridge/core/frame/window.h | 13 ++ .../canvas/canvas_rendering_context_2d.cc | 2 +- .../html/canvas/canvas_rendering_context_2d.h | 2 +- bridge/core/input/touch.cc | 5 + bridge/core/input/touch.h | 2 + bridge/core/input/touch_list.h | 9 +- bridge/core/page.cc | 5 +- bridge/foundation/native_value.h | 2 +- bridge/foundation/native_value_converter.h | 12 +- .../code_generator/src/idl/generateHeader.ts | 7 +- .../code_generator/src/idl/generateSource.ts | 37 ++++- .../static/idl_templates/base.cc.tpl | 1 + .../static/idl_templates/dictionary.h.tpl | 6 +- .../static/idl_templates/interface.cc.tpl | 4 +- .../static/idl_templates/interface.h.tpl | 29 ++++ .../json_templates/event_factory.cc.tpl | 90 ++++++++++++ .../static/json_templates/event_factory.h.tpl | 23 +++ .../json_templates/event_type_helper.h.tpl | 50 +++++++ webf/lib/src/bridge/binding.dart | 14 +- webf/lib/src/bridge/native_value.dart | 4 +- webf/lib/src/dom/event.dart | 14 +- 74 files changed, 1531 insertions(+), 314 deletions(-) create mode 100644 bridge/core/dom/events/custom_event.d.ts create mode 100644 bridge/core/dom/events/custom_event_init.d.ts create mode 100644 bridge/core/events/dart_created_events.json5 create mode 100644 bridge/core/events/mouse_event.cc create mode 100644 bridge/core/events/mouse_event.h create mode 100644 bridge/core/events/pointer_event.cc create mode 100644 bridge/core/events/pointer_event.h create mode 100644 bridge/core/events/touch_event.cc create mode 100644 bridge/core/events/touch_event.h create mode 100644 bridge/core/events/transition_event.cc create mode 100644 bridge/core/events/transition_event.h create mode 100644 bridge/scripts/code_generator/static/json_templates/event_factory.cc.tpl create mode 100644 bridge/scripts/code_generator/static/json_templates/event_factory.h.tpl create mode 100644 bridge/scripts/code_generator/static/json_templates/event_type_helper.h.tpl diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index c4da7b5f8b..ce5558a20c 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -7,9 +7,9 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") -endif() +#if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") +# set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") +#endif() if (${ENABLE_PROFILE}) add_definitions(-DENABLE_PROFILE=1) @@ -220,6 +220,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") 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 @@ -247,6 +248,10 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") 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/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 @@ -307,6 +312,17 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") 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 diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 7fa6fb89bc..d72188ea8c 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -43,6 +43,11 @@ #include "qjs_message_event.h" #include "qjs_module_manager.h" #include "qjs_node.h" +#include "qjs_custom_event.h" +#include "qjs_mouse_event.h" +#include "qjs_touch_event.h" +#include "qjs_transition_event.h" +#include "qjs_pointer_event.h" #include "qjs_node_list.h" #include "qjs_promise_rejection_event.h" #include "qjs_screen.h" @@ -74,6 +79,11 @@ void InstallBindings(ExecutingContext* context) { QJSFocusEvent::Install(context); QJSGestureEvent::Install(context); QJSInputEvent::Install(context); + QJSCustomEvent::Install(context); + QJSMouseEvent::Install(context); + QJSPointerEvent::Install(context); + QJSTouchEvent::Install(context); + QJSTransitionEvent::Install(context); QJSIntersectionChangeEvent::Install(context); QJSKeyboardEvent::Install(context); QJSNode::Install(context); diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc index fc4ce41650..8d40b5f97b 100644 --- a/bridge/bindings/qjs/js_based_event_listener.cc +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -4,6 +4,7 @@ */ #include "js_based_event_listener.h" +#include "core/dom/events/event.h" namespace webf { diff --git a/bridge/bindings/qjs/js_event_listener.cc b/bridge/bindings/qjs/js_event_listener.cc index 0e662aeb5e..4f2f9a46e1 100644 --- a/bridge/bindings/qjs/js_event_listener.cc +++ b/bridge/bindings/qjs/js_event_listener.cc @@ -4,6 +4,7 @@ */ #include "js_event_listener.h" +#include "core/dom/events/event.h" #include #include "core/dom/events/event_target.h" diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index faeed10ea2..83cd79100a 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -12,6 +12,7 @@ namespace webf { class EventTarget; +class TouchList; // Define all built-in wrapper class id. enum { @@ -22,6 +23,11 @@ enum { 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, diff --git a/bridge/core/binding_call_methods.json5 b/bridge/core/binding_call_methods.json5 index 4448d194a0..712599124f 100644 --- a/bridge/core/binding_call_methods.json5 +++ b/bridge/core/binding_call_methods.json5 @@ -141,5 +141,7 @@ "cols", "rows", "wrap", + "dispatchEvent", + "getModifierState" ] } diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 6ec2de7fe2..2cf7234b9f 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -64,4 +64,12 @@ NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, return InvokeBindingMethod(binding_call_methods::ksetPropertyMagic, 2, argv, exception_state); } +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 index 39a7f6416a..69595094f6 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -52,7 +52,7 @@ class BindingObject { explicit BindingObject(ExecutingContext* context); // Handle call from dart side. - virtual NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const = 0; + virtual NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) = 0; // Invoke methods which implemented at dart side. NativeValue InvokeBindingMethod(const AtomicString& method, int32_t argc, @@ -63,6 +63,13 @@ class BindingObject { NativeBindingObject* bindingObject() const { return binding_object_; } + inline static BindingObject* From(NativeBindingObject* native_binding_object) { + return native_binding_object->binding_target_; + }; + + virtual bool IsEventTarget() const; + virtual bool IsTouchList() const; + protected: // NativeBindingObject may allocated at Dart side. Binding this with Dart allocated NativeBindingObject. explicit BindingObject(ExecutingContext* context, NativeBindingObject* native_binding_object); diff --git a/bridge/core/dom/events/custom_event.cc b/bridge/core/dom/events/custom_event.cc index 2e114450e2..1265c490b2 100644 --- a/bridge/core/dom/events/custom_event.cc +++ b/bridge/core/dom/events/custom_event.cc @@ -3,131 +3,50 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "custom_event.h" -#include "bindings/qjs/native_value.h" -#include "bindings/qjs/qjs_engine_patch.h" - -#include +#include "native_value_converter.h" namespace webf { -void bindCustomEvent(std::unique_ptr& context) { - JSValue constructor = context->contextData()->constructorForType(&customEventTypeInfo); - JSValue prototype = context->contextData()->prototypeForType(&customEventTypeInfo); - - // Install methods on prototype. - INSTALL_FUNCTION(CustomEvent, prototype, initCustomEvent, 4); - - // Install readonly properties on prototype. - INSTALL_READONLY_PROPERTY(CustomEvent, prototype, detail); - - context->defineGlobalProperty("CustomEvent", constructor); -} - -JSClassID CustomEvent::classId{0}; - -CustomEvent* CustomEvent::create(JSContext* ctx, JSValue eventType, JSValue init) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - JSValue prototype = context->contextData()->prototypeForType(&eventTypeInfo); - - auto* event = makeGarbageCollected(eventType, init)->initialize(ctx, &classId); - - if (!JS_IsNull(init)) { - JSAtom detailKey = JS_NewAtom(ctx, "detail"); - if (JS_HasProperty(ctx, init, detailKey)) { - JSValue detailValue = JS_GetProperty(ctx, init, detailKey); - event->m_detail = JS_DupValue(ctx, detailValue); - JS_FreeValue(ctx, detailValue); - } - JS_FreeAtom(ctx, detailKey); - } - - // Let instance inherit prototype methods. - JS_SetPrototype(ctx, event->toQuickJS(), prototype); - - return event; +CustomEvent *CustomEvent::Create(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state) { + return MakeGarbageCollected(context, type, exception_state); } -CustomEvent* CustomEvent::create(JSContext* ctx, NativeCustomEvent* nativeCustomEvent) { - auto* context = static_cast(JS_GetContextOpaque(ctx)); - JSValue prototype = context->contextData()->prototypeForType(&eventTypeInfo); - - auto* event = makeGarbageCollected(nativeCustomEvent)->initialize(ctx, &classId); - - // Let instance inherit prototype methods. - JS_SetPrototype(ctx, event->toQuickJS(), prototype); - - return event; +CustomEvent *CustomEvent::Create(ExecutingContext *context, + const AtomicString &type, + NativeCustomEvent *native_custom_event) { + return MakeGarbageCollected(context, type, native_custom_event); } -JSValue CustomEvent::constructor(ExecutionContext* context) { - return context->contextData()->constructorForType(&customEventTypeInfo); +CustomEvent *CustomEvent::Create(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initialize, + ExceptionState &exception_state) { + return MakeGarbageCollected(context, type, initialize, exception_state); } -JSValue CustomEvent::prototype(ExecutionContext* context) { - return context->contextData()->prototypeForType(&customEventTypeInfo); +CustomEvent::CustomEvent(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state): Event(context, type) { } -CustomEvent::CustomEvent(JSValue eventType, JSValue eventInit) : Event(eventType, eventInit) { - if (!JS_IsNull(eventInit)) { - JSAtom detailKey = JS_NewAtom(m_ctx, "detail"); - if (JS_HasProperty(m_ctx, eventInit, detailKey)) { - JSValue detailValue = JS_GetProperty(m_ctx, eventInit, detailKey); - m_detail = JS_DupValue(m_ctx, detailValue); - JS_FreeValue(m_ctx, detailValue); - } - JS_FreeAtom(m_ctx, detailKey); - } +CustomEvent::CustomEvent(ExecutingContext *context, const AtomicString &type, NativeCustomEvent *native_custom_event) : + Event(context, type, &native_custom_event->native_event), + detail_(ScriptValue::CreateJsonObject(ctx(), native_custom_event->detail, strlen(native_custom_event->detail))) { } -CustomEvent::CustomEvent(NativeCustomEvent* nativeEvent) - : m_nativeCustomEvent(nativeEvent), Event(reinterpret_cast(nativeEvent)) { - m_detail = JS_NewUnicodeString(m_runtime, m_ctx, nativeEvent->detail->string, nativeEvent->detail->length); -} - -void CustomEvent::trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const { - Event::trace(rt, val, mark_func); - JS_MarkValue(rt, m_detail, mark_func); -} +CustomEvent::CustomEvent(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initialize, + ExceptionState &exception_state): + Event(context, type), + detail_(initialize->detail()) { -void CustomEvent::dispose() const { - // No needs to free m_nativeCustomEvent, Event::dispose() will handle this. - Event::dispose(); - JS_FreeValueRT(m_runtime, m_detail); } -IMPL_FUNCTION(CustomEvent, initCustomEvent)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - if (argc < 1) { - return JS_ThrowTypeError( - ctx, "Failed to execute 'initCustomEvent' on 'CustomEvent': 1 argument required, but only 0 present"); - } - - auto* eventInstance = static_cast(JS_GetOpaque(this_val, CustomEvent::classId)); - if (eventInstance == nullptr) { - return JS_ThrowTypeError(ctx, "Failed to addEventListener: this is not an EventTarget object."); - } - - JSValue typeValue = argv[0]; - eventInstance->nativeEvent->type = jsValueToNativeString(ctx, typeValue).release(); - - if (argc <= 2) { - bool canBubble = JS_ToBool(ctx, argv[1]); - eventInstance->nativeEvent->bubbles = canBubble ? 1 : 0; - } - - if (argc <= 3) { - bool cancelable = JS_ToBool(ctx, argv[2]); - eventInstance->nativeEvent->cancelable = cancelable ? 1 : 0; - } - - if (argc <= 4) { - eventInstance->m_detail = JS_DupValue(ctx, argv[3]); - } - return JS_NULL; +ScriptValue CustomEvent::detail() const { + return detail_; } -IMPL_PROPERTY_GETTER(CustomEvent, detail)(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) { - auto* customEventInstance = static_cast(JS_GetOpaque(this_val, CustomEvent::classId)); - return JS_DupValue(ctx, customEventInstance->m_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..f5ebedac68 --- /dev/null +++ b/bridge/core/dom/events/custom_event.d.ts @@ -0,0 +1,8 @@ +/** Events providing information related to animations. */ +import {Event} from "./event"; +import {CustomEventInit} from "./custom_event_init"; + +interface CustomEvent extends Event { + readonly detail: any; + 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 index 737e876e13..e915123fae 100644 --- a/bridge/core/dom/events/custom_event.h +++ b/bridge/core/dom/events/custom_event.h @@ -6,71 +6,42 @@ #define BRIDGE_CUSTOM_EVENT_H #include "event.h" +#include "qjs_custom_event_init.h" namespace webf { -void bindCustomEvent(ExecutionContext* context); - struct NativeCustomEvent { - NativeEvent nativeEvent; - NativeString* detail{nullptr}; + NativeEvent native_event; + const char* detail{nullptr}; }; -class CustomEvent : public Event { +class CustomEvent final : public Event { + DEFINE_WRAPPERTYPEINFO(); public: - static JSClassID classId; - static CustomEvent* create(JSContext* ctx, JSValue eventType, JSValue init); - static CustomEvent* create(JSContext* ctx, NativeCustomEvent* nativeCustomEvent); - static JSValue constructor(ExecutionContext* context); - static JSValue prototype(ExecutionContext* context); + using ImplType = CustomEvent*; - CustomEvent(JSValue eventType, JSValue init); - CustomEvent(NativeCustomEvent* nativeEvent); + 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& initialize, ExceptionState& exception_state); - void trace(JSRuntime* rt, JSValue val, JS_MarkFunc* mark_func) const override; - void dispose() const override; + 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& initialize, ExceptionState& exception_state); - DEFINE_FUNCTION(initCustomEvent); + ScriptValue detail() const; - DEFINE_PROTOTYPE_READONLY_PROPERTY(detail); + bool IsCustomEvent() const override; private: - NativeCustomEvent* m_nativeCustomEvent{nullptr}; - JSValue m_detail{JS_NULL}; + ScriptValue detail_; }; -// class CustomEventInstance : public EventInstance { -// public: -// explicit CustomEventInstance(CustomEvent* jsCustomEvent, JSAtom CustomEventType, JSValue eventInit); -// explicit CustomEventInstance(CustomEvent* jsCustomEvent, NativeCustomEvent* nativeCustomEvent); -// -// private: - -// NativeCustomEvent* nativeCustomEvent{nullptr}; -// friend CustomEvent; -//}; - -auto customEventCreator = - [](JSContext* ctx, JSValueConst func_obj, JSValueConst this_val, int argc, JSValueConst* argv, int flags) - -> JSValue { - if (argc < 1) { - return JS_ThrowTypeError(ctx, "Failed to construct 'CustomEvent': 1 argument required, but only 0 present."); - } - - JSValue typeValue = argv[0]; - JSValue customEventInit = JS_NULL; - - if (argc == 2) { - customEventInit = argv[1]; - } - - auto* customEvent = CustomEvent::create(ctx, typeValue, customEventInit); - // auto* customEvent = new CustomEventInstance(CustomEvent::instance(context()), typeAtom, customEventInit); - // JS_FreeAtom(m_ctx, typeAtom); - return customEvent->toQuickJS(); +template <> +struct DowncastTraits { + static bool AllowFrom(const Event& event) { return event.IsCustomEvent(); } }; -const WrapperTypeInfo customEventTypeInfo = {"CustomEvent", &eventTypeInfo, customEventCreator}; } // namespace webf 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/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index cf33ab0883..7781c493bd 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -5,22 +5,10 @@ #include "event.h" #include "core/executing_context.h" #include "event_target.h" +#include "bindings/qjs/cppgc/gc_visitor.h" namespace webf { -Event* Event::From(ExecutingContext* context, NativeEvent* native_event) { - AtomicString event_type = AtomicString::From(context->ctx(), native_event->type); - - auto* event = - MakeGarbageCollected(context, event_type, native_event->bubbles == 0 ? Bubbles::kNo : Bubbles::kYes, - native_event->cancelable == 0 ? Cancelable::kNo : Cancelable::kYes, - ComposedMode::kComposed, native_event->timeStamp); - event->SetTarget(static_cast(native_event->target)); - event->SetCurrentTarget(static_cast(native_event->currentTarget)); - event->default_prevented_ = native_event->defaultPrevented; - return event; -} - Event::Event(ExecutingContext* context, const AtomicString& event_type) : Event(context, event_type, @@ -61,15 +49,25 @@ Event::Event(ExecutingContext* context, current_target_(nullptr), time_stamp_(time_stamp) {} -void Event::SetType(const AtomicString& type) { +Event::Event(ExecutingContext *context, const AtomicString &event_type, NativeEvent *native_event) : + ScriptWrappable(context->ctx()), + type_(event_type), + bubbles_(native_event->bubbles), + cancelable_(native_event->cancelable), + time_stamp_(native_event->timeStamp), + default_prevented_(native_event->defaultPrevented), + target_(DynamicTo(BindingObject::From(native_event->target))), + current_target_(DynamicTo(BindingObject::From(native_event->currentTarget))) {} + +void Event::SetType(const AtomicString &type) { type_ = type; } -EventTarget* Event::target() const { +EventTarget *Event::target() const { return target_; } -void Event::SetTarget(EventTarget* target) { +void Event::SetTarget(EventTarget *target) { target_ = target; } @@ -85,7 +83,7 @@ void Event::SetCurrentTarget(EventTarget* target) { current_target_ = target; } -bool Event::IsUIEvent() const { +bool Event::IsUiEvent() const { return false; } @@ -117,6 +115,30 @@ 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::IsIntersectionchangeEvent() const { + return false; +} + bool Event::IsDragEvent() const { return false; } @@ -160,6 +182,9 @@ void Event::SetHandlingPassive(PassiveMode mode) { handling_passive_ = mode; } -void Event::Trace(GCVisitor* visitor) const {} +void Event::Trace(GCVisitor *visitor) const { + visitor->Trace(target_); + visitor->Trace(current_target_); +} } // namespace webf diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 62db134af5..a0ec6bf578 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -8,6 +8,8 @@ #include #include "bindings/qjs/atomic_string.h" #include "bindings/qjs/script_wrappable.h" +#include "core/dom/events/event_target.h" +#include "bindings/qjs/cppgc/member.h" #include "core/executing_context.h" #include "foundation/native_string.h" #include "qjs_event_init.h" @@ -16,6 +18,7 @@ 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. @@ -41,9 +44,9 @@ struct NativeEvent { int64_t timeStamp{0}; int64_t defaultPrevented{0}; // The pointer address of target EventTargetInstance object. - void* target{nullptr}; + NativeBindingObject* target{nullptr}; // The pointer address of current target EventTargetInstance object. - void* currentTarget{nullptr}; + NativeBindingObject* currentTarget{nullptr}; }; #endif @@ -52,6 +55,13 @@ struct RawEvent { int64_t length; }; +template +T* toNativeEvent(RawEvent* raw_event) { + // NativeEvent members are memory aligned corresponding to NativeEvent. + // So we can reinterpret_cast raw bytes pointer to NativeEvent type directly. + return reinterpret_cast(raw_event->bytes); +} + class Event : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); @@ -99,8 +109,6 @@ class Event : public ScriptWrappable { return MakeGarbageCollected(context, type, init); }; - static Event* From(ExecutingContext* context, NativeEvent* native_event); - Event() = delete; explicit Event(ExecutingContext* context, const AtomicString& event_type); explicit Event(ExecutingContext* context, const AtomicString& type, const std::shared_ptr& init); @@ -110,13 +118,14 @@ class Event : public ScriptWrappable { 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() { return type_; }; + const AtomicString& type() const { return type_; }; void SetType(const AtomicString& type); EventTarget* target() const; void SetTarget(EventTarget* target); @@ -127,7 +136,7 @@ class Event : public ScriptWrappable { void SetEventPhase(uint8_t event_phase) { event_phase_ = event_phase; } // These events are general classes of events. - virtual bool IsUIEvent() const; + virtual bool IsUiEvent() const; virtual bool IsMouseEvent() const; virtual bool IsFocusEvent() const; virtual bool IsKeyboardEvent() const; @@ -135,6 +144,12 @@ class Event : public ScriptWrappable { 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 IsIntersectionchangeEvent() const; // Drag events are a subset of mouse events. virtual bool IsDragEvent() const; @@ -224,8 +239,8 @@ class Event : public ScriptWrappable { unsigned fire_only_capture_listeners_at_target_ : 1; unsigned fire_only_non_capture_listeners_at_target_ : 1; - EventTarget* target_{nullptr}; - EventTarget* current_target_{nullptr}; + Member target_; + Member current_target_; }; } // namespace webf diff --git a/bridge/core/dom/events/event_listener.h b/bridge/core/dom/events/event_listener.h index 22aeec1a1a..be4db43a8e 100644 --- a/bridge/core/dom/events/event_listener.h +++ b/bridge/core/dom/events/event_listener.h @@ -6,11 +6,11 @@ #define BRIDGE_CORE_DOM_EVENTS_EVENT_LISTENER_H_ #include "core/executing_context.h" -#include "event.h" namespace webf { class JSBasedEventListener; +class Event; // EventListener represents 'callback' in 'event listener' in DOM standard. // https://dom.spec.whatwg.org/#concept-event-listener diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 6b38a65ef2..e59f1ed7e1 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -6,6 +6,10 @@ #include "bindings/qjs/converter_impl.h" #include "event_type_names.h" #include "qjs_add_event_listener_options.h" +#include "binding_call_methods.h" +#include "native_value_converter.h" +#include "event_factory.h" +#include "custom_event.h" #define PROPAGATION_STOPPED 1 #define PROPAGATION_CONTINUE 0 @@ -172,6 +176,10 @@ EventListenerVector* EventTarget::GetEventListeners(const AtomicString& event_ty return data->event_listener_map.Find(event_type); } +bool EventTarget::IsEventTarget() const { + return true; +} + bool EventTarget::AddEventListenerInternal(const AtomicString& event_type, const std::shared_ptr& listener, const std::shared_ptr& options) { @@ -238,10 +246,42 @@ DispatchEventResult EventTarget::DispatchEventInternal(Event& event, ExceptionSt return dispatch_result; } -NativeValue EventTarget::HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const { +NativeValue EventTarget::HandleCallFromDartSide(NativeString* native_method, int32_t argc, const NativeValue* argv) { + MemberMutationScope mutation_scope{GetExecutingContext()}; + AtomicString method = AtomicString(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 == 3); + AtomicString event_type = NativeValueConverter::FromNativeValue(ctx(), argv[0]); + RawEvent* raw_event = NativeValueConverter>::FromNativeValue(argv[1]); + bool is_custom_event = NativeValueConverter::FromNativeValue(argv[2]); + + Event* event; + if (is_custom_event) { + event = MakeGarbageCollected(GetExecutingContext(), event_type, toNativeEvent(raw_event)); + } else { + event = EventFactory::Create(GetExecutingContext(), event_type, raw_event); + } + + ExceptionState exception_state; + bool result = dispatchEvent(event, exception_state); + + if (exception_state.HasException()) { + JSValue error = JS_GetException(ctx()); + GetExecutingContext()->ReportError(error); + JS_FreeValue(ctx(), error); + } + + return NativeValueConverter::ToNativeValue(result); +} + RegisteredEventListener* EventTarget::GetAttributeRegisteredEventListener(const AtomicString& event_type) { EventListenerVector* listener_vector = GetEventListeners(event_type); if (!listener_vector) diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index a48ac0fe01..6155930ef0 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -129,6 +129,7 @@ class EventTarget : public ScriptWrappable, public BindingObject { int32_t eventTargetId() const { return event_target_id_; } virtual bool IsWindowOrWorkerGlobalScope() const { return false; } + bool IsEventTarget() const override; protected: virtual bool AddEventListenerInternal(const AtomicString& event_type, @@ -140,7 +141,8 @@ class EventTarget : public ScriptWrappable, public BindingObject { DispatchEventResult DispatchEventInternal(Event& event, ExceptionState& exception_state); - NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const override; + NativeValue HandleCallFromDartSide(NativeString* 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. @@ -154,6 +156,13 @@ class EventTarget : public ScriptWrappable, public BindingObject { bool FireEventListeners(Event&, EventTargetData*, EventListenerVector&, ExceptionState&); }; +template <> +struct DowncastTraits { + static bool AllowFrom(const BindingObject& binding_object) { + return binding_object.IsEventTarget(); + } +}; + // Provide EventTarget with inlined EventTargetData for improved performance. class EventTargetWithInlineData : public EventTarget { public: diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc index 501eeb8d38..80bf354aaf 100644 --- a/bridge/core/dom/events/registered_eventListener.cc +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -4,6 +4,7 @@ */ #include "registered_eventListener.h" #include "qjs_add_event_listener_options.h" +#include "event.h" namespace webf { diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc index 6eda612ced..c82bc692fd 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.cc +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -17,7 +17,7 @@ BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBindingO NativeValue BoundingClientRect::HandleCallFromDartSide(NativeString* method, int32_t argc, - const NativeValue* argv) const { + const NativeValue* argv) { return Native_NewNull(); } diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index ae84fc038a..91511bf9f6 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -22,7 +22,7 @@ class BoundingClientRect : public ScriptWrappable, public BindingObject { static BoundingClientRect* Create(ExecutingContext* context, NativeBindingObject* native_binding_object); explicit BoundingClientRect(ExecutingContext* context, NativeBindingObject* native_binding_object); - NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const override; + NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) override; double x() const { return x_; } double y() const { return y_; } diff --git a/bridge/core/events/animation_event.cc b/bridge/core/events/animation_event.cc index d519fa79ce..f936c8ab42 100644 --- a/bridge/core/events/animation_event.cc +++ b/bridge/core/events/animation_event.cc @@ -47,7 +47,7 @@ AnimationEvent::AnimationEvent(ExecutingContext* context, const AtomicString& type, const std::shared_ptr& initializer, ExceptionState& exception_state) - : Event(context, event_type_names::kerror), + : Event(context, type), animation_name_(initializer->animationName()), pseudo_element_(initializer->pseudoElement()), elapsed_time_(initializer->elapsedTime()) {} @@ -56,6 +56,10 @@ const AtomicString& AnimationEvent::animationName() const { return animation_name_; } +bool AnimationEvent::IsAnimationEvent() const { + return true; +} + double AnimationEvent::elapsedTime() const { return elapsed_time_; } diff --git a/bridge/core/events/animation_event.h b/bridge/core/events/animation_event.h index 3570fff592..46cd8943d6 100644 --- a/bridge/core/events/animation_event.h +++ b/bridge/core/events/animation_event.h @@ -46,6 +46,8 @@ class AnimationEvent : public Event { double elapsedTime() const; const AtomicString& pseudoElement() const; + bool IsAnimationEvent() const override; + private: AtomicString animation_name_; AtomicString pseudo_element_; diff --git a/bridge/core/events/close_event.cc b/bridge/core/events/close_event.cc index 3362e84bb8..7d44db8261 100644 --- a/bridge/core/events/close_event.cc +++ b/bridge/core/events/close_event.cc @@ -3,6 +3,7 @@ */ #include "close_event.h" +#include "qjs_close_event.h" namespace webf { @@ -37,20 +38,31 @@ CloseEvent::CloseEvent(ExecutingContext* context, 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& initializer, - ExceptionState& exception_state) +CloseEvent::CloseEvent(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) : Event(context, type), code_(initializer->code()), reason_(initializer->reason()), was_clean_(initializer->wasClean()) {} -int32_t CloseEvent::code() const { +CloseEvent::CloseEvent(ExecutingContext *context, const AtomicString &type, NativeCloseEvent *native_close_event) + : Event(context, type, &native_close_event->native_event), + code_(native_close_event->code), + reason_(AtomicString(context->ctx(), native_close_event->reason)), + was_clean_(native_close_event->wasClean) { +} + +bool CloseEvent::IsCloseEvent() const { + return true; +} + +int64_t CloseEvent::code() const { return code_; } -const AtomicString& CloseEvent::reason() const { +const AtomicString &CloseEvent::reason() const { return reason_; } diff --git a/bridge/core/events/close_event.h b/bridge/core/events/close_event.h index dcfc9870f2..fb19fffcaa 100644 --- a/bridge/core/events/close_event.h +++ b/bridge/core/events/close_event.h @@ -12,6 +12,8 @@ namespace webf { +struct NativeCloseEvent; + class CloseEvent : public Event { DEFINE_WRAPPERTYPEINFO(); @@ -42,13 +44,16 @@ class CloseEvent : public Event { const AtomicString& type, const std::shared_ptr& initializer, ExceptionState& exception_state); + explicit CloseEvent(ExecutingContext* context, const AtomicString& type, NativeCloseEvent* raw_event); + + bool IsCloseEvent() const override; - int32_t code() const; + int64_t code() const; const AtomicString& reason() const; bool wasClean() const; private: - int32_t code_; + int64_t code_; AtomicString reason_; bool was_clean_; }; diff --git a/bridge/core/events/dart_created_events.json5 b/bridge/core/events/dart_created_events.json5 new file mode 100644 index 0000000000..5437fbdf61 --- /dev/null +++ b/bridge/core/events/dart_created_events.json5 @@ -0,0 +1,99 @@ +{ + "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": "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": [ + "mousedown", + "mouseenter", + "mouseleave", + "mousemove", + "mouseout", + "mouseover", + "mouseup", + "mousewheel" + ] + }, + { + "class": "TransitionEvent", + "types": [ + "transitioncancel", + "transitionend", + "transitionrun", + "transitionstart" + ] + } + ] +} diff --git a/bridge/core/events/event_type_names.json5 b/bridge/core/events/event_type_names.json5 index be26438462..74215aebcf 100644 --- a/bridge/core/events/event_type_names.json5 +++ b/bridge/core/events/event_type_names.json5 @@ -147,6 +147,9 @@ "pointerout", "pointerover", "pointerup", + "gesturestart", + "gesturechange", + "gestureend", "popstate", "progress", "processorerror", @@ -217,6 +220,7 @@ "webglcontextlost", "webglcontextrestored", "wheel", - "zoom" + "zoom", + "intersectionchange" ] } diff --git a/bridge/core/events/focus_event.cc b/bridge/core/events/focus_event.cc index ac07644503..8e4b3c48a3 100644 --- a/bridge/core/events/focus_event.cc +++ b/bridge/core/events/focus_event.cc @@ -5,6 +5,7 @@ #include "focus_event.h" #include "core/dom/events/event_target.h" #include "core/frame/window.h" +#include "qjs_focus_event.h" namespace webf { @@ -32,23 +33,31 @@ FocusEvent* FocusEvent::Create(ExecutingContext* context, FocusEvent::FocusEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) : UIEvent(context, type, exception_state) {} -FocusEvent::FocusEvent(ExecutingContext* context, - const AtomicString& type, +FocusEvent::FocusEvent(ExecutingContext *context, + const AtomicString &type, double detail, - Window* view, + Window *view, double which, - EventTarget* relatedTarget, - ExceptionState& exception_state) + 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& initializer, - ExceptionState& exception_state) +FocusEvent::FocusEvent(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) : UIEvent(context, type, initializer, exception_state), related_target_(initializer->relatedTarget()) {} -EventTarget* FocusEvent::relatedTarget() const { +FocusEvent::FocusEvent(ExecutingContext *context, const AtomicString &type, NativeFocusEvent *native_focus_event) + : UIEvent(context, type, &native_focus_event->native_event), + related_target_(DynamicTo(BindingObject::From(native_focus_event->relatedTarget))) {} + +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.h b/bridge/core/events/focus_event.h index e5d210b2f0..2ce131f18c 100644 --- a/bridge/core/events/focus_event.h +++ b/bridge/core/events/focus_event.h @@ -14,6 +14,8 @@ namespace webf { +struct NativeFocusEvent; + class FocusEvent : public UIEvent { DEFINE_WRAPPERTYPEINFO(); @@ -49,8 +51,14 @@ class FocusEvent : public UIEvent { const std::shared_ptr& initializer, ExceptionState& exception_state); + explicit FocusEvent(ExecutingContext* context, + const AtomicString& type, + NativeFocusEvent* native_focus_event); + EventTarget* relatedTarget() const; + bool IsFocusEvent() const override; + private: Member related_target_; }; diff --git a/bridge/core/events/gesture_event.cc b/bridge/core/events/gesture_event.cc index 7f6ee00ec0..ec6062db83 100644 --- a/bridge/core/events/gesture_event.cc +++ b/bridge/core/events/gesture_event.cc @@ -4,6 +4,7 @@ */ #include "gesture_event.h" +#include "qjs_gesture_event.h" namespace webf { @@ -23,10 +24,10 @@ GestureEvent* GestureEvent::Create(ExecutingContext* context, GestureEvent::GestureEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) : Event(context, type) {} -GestureEvent::GestureEvent(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr& initializer, - ExceptionState& exception_state) +GestureEvent::GestureEvent(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) : Event(context, type), state_(initializer->state()), direction_(initializer->direction()), @@ -35,11 +36,29 @@ GestureEvent::GestureEvent(ExecutingContext* context, scale_(initializer->scale()), rotation_(initializer->rotation()) {} -const AtomicString& GestureEvent::state() const { +GestureEvent::GestureEvent(ExecutingContext *context, + const AtomicString &type, + NativeGestureEvent *native_gesture_event) : + Event(context, type, &native_gesture_event->native_event), + state_(AtomicString(ctx(), native_gesture_event->state)), + direction_(AtomicString(ctx(), native_gesture_event->direction)), + 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 { +const AtomicString &GestureEvent::direction() const { return direction_; } diff --git a/bridge/core/events/gesture_event.h b/bridge/core/events/gesture_event.h index 1f0e393cf6..6c6a6be69c 100644 --- a/bridge/core/events/gesture_event.h +++ b/bridge/core/events/gesture_event.h @@ -13,6 +13,8 @@ namespace webf { +struct NativeGestureEvent; + class GestureEvent : public Event { DEFINE_WRAPPERTYPEINFO(); @@ -33,6 +35,8 @@ class GestureEvent : public Event { const std::shared_ptr& 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; @@ -42,6 +46,8 @@ class GestureEvent : public Event { double scale() const; double rotation() const; + bool IsGestureEvent() const override; + private: AtomicString state_; AtomicString direction_; diff --git a/bridge/core/events/input_event.cc b/bridge/core/events/input_event.cc index 213bf921d0..43a215a9ce 100644 --- a/bridge/core/events/input_event.cc +++ b/bridge/core/events/input_event.cc @@ -4,6 +4,7 @@ */ #include "input_event.h" +#include "qjs_input_event.h" namespace webf { @@ -21,20 +22,31 @@ InputEvent* InputEvent::Create(ExecutingContext* context, 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& initializer, - ExceptionState& exception_state) +InputEvent::InputEvent(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) : UIEvent(context, type, initializer, exception_state), input_type_(initializer->inputType()), data_(initializer->data()) {} -const AtomicString& InputEvent::inputType() const { +InputEvent::InputEvent(ExecutingContext *context, + const AtomicString &type, + NativeInputEvent *native_input_event) : + UIEvent(context, type, &native_input_event->native_event), + input_type_(AtomicString(ctx(), native_input_event->inputType)), + data_(AtomicString(ctx(), native_input_event->data)) {} + +const AtomicString &InputEvent::inputType() const { return input_type_; } -const AtomicString& InputEvent::data() const { +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.h b/bridge/core/events/input_event.h index c5813ff706..a995225b60 100644 --- a/bridge/core/events/input_event.h +++ b/bridge/core/events/input_event.h @@ -13,6 +13,8 @@ namespace webf { +struct NativeInputEvent; + class InputEvent : public UIEvent { DEFINE_WRAPPERTYPEINFO(); @@ -33,9 +35,13 @@ class InputEvent : public UIEvent { const std::shared_ptr& 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_; diff --git a/bridge/core/events/intersection_change_event.cc b/bridge/core/events/intersection_change_event.cc index 89663718e8..be365f87b0 100644 --- a/bridge/core/events/intersection_change_event.cc +++ b/bridge/core/events/intersection_change_event.cc @@ -4,36 +4,47 @@ */ #include "intersection_change_event.h" +#include "qjs_intersection_change_event.h" namespace webf { -IntersectionChangeEvent* IntersectionChangeEvent::Create(ExecutingContext* context, - const AtomicString& type, - ExceptionState& exception_state) { +IntersectionChangeEvent *IntersectionChangeEvent::Create(ExecutingContext *context, + const AtomicString &type, + ExceptionState &exception_state) { return MakeGarbageCollected(context, type, exception_state); } -IntersectionChangeEvent* IntersectionChangeEvent::Create( - ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr& initializer, - ExceptionState& exception_state) { +IntersectionChangeEvent *IntersectionChangeEvent::Create( + ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) { return MakeGarbageCollected(context, type, initializer, exception_state); } -IntersectionChangeEvent::IntersectionChangeEvent(ExecutingContext* context, - const AtomicString& type, - ExceptionState& 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& initializer, - ExceptionState& exception_state) +IntersectionChangeEvent::IntersectionChangeEvent(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) : Event(context, type), intersection_ratio_(initializer->intersectionRatio()) {} +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.h b/bridge/core/events/intersection_change_event.h index 98adb0cae3..23f4c570a6 100644 --- a/bridge/core/events/intersection_change_event.h +++ b/bridge/core/events/intersection_change_event.h @@ -13,6 +13,8 @@ namespace webf { +struct NativeIntersectionChangeEvent; + class IntersectionChangeEvent : public Event { DEFINE_WRAPPERTYPEINFO(); @@ -37,8 +39,12 @@ class IntersectionChangeEvent : public Event { 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_; }; diff --git a/bridge/core/events/keyboard_event.cc b/bridge/core/events/keyboard_event.cc index d78053ba89..ae79307143 100644 --- a/bridge/core/events/keyboard_event.cc +++ b/bridge/core/events/keyboard_event.cc @@ -4,6 +4,7 @@ */ #include "keyboard_event.h" +#include "qjs_keyboard_event.h" namespace webf { @@ -45,7 +46,23 @@ KeyboardEvent::KeyboardEvent(ExecutingContext* context, repeat_(initializer->repeat()), shift_key_(initializer->shiftKey()) {} -bool KeyboardEvent::getModifierState(const AtomicString& key_args, ExceptionState& exception_state) { +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), + code_(AtomicString(ctx(), native_keyboard_event->code)), + ctrl_key_(native_keyboard_event->ctrlKey), + is_composing_(native_keyboard_event->isComposing), + key_(AtomicString(ctx(), native_keyboard_event->key)), + 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; } diff --git a/bridge/core/events/keyboard_event.h b/bridge/core/events/keyboard_event.h index d490d04eef..2cafeea526 100644 --- a/bridge/core/events/keyboard_event.h +++ b/bridge/core/events/keyboard_event.h @@ -13,6 +13,8 @@ namespace webf { +struct NativeKeyboardEvent; + class KeyboardEvent : public UIEvent { DEFINE_WRAPPERTYPEINFO(); @@ -44,6 +46,10 @@ class KeyboardEvent : public UIEvent { const std::shared_ptr& 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; @@ -74,11 +80,6 @@ class KeyboardEvent : public UIEvent { bool shift_key_; }; -template <> -struct DowncastTraits { - static bool AllowFrom(const Event& event) { return event.IsKeyboardEvent(); } -}; - } // namespace webf #endif // BRIDGE_CORE_EVENTS_KEYBOARD_EVENT_H_ diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index 26ef891941..c4d6016747 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -4,33 +4,51 @@ */ #include "message_event.h" +#include "core/dom/events/event.h" namespace webf { -MessageEvent* MessageEvent::Create(ExecutingContext* context, - const AtomicString& type, - ExceptionState& exception_state) { +struct NativeMessageEvent { + NativeEvent native_event; + const char *data; + NativeString *origin; + NativeString *lastEventId; + NativeString *source; +}; + +MessageEvent *MessageEvent::Create(ExecutingContext *context, + const AtomicString &type, + ExceptionState &exception_state) { return MakeGarbageCollected(context, type); } -MessageEvent* MessageEvent::Create(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr& init, +MessageEvent *MessageEvent::Create(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &init, ExceptionState& exception_state) { return MakeGarbageCollected(context, type, init); } -MessageEvent::MessageEvent(ExecutingContext* context, const AtomicString& type) : Event(context, type) {} +MessageEvent::MessageEvent(ExecutingContext *context, const AtomicString &type) : Event(context, type) {} -MessageEvent::MessageEvent(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr& init) +MessageEvent::MessageEvent(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &init) : Event(context, type), data_(init->data()), origin_(init->origin()), lastEventId_(init->lastEventId()), source_(init->source()) {} +MessageEvent::MessageEvent(ExecutingContext *context, + const AtomicString &type, + NativeMessageEvent *native_message_event) : + Event(context, type, &native_message_event->native_event), + data_(ScriptValue::CreateJsonObject(ctx(), native_message_event->data, strlen(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)) {} + ScriptValue MessageEvent::data() const { return data_; } @@ -47,4 +65,8 @@ AtomicString MessageEvent::source() const { return source_; } +bool MessageEvent::IsMessageEvent() const { + return true; +} + } // namespace webf diff --git a/bridge/core/events/message_event.h b/bridge/core/events/message_event.h index 0b40cd6b7b..090830168c 100644 --- a/bridge/core/events/message_event.h +++ b/bridge/core/events/message_event.h @@ -11,6 +11,8 @@ namespace webf { +struct NativeMessageEvent; + class MessageEvent : public Event { DEFINE_WRAPPERTYPEINFO(); @@ -27,12 +29,15 @@ class MessageEvent : public Event { explicit MessageEvent(ExecutingContext* context, const AtomicString& type, const std::shared_ptr& 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_; diff --git a/bridge/core/events/mouse_event.cc b/bridge/core/events/mouse_event.cc new file mode 100644 index 0000000000..eb3a775d4d --- /dev/null +++ b/bridge/core/events/mouse_event.cc @@ -0,0 +1,134 @@ +/* + * 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(context, type, exception_state); +} + +MouseEvent *MouseEvent::Create(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) { + return MakeGarbageCollected(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 &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); +} + +} \ No newline at end of file diff --git a/bridge/core/events/mouse_event.d.ts b/bridge/core/events/mouse_event.d.ts index d57d425356..6510bb5b10 100644 --- a/bridge/core/events/mouse_event.d.ts +++ b/bridge/core/events/mouse_event.d.ts @@ -1,6 +1,5 @@ import {UIEvent} from "./ui_event"; import {EventTarget} from "../dom/events/event_target"; -import {Window} from "../frame/window"; 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. */ @@ -24,7 +23,5 @@ interface MouseEvent extends UIEvent { readonly shiftKey: boolean; readonly x: number; readonly y: number; - getModifierState(keyArg: string): boolean; - initMouseEvent(typeArg: string, canBubbleArg: boolean, cancelableArg: boolean, viewArg: Window, detailArg: number, screenXArg: number, screenYArg: number, clientXArg: number, clientYArg: number, ctrlKeyArg: boolean, altKeyArg: boolean, shiftKeyArg: boolean, metaKeyArg: boolean, buttonArg: number, relatedTargetArg: EventTarget | null): void; 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..39237b4bf5 --- /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 "ui_event.h" +#include "qjs_mouse_event_init.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 &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 &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 related_target_; +}; + +} + +#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 index d1207ed0bb..ea68fb5251 100644 --- a/bridge/core/events/mouse_event_init.d.ts +++ b/bridge/core/events/mouse_event_init.d.ts @@ -1,9 +1,9 @@ -import { EventInit } from "../dom/events/event_init"; import {EventTarget} from "../dom/events/event_target"; +import {UIEventInit} from "./ui_event_init"; // @ts-ignore @Dictionary() -export interface MouseEventInit extends EventInit { +export interface MouseEventInit extends UIEventInit { altKey?: boolean; button?: number; buttons?: number; diff --git a/bridge/core/events/pointer_event.cc b/bridge/core/events/pointer_event.cc new file mode 100644 index 0000000000..ea6d500e9f --- /dev/null +++ b/bridge/core/events/pointer_event.cc @@ -0,0 +1,95 @@ +/* + * 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(context, type, exception_state); +} + +PointerEvent *PointerEvent::Create(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) { + return MakeGarbageCollected(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 &initializer, + ExceptionState &exception_state) : + MouseEvent(context, type, initializer, exception_state), + height_(initializer->height()), + is_primary(initializer->isPrimary()), + pointer_id_(initializer->pointerId()), + pointer_type_(initializer->pointerType()), + pressure_(initializer->pressure()), + tangential_pressure_(initializer->tangentialPressure()), + tilt_x_(initializer->tiltX()), + tilt_y_(initializer->tiltY()), + twist_(initializer->twist()), + width_(initializer->width()) { +} + +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), + pointer_type_(AtomicString(ctx(), native_pointer_event->pointerType)), + 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; +} + +} \ 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..98f70a0291 --- /dev/null +++ b/bridge/core/events/pointer_event.h @@ -0,0 +1,66 @@ +/* + * 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 &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 &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_; +}; + +} + +#endif //WEBF_CORE_EVENTS_TOUCH_EVENT_H_ diff --git a/bridge/core/events/promise_rejection_event.h b/bridge/core/events/promise_rejection_event.h index 3aeb6b3b73..f32f1546ce 100644 --- a/bridge/core/events/promise_rejection_event.h +++ b/bridge/core/events/promise_rejection_event.h @@ -41,11 +41,6 @@ class PromiseRejectionEvent : public Event { ScriptValue reason_; }; -template <> -struct DowncastTraits { - static bool AllowFrom(const Event& event) { return event.IsErrorEvent(); } -}; - } // namespace webf #endif // BRIDGE_CORE_EVENTS_PROMISE_REJECTION_EVENT_H_ diff --git a/bridge/core/events/touch_event.cc b/bridge/core/events/touch_event.cc new file mode 100644 index 0000000000..c8784a44a4 --- /dev/null +++ b/bridge/core/events/touch_event.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 "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(context, type, exception_state); +} + +TouchEvent *TouchEvent::Create(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) { + return MakeGarbageCollected(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 &initializer, + ExceptionState &exception_state) : UIEvent(context, type, initializer, exception_state) {} + +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), + changed_touches_(DynamicTo(BindingObject::From(native_touch_event->changedTouches))), + target_touches_(DynamicTo(BindingObject::From(native_touch_event->targetTouches))), + touches_(DynamicTo(BindingObject::From(native_touch_event->touches))) {} + +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; +} + +} diff --git a/bridge/core/events/touch_event.h b/bridge/core/events/touch_event.h new file mode 100644 index 0000000000..59a742774e --- /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 "ui_event.h" +#include "core/input/touch_list.h" +#include "qjs_touch_event_init.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 &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 &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 changed_touches_; + Member target_touches_; + Member touches_; +}; + +} + +#endif //WEBF_CORE_EVENTS_TOUCH_EVENT_H_ diff --git a/bridge/core/events/transition_event.cc b/bridge/core/events/transition_event.cc new file mode 100644 index 0000000000..b8641dfc6a --- /dev/null +++ b/bridge/core/events/transition_event.cc @@ -0,0 +1,61 @@ +/* + * 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(context, type, exception_state); +} + +TransitionEvent *TransitionEvent::Create(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) { + return MakeGarbageCollected(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 &initializer, + ExceptionState &exception_state) : + Event(context, type, initializer), + elapsed_time_(initializer->elapsedTime()), + property_name_(initializer->propertyName()), + pseudo_element_(initializer->pseudoElement()) { +} + +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), + property_name_(AtomicString(ctx(), native_transition_event->propertyName)), + pseudo_element_(AtomicString(ctx(), native_transition_event->pseudoElement)) { +} + +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; +} + +} \ 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..86b86505a7 --- /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 &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 &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_; +}; + +} + +#endif //WEBF_CORE_EVENTS_TRANSITION_EVENT_H_ diff --git a/bridge/core/events/ui_event.cc b/bridge/core/events/ui_event.cc index 3144479364..40214aab3e 100644 --- a/bridge/core/events/ui_event.cc +++ b/bridge/core/events/ui_event.cc @@ -4,19 +4,21 @@ #include "ui_event.h" #include "core/frame/window.h" +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "qjs_ui_event.h" namespace webf { -UIEvent* UIEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { +UIEvent *UIEvent::Create(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state) { return MakeGarbageCollected(context, type, exception_state); } -UIEvent* UIEvent::Create(ExecutingContext* context, - const AtomicString& type, +UIEvent *UIEvent::Create(ExecutingContext *context, + const AtomicString &type, double detail, - Window* view, + Window *view, double which, - ExceptionState& exception_state) { + ExceptionState &exception_state) { return MakeGarbageCollected(context, type, detail, view, which, exception_state); } @@ -31,25 +33,32 @@ UIEvent* UIEvent::Create(ExecutingContext* context, UIEvent::UIEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) : Event(context, type) {} -UIEvent::UIEvent(ExecutingContext* context, - const AtomicString& type, +UIEvent::UIEvent(ExecutingContext *context, + const AtomicString &type, double detail, - Window* view, + Window *view, double which, - ExceptionState& exception_state) + ExceptionState &exception_state) : Event(context, type), detail_(detail), view_(view), which_(which) {} -UIEvent::UIEvent(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr& initializer, - ExceptionState& exception_state) +UIEvent::UIEvent(ExecutingContext *context, + const AtomicString &type, + const std::shared_ptr &initializer, + ExceptionState &exception_state) : Event(context, type), detail_(initializer->detail()), view_(initializer->view()), which_(initializer->which()) {} +UIEvent::UIEvent(ExecutingContext *context, const AtomicString &type, NativeUIEvent *native_ui_event) : + Event(context, type, &native_ui_event->native_event), + detail_(native_ui_event->detail), + view_(DynamicTo(BindingObject::From(native_ui_event->view))), + which_(native_ui_event->which) { +} + double UIEvent::detail() const { return detail_; } -Window* UIEvent::view() const { +Window *UIEvent::view() const { return view_; } @@ -57,7 +66,13 @@ double UIEvent::which() const { return which_; } -bool UIEvent::IsUIEvent() const { +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.h b/bridge/core/events/ui_event.h index 4dd38180ac..e6e67f64a9 100644 --- a/bridge/core/events/ui_event.h +++ b/bridge/core/events/ui_event.h @@ -14,6 +14,8 @@ namespace webf { +struct NativeUIEvent; + class UIEvent : public Event { DEFINE_WRAPPERTYPEINFO(); @@ -47,11 +49,15 @@ class UIEvent : public Event { const std::shared_ptr& 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; + bool IsUiEvent() const override; + + void Trace(GCVisitor* visitor) const override; private: double detail_; @@ -61,7 +67,7 @@ class UIEvent : public Event { template <> struct DowncastTraits { - static bool AllowFrom(const Event& event) { return event.IsUIEvent(); } + static bool AllowFrom(const Event& event) { return event.IsUiEvent(); } }; } // namespace webf diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index b2201898df..2d516ac6a0 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -320,7 +320,7 @@ void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* c void ExecutingContext::DispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValue promise, JSValue error) { // Trigger rejectionhandled event. - DispatchPromiseRejectionEvent(event_type_names::krejectionhandled, context, promise, error); + DispatchPromiseRejectionEvent(event_type_names::krejectionhandled, context, promise, error); } std::unordered_map ExecutingContext::pluginByteCode{}; diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index 10fc5f6b47..0da435c3b6 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -135,6 +135,10 @@ void Window::cancelAnimationFrame(double request_id, ExceptionState& exception_s GetExecutingContext()->document()->CancelAnimationFrame(static_cast(request_id), exception_state); } +bool Window::IsWindowOrWorkerGlobalScope() const { + return true; +} + void Window::Trace(GCVisitor* visitor) const { visitor->Trace(screen_); EventTargetWithInlineData::Trace(visitor); diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index 22112ca865..6fcdf85559 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -45,6 +45,8 @@ class Window : public EventTargetWithInlineData { double requestAnimationFrame(const std::shared_ptr& 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. @@ -54,6 +56,17 @@ class Window : public EventTargetWithInlineData { Member screen_; }; +template <> +struct DowncastTraits { + static bool AllowFrom(const EventTarget& event_target) { + return event_target.IsWindowOrWorkerGlobalScope(); + } + static bool AllowFrom(const BindingObject& binding_object) { + return binding_object.IsEventTarget() && DynamicTo(binding_object)->IsWindowOrWorkerGlobalScope(); + } +}; + + } // namespace webf #endif // BRIDGE_WINDOW_H diff --git a/bridge/core/html/canvas/canvas_rendering_context_2d.cc b/bridge/core/html/canvas/canvas_rendering_context_2d.cc index acb64474dd..71dfd9e7b0 100644 --- a/bridge/core/html/canvas/canvas_rendering_context_2d.cc +++ b/bridge/core/html/canvas/canvas_rendering_context_2d.cc @@ -16,7 +16,7 @@ CanvasRenderingContext2D::CanvasRenderingContext2D(ExecutingContext* context, NativeValue CanvasRenderingContext2D::HandleCallFromDartSide(NativeString* method, int32_t argc, - const NativeValue* argv) const { + const NativeValue* argv) { return Native_NewNull(); } diff --git a/bridge/core/html/canvas/canvas_rendering_context_2d.h b/bridge/core/html/canvas/canvas_rendering_context_2d.h index 7104912c7e..4c23be5b64 100644 --- a/bridge/core/html/canvas/canvas_rendering_context_2d.h +++ b/bridge/core/html/canvas/canvas_rendering_context_2d.h @@ -18,7 +18,7 @@ class CanvasRenderingContext2D : public CanvasRenderingContext, public BindingOb CanvasRenderingContext2D() = delete; explicit CanvasRenderingContext2D(ExecutingContext* context, NativeBindingObject* native_binding_object); - NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) const override; + NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) override; bool IsCanvas2d() const override; }; diff --git a/bridge/core/input/touch.cc b/bridge/core/input/touch.cc index 9dce8361eb..dc5832c9f2 100644 --- a/bridge/core/input/touch.cc +++ b/bridge/core/input/touch.cc @@ -3,6 +3,7 @@ */ #include "touch.h" +#include "bindings/qjs/cppgc/gc_visitor.h" namespace webf { @@ -89,4 +90,8 @@ 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.h b/bridge/core/input/touch.h index 6b110a2f7e..da4636739c 100644 --- a/bridge/core/input/touch.h +++ b/bridge/core/input/touch.h @@ -42,6 +42,8 @@ class Touch : public ScriptWrappable { double screenY() const; EventTarget* target() const; + void Trace(GCVisitor *visitor) const override; + private: double altitude_angle_; double azimuth_angle_; diff --git a/bridge/core/input/touch_list.h b/bridge/core/input/touch_list.h index 03f474d1ba..bae4bb0c08 100644 --- a/bridge/core/input/touch_list.h +++ b/bridge/core/input/touch_list.h @@ -9,7 +9,7 @@ namespace webf { -class TouchList : public ScriptWrappable { +class TouchList : public ScriptWrappable, public BindingObject { DEFINE_WRAPPERTYPEINFO(); public: @@ -27,6 +27,13 @@ class TouchList : public ScriptWrappable { std::vector values_; }; +template <> +struct DowncastTraits { + static bool AllowFrom(const BindingObject& binding_object) { + return binding_object.IsTouchList(); + } +}; + } // namespace webf #endif // BRIDGE_CORE_INPUT_TOUCH_LIST_H_ diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 385720a484..3701628fd3 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -12,6 +12,7 @@ #include "core/html/html_html_element.h" #include "core/html/parser/html_parser.h" #include "foundation/logging.h" +#include "event_factory.h" #include "page.h" #include "polyfill.h" @@ -59,8 +60,8 @@ void WebFPage::invokeModuleEvent(const NativeString* moduleName, Event* event = nullptr; if (ptr != nullptr) { std::string type = std::string(eventType); - auto* rawEvent = static_cast(ptr)->bytes; - event = Event::From(context_, reinterpret_cast(rawEvent)); + auto* rawEvent = static_cast(ptr); + event = EventFactory::Create(context_, AtomicString(ctx, type), rawEvent); } ScriptValue extraObject = ScriptValue::Empty(ctx); diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 1ec2448197..0e6bc60e23 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -26,7 +26,7 @@ enum NativeTag { TAG_ASYNC_FUNCTION = 8, }; -enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, BindingObject = 4 }; +enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, Others = 2 }; class ExecutingContext; class ScriptValue; diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 7ccb763847..9a762f35d4 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -77,14 +77,14 @@ struct NativeValueConverter : public NativeValueConverterBase -struct NativeValueConverter> - : public NativeValueConverterBase> { - static NativeValue ToNativeValue(ImplType value) { return Native_NewPtr(JSPointerType::BindingObject, value); } +template +struct NativeValueConverter> + : public NativeValueConverterBase> { + static NativeValue ToNativeValue(T* value) { return Native_NewPtr(JSPointerType::Others, value); } static NativeValue ToNativeValue(BindingObject* value) { - return Native_NewPtr(JSPointerType::BindingObject, value->bindingObject()); + return Native_NewPtr(JSPointerType::Others, value->bindingObject()); } - static ImplType FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } + static T* FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } }; std::shared_ptr CreateSyncCallback(JSContext* ctx, int function_id); diff --git a/bridge/scripts/code_generator/src/idl/generateHeader.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts index a724b37e43..80e9e273a2 100644 --- a/bridge/scripts/code_generator/src/idl/generateHeader.ts +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -4,7 +4,7 @@ import {IDLBlob} from "./IDLBlob"; import {getClassName} from "./utils"; import fs from 'fs'; import path from 'path'; -import {generateTypeValue} from "./generateSource"; +import {generateCoreTypeValue, generateRawTypeValue} from "./generateSource"; import {GenerateOptions} from "./generator"; export enum TemplateKind { @@ -46,11 +46,14 @@ export function generateCppHeader(blob: IDLBlob, options: GenerateOptions) { 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 }); } @@ -65,7 +68,7 @@ export function generateCppHeader(blob: IDLBlob, options: GenerateOptions) { blob: blob, object: object, props, - generateTypeValue: generateTypeValue + generateCoreTypeValue }); } return ''; diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index b4b78c50d6..193113aaab 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -44,7 +44,7 @@ export function isTypeNeedAllocate(type: ParameterType[]) { } } -export function generateTypeValue(type: ParameterType[]): string { +export function generateCoreTypeValue(type: ParameterType[]): string { switch (type[0]) { case FunctionArgumentType.int64: { return 'int64_t'; @@ -76,6 +76,38 @@ export function generateTypeValue(type: ParameterType[]): string { return ''; } +export function generateRawTypeValue(type: ParameterType[], is32Bit: boolean = false): string { + if (is32Bit) { + return 'int64_t'; + } + + 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: { + return 'NativeString*'; + } + default: + return 'NativeBindingObject*'; + } + + if (typeof type[0] == 'string') { + return 'NativeBindingObject*'; + } + + return ''; +} + export function generateIDLTypeConverter(type: ParameterType[], isOptional?: boolean): string { let haveNull = type.some(t => t === FunctionArgumentType.null); let returnValue = ''; @@ -468,7 +500,8 @@ const WrapperTypeInfo& ${getClassName(blob)}::wrapper_type_info_ = QJS${getClass object: object, mixinObjects, generateFunctionBody, - generateTypeValue, + generateCoreTypeValue, + generateRawTypeValue, generateOverLoadSwitchBody, isTypeNeedAllocate, overloadMethods, diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl index 4238566054..4aad0ab72b 100644 --- a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl @@ -18,6 +18,7 @@ #include "core/dom/document.h" #include "core/dom/document_fragment.h" #include "core/dom/comment.h" +#include "core/input/touch_list.h" namespace webf { diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl index 1759a3c621..d3e649099d 100644 --- a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl @@ -17,14 +17,14 @@ class <%= className %> : public <%= object.parent ? object.parent : 'DictionaryB explicit <%= className %>(JSContext* ctx, JSValue value, ExceptionState& exception_state); <% _.forEach(props, (function(prop, index) { %> - <%= generateTypeValue(prop.type) %> <%= prop.name %>() const { return <%= prop.name %>_; } - void set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(<%= generateTypeValue(prop.type) %> value) { <%= prop.name %>_ = value; } + <%= generateCoreTypeValue(prop.type) %> <%= prop.name %>() const { return <%= prop.name %>_; } + void set<%= prop.name[0].toUpperCase() + prop.name.slice(1) %>(<%= generateCoreTypeValue(prop.type) %> value) { <%= prop.name %>_ = value; } <% })); %> 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) { %> - <%= generateTypeValue(prop.type) %> <%= prop.name %>_; + <%= generateCoreTypeValue(prop.type) %> <%= prop.name %>_; <% })); %> }; diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl index 2b0f87a296..b408891882 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl @@ -40,7 +40,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob } ExceptionState exception_state; MemberMutationScope scope{ExecutingContext::From(ctx)}; - <%= generateTypeValue(object.indexedProp.type) %> result = self->item(index, exception_state); + <%= generateCoreTypeValue(object.indexedProp.type) %> result = self->item(index, exception_state); if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } @@ -52,7 +52,7 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob auto* self = toScriptWrappable<<%= className %>>(obj); ExceptionState exception_state; MemberMutationScope scope{ExecutingContext::From(ctx)}; - ${generateTypeValue(object.indexedProp.type)} result = self->item(AtomicString(ctx, key), exception_state); + ${generateCoreTypeValue(object.indexedProp.type)} result = self->item(AtomicString(ctx, key), exception_state); if (UNLIKELY(exception_state.HasException())) { return exception_state.ToQuickJS(); } diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl index 28f8675b6b..8106a8363e 100644 --- a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl +++ b/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl @@ -1,9 +1,38 @@ #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 +<% } %> + class QJS<%= className %> : public QJSInterfaceBridge, <%= className%>> { public: static void Install(ExecutingContext* context); diff --git a/bridge/scripts/code_generator/static/json_templates/event_factory.cc.tpl b/bridge/scripts/code_generator/static/json_templates/event_factory.cc.tpl new file mode 100644 index 0000000000..6d96432ed0 --- /dev/null +++ b/bridge/scripts/code_generator/static/json_templates/event_factory.cc.tpl @@ -0,0 +1,90 @@ +/* +* 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 +#include "event_type_names.h" +#include "bindings/qjs/cppgc/garbage_collected.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; + +static 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) { + return MakeGarbageCollected<<%= _.upperFirst(_.camelCase(item)) %>Event>(context, type, toNativeEventEvent>(raw_event)); + } + <% } else if (_.isObject(item)) { %> + static Event* <%= item.class %>Constructor(ExecutingContext* context, const AtomicString& type, RawEvent* raw_event) { + return MakeGarbageCollected<<%= item.class %>>(context, type, toNativeEvent>(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(); + auto it = g_event_constructors->find(type); + if (it == g_event_constructors->end()) + return MakeGarbageCollected(context, type, toNativeEvent(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/static/json_templates/event_factory.h.tpl b/bridge/scripts/code_generator/static/json_templates/event_factory.h.tpl new file mode 100644 index 0000000000..b8d3d7bf8c --- /dev/null +++ b/bridge/scripts/code_generator/static/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/static/json_templates/event_type_helper.h.tpl b/bridge/scripts/code_generator/static/json_templates/event_type_helper.h.tpl new file mode 100644 index 0000000000..7f82137a64 --- /dev/null +++ b/bridge/scripts/code_generator/static/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 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/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index a8b9209a57..1a1c13e5d3 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -95,12 +95,6 @@ void _invokeBindingMethodFromNativeImpl(Pointer nativeBindi } } -List prepareDispatchEventArguments(Event event) { - Pointer rawEvent = event.toRaw().cast(); - bool isCustomEvent = event is CustomEvent; - return [event.type, rawEvent, isCustomEvent ? 1 : 0]; -} - // Dispatch the event to the binding side. void _dispatchEventToNative(Event event) { Pointer? pointer = event.currentTarget?.pointer; @@ -113,13 +107,19 @@ void _dispatchEventToNative(Event event) { // Call methods implements at C++ side. DartInvokeBindingMethodsFromDart f = pointer.ref.invokeBindingMethodFromDart.asFunction(); - List dispatchEventArguments = prepareDispatchEventArguments(event); + Pointer rawEvent = event.toRaw().cast(); + bool isCustomEvent = event is CustomEvent; + List dispatchEventArguments = [event.type, rawEvent, isCustomEvent ? 1 : 0]; Pointer method = stringToNativeString('dispatchEvent'); Pointer allocatedNativeArguments = makeNativeValueArguments(dispatchEventArguments); f(pointer, nullptr, method, dispatchEventArguments.length, allocatedNativeArguments); + // Native can mutate rawEvent directly, so we sync properties value from native side with rawEvent. + event.syncFromRaw(rawEvent.cast()); + // Free the allocated arguments. + malloc.free(rawEvent); malloc.free(method); malloc.free(allocatedNativeArguments); } diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index fee3d61a82..2d98a657b2 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -33,9 +33,7 @@ enum JSValueType { enum JSPointerType { AsyncFunctionContext, NativeFunctionContext, - NativeBoundingClientRect, - NativeCanvasRenderingContext2D, - BindingObject + Others } typedef AnonymousNativeFunction = dynamic Function(List args); diff --git a/webf/lib/src/dom/event.dart b/webf/lib/src/dom/event.dart index 45839178bb..f22c8ec5db 100644 --- a/webf/lib/src/dom/event.dart +++ b/webf/lib/src/dom/event.dart @@ -183,6 +183,14 @@ class Event { bubbles = false; } + // Sync event properties from Raw pointer data. + void syncFromRaw(Pointer raw) { + assert(raw.ref.length >= 7); + bubbles = raw.ref.bytes[1] == 1; + cancelable = raw.ref.bytes[2] == 1; + defaultPrevented = raw.ref.bytes[4] == 1; + } + Pointer toRaw([int extraLength = 0]) { Pointer event = malloc.allocate(sizeOf()); @@ -373,7 +381,9 @@ class CustomEvent extends Event { @override Pointer toRaw([int methodLength = 0]) { - List methods = [stringToNativeString(detail).address]; + List methods = [ + detail.toNativeUtf8().address + ]; Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); @@ -468,7 +478,7 @@ class MessageEvent extends Event { @override Pointer toRaw([int methodLength = 0]) { - List methods = [stringToNativeString(jsonEncode(data)).address, stringToNativeString(origin).address]; + List methods = [(jsonEncode(data)).toNativeUtf8().address, stringToNativeString(origin).address]; Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); From 5431098428ab88aafe9d4e2cfc3b90116dc8bad7 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Thu, 8 Sep 2022 12:11:21 +0800 Subject: [PATCH 281/498] feat: animation & calc specs --- .../calc-background-position-1-ref.html.1.png | Bin 0 -> 2428 bytes .../calc-background-position-1.html.1.png | Bin 0 -> 2428 bytes webf/lib/src/css/background.dart | 4 ++ webf/lib/src/css/render_style.dart | 4 +- webf/lib/src/css/values/calc.dart | 32 ++++++++------- webf/lib/src/css/values/length.dart | 19 ++++++++- webf/lib/src/css/values/position.dart | 2 +- webf/lib/src/dom/element.dart | 6 ++- .../src/rendering/box_decoration_painter.dart | 38 ++++++++++++++---- 9 files changed, 77 insertions(+), 28 deletions(-) create mode 100644 integration_tests/snapshots/css/css-values/calc-background-position-1-ref.html.1.png create mode 100644 integration_tests/snapshots/css/css-values/calc-background-position-1.html.1.png diff --git a/integration_tests/snapshots/css/css-values/calc-background-position-1-ref.html.1.png b/integration_tests/snapshots/css/css-values/calc-background-position-1-ref.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..c00eca85654d6cf787cf80bbda6d1a059be26264 GIT binary patch literal 2428 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_n+wPZ!6KiaBrZ9?UvyAj06df6@Q{sb4QO3-h$ftZF@XhsAG=!=^nA z8=vo5SIgM{?c3)EbJky$W_U2uf!Ss4??d-3=bf2j`#-*3_xTt0HhHy-28~9x4k7ZX z6<>cV{wbFWFvl!6!4sB5)`#>>Ak9*`C`ahL*hJ|~>IQWg8I|6WS{HzSWC zQ(;l!eNnYDa?)%$#uBQ#6_>}e?ur{1XUQ|{F?0lb;L5G)`S+L^_VJUX< z4B-HR8jh3>1_n+wPZ!6KiaBrZ9?UvyAj06df6@Q{sb4QO3-h$ftZF@XhsAG=!=^nA z8=vo5SIgM{?c3)EbJky$W_U2uf!Ss4??d-3=bf2j`#-*3_xTt0HhHy-28~9x4k7ZX z6<>cV{wbFWFvl!6!4sB5)`#>>Ak9*`C`ahL*hJ|~>IQWg8I|6WS{HzSWC zQ(;l!eNnYDa?)%$#uBQ#6_>}e?ur{1XUQ|{F?0lb;L5G)`S+L^_ fns = CSSFunction.parseFunction(propertyValue); if (fns.first.args.isNotEmpty) { assert(fns.first.args.length == 1, 'Calc parameters count must be = 1'); final expression = fns.first.args.first; - final _CSSCalcParser parser = _CSSCalcParser(renderStyle, expression); + final _CSSCalcParser parser = _CSSCalcParser(propertyName, renderStyle, expression); CalcExpressionNode? node = parser.processCalcExpression(); return CSSCalcValue(renderStyle, node); } @@ -100,12 +101,13 @@ class CalcOperationExpressionNode extends CalcExpressionNode { } class _CSSCalcParser { + final String propertyName; final RenderStyle _renderStyle; final Tokenizer tokenizer; late Token _peekToken; - _CSSCalcParser(this._renderStyle, String text, {int start = 0}) + _CSSCalcParser(this.propertyName, this._renderStyle, String text, {int start = 0}) : tokenizer = Tokenizer(SourceFile.fromString(text), text, true, start) { _peekToken = tokenizer.next(); } @@ -151,15 +153,16 @@ class _CSSCalcParser { } nodes.add(firstNode); while (_peek() != TokenKind.END_OF_FILE) { - int operator = _peekToken.kind; - if (!_maybeEat(TokenKind.PLUS) && !_maybeEat(TokenKind.MINUS)) { + String operator = _peekToken.text; + if (_peekToken.text != '+' && _peekToken.text != '-') { break; } + _next(); secondNode = processCalcProduct(); if (secondNode == null) { return null; } - if (operator == TokenKind.MINUS) { + if (operator == '-') { secondNode = CalcNegateNode(secondNode); } nodes.add(secondNode); @@ -183,15 +186,16 @@ class _CSSCalcParser { } nodes.add(firstNode); while (_peek() != TokenKind.END_OF_FILE) { - int operator = _peekToken.kind; - if (!_maybeEat(TokenKind.ASTERISK) && !_maybeEat(TokenKind.SLASH)) { + String operator = _peekToken.text; + if (_peekToken.text != '*' && _peekToken.text != '/') { break; } + _next(); secondNode = processCalcValue(); if (secondNode == null) { return null; } - if (operator == TokenKind.SLASH) { + if (operator == '/') { secondNode = CalcInvertNode(secondNode); } nodes.add(secondNode); @@ -214,14 +218,12 @@ class _CSSCalcParser { String unit = _peekToken.text; // ignore unit type if (TokenKind.matchUnits(unit, 0, unit.length) == -1) { - return CalcLengthNode(CSSLengthValue(double.tryParse(value), CSSLengthType.PX)); + return CalcLengthNode(CSSLengthValue(double.tryParse(value), CSSLengthType.PX, _renderStyle, propertyName)); } value += unit; - if (!CSSLength.isLength(value)) { - return null; - } + CSSLengthValue lengthValue = CSSLength.parseLength(value, _renderStyle, propertyName); _next(); - return CalcLengthNode(CSSLength.parseLength(value, _renderStyle)); + return CalcLengthNode(lengthValue); } int _peek() { diff --git a/webf/lib/src/css/values/length.dart b/webf/lib/src/css/values/length.dart index 23a967b49e..cdc2d8d32d 100644 --- a/webf/lib/src/css/values/length.dart +++ b/webf/lib/src/css/values/length.dart @@ -312,6 +312,24 @@ class CSSLengthValue { : 0; } break; + case BACKGROUND_POSITION_X: + double? borderBoxWidth = renderStyle!.borderBoxWidth ?? renderStyle!.borderBoxLogicalWidth; + if (isPercentage && borderBoxWidth != null) { + final destinationWidth = renderBoxModel!.boxPainter?.backgroundImageSize?.width.toDouble() ?? 0; + _computedValue = (borderBoxWidth - destinationWidth) * value!; + } else { + _computedValue = value!; + } + break; + case BACKGROUND_POSITION_Y: + double? borderBoxHeight = renderStyle!.borderBoxHeight ?? renderStyle!.borderBoxLogicalHeight; + if (isPercentage && borderBoxHeight != null) { + final destinationHeight = renderBoxModel!.boxPainter?.backgroundImageSize?.height.toDouble() ?? 0; + _computedValue = (borderBoxHeight - destinationHeight) * value!; + } else { + _computedValue = value!; + } + break; } break; default: @@ -324,7 +342,6 @@ class CSSLengthValue { RenderBoxModel? renderBoxModel = renderStyle!.renderBoxModel; cacheComputedValue(renderBoxModel.hashCode, propertyName!, _computedValue!); } - return _computedValue!; } diff --git a/webf/lib/src/css/values/position.dart b/webf/lib/src/css/values/position.dart index f76c027f5a..b29d64c421 100644 --- a/webf/lib/src/css/values/position.dart +++ b/webf/lib/src/css/values/position.dart @@ -7,7 +7,7 @@ import 'package:flutter/painting.dart'; import 'package:webf/css.dart'; import 'package:quiver/collection.dart'; -final RegExp _splitRegExp = RegExp(r'(?> _cachedParsedPosition = LinkedLruHashMap(maximumSize: 100); /// CSS Values and Units: https://drafts.csswg.org/css-values-3/#position diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 8fdd54fca0..70c6c2f8cc 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1061,7 +1061,11 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } if (value is CSSCalcValue) { - value = CSSLengthValue(value.computedValue(name), CSSLengthType.PX); + if (name == BACKGROUND_POSITION_X || name == BACKGROUND_POSITION_Y) { + value = CSSBackgroundPosition(calcValue: value); + } else { + value = value.computedValue(name); + } } switch (name) { diff --git a/webf/lib/src/rendering/box_decoration_painter.dart b/webf/lib/src/rendering/box_decoration_painter.dart index e273967ffe..b7bf58e853 100644 --- a/webf/lib/src/rendering/box_decoration_painter.dart +++ b/webf/lib/src/rendering/box_decoration_painter.dart @@ -21,6 +21,16 @@ class BoxDecorationPainter extends BoxPainter { CSSRenderStyle renderStyle; CSSBoxDecoration get _decoration => renderStyle.decoration!; + Size? get backgroundImageSize { + ui.Image? image = _imagePainter?._image?.image; + if (image == null) { + return null; + } + double imageWidth = image.width.toDouble(); + double imageHeight = image.height.toDouble(); + return Size(imageWidth, imageHeight); + } + Paint? _cachedBackgroundPaint; Rect? _rectForCachedBackgroundPaint; @@ -616,12 +626,16 @@ void _paintImage({ final double halfHeightDelta = (outputSize.height - destinationSize.height) / 2.0; // Use position as length type if specified in positionX/ positionY, otherwise use as percentage type. - final double dx = positionX.length != null - ? positionX.length!.computedValue - : halfWidthDelta + (flipHorizontally ? -positionX.percentage! : positionX.percentage!) * halfWidthDelta; - final double dy = positionY.length != null - ? positionY.length!.computedValue - : halfHeightDelta + positionY.percentage! * halfHeightDelta; + final double dx = positionX.calcValue != null + ? positionX.calcValue!.computedValue(BACKGROUND_POSITION_X) + : positionX.length != null + ? positionX.length!.computedValue + : halfWidthDelta + (flipHorizontally ? -positionX.percentage! : positionX.percentage!) * halfWidthDelta; + final double dy = positionY.calcValue != null + ? positionY.calcValue!.computedValue(BACKGROUND_POSITION_Y) + : positionY.length != null + ? positionY.length!.computedValue + : halfHeightDelta + positionY.percentage! * halfHeightDelta; final Offset destinationPosition = rect.topLeft.translate(dx, dy); final Rect destinationRect = destinationPosition & destinationSize; @@ -723,8 +737,16 @@ void _paintImage({ final double halfHeightDelta = (inputSize.height - sourceSize.height) / 2.0; // Always to draw image on 0 when position length type is specified. final Rect sourceRect = Rect.fromLTWH( - positionX.length != null ? 0 : halfWidthDelta + positionX.percentage! * halfWidthDelta, - positionY.length != null ? 0 : halfHeightDelta + positionY.percentage! * halfHeightDelta, + positionX.calcValue != null + ? 0 + : positionX.length != null + ? 0 + : halfWidthDelta + positionX.percentage! * halfWidthDelta, + positionY.calcValue != null + ? 0 + : positionY.length != null + ? 0 + : halfHeightDelta + positionY.percentage! * halfHeightDelta, sourceSize.width, sourceSize.height, ); From 8c6b5b08327fa09258506ddbd0688262019cd552 Mon Sep 17 00:00:00 2001 From: andycall Date: Thu, 8 Sep 2022 14:17:54 +0800 Subject: [PATCH 282/498] fix: sync current event implementation with dart side. --- bridge/core/events/dart_created_events.json5 | 87 ++++---- bridge/core/events/event_type_names.json5 | 2 + bridge/core/events/focus_event.cc | 2 +- bridge/core/events/mouse_event.cc | 57 ++--- bridge/core/events/mouse_event.d.ts | 30 +-- bridge/core/events/mouse_event_init.d.ts | 22 +- bridge/core/events/touch_event.cc | 8 +- bridge/core/events/touch_event.d.ts | 8 +- bridge/core/events/ui_event.cc | 2 +- bridge/core/events/ui_event.d.ts | 4 + bridge/core/input/touch.cc | 32 ++- bridge/core/input/touch.h | 39 +++- bridge/core/input/touch_list.cc | 9 +- bridge/core/input/touch_list.h | 19 +- bridge/polyfill/src/events.ts | 77 ------- bridge/polyfill/src/global-event-handlers.ts | 47 ----- .../code_generator/src/idl/generateSource.ts | 2 +- webf/lib/src/bridge/binding.dart | 2 +- webf/lib/src/bridge/native_types.dart | 199 +----------------- webf/lib/src/dom/element.dart | 7 +- webf/lib/src/dom/event.dart | 196 +++++++++++------ webf/lib/src/gesture/gesture_dispatcher.dart | 1 + 22 files changed, 340 insertions(+), 512 deletions(-) delete mode 100644 bridge/polyfill/src/events.ts delete mode 100644 bridge/polyfill/src/global-event-handlers.ts diff --git a/bridge/core/events/dart_created_events.json5 b/bridge/core/events/dart_created_events.json5 index 5437fbdf61..b2e39ef264 100644 --- a/bridge/core/events/dart_created_events.json5 +++ b/bridge/core/events/dart_created_events.json5 @@ -23,7 +23,7 @@ // }, "close", // "error", - "focus", +// "focus", { "class": "GestureEvent", "types": [ @@ -39,31 +39,31 @@ "intersectionchange" ] }, - { - "class": "KeyboardEvent", - "types": [ - "keydown", - "keypress", - "keystatuseschange", - "keyup" - ] - }, +// { +// "class": "KeyboardEvent", +// "types": [ +// "keydown", +// "keypress", +// "keystatuseschange", +// "keyup" +// ] +// }, "message", - { - "class": "PointerEvent", - "types": [ - "pointercancel", - "pointerdown", - "pointerenter", - "pointerleave", - "pointerlockchange", - "pointerlockerror", - "pointermove", - "pointerout", - "pointerover", - "pointerup" - ] - }, +// { +// "class": "PointerEvent", +// "types": [ +// "pointercancel", +// "pointerdown", +// "pointerenter", +// "pointerleave", +// "pointerlockchange", +// "pointerlockerror", +// "pointermove", +// "pointerout", +// "pointerover", +// "pointerup" +// ] +// }, { "class": "TouchEvent", "types": [ @@ -76,24 +76,27 @@ { "class": "MouseEvent", "types": [ - "mousedown", - "mouseenter", - "mouseleave", - "mousemove", - "mouseout", - "mouseover", - "mouseup", - "mousewheel" + "click", + "dbclick", + "longpress" +// "mousedown", +// "mouseenter", +// "mouseleave", +// "mousemove", +// "mouseout", +// "mouseover", +// "mouseup", +// "mousewheel" ] }, - { - "class": "TransitionEvent", - "types": [ - "transitioncancel", - "transitionend", - "transitionrun", - "transitionstart" - ] - } +// { +// "class": "TransitionEvent", +// "types": [ +// "transitioncancel", +// "transitionend", +// "transitionrun", +// "transitionstart" +// ] +// } ] } diff --git a/bridge/core/events/event_type_names.json5 b/bridge/core/events/event_type_names.json5 index 74215aebcf..57ec82797c 100644 --- a/bridge/core/events/event_type_names.json5 +++ b/bridge/core/events/event_type_names.json5 @@ -46,6 +46,8 @@ "change", "checking", "click", + "dbclick", + "longpress", "close", "closing", "complete", diff --git a/bridge/core/events/focus_event.cc b/bridge/core/events/focus_event.cc index 8e4b3c48a3..ed69904d46 100644 --- a/bridge/core/events/focus_event.cc +++ b/bridge/core/events/focus_event.cc @@ -50,7 +50,7 @@ FocusEvent::FocusEvent(ExecutingContext *context, FocusEvent::FocusEvent(ExecutingContext *context, const AtomicString &type, NativeFocusEvent *native_focus_event) : UIEvent(context, type, &native_focus_event->native_event), - related_target_(DynamicTo(BindingObject::From(native_focus_event->relatedTarget))) {} + related_target_(DynamicTo(BindingObject::From(static_cast(native_focus_event->relatedTarget)))) {} EventTarget *FocusEvent::relatedTarget() const { return related_target_; diff --git a/bridge/core/events/mouse_event.cc b/bridge/core/events/mouse_event.cc index eb3a775d4d..05dea73062 100644 --- a/bridge/core/events/mouse_event.cc +++ b/bridge/core/events/mouse_event.cc @@ -28,40 +28,41 @@ MouseEvent::MouseEvent(ExecutingContext *context, const AtomicString &type, const std::shared_ptr &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()) {} + 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), +// 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), +// 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) { -} + 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_; diff --git a/bridge/core/events/mouse_event.d.ts b/bridge/core/events/mouse_event.d.ts index 6510bb5b10..ae83c9d759 100644 --- a/bridge/core/events/mouse_event.d.ts +++ b/bridge/core/events/mouse_event.d.ts @@ -4,24 +4,24 @@ 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 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 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; + // 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_init.d.ts b/bridge/core/events/mouse_event_init.d.ts index ea68fb5251..42307db321 100644 --- a/bridge/core/events/mouse_event_init.d.ts +++ b/bridge/core/events/mouse_event_init.d.ts @@ -4,15 +4,15 @@ 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; + // 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/touch_event.cc b/bridge/core/events/touch_event.cc index c8784a44a4..ef27c12fed 100644 --- a/bridge/core/events/touch_event.cc +++ b/bridge/core/events/touch_event.cc @@ -40,9 +40,11 @@ TouchEvent::TouchEvent(ExecutingContext *context, ctrl_key_(native_touch_event->ctrlKey), meta_key_(native_touch_event->metaKey), shift_key_(native_touch_event->shiftKey), - changed_touches_(DynamicTo(BindingObject::From(native_touch_event->changedTouches))), - target_touches_(DynamicTo(BindingObject::From(native_touch_event->targetTouches))), - touches_(DynamicTo(BindingObject::From(native_touch_event->touches))) {} + changed_touches_(MakeGarbageCollected(context, + static_cast(native_touch_event->changedTouches))), + target_touches_(MakeGarbageCollected(context, + static_cast(native_touch_event->targetTouches))), + touches_(MakeGarbageCollected(context, static_cast(native_touch_event->touches))) {} bool TouchEvent::altKey() const { return alt_key_; diff --git a/bridge/core/events/touch_event.d.ts b/bridge/core/events/touch_event.d.ts index 8e56c6cf0b..d3675f991f 100644 --- a/bridge/core/events/touch_event.d.ts +++ b/bridge/core/events/touch_event.d.ts @@ -5,12 +5,12 @@ 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 altKey: boolean; + readonly touches: TouchList; + readonly targetTouches: TouchList; readonly changedTouches: TouchList; - readonly ctrlKey: boolean; + readonly altKey: boolean; readonly metaKey: boolean; + readonly ctrlKey: boolean; readonly shiftKey: boolean; - readonly targetTouches: TouchList; - readonly touches: TouchList; new(type: string, init?: TouchEventInit): TouchEvent; } \ No newline at end of file diff --git a/bridge/core/events/ui_event.cc b/bridge/core/events/ui_event.cc index 40214aab3e..cce03604ef 100644 --- a/bridge/core/events/ui_event.cc +++ b/bridge/core/events/ui_event.cc @@ -50,7 +50,7 @@ UIEvent::UIEvent(ExecutingContext *context, UIEvent::UIEvent(ExecutingContext *context, const AtomicString &type, NativeUIEvent *native_ui_event) : Event(context, type, &native_ui_event->native_event), detail_(native_ui_event->detail), - view_(DynamicTo(BindingObject::From(native_ui_event->view))), + view_(DynamicTo(BindingObject::From(static_cast(native_ui_event->view)))), which_(native_ui_event->which) { } diff --git a/bridge/core/events/ui_event.d.ts b/bridge/core/events/ui_event.d.ts index bc076e52df..ce831a16b7 100644 --- a/bridge/core/events/ui_event.d.ts +++ b/bridge/core/events/ui_event.d.ts @@ -4,6 +4,10 @@ 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 */ diff --git a/bridge/core/input/touch.cc b/bridge/core/input/touch.cc index dc5832c9f2..f35fcd8886 100644 --- a/bridge/core/input/touch.cc +++ b/bridge/core/input/touch.cc @@ -7,19 +7,23 @@ namespace webf { -Touch* Touch::Create(ExecutingContext* context, ExceptionState& exception_state) { +Touch *Touch::Create(ExecutingContext *context, ExceptionState &exception_state) { return MakeGarbageCollected(context, exception_state); } -Touch* Touch::Create(ExecutingContext* context, - const std::shared_ptr& initializer, - ExceptionState& exception_state) { +Touch *Touch::Create(ExecutingContext *context, + const std::shared_ptr &initializer, + ExceptionState &exception_state) { return MakeGarbageCollected(context, initializer, exception_state); } -Touch::Touch(ExecutingContext* context, ExceptionState& exception_state) : ScriptWrappable(context->ctx()) {} +Touch *Touch::Create(ExecutingContext *context, NativeTouch *native_touch) { + return MakeGarbageCollected(context, native_touch); +} + +Touch::Touch(ExecutingContext *context, ExceptionState &exception_state) : ScriptWrappable(context->ctx()) {} -Touch::Touch(ExecutingContext* context, const std::shared_ptr& initializer, ExceptionState& exception_state) +Touch::Touch(ExecutingContext *context, const std::shared_ptr &initializer, ExceptionState &exception_state) : ScriptWrappable(context->ctx()), identifier_(initializer->identifier()), target_(initializer->target()), @@ -34,6 +38,22 @@ Touch::Touch(ExecutingContext* context, const std::shared_ptr& initia 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_; } diff --git a/bridge/core/input/touch.h b/bridge/core/input/touch.h index da4636739c..b1e83a8d72 100644 --- a/bridge/core/input/touch.h +++ b/bridge/core/input/touch.h @@ -12,20 +12,39 @@ 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(); + DEFINE_WRAPPERTYPEINFO(); public: - using ImplType = Touch*; - static Touch* Create(ExecutingContext* context, ExceptionState& exception_state); - static Touch* Create(ExecutingContext* context, - const std::shared_ptr& initializer, - ExceptionState& exception_state); + using ImplType = Touch *; + static Touch *Create(ExecutingContext *context, ExceptionState &exception_state); + static Touch *Create(ExecutingContext *context, + const std::shared_ptr &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& initializer, - ExceptionState& exception_state); + explicit Touch(ExecutingContext *context, ExceptionState &exception_state); + explicit Touch(ExecutingContext *context, + const std::shared_ptr &initializer, + ExceptionState &exception_state); + explicit Touch(ExecutingContext *context, NativeTouch *native_touch); double altitudeAngle() const; double azimuthAngle() const; diff --git a/bridge/core/input/touch_list.cc b/bridge/core/input/touch_list.cc index 047a477c60..0435851c1a 100644 --- a/bridge/core/input/touch_list.cc +++ b/bridge/core/input/touch_list.cc @@ -6,15 +6,20 @@ namespace webf { +TouchList::TouchList(ExecutingContext *context, NativeTouchList *native_touch_list) : + ScriptWrappable(context->ctx()), + native_touch_list_(native_touch_list) { +} + uint32_t TouchList::length() const { return values_.size(); } -Touch* TouchList::item(uint32_t index, ExceptionState& exception_state) const { +Touch *TouchList::item(uint32_t index, ExceptionState &exception_state) const { return values_[index]; } -bool TouchList::SetItem(uint32_t index, Touch* touch, ExceptionState& exception_state) { +bool TouchList::SetItem(uint32_t index, Touch *touch, ExceptionState &exception_state) { if (index >= values_.size()) { values_.emplace_back(touch); } else { diff --git a/bridge/core/input/touch_list.h b/bridge/core/input/touch_list.h index bae4bb0c08..cfc1a25503 100644 --- a/bridge/core/input/touch_list.h +++ b/bridge/core/input/touch_list.h @@ -9,11 +9,20 @@ namespace webf { -class TouchList : public ScriptWrappable, public BindingObject { +struct NativeTouchList { + int64_t length; + NativeTouch* touches; +}; + +class TouchList : public ScriptWrappable { DEFINE_WRAPPERTYPEINFO(); public: using ImplType = TouchList*; + + 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); @@ -25,13 +34,7 @@ class TouchList : public ScriptWrappable, public BindingObject { private: std::vector values_; -}; - -template <> -struct DowncastTraits { - static bool AllowFrom(const BindingObject& binding_object) { - return binding_object.IsTouchList(); - } + NativeTouchList* native_touch_list_; }; } // namespace webf 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; - 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/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 193113aaab..4c91e450df 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -98,7 +98,7 @@ export function generateRawTypeValue(type: ParameterType[], is32Bit: boolean = f return 'NativeString*'; } default: - return 'NativeBindingObject*'; + return 'void*'; } if (typeof type[0] == 'string') { diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 1a1c13e5d3..fb9287fda2 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -116,7 +116,7 @@ void _dispatchEventToNative(Event event) { f(pointer, nullptr, method, dispatchEventArguments.length, allocatedNativeArguments); // Native can mutate rawEvent directly, so we sync properties value from native side with rawEvent. - event.syncFromRaw(rawEvent.cast()); + event.syncFromRaw(rawEvent.cast()); // Free the allocated arguments. malloc.free(rawEvent); diff --git a/webf/lib/src/bridge/native_types.dart b/webf/lib/src/bridge/native_types.dart index 06df303801..a873ade407 100644 --- a/webf/lib/src/bridge/native_types.dart +++ b/webf/lib/src/bridge/native_types.dart @@ -25,179 +25,17 @@ 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; } -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 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; -} - -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,35 +79,6 @@ 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, diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 91fdf7b92a..c2053f1b64 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -48,7 +48,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; @@ -134,6 +136,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element } bool _forceToRepaintBoundary = false; + set forceToRepaintBoundary(bool value) { if (_forceToRepaintBoundary == value) { return; @@ -529,6 +532,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element BoundingClientRect getBoundingClientRect() => boundingClientRect; bool _shouldConsumeScrollTicker = false; + void _consumeScrollTicker(_) { if (_shouldConsumeScrollTicker && hasEventListener(EVENT_SCROLL)) { _dispatchScrollEvent(); @@ -1584,7 +1588,8 @@ 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, MouseEventInit(bubbles: true, cancelable: true, 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/event.dart b/webf/lib/src/dom/event.dart index f22c8ec5db..bad479051c 100644 --- a/webf/lib/src/dom/event.dart +++ b/webf/lib/src/dom/event.dart @@ -175,6 +175,7 @@ class Event { } bool canBubble() => _immediateBubble; + void stopImmediatePropagation() { _immediateBubble = false; } @@ -184,7 +185,7 @@ class Event { } // Sync event properties from Raw pointer data. - void syncFromRaw(Pointer raw) { + void syncFromRaw(Pointer raw) { assert(raw.ref.length >= 7); bubbles = raw.ref.bytes[1] == 1; cancelable = raw.ref.bytes[2] == 1; @@ -192,7 +193,7 @@ class Event { } Pointer toRaw([int extraLength = 0]) { - Pointer event = malloc.allocate(sizeOf()); + Pointer event = malloc.allocate(sizeOf()); EventTarget? _target = target; EventTarget? _currentTarget = currentTarget; @@ -224,24 +225,32 @@ class Event { } class EventInit { + // A boolean value indicating whether the event bubbles. The default is false. final bool bubbles; + + // A boolean value indicating whether the event can be cancelled. The default is false. final bool cancelable; + // A boolean value indicating whether the event will trigger listeners outside of a shadow root (see Event.composed for more details). + final bool composed; + EventInit({ this.bubbles = false, this.cancelable = false, + this.composed = false, }); } class PopStateEvent extends Event { final PopStateEventInit _popStateEventInit; + PopStateEvent(this._popStateEventInit) : super('popstate', _popStateEventInit); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int methodLength = 0]) { List methods = [stringToNativeString(jsonEncode(_popStateEventInit.state)).address]; - Pointer rawEvent = super.toRaw(methods.length).cast(); + Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); bytes.setAll(rawEvent.ref.length, methods); @@ -251,24 +260,86 @@ class PopStateEvent extends Event { class PopStateEventInit extends EventInit { final dynamic state; + PopStateEventInit(this.state); } -/// reference: https://developer.mozilla.org/zh-CN/docs/Web/API/MouseEvent -class MouseEvent extends Event { - final MouseEventInit _mouseEventInit; +class UIEventInit extends EventInit { + // 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; + + UIEventInit( + {bool bubbles = false, + bool cancelable = false, + bool composed = false, + this.detail = 0, + this.view, + this.which = 0}) + : super(bubbles: bubbles, cancelable: cancelable, composed: composed); +} + +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; - double get clientX => _mouseEventInit.clientX; - double get clientY => _mouseEventInit.clientY; - double get offsetX => _mouseEventInit.offsetX; - double get offsetY => _mouseEventInit.offsetY; + UIEvent(String type, [UIEventInit? init]) + : detail = init?.detail ?? 0, + view = init?.view, + which = init?.which ?? 0, + super(type, init); + + @override + Pointer toRaw([int methodLength = 0]) { + List methods = [doubleToUint64(detail), view?.pointer ?? nullptr, doubleToUint64(which)]; + + Pointer rawEvent = super.toRaw(methods.length).cast(); + Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + bytes.setAll(rawEvent.ref.length, methods); + + return rawEvent; + } +} - MouseEvent(String type, MouseEventInit mouseEventInit) - : _mouseEventInit = 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, [MouseEventInit? mouseEventInit]) + : clientX = mouseEventInit?.clientX ?? 0.0, + clientY = mouseEventInit?.clientY ?? 0.0, + offsetX = mouseEventInit?.offsetX ?? 0.0, + offsetY = mouseEventInit?.offsetY ?? 0.0, super(type, mouseEventInit); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int methodLength = 0]) { List methods = [ doubleToUint64(clientX), doubleToUint64(clientY), @@ -276,7 +347,7 @@ class MouseEvent extends Event { doubleToUint64(offsetY) ]; - Pointer rawEvent = super.toRaw(methods.length).cast(); + Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); bytes.setAll(rawEvent.ref.length, methods); @@ -284,20 +355,24 @@ class MouseEvent extends Event { } } -class MouseEventInit extends EventInit { +class MouseEventInit extends UIEventInit { 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); + MouseEventInit( + {bool bubbles = false, + bool cancelable = false, + bool composed = false, + this.clientX = 0.0, + this.clientY = 0.0, + this.offsetX = 0.0, + this.offsetY = 0.0, + required EventTarget view, + double detail = 0.0, + double which = 0.0}) + : super(view: view, detail: detail, which: which, bubbles: bubbles, cancelable: cancelable, composed: composed); } class GestureEventInit extends EventInit { @@ -329,12 +404,19 @@ 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) @@ -342,7 +424,7 @@ class GestureEvent extends Event { super(type, gestureEventInit); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int methodLength = 0]) { List methods = [ stringToNativeString(state).address, stringToNativeString(direction).address, @@ -354,7 +436,7 @@ class GestureEvent extends Event { doubleToUint64(rotation) ]; - Pointer rawEvent = super.toRaw(methods.length).cast(); + Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); bytes.setAll(rawEvent.ref.length, methods); @@ -372,20 +454,17 @@ class CustomEventInit extends EventInit { /// 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; + String detail; - CustomEvent(String type, CustomEventInit customEventInit) - : _customEventInit = customEventInit, + CustomEvent(String type, [CustomEventInit? customEventInit]) + : detail = customEventInit?.detail ?? '', super(type, customEventInit); @override - Pointer toRaw([int methodLength = 0]) { - List methods = [ - detail.toNativeUtf8().address - ]; + Pointer toRaw([int methodLength = 0]) { + List methods = [detail.toNativeUtf8().address]; - Pointer rawEvent = super.toRaw(methods.length).cast(); + Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); bytes.setAll(rawEvent.ref.length, methods); @@ -402,10 +481,10 @@ class InputEvent extends Event { final String data; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int methodLength = 0]) { List methods = [stringToNativeString(inputType).address, stringToNativeString(data).address]; - Pointer rawEvent = super.toRaw(methods.length).cast(); + Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); bytes.setAll(rawEvent.ref.length, methods); @@ -434,10 +513,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; } @@ -453,10 +535,10 @@ class MediaError extends Event { final String message; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int methodLength = 0]) { List methods = [code, stringToNativeString(message).address]; - Pointer rawEvent = super.toRaw(methods.length).cast(); + Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); bytes.setAll(rawEvent.ref.length, methods); @@ -477,10 +559,10 @@ class MessageEvent extends Event { MessageEvent(this.data, {this.origin = ''}) : super(EVENT_MESSAGE); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int methodLength = 0]) { List methods = [(jsonEncode(data)).toNativeUtf8().address, stringToNativeString(origin).address]; - Pointer rawEvent = super.toRaw(methods.length).cast(); + Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); bytes.setAll(rawEvent.ref.length, methods); @@ -502,10 +584,10 @@ class CloseEvent extends Event { CloseEvent(this.code, this.reason, this.wasClean) : super(EVENT_CLOSE); @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int methodLength = 0]) { List methods = [code, stringToNativeString(reason).address, wasClean ? 1 : 0]; - Pointer rawEvent = super.toRaw(methods.length).cast(); + Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); bytes.setAll(rawEvent.ref.length, methods); @@ -518,11 +600,10 @@ class IntersectionChangeEvent extends Event { final double intersectionRatio; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int methodLength = 0]) { List methods = [doubleToUint64(intersectionRatio)]; - Pointer rawEvent = - super.toRaw(methods.length).cast(); + Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); bytes.setAll(rawEvent.ref.length, methods); @@ -544,21 +625,18 @@ class TouchEvent extends Event { bool shiftKey = false; @override - Pointer toRaw([int methodLength = 0]) { + Pointer toRaw([int methodLength = 0]) { 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(); + Pointer rawEvent = super.toRaw(methods.length).cast(); Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); bytes.setAll(rawEvent.ref.length, methods); @@ -587,7 +665,6 @@ class Touch { final double force; final double altitudeAngle; final double azimuthAngle; - final TouchType touchType; const Touch({ required this.identifier, @@ -604,7 +681,6 @@ class Touch { this.force = 0, this.altitudeAngle = 0, this.azimuthAngle = 0, - this.touchType = TouchType.direct, }); Pointer toNative() { @@ -623,7 +699,6 @@ class Touch { nativeTouch.ref.force = force; nativeTouch.ref.altitudeAngle = altitudeAngle; nativeTouch.ref.azimuthAngle = azimuthAngle; - nativeTouch.ref.touchType = touchType.index; return nativeTouch; } } @@ -631,6 +706,7 @@ class Touch { /// reference: https://w3c.github.io/touch-events/#touchlist-interface class TouchList { final List _items = []; + int get length => _items.length; Touch item(int index) { @@ -646,13 +722,15 @@ 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(); + touches[i] = _items[i].toNative(); } - + touchList.ref.length = _items.length; + touchList.ref.touches = touches; return touchList; } } diff --git a/webf/lib/src/gesture/gesture_dispatcher.dart b/webf/lib/src/gesture/gesture_dispatcher.dart index 4e60c39231..97e63f7d25 100644 --- a/webf/lib/src/gesture/gesture_dispatcher.dart +++ b/webf/lib/src/gesture/gesture_dispatcher.dart @@ -342,6 +342,7 @@ class GestureDispatcher { clientY: clientY, offsetX: localPosition.dx, offsetY: localPosition.dy, + view: (_target as Node).ownerDocument.defaultView )); _target?.dispatchEvent(event); } From 6e07e33227d613c7228f98c7977d87f98ad1ad21 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Thu, 8 Sep 2022 06:19:03 +0000 Subject: [PATCH 283/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 10 +-- bridge/core/dom/events/custom_event.cc | 40 +++++---- bridge/core/dom/events/custom_event.h | 16 +++- bridge/core/dom/events/event.cc | 30 +++---- bridge/core/dom/events/event.h | 4 +- bridge/core/dom/events/event_target.cc | 13 +-- bridge/core/dom/events/event_target.h | 4 +- .../dom/events/registered_eventListener.cc | 2 +- .../core/dom/legacy/bounding_client_rect.cc | 4 +- bridge/core/events/close_event.cc | 15 ++-- bridge/core/events/focus_event.cc | 25 +++--- bridge/core/events/focus_event.h | 4 +- bridge/core/events/gesture_event.cc | 37 +++++---- bridge/core/events/input_event.cc | 22 +++-- .../core/events/intersection_change_event.cc | 40 ++++----- .../core/events/intersection_change_event.h | 4 +- bridge/core/events/keyboard_event.cc | 34 ++++---- bridge/core/events/message_event.cc | 44 +++++----- bridge/core/events/mouse_event.cc | 58 +++++++------ bridge/core/events/mouse_event.h | 36 ++++----- bridge/core/events/pointer_event.cc | 81 +++++++++---------- bridge/core/events/pointer_event.h | 51 ++++++------ bridge/core/events/touch_event.cc | 65 +++++++-------- bridge/core/events/touch_event.h | 34 ++++---- bridge/core/events/transition_event.cc | 55 +++++++------ bridge/core/events/transition_event.h | 30 +++---- bridge/core/events/ui_event.cc | 43 +++++----- bridge/core/executing_context.cc | 2 +- bridge/core/frame/window.h | 5 +- bridge/core/input/touch.cc | 46 +++++------ bridge/core/input/touch.h | 28 +++---- bridge/core/input/touch_list.cc | 10 +-- bridge/core/page.cc | 2 +- bridge/foundation/native_value_converter.h | 3 +- 34 files changed, 438 insertions(+), 459 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index d72188ea8c..bbff263126 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -16,6 +16,7 @@ #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_element.h" #include "qjs_element_attributes.h" @@ -42,18 +43,17 @@ #include "qjs_location.h" #include "qjs_message_event.h" #include "qjs_module_manager.h" -#include "qjs_node.h" -#include "qjs_custom_event.h" #include "qjs_mouse_event.h" -#include "qjs_touch_event.h" -#include "qjs_transition_event.h" -#include "qjs_pointer_event.h" +#include "qjs_node.h" #include "qjs_node_list.h" +#include "qjs_pointer_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_window.h" #include "qjs_window_or_worker_global_scope.h" diff --git a/bridge/core/dom/events/custom_event.cc b/bridge/core/dom/events/custom_event.cc index 1265c490b2..88ce0270f6 100644 --- a/bridge/core/dom/events/custom_event.cc +++ b/bridge/core/dom/events/custom_event.cc @@ -7,39 +7,35 @@ namespace webf { -CustomEvent *CustomEvent::Create(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state) { +CustomEvent* CustomEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { return MakeGarbageCollected(context, type, exception_state); } -CustomEvent *CustomEvent::Create(ExecutingContext *context, - const AtomicString &type, - NativeCustomEvent *native_custom_event) { +CustomEvent* CustomEvent::Create(ExecutingContext* context, + const AtomicString& type, + NativeCustomEvent* native_custom_event) { return MakeGarbageCollected(context, type, native_custom_event); } -CustomEvent *CustomEvent::Create(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initialize, - ExceptionState &exception_state) { +CustomEvent* CustomEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initialize, + ExceptionState& exception_state) { return MakeGarbageCollected(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::CreateJsonObject(ctx(), native_custom_event->detail, strlen(native_custom_event->detail))) { -} +CustomEvent::CustomEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) + : Event(context, type) {} -CustomEvent::CustomEvent(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initialize, - ExceptionState &exception_state): - Event(context, type), - detail_(initialize->detail()) { +CustomEvent::CustomEvent(ExecutingContext* context, const AtomicString& type, NativeCustomEvent* native_custom_event) + : Event(context, type, &native_custom_event->native_event), + detail_(ScriptValue::CreateJsonObject(ctx(), native_custom_event->detail, strlen(native_custom_event->detail))) {} -} +CustomEvent::CustomEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initialize, + ExceptionState& exception_state) + : Event(context, type), detail_(initialize->detail()) {} ScriptValue CustomEvent::detail() const { return detail_; diff --git a/bridge/core/dom/events/custom_event.h b/bridge/core/dom/events/custom_event.h index e915123fae..e05cd39b1c 100644 --- a/bridge/core/dom/events/custom_event.h +++ b/bridge/core/dom/events/custom_event.h @@ -17,17 +17,26 @@ struct NativeCustomEvent { 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& initialize, 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& 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& initialize, ExceptionState& exception_state); + explicit CustomEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initialize, + ExceptionState& exception_state); ScriptValue detail() const; @@ -42,7 +51,6 @@ struct DowncastTraits { static bool AllowFrom(const Event& event) { return event.IsCustomEvent(); } }; - } // namespace webf #endif // BRIDGE_CUSTOM_EVENT_H diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 7781c493bd..5819489bfe 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -3,9 +3,9 @@ * 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" -#include "bindings/qjs/cppgc/gc_visitor.h" namespace webf { @@ -49,25 +49,25 @@ Event::Event(ExecutingContext* context, 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), - cancelable_(native_event->cancelable), - time_stamp_(native_event->timeStamp), - default_prevented_(native_event->defaultPrevented), - target_(DynamicTo(BindingObject::From(native_event->target))), - current_target_(DynamicTo(BindingObject::From(native_event->currentTarget))) {} - -void Event::SetType(const AtomicString &type) { +Event::Event(ExecutingContext* context, const AtomicString& event_type, NativeEvent* native_event) + : ScriptWrappable(context->ctx()), + type_(event_type), + bubbles_(native_event->bubbles), + cancelable_(native_event->cancelable), + time_stamp_(native_event->timeStamp), + default_prevented_(native_event->defaultPrevented), + target_(DynamicTo(BindingObject::From(native_event->target))), + current_target_(DynamicTo(BindingObject::From(native_event->currentTarget))) {} + +void Event::SetType(const AtomicString& type) { type_ = type; } -EventTarget *Event::target() const { +EventTarget* Event::target() const { return target_; } -void Event::SetTarget(EventTarget *target) { +void Event::SetTarget(EventTarget* target) { target_ = target; } @@ -182,7 +182,7 @@ void Event::SetHandlingPassive(PassiveMode mode) { handling_passive_ = mode; } -void Event::Trace(GCVisitor *visitor) const { +void Event::Trace(GCVisitor* visitor) const { visitor->Trace(target_); visitor->Trace(current_target_); } diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index a0ec6bf578..36380c0119 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -7,9 +7,9 @@ #include #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 "bindings/qjs/cppgc/member.h" #include "core/executing_context.h" #include "foundation/native_string.h" #include "qjs_event_init.h" @@ -55,7 +55,7 @@ struct RawEvent { int64_t length; }; -template +template 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. diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index e59f1ed7e1..06d04a09f7 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -3,13 +3,13 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "event_target.h" +#include "binding_call_methods.h" #include "bindings/qjs/converter_impl.h" +#include "custom_event.h" +#include "event_factory.h" #include "event_type_names.h" -#include "qjs_add_event_listener_options.h" -#include "binding_call_methods.h" #include "native_value_converter.h" -#include "event_factory.h" -#include "custom_event.h" +#include "qjs_add_event_listener_options.h" #define PROPAGATION_STOPPED 1 #define PROPAGATION_CONTINUE 0 @@ -257,7 +257,7 @@ NativeValue EventTarget::HandleCallFromDartSide(NativeString* native_method, int return Native_NewNull(); } -NativeValue EventTarget::HandleDispatchEventFromDart(int32_t argc, const NativeValue *argv) { +NativeValue EventTarget::HandleDispatchEventFromDart(int32_t argc, const NativeValue* argv) { assert(argc == 3); AtomicString event_type = NativeValueConverter::FromNativeValue(ctx(), argv[0]); RawEvent* raw_event = NativeValueConverter>::FromNativeValue(argv[1]); @@ -265,7 +265,8 @@ NativeValue EventTarget::HandleDispatchEventFromDart(int32_t argc, const NativeV Event* event; if (is_custom_event) { - event = MakeGarbageCollected(GetExecutingContext(), event_type, toNativeEvent(raw_event)); + event = MakeGarbageCollected(GetExecutingContext(), event_type, + toNativeEvent(raw_event)); } else { event = EventFactory::Create(GetExecutingContext(), event_type, raw_event); } diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 6155930ef0..7f0d8a6ecf 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -158,9 +158,7 @@ class EventTarget : public ScriptWrappable, public BindingObject { template <> struct DowncastTraits { - static bool AllowFrom(const BindingObject& binding_object) { - return binding_object.IsEventTarget(); - } + static bool AllowFrom(const BindingObject& binding_object) { return binding_object.IsEventTarget(); } }; // Provide EventTarget with inlined EventTargetData for improved performance. diff --git a/bridge/core/dom/events/registered_eventListener.cc b/bridge/core/dom/events/registered_eventListener.cc index 80bf354aaf..ae66723063 100644 --- a/bridge/core/dom/events/registered_eventListener.cc +++ b/bridge/core/dom/events/registered_eventListener.cc @@ -3,8 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "registered_eventListener.h" -#include "qjs_add_event_listener_options.h" #include "event.h" +#include "qjs_add_event_listener_options.h" namespace webf { diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc index c82bc692fd..30d9c69f17 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.cc +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -15,9 +15,7 @@ BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, Native BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBindingObject* native_binding_object) : ScriptWrappable(context->ctx()), BindingObject(context, native_binding_object) {} -NativeValue BoundingClientRect::HandleCallFromDartSide(NativeString* method, - int32_t argc, - const NativeValue* argv) { +NativeValue BoundingClientRect::HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) { return Native_NewNull(); } diff --git a/bridge/core/events/close_event.cc b/bridge/core/events/close_event.cc index 7d44db8261..b1116e5a71 100644 --- a/bridge/core/events/close_event.cc +++ b/bridge/core/events/close_event.cc @@ -38,21 +38,20 @@ CloseEvent::CloseEvent(ExecutingContext* context, 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 &initializer, - ExceptionState &exception_state) +CloseEvent::CloseEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) : Event(context, type), code_(initializer->code()), reason_(initializer->reason()), was_clean_(initializer->wasClean()) {} -CloseEvent::CloseEvent(ExecutingContext *context, const AtomicString &type, NativeCloseEvent *native_close_event) +CloseEvent::CloseEvent(ExecutingContext* context, const AtomicString& type, NativeCloseEvent* native_close_event) : Event(context, type, &native_close_event->native_event), code_(native_close_event->code), reason_(AtomicString(context->ctx(), native_close_event->reason)), - was_clean_(native_close_event->wasClean) { -} + was_clean_(native_close_event->wasClean) {} bool CloseEvent::IsCloseEvent() const { return true; @@ -62,7 +61,7 @@ int64_t CloseEvent::code() const { return code_; } -const AtomicString &CloseEvent::reason() const { +const AtomicString& CloseEvent::reason() const { return reason_; } diff --git a/bridge/core/events/focus_event.cc b/bridge/core/events/focus_event.cc index ed69904d46..e7109d2395 100644 --- a/bridge/core/events/focus_event.cc +++ b/bridge/core/events/focus_event.cc @@ -33,26 +33,27 @@ FocusEvent* FocusEvent::Create(ExecutingContext* context, FocusEvent::FocusEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) : UIEvent(context, type, exception_state) {} -FocusEvent::FocusEvent(ExecutingContext *context, - const AtomicString &type, +FocusEvent::FocusEvent(ExecutingContext* context, + const AtomicString& type, double detail, - Window *view, + Window* view, double which, - EventTarget *relatedTarget, - ExceptionState &exception_state) + 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 &initializer, - ExceptionState &exception_state) +FocusEvent::FocusEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) : UIEvent(context, type, initializer, exception_state), related_target_(initializer->relatedTarget()) {} -FocusEvent::FocusEvent(ExecutingContext *context, const AtomicString &type, NativeFocusEvent *native_focus_event) +FocusEvent::FocusEvent(ExecutingContext* context, const AtomicString& type, NativeFocusEvent* native_focus_event) : UIEvent(context, type, &native_focus_event->native_event), - related_target_(DynamicTo(BindingObject::From(static_cast(native_focus_event->relatedTarget)))) {} + related_target_(DynamicTo( + BindingObject::From(static_cast(native_focus_event->relatedTarget)))) {} -EventTarget *FocusEvent::relatedTarget() const { +EventTarget* FocusEvent::relatedTarget() const { return related_target_; } diff --git a/bridge/core/events/focus_event.h b/bridge/core/events/focus_event.h index 2ce131f18c..c9fa1cc5f3 100644 --- a/bridge/core/events/focus_event.h +++ b/bridge/core/events/focus_event.h @@ -51,9 +51,7 @@ class FocusEvent : public UIEvent { const std::shared_ptr& initializer, ExceptionState& exception_state); - explicit FocusEvent(ExecutingContext* context, - const AtomicString& type, - NativeFocusEvent* native_focus_event); + explicit FocusEvent(ExecutingContext* context, const AtomicString& type, NativeFocusEvent* native_focus_event); EventTarget* relatedTarget() const; diff --git a/bridge/core/events/gesture_event.cc b/bridge/core/events/gesture_event.cc index ec6062db83..cb70b3f4d4 100644 --- a/bridge/core/events/gesture_event.cc +++ b/bridge/core/events/gesture_event.cc @@ -24,10 +24,10 @@ GestureEvent* GestureEvent::Create(ExecutingContext* context, GestureEvent::GestureEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) : Event(context, type) {} -GestureEvent::GestureEvent(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state) +GestureEvent::GestureEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) : Event(context, type), state_(initializer->state()), direction_(initializer->direction()), @@ -36,29 +36,28 @@ GestureEvent::GestureEvent(ExecutingContext *context, scale_(initializer->scale()), rotation_(initializer->rotation()) {} -GestureEvent::GestureEvent(ExecutingContext *context, - const AtomicString &type, - NativeGestureEvent *native_gesture_event) : - Event(context, type, &native_gesture_event->native_event), - state_(AtomicString(ctx(), native_gesture_event->state)), - direction_(AtomicString(ctx(), native_gesture_event->direction)), - 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) { -} +GestureEvent::GestureEvent(ExecutingContext* context, + const AtomicString& type, + NativeGestureEvent* native_gesture_event) + : Event(context, type, &native_gesture_event->native_event), + state_(AtomicString(ctx(), native_gesture_event->state)), + direction_(AtomicString(ctx(), native_gesture_event->direction)), + 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 { +const AtomicString& GestureEvent::state() const { return state_; } -const AtomicString &GestureEvent::direction() const { +const AtomicString& GestureEvent::direction() const { return direction_; } diff --git a/bridge/core/events/input_event.cc b/bridge/core/events/input_event.cc index 43a215a9ce..c41f249dbd 100644 --- a/bridge/core/events/input_event.cc +++ b/bridge/core/events/input_event.cc @@ -22,26 +22,24 @@ InputEvent* InputEvent::Create(ExecutingContext* context, 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 &initializer, - ExceptionState &exception_state) +InputEvent::InputEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) : UIEvent(context, type, initializer, exception_state), input_type_(initializer->inputType()), data_(initializer->data()) {} -InputEvent::InputEvent(ExecutingContext *context, - const AtomicString &type, - NativeInputEvent *native_input_event) : - UIEvent(context, type, &native_input_event->native_event), - input_type_(AtomicString(ctx(), native_input_event->inputType)), - data_(AtomicString(ctx(), native_input_event->data)) {} +InputEvent::InputEvent(ExecutingContext* context, const AtomicString& type, NativeInputEvent* native_input_event) + : UIEvent(context, type, &native_input_event->native_event), + input_type_(AtomicString(ctx(), native_input_event->inputType)), + data_(AtomicString(ctx(), native_input_event->data)) {} -const AtomicString &InputEvent::inputType() const { +const AtomicString& InputEvent::inputType() const { return input_type_; } -const AtomicString &InputEvent::data() const { +const AtomicString& InputEvent::data() const { return data_; } diff --git a/bridge/core/events/intersection_change_event.cc b/bridge/core/events/intersection_change_event.cc index be365f87b0..8fabdc4674 100644 --- a/bridge/core/events/intersection_change_event.cc +++ b/bridge/core/events/intersection_change_event.cc @@ -8,36 +8,36 @@ namespace webf { -IntersectionChangeEvent *IntersectionChangeEvent::Create(ExecutingContext *context, - const AtomicString &type, - ExceptionState &exception_state) { +IntersectionChangeEvent* IntersectionChangeEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { return MakeGarbageCollected(context, type, exception_state); } -IntersectionChangeEvent *IntersectionChangeEvent::Create( - ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state) { +IntersectionChangeEvent* IntersectionChangeEvent::Create( + ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) { return MakeGarbageCollected(context, type, initializer, exception_state); } -IntersectionChangeEvent::IntersectionChangeEvent(ExecutingContext *context, - const AtomicString &type, - ExceptionState &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 &initializer, - ExceptionState &exception_state) +IntersectionChangeEvent::IntersectionChangeEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) : Event(context, type), intersection_ratio_(initializer->intersectionRatio()) {} -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) {} +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_; diff --git a/bridge/core/events/intersection_change_event.h b/bridge/core/events/intersection_change_event.h index 23f4c570a6..8feee073d3 100644 --- a/bridge/core/events/intersection_change_event.h +++ b/bridge/core/events/intersection_change_event.h @@ -39,7 +39,9 @@ class IntersectionChangeEvent : public Event { const AtomicString& type, ExceptionState& exception_state); - explicit IntersectionChangeEvent(ExecutingContext* context, const AtomicString& type, NativeIntersectionChangeEvent* native_intersectionchange_event); + explicit IntersectionChangeEvent(ExecutingContext* context, + const AtomicString& type, + NativeIntersectionChangeEvent* native_intersectionchange_event); double intersectionRatio() const; diff --git a/bridge/core/events/keyboard_event.cc b/bridge/core/events/keyboard_event.cc index ae79307143..2658ce731f 100644 --- a/bridge/core/events/keyboard_event.cc +++ b/bridge/core/events/keyboard_event.cc @@ -46,23 +46,23 @@ KeyboardEvent::KeyboardEvent(ExecutingContext* context, repeat_(initializer->repeat()), shift_key_(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), - code_(AtomicString(ctx(), native_keyboard_event->code)), - ctrl_key_(native_keyboard_event->ctrlKey), - is_composing_(native_keyboard_event->isComposing), - key_(AtomicString(ctx(), native_keyboard_event->key)), - 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) { +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), + code_(AtomicString(ctx(), native_keyboard_event->code)), + ctrl_key_(native_keyboard_event->ctrlKey), + is_composing_(native_keyboard_event->isComposing), + key_(AtomicString(ctx(), native_keyboard_event->key)), + 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; } diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index c4d6016747..b675b06947 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -10,44 +10,44 @@ namespace webf { struct NativeMessageEvent { NativeEvent native_event; - const char *data; - NativeString *origin; - NativeString *lastEventId; - NativeString *source; + const char* data; + NativeString* origin; + NativeString* lastEventId; + NativeString* source; }; -MessageEvent *MessageEvent::Create(ExecutingContext *context, - const AtomicString &type, - ExceptionState &exception_state) { +MessageEvent* MessageEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { return MakeGarbageCollected(context, type); } -MessageEvent *MessageEvent::Create(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &init, +MessageEvent* MessageEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& init, ExceptionState& exception_state) { return MakeGarbageCollected(context, type, init); } -MessageEvent::MessageEvent(ExecutingContext *context, const AtomicString &type) : Event(context, type) {} +MessageEvent::MessageEvent(ExecutingContext* context, const AtomicString& type) : Event(context, type) {} -MessageEvent::MessageEvent(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &init) +MessageEvent::MessageEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& init) : Event(context, type), data_(init->data()), origin_(init->origin()), lastEventId_(init->lastEventId()), source_(init->source()) {} -MessageEvent::MessageEvent(ExecutingContext *context, - const AtomicString &type, - NativeMessageEvent *native_message_event) : - Event(context, type, &native_message_event->native_event), - data_(ScriptValue::CreateJsonObject(ctx(), native_message_event->data, strlen(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)) {} +MessageEvent::MessageEvent(ExecutingContext* context, + const AtomicString& type, + NativeMessageEvent* native_message_event) + : Event(context, type, &native_message_event->native_event), + data_(ScriptValue::CreateJsonObject(ctx(), native_message_event->data, strlen(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)) {} ScriptValue MessageEvent::data() const { return data_; diff --git a/bridge/core/events/mouse_event.cc b/bridge/core/events/mouse_event.cc index 05dea73062..8908433c4e 100644 --- a/bridge/core/events/mouse_event.cc +++ b/bridge/core/events/mouse_event.cc @@ -8,27 +8,25 @@ namespace webf { -MouseEvent *MouseEvent::Create(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state) { +MouseEvent* MouseEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { return MakeGarbageCollected(context, type, exception_state); } -MouseEvent *MouseEvent::Create(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state) { +MouseEvent* MouseEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) { return MakeGarbageCollected(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, ExceptionState& exception_state) + : UIEvent(context, type, exception_state) {} -MouseEvent::MouseEvent(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state) : - UIEvent(context, type, initializer, exception_state) +MouseEvent::MouseEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) + : UIEvent(context, type, initializer, exception_state) // alt_key_(initializer->altKey()), // button_(initializer->button()), // buttons_(initializer->buttons()), @@ -42,19 +40,19 @@ MouseEvent::MouseEvent(ExecutingContext *context, // 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) +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), @@ -119,7 +117,7 @@ double MouseEvent::y() const { return y_; }; -EventTarget *MouseEvent::relatedTarget() const { +EventTarget* MouseEvent::relatedTarget() const { return related_target_; } @@ -127,9 +125,9 @@ bool MouseEvent::IsMouseEvent() const { return true; } -void MouseEvent::Trace(GCVisitor *visitor) const { +void MouseEvent::Trace(GCVisitor* visitor) const { visitor->Trace(related_target_); UIEvent::Trace(visitor); } -} \ No newline at end of file +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/mouse_event.h b/bridge/core/events/mouse_event.h index 39237b4bf5..46191c7d9f 100644 --- a/bridge/core/events/mouse_event.h +++ b/bridge/core/events/mouse_event.h @@ -5,32 +5,32 @@ #ifndef WEBF_CORE_EVENTS_MOUSE_EVENT_H_ #define WEBF_CORE_EVENTS_MOUSE_EVENT_H_ -#include "ui_event.h" #include "qjs_mouse_event_init.h" +#include "ui_event.h" namespace webf { struct NativeMouseEvent; class MouseEvent : public UIEvent { - DEFINE_WRAPPERTYPEINFO(); - public: + DEFINE_WRAPPERTYPEINFO(); - using ImplType = MouseEvent *; + public: + using ImplType = MouseEvent*; - static MouseEvent *Create(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state); + static MouseEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); - static MouseEvent *Create(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state); + static MouseEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); - explicit MouseEvent(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state); + explicit MouseEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); - explicit MouseEvent(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state); + explicit MouseEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); explicit MouseEvent(ExecutingContext* context, const AtomicString& type, NativeMouseEvent* native_mouse_event); @@ -52,9 +52,9 @@ class MouseEvent : public UIEvent { bool shiftKey() const; double x() const; double y() const; - EventTarget *relatedTarget() const; + EventTarget* relatedTarget() const; - void Trace(GCVisitor *visitor) const override; + void Trace(GCVisitor* visitor) const override; bool IsMouseEvent() const override; @@ -80,6 +80,6 @@ class MouseEvent : public UIEvent { Member related_target_; }; -} +} // namespace webf -#endif //WEBF_CORE_EVENTS_TOUCH_EVENT_H_ +#endif // WEBF_CORE_EVENTS_TOUCH_EVENT_H_ diff --git a/bridge/core/events/pointer_event.cc b/bridge/core/events/pointer_event.cc index ea6d500e9f..7227929558 100644 --- a/bridge/core/events/pointer_event.cc +++ b/bridge/core/events/pointer_event.cc @@ -7,55 +7,52 @@ namespace webf { -PointerEvent *PointerEvent::Create(ExecutingContext *context, - const AtomicString &type, - ExceptionState &exception_state) { +PointerEvent* PointerEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { return MakeGarbageCollected(context, type, exception_state); } -PointerEvent *PointerEvent::Create(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state) { +PointerEvent* PointerEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) { return MakeGarbageCollected(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, ExceptionState& exception_state) + : MouseEvent(context, type, exception_state) {} -} - -PointerEvent::PointerEvent(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state) : - MouseEvent(context, type, initializer, exception_state), - height_(initializer->height()), - is_primary(initializer->isPrimary()), - pointer_id_(initializer->pointerId()), - pointer_type_(initializer->pointerType()), - pressure_(initializer->pressure()), - tangential_pressure_(initializer->tangentialPressure()), - tilt_x_(initializer->tiltX()), - tilt_y_(initializer->tiltY()), - twist_(initializer->twist()), - width_(initializer->width()) { -} +PointerEvent::PointerEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) + : MouseEvent(context, type, initializer, exception_state), + height_(initializer->height()), + is_primary(initializer->isPrimary()), + pointer_id_(initializer->pointerId()), + pointer_type_(initializer->pointerType()), + pressure_(initializer->pressure()), + tangential_pressure_(initializer->tangentialPressure()), + tilt_x_(initializer->tiltX()), + tilt_y_(initializer->tiltY()), + twist_(initializer->twist()), + width_(initializer->width()) {} -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), - pointer_type_(AtomicString(ctx(), native_pointer_event->pointerType)), - 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) {} +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), + pointer_type_(AtomicString(ctx(), native_pointer_event->pointerType)), + 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_; @@ -92,4 +89,4 @@ bool PointerEvent::IsPointerEvent() const { return true; } -} \ No newline at end of file +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/pointer_event.h b/bridge/core/events/pointer_event.h index 98f70a0291..02efbb0b40 100644 --- a/bridge/core/events/pointer_event.h +++ b/bridge/core/events/pointer_event.h @@ -13,24 +13,24 @@ namespace webf { struct NativePointerEvent; class PointerEvent : public MouseEvent { - DEFINE_WRAPPERTYPEINFO(); - public: + DEFINE_WRAPPERTYPEINFO(); - using ImplType = UIEvent *; + public: + using ImplType = UIEvent*; - static PointerEvent *Create(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state); + static PointerEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); - static PointerEvent *Create(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state); + static PointerEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); - explicit PointerEvent(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state); + explicit PointerEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); - explicit PointerEvent(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state); + explicit PointerEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); explicit PointerEvent(ExecutingContext* context, const AtomicString& type, NativePointerEvent* native_pointer_event); @@ -48,19 +48,18 @@ class PointerEvent : public MouseEvent { 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_; + 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_ +#endif // WEBF_CORE_EVENTS_TOUCH_EVENT_H_ diff --git a/bridge/core/events/touch_event.cc b/bridge/core/events/touch_event.cc index ef27c12fed..d12d0a2b47 100644 --- a/bridge/core/events/touch_event.cc +++ b/bridge/core/events/touch_event.cc @@ -9,42 +9,37 @@ namespace webf { -TouchEvent *TouchEvent::Create(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state) { +TouchEvent* TouchEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { return MakeGarbageCollected(context, type, exception_state); } -TouchEvent *TouchEvent::Create(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state) { +TouchEvent* TouchEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) { return MakeGarbageCollected(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 &initializer, - ExceptionState &exception_state) : UIEvent(context, type, initializer, exception_state) {} - -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), - changed_touches_(MakeGarbageCollected(context, - static_cast(native_touch_event->changedTouches))), - target_touches_(MakeGarbageCollected(context, - static_cast(native_touch_event->targetTouches))), - touches_(MakeGarbageCollected(context, static_cast(native_touch_event->touches))) {} +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& initializer, + ExceptionState& exception_state) + : UIEvent(context, type, initializer, exception_state) {} + +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), + changed_touches_( + MakeGarbageCollected(context, static_cast(native_touch_event->changedTouches))), + target_touches_( + MakeGarbageCollected(context, static_cast(native_touch_event->targetTouches))), + touches_(MakeGarbageCollected(context, static_cast(native_touch_event->touches))) {} bool TouchEvent::altKey() const { return alt_key_; @@ -61,22 +56,22 @@ bool TouchEvent::shiftKey() const { return shift_key_; } -TouchList *TouchEvent::changedTouches() const { +TouchList* TouchEvent::changedTouches() const { return changed_touches_; } -TouchList *TouchEvent::targetTouches() const { +TouchList* TouchEvent::targetTouches() const { return target_touches_; } -void TouchEvent::Trace(GCVisitor *visitor) const { +void TouchEvent::Trace(GCVisitor* visitor) const { visitor->Trace(touches_); visitor->Trace(changed_touches_); visitor->Trace(target_touches_); UIEvent::Trace(visitor); } -TouchList *TouchEvent::touches() const { +TouchList* TouchEvent::touches() const { return touches_; } @@ -84,4 +79,4 @@ bool TouchEvent::IsTouchEvent() const { return true; } -} +} // namespace webf diff --git a/bridge/core/events/touch_event.h b/bridge/core/events/touch_event.h index 59a742774e..08e9bf2ece 100644 --- a/bridge/core/events/touch_event.h +++ b/bridge/core/events/touch_event.h @@ -5,33 +5,33 @@ #ifndef WEBF_CORE_EVENTS_TOUCH_EVENT_H_ #define WEBF_CORE_EVENTS_TOUCH_EVENT_H_ -#include "ui_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: + DEFINE_WRAPPERTYPEINFO(); - using ImplType = TouchEvent *; + public: + using ImplType = TouchEvent*; - static TouchEvent *Create(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state); + static TouchEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); - static TouchEvent *Create(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state); + static TouchEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); - explicit TouchEvent(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state); + explicit TouchEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); - explicit TouchEvent(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state); + explicit TouchEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); explicit TouchEvent(ExecutingContext* context, const AtomicString& type, NativeTouchEvent* native_touch_event); @@ -43,7 +43,7 @@ class TouchEvent : public UIEvent { TouchList* targetTouches() const; TouchList* touches() const; - void Trace(GCVisitor *visitor) const override; + void Trace(GCVisitor* visitor) const override; bool IsTouchEvent() const override; @@ -57,6 +57,6 @@ class TouchEvent : public UIEvent { Member touches_; }; -} +} // namespace webf -#endif //WEBF_CORE_EVENTS_TOUCH_EVENT_H_ +#endif // WEBF_CORE_EVENTS_TOUCH_EVENT_H_ diff --git a/bridge/core/events/transition_event.cc b/bridge/core/events/transition_event.cc index b8641dfc6a..7e6e698fd3 100644 --- a/bridge/core/events/transition_event.cc +++ b/bridge/core/events/transition_event.cc @@ -7,40 +7,39 @@ namespace webf { -TransitionEvent *TransitionEvent::Create(ExecutingContext *context, - const AtomicString &type, - ExceptionState &exception_state) { +TransitionEvent* TransitionEvent::Create(ExecutingContext* context, + const AtomicString& type, + ExceptionState& exception_state) { return MakeGarbageCollected(context, type, exception_state); } -TransitionEvent *TransitionEvent::Create(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state) { +TransitionEvent* TransitionEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) { return MakeGarbageCollected(context, type, initializer, exception_state); } -TransitionEvent::TransitionEvent(ExecutingContext *context, const AtomicString &type, ExceptionState &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 &initializer, - ExceptionState &exception_state) : - Event(context, type, initializer), - elapsed_time_(initializer->elapsedTime()), - property_name_(initializer->propertyName()), - pseudo_element_(initializer->pseudoElement()) { -} - -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), - property_name_(AtomicString(ctx(), native_transition_event->propertyName)), - pseudo_element_(AtomicString(ctx(), native_transition_event->pseudoElement)) { -} +TransitionEvent::TransitionEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) + : Event(context, type, initializer), + elapsed_time_(initializer->elapsedTime()), + property_name_(initializer->propertyName()), + pseudo_element_(initializer->pseudoElement()) {} + +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), + property_name_(AtomicString(ctx(), native_transition_event->propertyName)), + pseudo_element_(AtomicString(ctx(), native_transition_event->pseudoElement)) {} double TransitionEvent::elapsedTime() const { return elapsed_time_; @@ -58,4 +57,4 @@ bool TransitionEvent::IsTransitionEvent() const { return true; } -} \ No newline at end of file +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/transition_event.h b/bridge/core/events/transition_event.h index 86b86505a7..a07f20899a 100644 --- a/bridge/core/events/transition_event.h +++ b/bridge/core/events/transition_event.h @@ -13,24 +13,24 @@ namespace webf { struct NativeTransitionEvent; class TransitionEvent : public Event { - DEFINE_WRAPPERTYPEINFO(); - public: + DEFINE_WRAPPERTYPEINFO(); - using ImplType = TransitionEvent *; + public: + using ImplType = TransitionEvent*; - static TransitionEvent *Create(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state); + static TransitionEvent* Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); - static TransitionEvent *Create(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state); + static TransitionEvent* Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); - explicit TransitionEvent(ExecutingContext *context, const AtomicString &type, ExceptionState &exception_state); + explicit TransitionEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state); - explicit TransitionEvent(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state); + explicit TransitionEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); explicit TransitionEvent(ExecutingContext* context, const AtomicString& type, @@ -48,6 +48,6 @@ class TransitionEvent : public Event { AtomicString pseudo_element_; }; -} +} // namespace webf -#endif //WEBF_CORE_EVENTS_TRANSITION_EVENT_H_ +#endif // WEBF_CORE_EVENTS_TRANSITION_EVENT_H_ diff --git a/bridge/core/events/ui_event.cc b/bridge/core/events/ui_event.cc index cce03604ef..a53bd445fa 100644 --- a/bridge/core/events/ui_event.cc +++ b/bridge/core/events/ui_event.cc @@ -3,22 +3,22 @@ */ #include "ui_event.h" -#include "core/frame/window.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) { +UIEvent* UIEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { return MakeGarbageCollected(context, type, exception_state); } -UIEvent *UIEvent::Create(ExecutingContext *context, - const AtomicString &type, +UIEvent* UIEvent::Create(ExecutingContext* context, + const AtomicString& type, double detail, - Window *view, + Window* view, double which, - ExceptionState &exception_state) { + ExceptionState& exception_state) { return MakeGarbageCollected(context, type, detail, view, which, exception_state); } @@ -33,32 +33,31 @@ UIEvent* UIEvent::Create(ExecutingContext* context, UIEvent::UIEvent(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) : Event(context, type) {} -UIEvent::UIEvent(ExecutingContext *context, - const AtomicString &type, +UIEvent::UIEvent(ExecutingContext* context, + const AtomicString& type, double detail, - Window *view, + Window* view, double which, - ExceptionState &exception_state) + ExceptionState& exception_state) : Event(context, type), detail_(detail), view_(view), which_(which) {} -UIEvent::UIEvent(ExecutingContext *context, - const AtomicString &type, - const std::shared_ptr &initializer, - ExceptionState &exception_state) +UIEvent::UIEvent(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) : Event(context, type), detail_(initializer->detail()), view_(initializer->view()), which_(initializer->which()) {} -UIEvent::UIEvent(ExecutingContext *context, const AtomicString &type, NativeUIEvent *native_ui_event) : - Event(context, type, &native_ui_event->native_event), - detail_(native_ui_event->detail), - view_(DynamicTo(BindingObject::From(static_cast(native_ui_event->view)))), - which_(native_ui_event->which) { -} +UIEvent::UIEvent(ExecutingContext* context, const AtomicString& type, NativeUIEvent* native_ui_event) + : Event(context, type, &native_ui_event->native_event), + detail_(native_ui_event->detail), + view_(DynamicTo(BindingObject::From(static_cast(native_ui_event->view)))), + which_(native_ui_event->which) {} double UIEvent::detail() const { return detail_; } -Window *UIEvent::view() const { +Window* UIEvent::view() const { return view_; } @@ -70,7 +69,7 @@ bool UIEvent::IsUiEvent() const { return true; } -void UIEvent::Trace(GCVisitor *visitor) const { +void UIEvent::Trace(GCVisitor* visitor) const { visitor->Trace(view_); Event::Trace(visitor); } diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 2d516ac6a0..b2201898df 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -320,7 +320,7 @@ void ExecutingContext::DispatchGlobalUnhandledRejectionEvent(ExecutingContext* c void ExecutingContext::DispatchGlobalRejectionHandledEvent(ExecutingContext* context, JSValue promise, JSValue error) { // Trigger rejectionhandled event. - DispatchPromiseRejectionEvent(event_type_names::krejectionhandled, context, promise, error); + DispatchPromiseRejectionEvent(event_type_names::krejectionhandled, context, promise, error); } std::unordered_map ExecutingContext::pluginByteCode{}; diff --git a/bridge/core/frame/window.h b/bridge/core/frame/window.h index 6fcdf85559..fe9ed1429c 100644 --- a/bridge/core/frame/window.h +++ b/bridge/core/frame/window.h @@ -58,15 +58,12 @@ class Window : public EventTargetWithInlineData { template <> struct DowncastTraits { - static bool AllowFrom(const EventTarget& event_target) { - return event_target.IsWindowOrWorkerGlobalScope(); - } + static bool AllowFrom(const EventTarget& event_target) { return event_target.IsWindowOrWorkerGlobalScope(); } static bool AllowFrom(const BindingObject& binding_object) { return binding_object.IsEventTarget() && DynamicTo(binding_object)->IsWindowOrWorkerGlobalScope(); } }; - } // namespace webf #endif // BRIDGE_WINDOW_H diff --git a/bridge/core/input/touch.cc b/bridge/core/input/touch.cc index f35fcd8886..1ff461e68b 100644 --- a/bridge/core/input/touch.cc +++ b/bridge/core/input/touch.cc @@ -7,23 +7,23 @@ namespace webf { -Touch *Touch::Create(ExecutingContext *context, ExceptionState &exception_state) { +Touch* Touch::Create(ExecutingContext* context, ExceptionState& exception_state) { return MakeGarbageCollected(context, exception_state); } -Touch *Touch::Create(ExecutingContext *context, - const std::shared_ptr &initializer, - ExceptionState &exception_state) { +Touch* Touch::Create(ExecutingContext* context, + const std::shared_ptr& initializer, + ExceptionState& exception_state) { return MakeGarbageCollected(context, initializer, exception_state); } -Touch *Touch::Create(ExecutingContext *context, NativeTouch *native_touch) { +Touch* Touch::Create(ExecutingContext* context, NativeTouch* native_touch) { return MakeGarbageCollected(context, native_touch); } -Touch::Touch(ExecutingContext *context, ExceptionState &exception_state) : ScriptWrappable(context->ctx()) {} +Touch::Touch(ExecutingContext* context, ExceptionState& exception_state) : ScriptWrappable(context->ctx()) {} -Touch::Touch(ExecutingContext *context, const std::shared_ptr &initializer, ExceptionState &exception_state) +Touch::Touch(ExecutingContext* context, const std::shared_ptr& initializer, ExceptionState& exception_state) : ScriptWrappable(context->ctx()), identifier_(initializer->identifier()), target_(initializer->target()), @@ -38,21 +38,21 @@ Touch::Touch(ExecutingContext *context, const std::shared_ptr &initia 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) {} +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_; @@ -110,7 +110,7 @@ EventTarget* Touch::target() const { return target_; } -void Touch::Trace(GCVisitor *visitor) const { +void Touch::Trace(GCVisitor* visitor) const { visitor->Trace(target_); } diff --git a/bridge/core/input/touch.h b/bridge/core/input/touch.h index b1e83a8d72..c3bd121575 100644 --- a/bridge/core/input/touch.h +++ b/bridge/core/input/touch.h @@ -14,7 +14,7 @@ namespace webf { struct NativeTouch { int64_t identifier; - NativeBindingObject *target; + NativeBindingObject* target; double clientX; double clientY; double screenX; @@ -30,21 +30,21 @@ struct NativeTouch { }; class Touch : public ScriptWrappable { - DEFINE_WRAPPERTYPEINFO(); + DEFINE_WRAPPERTYPEINFO(); public: - using ImplType = Touch *; - static Touch *Create(ExecutingContext *context, ExceptionState &exception_state); - static Touch *Create(ExecutingContext *context, - const std::shared_ptr &initializer, - ExceptionState &exception_state); - static Touch *Create(ExecutingContext *context, NativeTouch *native_touch); + using ImplType = Touch*; + static Touch* Create(ExecutingContext* context, ExceptionState& exception_state); + static Touch* Create(ExecutingContext* context, + const std::shared_ptr& 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 &initializer, - ExceptionState &exception_state); - explicit Touch(ExecutingContext *context, NativeTouch *native_touch); + explicit Touch(ExecutingContext* context, ExceptionState& exception_state); + explicit Touch(ExecutingContext* context, + const std::shared_ptr& initializer, + ExceptionState& exception_state); + explicit Touch(ExecutingContext* context, NativeTouch* native_touch); double altitudeAngle() const; double azimuthAngle() const; @@ -61,7 +61,7 @@ class Touch : public ScriptWrappable { double screenY() const; EventTarget* target() const; - void Trace(GCVisitor *visitor) const override; + void Trace(GCVisitor* visitor) const override; private: double altitude_angle_; diff --git a/bridge/core/input/touch_list.cc b/bridge/core/input/touch_list.cc index 0435851c1a..6e6b588648 100644 --- a/bridge/core/input/touch_list.cc +++ b/bridge/core/input/touch_list.cc @@ -6,20 +6,18 @@ namespace webf { -TouchList::TouchList(ExecutingContext *context, NativeTouchList *native_touch_list) : - ScriptWrappable(context->ctx()), - native_touch_list_(native_touch_list) { -} +TouchList::TouchList(ExecutingContext* context, NativeTouchList* native_touch_list) + : ScriptWrappable(context->ctx()), native_touch_list_(native_touch_list) {} uint32_t TouchList::length() const { return values_.size(); } -Touch *TouchList::item(uint32_t index, ExceptionState &exception_state) const { +Touch* TouchList::item(uint32_t index, ExceptionState& exception_state) const { return values_[index]; } -bool TouchList::SetItem(uint32_t index, Touch *touch, ExceptionState &exception_state) { +bool TouchList::SetItem(uint32_t index, Touch* touch, ExceptionState& exception_state) { if (index >= values_.size()) { values_.emplace_back(touch); } else { diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 3701628fd3..fab9fa1679 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -11,8 +11,8 @@ #include "core/frame/window.h" #include "core/html/html_html_element.h" #include "core/html/parser/html_parser.h" -#include "foundation/logging.h" #include "event_factory.h" +#include "foundation/logging.h" #include "page.h" #include "polyfill.h" diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 9a762f35d4..252564a52b 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -78,8 +78,7 @@ class BindingObject; struct NativeBindingObject; template -struct NativeValueConverter> - : public NativeValueConverterBase> { +struct NativeValueConverter> : public NativeValueConverterBase> { static NativeValue ToNativeValue(T* value) { return Native_NewPtr(JSPointerType::Others, value); } static NativeValue ToNativeValue(BindingObject* value) { return Native_NewPtr(JSPointerType::Others, value->bindingObject()); From 7bf863753570ea109b2b8e54d078a4fa27af33cc Mon Sep 17 00:00:00 2001 From: andycall Date: Thu, 8 Sep 2022 18:33:40 +0800 Subject: [PATCH 284/498] fix: fix dispatch event from dart side. --- bridge/core/dom/events/event.cc | 24 ++++++++++++++++++------ bridge/core/dom/events/event.h | 1 + webf/lib/src/bridge/binding.dart | 11 ++++++----- webf/lib/src/bridge/native_types.dart | 2 +- webf/lib/src/bridge/native_value.dart | 5 ++--- webf/lib/src/dom/event.dart | 12 ++++++------ 6 files changed, 34 insertions(+), 21 deletions(-) diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 5819489bfe..0f8ac34271 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -17,12 +17,13 @@ Event::Event(ExecutingContext* context, const AtomicString& event_type) ComposedMode::kComposed, std::chrono::system_clock::now().time_since_epoch().count()) {} -Event::Event(ExecutingContext* context, const AtomicString& type, const std::shared_ptr& init) - : ScriptWrappable(context->ctx()), - type_(type), - bubbles_(init->bubbles()), - cancelable_(init->cancelable()), - composed_(init->composed()) {} +Event::Event(ExecutingContext *context, const AtomicString &type, const std::shared_ptr &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, @@ -53,9 +54,20 @@ Event::Event(ExecutingContext* context, const AtomicString& event_type, NativeEv : 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), target_(DynamicTo(BindingObject::From(native_event->target))), current_target_(DynamicTo(BindingObject::From(native_event->currentTarget))) {} diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 36380c0119..e5561638e7 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -41,6 +41,7 @@ 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. diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index fb9287fda2..9886f61d60 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -100,16 +100,17 @@ void _dispatchEventToNative(Event event) { Pointer? pointer = event.currentTarget?.pointer; int? contextId = event.target?.contextId; if (contextId != null && pointer != null) { - if (isEnabledLog) { - print('dispatch event to native side: target: ${event.target} event: $event'); - } - // Call methods implements at C++ side. DartInvokeBindingMethodsFromDart f = pointer.ref.invokeBindingMethodFromDart.asFunction(); Pointer rawEvent = event.toRaw().cast(); bool isCustomEvent = event is CustomEvent; List dispatchEventArguments = [event.type, rawEvent, isCustomEvent ? 1 : 0]; + + if (isEnabledLog) { + print('dispatch event to native side: target: ${event.target} arguments: $dispatchEventArguments'); + } + Pointer method = stringToNativeString('dispatchEvent'); Pointer allocatedNativeArguments = makeNativeValueArguments(dispatchEventArguments); @@ -131,7 +132,7 @@ abstract class BindingBridge { 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'); diff --git a/webf/lib/src/bridge/native_types.dart b/webf/lib/src/bridge/native_types.dart index a873ade407..2516a9fd93 100644 --- a/webf/lib/src/bridge/native_types.dart +++ b/webf/lib/src/bridge/native_types.dart @@ -35,7 +35,7 @@ class RawEvent extends Struct { class NativeTouchList extends Struct { @Int64() external int length; - external Pointer> touches; + external Pointer touches; } class NativeTouch extends Struct { diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index 2d98a657b2..95dae7adc6 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -129,11 +129,10 @@ void toNativeValue(Pointer target, value) { } Pointer makeNativeValueArguments(List args) { - Pointer> buffer = malloc.allocate(sizeOf() * args.length).cast>(); + Pointer buffer = malloc.allocate(sizeOf() * args.length); for(int i = 0; i < args.length; i ++) { - buffer[i] = malloc.allocate(sizeOf()); - toNativeValue(buffer[i], args[i]); + toNativeValue(buffer.elementAt(i), args[i]); } return buffer.cast(); diff --git a/webf/lib/src/dom/event.dart b/webf/lib/src/dom/event.dart index bad479051c..6aea3d4866 100644 --- a/webf/lib/src/dom/event.dart +++ b/webf/lib/src/dom/event.dart @@ -154,6 +154,7 @@ class Event { String type; bool bubbles = false; bool cancelable = false; + bool composed = false; EventTarget? currentTarget; EventTarget? target; int timeStamp = DateTime.now().millisecondsSinceEpoch; @@ -202,6 +203,7 @@ 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, @@ -683,8 +685,7 @@ class Touch { this.azimuthAngle = 0, }); - Pointer toNative() { - Pointer nativeTouch = malloc.allocate(sizeOf()); + void toNative(Pointer nativeTouch) { nativeTouch.ref.identifier = identifier; nativeTouch.ref.target = target.pointer!; nativeTouch.ref.clientX = clientX; @@ -699,7 +700,6 @@ class Touch { nativeTouch.ref.force = force; nativeTouch.ref.altitudeAngle = altitudeAngle; nativeTouch.ref.azimuthAngle = azimuthAngle; - return nativeTouch; } } @@ -724,10 +724,10 @@ class TouchList { Pointer toNative() { Pointer touchList = malloc.allocate(sizeOf()); - Pointer> touches = - malloc.allocate>(sizeOf() * _items.length); + Pointer touches = + malloc.allocate(sizeOf() * _items.length); for (int i = 0; i < _items.length; i++) { - touches[i] = _items[i].toNative(); + _items[i].toNative(touches.elementAt(i)); } touchList.ref.length = _items.length; touchList.ref.touches = touches; From a1fd1c6fb6ba1873a281867873a6ecdd129ab70c Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Thu, 8 Sep 2022 10:34:38 +0000 Subject: [PATCH 285/498] Committing clang-format changes --- bridge/core/dom/events/event.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index 0f8ac34271..ead73f0047 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -17,7 +17,7 @@ Event::Event(ExecutingContext* context, const AtomicString& event_type) ComposedMode::kComposed, std::chrono::system_clock::now().time_since_epoch().count()) {} -Event::Event(ExecutingContext *context, const AtomicString &type, const std::shared_ptr &init) +Event::Event(ExecutingContext* context, const AtomicString& type, const std::shared_ptr& init) : Event(context, type, init->bubbles() ? Bubbles::kYes : Bubbles::kNo, From 8890ba182a9be66c848e1b46db380ab97c2be0d5 Mon Sep 17 00:00:00 2001 From: andycall Date: Thu, 8 Sep 2022 21:07:38 +0800 Subject: [PATCH 286/498] fix: fix returnValue and event factory leak. --- bridge/bindings/qjs/qjs_function.cc | 4 +++- bridge/core/script_state.cc | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 13118b0723..a3d99f1e16 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -27,7 +27,9 @@ ScriptValue QJSFunction::Invoke(JSContext* ctx, const ScriptValue& this_val, int // Free the previous duplicated function. JS_FreeValue(ctx, function_); - return ScriptValue(ctx, returnValue); + ScriptValue result = ScriptValue(ctx, returnValue); + JS_FreeValue(ctx, returnValue); + return result; } void QJSFunction::Trace(GCVisitor* visitor) const { diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 94e28d8437..41a0cb8b82 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -5,6 +5,7 @@ #include "script_state.h" #include "html_element_factory.h" #include "names_installer.h" +#include "event_factory.h" namespace webf { @@ -46,8 +47,8 @@ ScriptState::~ScriptState() { if (--runningContexts == 0) { // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. names_installer::Dispose(); - ; HTMLElementFactory::Dispose(); + EventFactory::Dispose(); JS_FreeRuntime(runtime_); runtime_ = nullptr; From 1d0e4bd7c211e44e1aca0edb9f95d8dfa5eca7a1 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Thu, 8 Sep 2022 13:09:04 +0000 Subject: [PATCH 287/498] Committing clang-format changes --- bridge/core/script_state.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 41a0cb8b82..98ea95273b 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -3,9 +3,9 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "script_state.h" +#include "event_factory.h" #include "html_element_factory.h" #include "names_installer.h" -#include "event_factory.h" namespace webf { From 9fbc7c81c149f118308742d59aeeb1c2c25c7db4 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Fri, 9 Sep 2022 00:51:15 +0800 Subject: [PATCH 288/498] Test: animation sepc --- .../animation-delay-001-manual.html.1.png | Bin 0 -> 19046 bytes .../animation-delay-001-manual.html.2.png | Bin 0 -> 19087 bytes .../animation-delay-002-manual.html.1.png | Bin 0 -> 16699 bytes .../animation-delay-002-manual.html.2.png | Bin 0 -> 16721 bytes .../animation-delay-003-manual.html.1.png | Bin 0 -> 14580 bytes .../animation-delay-003-manual.html.2.png | Bin 0 -> 14571 bytes .../animation-direction-001-manual.html.1.png | Bin 0 -> 18774 bytes .../animation-direction-001-manual.html.2.png | Bin 0 -> 18896 bytes .../animation-direction-001-manual.html.3.png | Bin 0 -> 18790 bytes .../animation-direction-002-manual.html.1.png | Bin 0 -> 19352 bytes .../animation-direction-002-manual.html.2.png | Bin 0 -> 19450 bytes .../animation-direction-002-manual.html.3.png | Bin 0 -> 19352 bytes .../animation-direction-003-manual.html.1.png | Bin 0 -> 18781 bytes .../animation-direction-003-manual.html.2.png | Bin 0 -> 18906 bytes .../animation-direction-003-manual.html.3.png | Bin 0 -> 18759 bytes .../animation-direction-004-manual.html.1.png | Bin 0 -> 18688 bytes .../animation-direction-004-manual.html.2.png | Bin 0 -> 18688 bytes .../animation-direction-004-manual.html.3.png | Bin 0 -> 18688 bytes .../animation-duration-001-manual.html.1.png | Bin 0 -> 17506 bytes .../animation-duration-001-manual.html.2.png | Bin 0 -> 17542 bytes .../animation-duration-002-manual.html.1.png | Bin 0 -> 16406 bytes .../animation-duration-005-manual.html.1.png | Bin 0 -> 12037 bytes .../animation-duration-006-manual.html.1.png | Bin 0 -> 12037 bytes .../animation-duration-007-manual.html.1.png | Bin 0 -> 15606 bytes .../animation-duration-008-manual.html.1.png | Bin 0 -> 15606 bytes .../animation-direction-001-manual.html | 54 ++++++++++++++++++ .../animation-direction-002-manual.html | 51 +++++++++++++++++ .../animation-direction-003-manual.html | 53 +++++++++++++++++ .../animation-direction-004-manual.html | 51 +++++++++++++++++ .../animation-duration-001-manual.html | 46 +++++++++++++++ .../animation-duration-002-manual.html | 39 +++++++++++++ .../animation-duration-003-manual.html | 48 ++++++++++++++++ .../animation-duration-004-manual.html | 48 ++++++++++++++++ .../animation-duration-005-manual.html | 50 ++++++++++++++++ .../animation-duration-006-manual.html | 50 ++++++++++++++++ .../animation-duration-007-manual.html | 38 ++++++++++++ .../animation-duration-008-manual.html | 38 ++++++++++++ webf/lib/src/css/animation.dart | 8 ++- webf/lib/src/css/css_animation.dart | 2 +- webf/lib/src/css/values/calc.dart | 7 +-- 40 files changed, 576 insertions(+), 7 deletions(-) create mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.3.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.3.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.3.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.3.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-002-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-005-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-007-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-008-manual.html.1.png create mode 100644 integration_tests/specs/css/css-animations/animation-direction-001-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-direction-002-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-direction-003-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-direction-004-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-duration-001-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-duration-002-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-duration-003-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-duration-004-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-duration-005-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-duration-006-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-duration-007-manual.html create mode 100644 integration_tests/specs/css/css-animations/animation-duration-008-manual.html diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3736f7351e53fafc6250db148787e390f98f14 GIT binary patch literal 19046 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 KoTF&=@c#gDcdl0e literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..fcf3b7f188fdf02b4ad2adbfd47f23af35f88173 GIT binary patch literal 16699 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.2.png new file mode 100644 index 0000000000000000000000000000000000000000..9a6b072d6fc000dd1b64a4acb64dd7a1a72050f1 GIT binary patch literal 16721 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..7b11c12730ae6d3dff08003fc9917c20a9b5d5d1 GIT binary patch literal 14580 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 N7-g;71vei&{~z^z1epK; literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.2.png new file mode 100644 index 0000000000000000000000000000000000000000..dd647b5bc35d08ae95cd8ad6f00d7e3170231145 GIT binary patch literal 18896 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.2.png new file mode 100644 index 0000000000000000000000000000000000000000..b3607f02057a194ce6a1516f115fbc9d2d3a6ab5 GIT binary patch literal 19450 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%0Al7kFRB3tNt7Mi$Y^@^QT0hp;XPkyHc39T zWMqCYr!{QyvgDe>xiFpuG4kSjr%!epC2oq+(+(9dK7CxAly1%$W*-?IP}H!Uz6}lN z%C&1}#=Li8Jv>{+P5~MI^5u)l0RNvhTV>UrpSqEJkLUjW{v5S>z&2Ki={3@h9rZo& z2F8htxU7!8&&}oj+0gLqUH}QQGKP=&_SB}`M}cD?Ed5)lMz4vUS})yDM${kXKb4+4 zma(?dH{xqEf(nLIRQc`IZ!-D2&BnT(MiiBljIhBnX=!P-2Hb%;)yG=&rUN2oS$3;X z=0>&tXKS0AA6I@Jh~P1n^6{zKkGP?nOaR+=y;We00J>ATuB~LmiWAa5FdZE>-gSM3lRuCwD?ygVq*ENJNauMU* zODnIc1ioejGfpz_z;#u-%B$34=QeOGK&{E8R+`YbO%GvO8r26;)&jSx{2T#zC}a)4e;y2W?P(CMrmSXPL5q#UslCy zctuikSPve<+SBFDhX8AhcGo5r=xb4W3CdK@o?3A`6wVtzKLp^6KU`~fE-^$3IDi|r zN4=-~9RPHBopP5an}@K?*di>s|}fu1FduE_dSV?8h>{~rbl zvx<=^Q0$f~xihn~*HM((>c**4av6wNcOM-Y+2z}|!>q)3FLyz`k$ED(#U)QejMsX2 zGd85j%X+Bz2_P^{giOG0l)b1GWbaNGM0qC7RIMeUNA`Og(+gGIK&0%K`gvNn|4vVv zU>N`O3R6o40>JsF^%{dx#Bq3Z)Z>0p+^a;R(EKBS1k3pEpLhVDP*hb_EeO*KKKtr1 zD{8iF{9vdcaXCR)h_a(s=6vkgx#y?w2@&Wml3wfe)RT3j5*HAe5n%;Qg@lBJBtkBZ z0YdW!UC?5~*0#1T;Jv(C>HO6}`zP$zAV4pHP!=FE#JY}>^P06?xGop-%#sNYAK%K> zS3^f9Gh1iV>8J0Xt3IjRdJZMMqL9aj@}k}ZGiZS z@!@fCoapxg)8`6f*Zj89KZDU!aiN2cV3CVqg&#F{j2tU-ehfu2vADjA=| z{4%Qzlkbg72E+G)<4lX9>mB$xVdsUbuy?M5+TY#C3OZgIUK>pH`gGL{&=zxNZik{* zM}AH>hx2z~cXp^fD)lZ8x6b}f2{t6Uz4gtD-}-QR4Npk`CcR5*zhbyp%#tnzay zHtEy4mm}7TJ2+rb-`JS3({uEXN3#xoMOp}^H*Wb|f4>IKu-#8_r@0TvWf)r5oP=Zd z!_`Z#XKx5j7p;zztJC)Z#X5tm|G;NVtz!=_N=ZpUU!AnHwAyUrz`7RhWwaSaS7Um0 ztlt;m*;-{yNNVWM1P^}axG|LCzNQDe(tB1{LnD=BXM4N%kVh4l6&eJu+_jd~;nGA~ z{h6#SXnRqyy=r%~i?c&$cq9v+_&`l3g@uwy0)g)~y~CC{a-tO(0uZ%zEuZg|Hr^_@ z4QCK?YU&h{p?s6K$Yp?|CXUC}S&q$qBvU+KbP)L%S%4iHl8k~ zo~d$C%}by}zW(rm3_Aw9NI@@*qa3`W2VPzk$tLQC10C9`T??T70<~R(ryhSVFzN@& z%g;9y&0@N*lvvWbFL;Ce3~&L2xSIfLdI-Nm*8$F=?3k~b(q$3F$pGvN*;!S$eq`aj zGEk5*@|oR)o(ln~%XqpeR3pzwI?}Qnr5Pxb0Mx&`znHTQk{`l4Qe!hfXZzMYdJBrz zW_w5TmR&*bA{H(-*PoX-BlCPV&Qne~;md~)$jcuHNVTmcUrgVFfQ0g$wVj;@*4CN( z2Y`!lkQXmsF1LNI>~%&S%F3ShQ)W{$)Am%|YLJfmC-5pJ18Nn5V#-Z$tg_$yC=X(&{Y9Hk}6KVD#KHkoiuFc_}#S}XP zL>2YfpK|^yn^m)|rk#`CsKt9m35K^+9JQ$OL_wiE(`#>~RcMYQFE5V@_MoQrm)Ku{ z4IEH?IL)=TwpIfT88vN|28)PtDmG+ZB`0n5^8@Rt-L#p{$f=S9?uDwy$lP!#uUd|t z&CdCTe7RL`mTxe{N8(^*MeP;#jA2UkM1AG?ZmJXD9iZ8K;9gdA1; z>Y6Jr@e8wg`-EZjDRrx7n$wt{O!#4^Q{Rm#?!Dk$gpo6{Ae>u7eDlwz<-mfWd(NE| z`Or+vk5&KPir6S#Qo|O03Fd{F>jm^LbUPdaUA({4F=d~m3Pu}vGTKva#+9$};FzOkbK5J|?d%bd)(;f&9P> z9e`dadULZsR`C;o3QD=bX&NZy-NRekmAv%cU?BIsHfa(sniqX{6+y$Ky zy?s%DSb{Fm8KgY_nFUy)L7lBu{*18JdU|1})MuwKMJ7P(Y%x#DZrwOH1b5yD@CtPp z4!64GQSL2y?;gMJ5`0G z7%~)7@MMCt7H$$Xc&PQg$PEn*Q8He&J4qBHjZ70yy+DZx7FpBpm zYBM~xdku_AuBLz?eU2>Wz0V|n((d8 z4FUC}0*n~dI8y1U?wu9Z*Q<5Acql3b;2Vzl#TSyZZ@4Jj-@=W=Xf+ zaNz2)wQ@<3P2=FT!Y0*QZ>hP8g?x*9OC7w+OF6WQQ^SvL4C%SKl^~Y^c-ZU|XsiV}<43!%l=Mvf3F*mH1`0bl>#d0^A zo`V2o4DdtH_t-1RJ1OSZF!T+3u)_fXfp=vL9x{xPGa#kx-LcD4hy@@P0MB0w8dvr4 zK3moCz7@KCz=4vMlKS~Ft`h$>&a$6Fcs0N;v2mdUiS#j_gLA&eoALa0uSw{Gpi>{W zSKQDjbGiYdQUWc!k2ipF#Mk6`Nzb2(dWhSXqu&TTckS1ekea%>L>bakO>^M=K79PR z4(<#k*&zF=FJN1%)rrlSnIEmoU-(bn`LC!7kkK@{JM~&$Gs;voUbBoX#NpHG6>v;j z7<00+*1)*42bt_-ZXN&ZVm}`s2EdKT94w{=PR0Tnd0KfBBbMw6nwXCL|FGCRh zkSTpNbrKcCzL~#<#X4(TP$2KY3jeACTztVVEszO*1n=bpPWwk=G}^FS)Yl#TS@jjj z2tpMnt|oSMl!INFMTFFploOyR{c3CD+}!n{<|sd({_7Uk+jLvw?Cid~+p*%_bl_fv zdbDpZ07QC26iq`H0EdXwHsN$ETuF%1F+cS%V}jX9su?@!kdSpg;zAg0+u2xC{j#U>92T zk-%M}X%%o(`2>2apr+L_HJ-!VObu~|Oa;Gy)AbM**X@kI>j+!o@%-*O}olU zN-|=Q%LFXb~uoZ?Jb3%9K>ZUm_aBiK`xp0)rOTh%6@rw5^$LQOY=sN;-mO7O&h)qr#IZ*>ADCGlW7_Dqe8y(PkOw;T&mEuym=6?clmxhl+Va;g+ONnApmU1xh0H$BB zI^1~7W7*B>>_M0$)DK=w_Vs>-c@|UDXsH+Qhco4V`8JLe14%0y(1s@)IIrpP{5E^XH zP+jd09&?jJ@G{R6QL&Q&%vQlr09uB4NSeiOwcivJ+P<*{?U#c3Vh0zo$@d z^tP$?xjqnovXK=59rdR}c|BLNyXN_J^nk5pnWku9Y-5qH$M!0$z9sZ$3gKNR&kq=I z6WkekN>5G>#Q>#!mT6^Gzll-29!!)oNS^PB4`^7-fSw;OlY-0gO*us_YQ29*zIpIN zw(J#F)`z^A-)ttPb=7+NEr~G1=I}Bmrl*tKPdRTdX-4BLvl^q~(@JGZQCj|vbVlRX z&?)wI@dNfxpFVwnc>qwNJ4gTIx$aGqtl#Q;B{_vu_;`4eO`&YiOYf_xg?a2wKqii) z(PWRt3Pn$??K0J0-6@qpdGQUjQ7rzyJpQz!&v)io*1?#vSWzGS zCQXDWEHf-PgjDy@vEyxl6k)QX!$ZCuarLO_*62(WiQ|@xrDiYb&v5*m*?e|< z=`X9~m05dxAcPg0MRii^+uw;-dtffW$>zlrI8b$&@#w@?BbB(9&DAQ(9-)nvXd^$iG%}uPwLlZ^k8}pr9a0 z6_qc-Iw@U0y1EKfI*r}6pL5t9rC#tEEwMj^eE9hBISlkEK&lH{&kyQj2(?>(gnhaB z4QO}DOuLSBbPP^o^Yq;!o^aTL4s>BT>hp00?5wP-XKz_0oeit|a@3rEEE|bGz4PP8 zj|f3~=G)f&erS{p+A`j=YNF1_qr!Z;Ka`;ZR4Xm{`Spt0B}yNxvW(<%`mTtHRd%nX zTN35`a>y6Pmmveo0Yw}S&}<2(bi>6!wyb){eLVON@moPj=>=r)03cV@J{crzR(m>~ zQ;}~Bynz6Sd?QQ@-mA75K3=pGOO?S_; zZw_#?p9n6GRWsHcnVL@L*MYZu*IO1o08+R7?aEOvIoffx_q*bc{Q+zlog5z}*_zt6 z4c^KLW|>X5G%g-?X;HoZxkSiBTLLs2(_I*LP5f2)vYY?q+8zWLBIB#RCn9K652hElNzR$e6$A6&ELYj>dR0k1?v~+XouGU77%q(Z&AUQ<*reT1)HaSCQgJJYM7(% z4>Gxzxw#d@@JfMCqVzmkz^*~!c%3vL{1vF1)DVuic-#68XeS=;U^TtXhl;aCa$bTa zdI|M^0Ux8FwY)2j55!@V{m=+LyNu3syXqqN5@YHU_{!>OB5AE`M$4SzczyGp`gy-|p?ldaiz(S;|hioK%<>zi2svb2sj6%6K(GT&H00GWRj3>O*Ok8S}=}J%5VXqW>Kcv zpm`SCj-tPj`FtKdxMr7+nbxpjLKnZ298aeAi5;$ zwVk;G`Gr8pZ(;309$*#})ZjgWEzjd~xU0m`&>Qs}wdybGH`0pmMdzGVj48 zZq~?cLx}J}6b%Ai!YzZxsCD^dv0_gUClDel;rp?PM}?W^<|QMt_2&9=;!QQAhicjK zGua1Vc3kp-C@VV1R|-HQH|qQ*4Jv?o<@ttDtAdtS;5QI3GBRTM!LFIP>8P)AQPrOe zl66uq@y@Mv+#^lHi3<-8*xGVJ;}1`w`0V#<+#zCux|6`q!62`jH?{VJ? z-VgLH=ElfLNB`tVN=ljl&9%tmR!TY;0spcLT}rY9=|ZxO3We$Z_;T4y2-ptafkhz) zL)4@>EY#`}Y$7QbUtm-kY&xs;R%vjAs#`Z4PZ?e0{k;SXo)u^lM2dnyd_=JvIygw^ zg}?hO%A59_@A%K}%=A4uj{4jStX`w!S%SyPT^IKENrZ;*8JR$2)w6Ye04#(AQWv53 zquIL#{~Rl@a}Er=TJOig{OQ7}sKAFx|1i2oV+*3 za|nZu|C2Thoz`|9DRb@`E_xTUS=~>#D%-_(WB}~ z>>to1*WqP;gLa4}qShd&@D+p<;3Ejcl)F6#AQw6Q@d3k+&+_7L#>Wk0BX6C7Qz4>g z))-)KGzE}raX1wLXB8l_PK?^ha7qN#j=Q5Zs`qB1`wJ~7(4-5z;0d@dG~*BXfsE~r zP!lh(t~yk1{VN!bhL%9weglwq0#xH#sIWkE{Y5-XJV3GMdHRbCjrazTGTztGxfI1` z@vYa{#LNtW(~6bzJOA5oxOMOeGzv5dE-EU@?Y{A}q_h+be7emg1fYubJ9-f+eQ#{2 z$EFLiI}>dOK`Hw}yD585DQM={4#+vsOioUck&{1%wvQg0K@W1=;^obQN!jX1 zK|!$&LFYs`vl18*()i*G(X*+BbJ#9z%`nLEC3ck98(+9^!2m*!Gy=Alwmihpe6+fr z`x%h!(3BPY@pO%f$Ar22IoeyN5pKx4!YLLK@FHnMT-Z@@3ck61CO-lH4uQK?B}a5* z=m7yKqqxpy2^2mNf?C`rbn#?KuuD9b0fws6hkg14XM%tqer?IQOVH(Tt9mT6c3|6J zQ_YUaKSIRSHtm3AXeyO|82o3{mgNRJyl7rS^8|7TYVkRl;ZS2b^fR&U>vRN@u)6k@9`c7-Afv0}M zx>Q2LD$2#w9f}On-4M`UyjtuKggQ6@*r3WP7wG27&ye{_(G_X5b$45NKMne ztJ9>iVy~L>tAea?$^nLRc&Cc(#)9GW3djx&yUqH?lWPnWVee;}N~w}w3A3>gfy}rt z@g6o#t5vQS^&o^Xtp^LwLQv;Eu-_OxH8e*Dnhyl@vwfm~`X}vEa0nGtKp-9m!NI|a znuRTmX*su{;>@6tf#Gd!X?kGUJf>Z)=%X1GN1wJ3{XV=%tmMCEWMlOHm66Q|aqXsK z0EYm)G!)c8W7f|ztiwZ+V<}3ik7Fx?zGr4F5W@1a<%`Kh@trL#EsYR$zr`XSM-2Qg zQ>&fpZV|xC7f%xejfNmP{%3ZJ&;>i`}3HKNkBloKupB( zTqSSwW1(5Y(qQ-L4>}AFGcW~k*Y;A)@BeJX+L#E4H77&d4=p^%->$&{0Q9^EEMKv^ zMQCF0GYBD*U%8JSuK*KMqtb&Pwfk>dXlHd5f5E7Z45+OIS%;wg|I@X{kVy{&9Sfp^ zSuK4(*qX21zD)%#k2ECeg^&Ita+(pMI(jx*xfqV~sKdSfw+!h>oTGySUsry8+78(4f|Z$&zjzCMz>*{ua+@c2Jv=VTf$ARsBIF`ht+y28mx0%1{rYgEu3oxZu~3;QjC--;^EW8nsf`@k{jfog~CfAj`@&U~bdU(apg4Dfxr z&n!=BYo4D%tuB}tG~h;~#~?xWGISOX0I~o_KG2Sg9?^kOy;e`H|D`OuZ(unO5V}US zj~Kx2ASk#52nC?wQg93=W@cW4eaEg-LIx*ZOyj@vqi4n7m<&o}AY6pTh{4{h0j#SC z1_TfWD~2%$OoA##Brm#tdV6&&1en|DGiTBQxMDQbpakpTc*1LlVNJr}sGk#c0dNXn z3zV`^zLDf77eT;nOPe;~qk|OQd(8sVA0s2WBbXMhyQ(OsAfo1!0$I-E41p>IQ+i6z z&?ne0aD0>b>QyP58fZe{^}vZvBRnQeUv7o8=kN8Yz2rxDpe16>l3I7*Y-NG#A60^h z-J2v2mVY#9x=q_R_24#DLmDT~Wm&6y(;$amC(moo0p|{Mx^R!GBCMTHy_A%cKXMra zA=GF}*PfDPE6Li+8%Eo3G=_T->p_0$k~M~Buy^~zr%&JX-WwjTLW-qZR2>2*prctb zP|(n@!oi%LMxlaC$Zy<$loDT#GXyJYA)WyPP{TVWE7`&MwvL}Swr3K2C}!7Db!Fbpe!&hYEkua2ds zVUTY}PaaBXX=z28?n5w@%lU?ibi?dBPz+PUmBk34X`oU%q&re&+sonLS;@1B7aqT( zgr*%tryaym?0XG8`U>`H*6|9U;3z|Ih$*3}&{XNPmL7Nxs5%BBbluEh{}2PvJvQ6n z5+>jg-G-2Xqyi#92H#{7$M_gv#3W39=4;oaQBlFi%%{{6@?UUC$+Sj|wv0p*XNVMj zjEPx#R%vEt_O_*^MJWe_76B0vZOH%D&y>7<3wD`=3f6}@R2@BB0cis?K$r-vUp;eZ!k0CuCK4t2s@dl)rWZp$$Nj`(Gqi-d-sZ7u!XnM62Hb5W81=CFDEc({&phGDI7~?QgZc6x`rHy z!VfOV^PP-8S2qi;l6RgU|CMRHc@I(}va7hyelE?jA-7MBgTTlQ-w7yy|gu1 zm$!IYf?GpiaF6zZFLe!J#OV7nv$hr!I9>y#_o=9d2eH^#F) z(rwi&+_U{G{?}nYuYt<>OSdkrav<$>P9>%+eiJ*dcPe)@mc3Ko1g3xfeEsZObRFq-^Zs=*61LNNW=hqm)!=-6Yuq;=%wyVO(?F=biHS<3wFN zy3#}ul3Qcku}@X}YqvNtqMUV3`3}r}w5D}-ry@>utz*l$E5)ll>pYM^+%QPTG zaams#ydM|(rKCuMrSFgy>|zPONPHzS5RSV@(8cu08qqBA<$rC`wfW0I4u2>46k=m2 z?Dgr)Wr`hAyQeiy55E)1u7*kY-RL;S{YsepqZd2wK2_oB*Vt8N_9ydoZ_9S%6Hbkv zt;wI?`$#Cuwmj;4phetwY7KWSbs%@0TervoFWI%5GLn;Au_sRuPq%SWU)q*>*=4o5 z&d{ST=G51}qrUB@a!0px#NV$T2~Z`w&bV_I?drUor?cE-d79MMN|fBZS$L4WB!VDB z@6g{1kkue1m7hAUUdd6dOAwJgjL72T;D12I?IT8oL~K1c8K;|qo4Iw*n=f9S;zSm!{A3l0sz zWrE~;ENKWyjgwb-ej)BSY1i56^AU2gy?*m$E6fojR!>KwN%fJ{A6tK}7ziK!!B3|6 z_GI-v7u^1M9QTnu6LY!`Ki`S{7I*8^=&Sj+^kN3WXLoS@4YySuV?-9o#jDBS z_U5&xS&8p1xcz=3=+(UcUzG^Xfkhl;ge`!Z1$h`SPx^Ah)q)Tqjd)Q`kepLQ`h32B zi-_C%?We%GAF1Q#4&E9F=k<43Yuxr5?1=MXdp<9vM=m=C(d?%5TF)*IcntQUC5hp5 zuZV0UEo33v=I^?*a+foCQ(je?J8^*g`nqN!* zyw^7{>RZC*cYmj)QaFE}OiDUpWrb9vL9q6wUytRMdz@d2$nRL&nVVd9W@81jgUB$R z{{CN-CKTI{^XCVCdR}LGPk|ThfBy;5oJ~YbjHXe4IJ;_&^M*mjvvSHymoAn4{P5}1 z8xGB)@gKf~$)Z8b5s{I8hlhtUx0inZez@45i)Y%GRbOb@hZsecW==aC?piMnm*#iR zOniTZ=N}NjR=75xG`R6-e_${)^I0YKqGplVT>}GyH5R+o@w>S$I~L{QfB*jVdLZ-k zX&Ax~az!wa~b{N>B(rdm{7+7w6~OT6n7m3+;cdwY97bZ_}{J}V15 zD7bUyj&Fw%1>yt$fR)Zg{SpV(cSa=>#Pt8z2y`ub??1Q+?$)Z`T!O8PVS zk~a<>ewdL~wLbA>J}C(a#H2Ul8;NF#6(cgfzqN3S<_%x`PW;K?8MlM&`mc{-+6#=k zY@+%mZLFCdclMx)6Rwu`9$oJq&cXF^>CRc~aYTst;4W+8z0JyWxojGjK!WoJ2j z>t;mYWAUTScMnws%{sE43EQhFD3E!1dC@f?F zbaZsof;KM`tb`nwuV_7cHZeJgOGQP+V>w#Uj>+eL90*I0>anM#rM22A$7LK>?YN>{ zVA?0YaR8HVOgCkACBk8`A18kxn2?Hd9CpI3m=*!MIkKvQW#Nx41{E8v!oG2LY-b)4 z(TPwa?UgQ$@YBV?Lb;=(`XH*cnHbBo=7eYE4*qZ6^aM+`?JsnHBZpD@8FziHX=x#z zY6$H+(lTu-q1ag&YZxxI?MRS3Q&Lg_OQlR(EsE{Uo`e;IXR77s;vnCLhAy{6^Wq_{ z>y0e%!xERBtlGco&A)zqhuzjuVy%b%RruPZdv9*=#OXIjB*3qEJ%hHm%!S>9Dz}4n z`1&p0veMG$J!#4aLCA!Rv~+@czJZm!y>EItyGD^&fca37Ag!gVs|a>ynV6K6)INru z&y3n)xFkVr26hzsE~PfiFyDAEkL3+{pOz$H7u1g(Z}Z!Wovej==yoJZqc3N_KB(Ta2M!6cc`7qhLO;CXX{h3PY; z@G8<9Gc7&l)sN%F?#s#L3qJ0>A>{JC(q*T;$Xs){%%1SW^+%Ya{jIg7;;|QV?Fp$* zk_q6{`DJ9>v|Ady2>YQfLp@)i5^G;qUtgnpYdp#@?yi;9t?Q5Df+MxD=io(ri;Ig} zjGvX-oaW}{_74iG{e0KEJxPYNuC7kE#_M#6^TyYiPwH=IcyH%OfHK2Pt0zzf!WWjkvpV92a+=TE zw`6AhIXHeKw3z$%{rAtU!VZUNVPBVR&dIq2vxz@3F=5o3aS0BkFC17VUS4J5>ez zy^y zho`W5?%K6$r*Lp^&Jq$z!n846z1mS|s@gJf*>qrF0R6%@m-%m53=8m>^^@i1=E95P z!ysSNdmNtUk2**HS(d+=h7X6evxa_X7oZ1i`3AQ#{d2eU{m_~`9*!8 zTE4-XGS@KuIA~raB?4c*el_{&u{Ez8jZf>4JTsKK@Kiz@B*-fTP}f*y}(@4nH|(ZokFe3-M0B=ll85q~Jj zLn7^;A|kFM{(`nM0wN4zR^t*l5@rK=(sx8|-BLivzJ2@V5MSWbsDig*fwYMsrujpXXye`2W5|Uisp_%u)zS<+4qiU>zpeW@wl6CA@X+-+4~=xS`%(9E*rPwj#&iHA%(N)^xnQwr zq4|Zxd%L^-o}Ng{ZurqrEva}n(a!q%1rCh@tKG7T@?&@7q4y%IKM1)P#Kh>}q(K>z zgksViD%2*jLFDNbdQ(tP5UmF$J!#X2+1|9-mD|xXWAK(gXIf&c_ty38EBdl^WX#NN zc6N3$_-hhGK*pa6%xAlw)sr&g8ruq!PApy7jg8edLe7r2J z(nQlM(~PXFXD{9msnB@hJOV?NhK^yMDK1@XF~Y+l7Pzh@^YA)3^u|}v$`X`PgH4hO+Q9OH18tIsSN) zWp{U1;ED&lEo`4|NArlh{QUEPEzV!PXVhy7PesAU6%nWQWvB419w_21R@1aIdmFPI zY0B*OPDVzoX)2i}j;^iS2~hqnX=`gcEtk%AKUYqBdN*6U@|J1u&GISxKhQ@7u$!!C zBctoRMNQ;Oq6C$0b8$fFs5s7kd$a4Y=}$lhUVS(Tb9Jc#PRw0 zK?CrwUG@v^KC;p3TfrzUU`8cwm2MhNF-dMv1W1VS06I4BKy z`0*A_R8$mNF|W(VqUQ^GKw0}aCEvI$&4~B!-y;-PHi}>J&`eBE`vOqJh)zI*Si-br zLl=4f;lonM3G^FiDOD|FYz=`N*_(<0Y0oq^Hj-ib5AjkmxKveDVg9ccue*FJ`nWKhvGAN-9xfU4NP1f*L~5xp7+Jr`4(mzRf7m^nEv58qW&qXwuK47B3q%a;sSui{l7uFECN z3Pz_8T)d$R?e_kI2S2i_j|0cYb%Uw7aZ?`0<16I6sI08CUYnGL63@iXPX&|(*7-a1 zKKnP4_}D%jWAVNxfV^_;{yrJ5_MituEV(pTxc2k1WxuPLVy~t8H`v$+gOCtSU)D28 zC~*5_fIo;~{Q)xJu{!nXw+!?=xR=ziEX%~a8cuE&ar@34s1i%=`wG30TOw=+q3dOV zKRU#orpVLAOc<{;tNdtm8@Lxr_XbvW97R3YwqL*6FYclf%2j=M+tW)964dwGPAo<9C|>4Vx!DL9#~zZJ!L)!k$Bli^d;ye1S-Kb}g|$X-F_ zr(IhQcc((5J=n*ZO!{*Yuue5?ZI4G}J-xhlrK4DUZS=itPW#%tUx7Z-0FVJa1sa7W z7qP0@hF6lM)!m^CNxNGp!t{vtMLY<J$52W2pqgPXm)PGx(O4mWu=VXJYuwpODG`C`*tIqU#sR{|WC>{l_7)&cniQEG8@H>W|RPr^^U7&xk1-R|!pQdbl zHq(6d>eX3NQW+SmHGrq?40Up7i`3jNPN6S6QR|naS!_Xu(cPNwx&QEC9V}AMnp#_N zUKK`Ck{jm2$;oLPm=U$G!+$l1Agth!A7CC|Xlv+Sr545VpePhz($ri>QuG?@1OS?c zB3s=`sTzehR5H|F!Co;X(YIR}rG@g#t&*wX3-g2z7_QfCSan4xKN-pk3K9_p1_lY* zV_327Y7=d4dHFLQEgSERaJi3rPIG;EDk z^mBH04ggBu?-r7%Q|&%)2ct}glMM+B>=dy$KHT%ox@lc>?87P`H&X7ng6Znn&@%0v zT3=gJj5h^rVr7*B)%4@Y?1FlAQGeMDUEpTEt&@I!euT)6t}dmsj8bN18QAGpHsSYQ zyhy}uy`66RBKx6x?WMlHac>X+C_oVxB>hP2u@R%aMaf#Uc00?@xS_XVx{gdp8&R00xocd*9ul*5A{x@NO!~K7{%e2z$V^x3BR7i*I~< zyn!>PT8_l--d+H}-?Y%n0#=`)Ciu>%wh86y?<|kZSDF7sNr?a)s}z!I*FBqrX-80j z?A>l;%a#V#+IUc5P+8|J9C*KtFIML^$4n)hcGjQ}De$u-E!U`ThWy znrh0*%1Tx1IQ9$Z?tY4LOI`pQcJ_gz)?u%O9hcF+p9;q-_v^ILQTVj&E3b4efY9Dh zLZInh@fH26nzBt@#V`kI0h8X zrD%hKrUq&gr=1NK97Sl&=uw0=V`y7C5VHa_7Cw_`F;eC$e@y8ccO(QK0$P!(T`4r* z{mt8XV}?;gB?tva`JcY-r;;`+)Op}^$%p@;2?$v z-imst@7}+%BcBHHV4|Oszc;E68Zj}+a(@o+zgCZmhGEN|MHaaug{F}+!Y9d{x(8>H zio;7&IR?yJ+<7*6L?eq}IED3xaQvsmjwWxk=IviKcqsk(v*;ffu&=u@56#-F_{k*} z`B+bsF1N*_s1ZSc52G{}Rs%uYcPGwN9BmH?*w2Sy~9*!IF>4j>k^j{QUt>$U6G zK|IujgBE?@+?=MGt=(T_eiY2C|sG`(YW(w0%(*bRV&%9JEJP_&DfR79WolwkozlL_<_2R(bB#F zjStt!iGKL~`$EwJ#ln}G>_Iax)FYi02TaVkOdkZ@2Y%LMU&fs5O2EaAE&i5gGRdio zIm>M~r+_2zXLfd|BB>K}T%X357j)eBl7)VkIku{dje_)7S7JTo6B!waWyybcRVEO& zdTMBth;tsu7UY1P!&leWZThFLOVxa8Mf_K)Pqgn04-~-bTz`1|gMicO=lxi~XB{xl z9n~Hl+#r%{wdw-V|MBv6fMxYj??eSF5b?VHTs_{}pCPpD53egFlvmj=_A3Iuy?b_T zb=i$wB|~9l)PRSk9`GrzBM0Hxv+cp+{QRmJTBT2bP^3Cu*Z@)`6H3R?>6FQQ?VA7E zR71L>H|vUYzvrQJpc@(p<%c{V`a-7E|5TkVjcKbO?O z3Iu)DDvDmn?mh^7YL${7Q&Lj=Lqm%N$C|cFoz`BQCnIAZ!uN&&U!ZqgeFdnI3>Lc# z8<`8$*?*xg+w>nv5v4F&yzs>H=g%iN5IZdNw#-;^7TR8P2H>mGF0}#lLRs~`1|BYM9S{$6LBx-D^%DLX=R(D)@vCwW6LDio+j z5a?%Tk;&dnP2ZwR-Zcyw1xAcT79(kwiu1&?)N(N+k%PsSD3AR*5C?4Q%C_X7<}56^ zeiOyS>}-njB2+Bj>gsACOYbwJltENnlrTR_AD-Se014_xU!Ur;6j0JsR$@~O#T|-7 zg@@>xK4f_oDZfu;`3%w<2jaiFYPB|C7#|?};rcnm*Mc_nW@>wTJH|J%cky&VzVS$v znw}60}?LqDEu$KL_24|9w z&nbJneAgxDAHkRTt~4Ie3rJ{A5pCS)PV-R;%6v57yMNt$0&GZP3kxICKeXh6O)qaUlU)h3u9BN1IT37 zTAb&GO9oR#X*=^<(NO|jied{600%6%xVYU7tN@+9;kN?wtAj#@_Tw48aP;X&9YAH? zuC1LNs(VR_e`aPV2ah>s8v&Q2;|-=4*GAfhO>VQZ%MqhYF%UkT-gSlnB%}pQ_6|rG zAQ)i)p<)m+*lI7O`*v*MJALk6WVZe(o8gtMq#1+O@> z*NV}M%hu4mvo~z1>$Vdx-K?sODvy&Yp*y`1UT0t%D?wXsD zuF!NXJUY>`Y7hTj(s`w?55X7vs`07-d<-~eCh|ozMEH-o>cNFMH$HI`n8}ve20X^W z>3wqQ)M*4TQiet$0lR9}%_ru40H|{f+vv=OOU^^VXGZmD#P@PZre^I@uGxptJpG`N zV=>bR^Imuo{5Mmhuv3d|bux&{`-*T@*Rm>;f%T#?G!_ z{{YkzJjBb}o3`~sQm;J%A}5|FrXkzg%PY1*qXCZ2*W>EE7Q6ZGMgygiyebMf7v`Z$l|z!w9Tj}K12fKM@awiDRrch?qmPNPVPDuPM4$;b%q(!U=zoqJT}r43n|mCZX^F>X z-dGPSKvLNSQ%ME=|21eplVc~x{HTISThy~uIhk2TL{x$}1D#Y$(EWy_uH*@$2V{9(+rQ`+>S}-;`Lu zaD6DcZLpLasf~-E#s@3w{Uf6kqIgjkK4|>}Tdf!UI`>6%zaw@Fy~L9md$x<)0;ItUX(+{{@V$QMg=g&(& z^Uh>h(#WJINaMn-6qf_{D5pnIt@o`^7)Dr;NcT}kLER9ZDP-6xOtNZl`#zC-Cm>Z%+z&z zThFv8x+Ex1G>njupcTKiM&F@G!>lh;GesL~3tF``*w9-~vTD4~5HfBSE)JT`6*i3O zJG6dHj%6z{<8S@_n*z>b^qr&Lr2#-H?uSk-J=EK**<o)D3`FaT!DKIRIqGbxTDf>*^%rouv$}61%{r$;2i`Mp`q0KAf zLW4#he^WiLZpOi)u|xSv@1ss^%nx&O;Udli<*fJWc`pM-_VSBc!xx4)%ty+UD;FOR zY~OoB13(36xsa=Qkw%{RQbVtqc^hVKjIK#CfJWKI#>NUvjuznn@c5p7dm?T7K2b!| zK~7I2Vrc+q>+0(2ck$yj(%-Ry`9w1%yJ5Yvi;HF;XbqS>;7S`Rw!F&4MapZ^og@5c zVdjM*0r7C}n`!Le7dcH6lN$i<&Lnm0{z{JqoyUh%{E!TMluoS?g(ukPANmYF5>N)M z{&q6AB!njBjD9tg(H{0e}+^8)BEXh9y``8&UEeg34dyxs@isf-BA zrpp#&7bZdZ%QsLvIX(gu_*?vUj2CE6&%q>&etyT`_jgct==V~hWCCy=U3&%c@HpTp zCm>l9%gfqd{c!ZdAXNfP871N%#Cbsw<(u}{q2@nSlj20`{CU|xps&~1@OjR^`$F9= zCG|cH3ClQLmZ9mZZ?w*_;{0}kcStFgZw4k5e6Z70oLa2jf;TRM2h$JD51Uda9u2Kh z7gP*2P|YpBx(dKt!eW{a~s_m6^kGPDjJct)-wMd`INhYP9(| z5)w+ZtKqqdnq3t8wG6(IyyeFem*g;Y)&qMpv&E!1=9Kk~4TzewKUMw9yl{Nor-%3; zs3}ib02n_MCIoYH+xtnW?-czb*$8Jvpoc;n1grt;SlU1R5U|0apx_4e z;`__nqLYF)t&oDStXO-Ewav>zrNxw;6V4^`5-FGHOqGd$Cq1O;#w-){RWZ!!Gt!E^ z=D(-6gns4zSKy=i>B@jM9W=SF+q53gjJkvu!?*d&c4+O;l)%>fX?}iwGpL!iyRbUUrWE8k(*;PqPB+ScYgukD^8sz(binyx4-OAFO3Gzo9{YhY-Y1g+6% zw8A;za)W@z45(`Ufq|HOgBDb#2CJjMY~TV&f3~|qDG#H(Kq`I?ZZ9f^M)O-{h3I_S zdE$C#7l57T9fl#kfts%yC4fdzTgvTsdUjQsilSgAv{H*Qo6dIcfqo&2l9a?x1d9ql%{1`SAx*v`&0jsa~x=tAf!=2^PdDvLnU$dAa{#< z0AOeks6zgr-D?rii)np|!x)n|I66|nl3xH3>z~iYyYGK9z&3GnCrj9Ye(O8a(s8?Q z=#N>8b{E!q=%YEW)?<(%5Yo$8L1jx6$+E+snEw9AE%`f6RI~EU9~ihV83rUP8CpKz z)wyN^x3{))tZaxhE_uN2srNcfCQX}{4JIi7uwmMp zF_2m)p}g<~Ir@^lfXL1Kf?ZKO0b^kITRM7rE}(j-mkshUO8U&8znoQEFIt`CWS40k zDdQO4-_8S-{{kWfRzW?QqgogP2JaRd7aR~A{M3>~1ei9;w%^6YWkqG@^;4PS7uZ-1 zL76t8TU=V=7%VVOIv(mhGV1E=JoVXJqPJP4IcxN~4v5w*7!X;3k^pz}qj$Fw;6#5` zlEKz?S~>v6A1#Coel(|zv+A~b*6kt&%0Df{TOc2xbinfJ$D>KGr8kJgp6r4|{RokG@PI(bZq5^|8xT0?C~a>n@}ALDO7jO- z$i5{$`8}T?UTb7?l8GMYgAXNJJQ+IKouY=%6cmQZUlu(e&+ubAh_pY7x=q@&;_dB? z!bX(V0_Up*C~|K6IaOlZQ9%Js=f&g!$}B_mfvF4*Ew*2U%G`zkXkufvjGWX9eIs8A z4#eUy`^(1)pm^gSJ35kSinuPkcM<^WRTwZ7#b0f0GWYJ`L()WN^18XNJ$v4|s`fxG z=tsfu_EQHd)nFExmoAT0SI;@kG~gunp}eNc!>AIxY<=XRKY+kN&q~DcCDsDF40A3v>uAriwGE%8~|}3F=oJ#1l|41eHf%*LZgr z`BYbndwNZMLRP^A9Y=Z7`gAjz*W@j^2yQ7Z0&n&LpkY5Ot=P$-ZH!JaDU<||%5zM5 zZj_r3Y)93-|Ac^;7}X-JfT^SL zYf$riL8_l2t*|Nm>xqMh=F|WRVbEfD4Bk5+Sq_pb0-mBS+q%$=kO$d}l^M|Hsl{9c z(5OzMEtB6}w*o*3bDXS2Cj9jf_&k{54CU)YELK0fj49Fdo#tL#t&k{W;=g|Sw#?uNV zl|%0{*M`c>e}JqGX|Kh8Zu^g!N2qNX-Rs0y;ybCRP(tuVvCu>rf-jGEUTwfJyL#siIRu|1eF-kw@#_f# z^tlQyo~*1a8eOc!ZZ&;5ukr1A-l4zwUy%Bt{(Hy7#B_bQ1g_q%U%w;(NtwsBvl2Ax zTR7(>i^RlAvlVa;xzP4@u^E3TDFlinvQ|gV_jo2O{NHw=eDOuYsodiD2pP$J%Vds2 z;8ti_3(9S->)!f{H&gH=w9>(mTB>U89HeSaUJq>PuhucsyBJq71xf^jnmOko zzLv=KSA^gnzi=&+9o;F8K zYb$p_bp*Dt?`Lv4p5Kx#f=ed^XqPzp00!lBM`zZcnK4jT(5nUW)c~M;(BD7+M#=Bu zw5B}_t#7z6cysS+(T@e==&xg0GK2G(DN%dCH6ZwY4N~?*DB&<4qyWX?9!jA8Qt7+) zwp2gxwtxBZTAa(WVBzxvG==Dvu@C85NvNMa=+%KF2=qihkBH?BV<&EkXmH{;>;GIO z`E)RpHn)$4?)-Vk#Al4Es#{O}Bu2koQBe^gD3&iTD-)_nPKi^o*W4`f5+0cNppRn3mm=nDtz2tnhmuVdmThj*}> zbFZLeO5MAMX3{{Y$rC=S|Aqd5C2Xn0|*Gr+I+tjF94QAR@5LcCga9We*?la{vIa>V`zIqQt$pTb2&U1I|+ey0u1C|Ipyw%7zi}M zy%Kz|sDHu?yp4#M$x)FVpnLJ+1$fkHbY%pDgtf4Z$>``pz{pOBkZ_1;At~B4983YPVhGsDdoU&IPMpBTDUL7>eMsxO1 zJ<+fp?o9uLRUtY{P1rq%RX{#iqbKvP`RTrL(Lod66}aNG zy0(@p=%#7L8?94jCyS5)VC)ldfIb|pVl!O|*PJ>;I3bLke;k;f;lo;zlCyQJn{KSp zwq9m49f&YMJ|Wu~l8#@p+=7}Ku9|N-^#0C>-OX91c-q7jBqZhn_Sx5ZOzA>gzQM%g z4HK6f8~>LeH_QzcbHQbgzd5U2>ZZG}Mr0Q*c%kW8kD~>hcOO6cgDbLiH7D=*)2H31 z6otyq5{fTYTpLPieE9OsE9=k_>`N?ANJ&m+glNBL#k=?K z6P<~BS1^za2nNnQ1xN|9SZT}p_Oq?DS46QCX!uY35jD|k%uzt^=FM!_^@xOogy&2t zii~Z%MZ!PH^rD4Keeb^ck&7mO6W4>T{5$$?`D=+qXB^?-AQ0BTEv4?zF0eMggXC_y zvDT_l3yu;x7T{i!*&avid=|rJKkLq(qRaBARDJjFkxcoVBS-UXaOdI0WL*Gyo}lJ^ zit{PdVu3!3&~ef?|8;S8HYSNGDK9rA;6hRz73AdPx+*hJkwjI;?IPrCqnFg%csH>T zkv6ThSlW#y#!`{@DwdTkzniS37T$F3e=;}b7j6EE@g+b*YS2DbLF8l-5TFK{l;dI? zp5F`f#}kAY20&PVX;4YyJO#yIbDB?zK)g~NX*Pi~gkEq#FP2!eK@W;H9!N5DX0x=( zaM_Bj6i!)Jw#Xa>8UK#>!D2o_z<*PdsxQK!E1#pZv^4av^X{}+g$=K7CAlwFqxK9y zRmg3C5lsdtNcr(?9MZlM$ZpZLhOXz9|JTcIS%ijYOt3>74{Y|&5R5^?z+3W96Dr6n zUH5Ls!S%D0LPba~B+ZOHNOu8bZO@esYEL3)RMC_BCgZGiVt%uddzXl-V4$ zL;*v4e;?*xF^6QDoP8vEoz9i?Z1PUSTgGTa!%WQPah2=quvX+U9b&&Ac{Q&zff^b zB}>cLRt#MMK#t?#s@iDK8BIO@V@%suVvA*fj6S_nu#(*?Y4(8e3|VP>U@v*0Sdr@?<=eTIT5G;Hw5Cq zoA`DRG0mKc2L1Ga-S~%usAMAvHcV1`1z63lq2IA_ah#CZ>c(G$>JkV63IgQC`kzK= zx_JAI>`Hu7VRlI4BtrEe=%@c3ZA{2k9-$P?9N(U4z`7Ll5LgcUF#E4aWbIEQ zmmx_s@5uZ5pS&Ya8!u>+XlAnkxLqLlFS4ZBr17CZQF>U9E5T?;MRdj$JIHNV_)GHH8L_r=Nym!PZ2B*P-HIbi(`?2^iN&^8ZB_}5VKpE}T9w!)`NG?mjyhQ^97AqGpiQ@0a z>V)S2;=rX@)FOkMq4I5sAM{{2knAN%d-`oX)o|LehagLf&+v8>-Bw2XN}VMWWEGG7 z-D513-6bhGaJ95{uQs=>PK08E7&=4$H#6ylgj{`I&_rCh@A?`%F@Wy0FI zllR>{*&e~m8Eak9KvMBPQ+b*r7PzUW#?K&@cmk&-zWpY^89$@@h^gXRPC_07ViW-PcHm+4NsSl_Z^)G{ZuJy2uMOp62{4VE`5KJ-e8A&i{yL3nb3TTLigV$acSZoxtgE1ehtSNXGTa@*q`Z6 zdpFSHtm1oy(i8FH84-Lx7EKR0p4%nSpb0?SZ%o=Q;}{W+Q+moG^LV?I-xVlMW!S8Y zk|Pedw~(`V3b9{pM7!n*87NMBkR2b7^AaOuK3vMCD z2ac5bVL5?k@q=4c9IR(+J|V~3x@ufV1Kq=h{h4_g9G)As#=eM}pVwRW)8DNq4i(il z$Jg+XSjoE*rj|C3rF^;JTV~FP6I^{j+1`{pOr>D8%eYkYm>B6b?;BA#wCWYkBtV#O zMEtxQhpZ7XnHt9POgC=e+m%M+>e{h|tPFaeVAY8E6UX0rCBGjnm0u|k!6CxWS*%&X zhFO(Kd1%39cjZeG#-le1N4-ztkkxC5YS;+(v&t?ZKQAl3gmGFs3IY;yGDXF;8zg_H z)sKeOhhDdd>{=3dmaz_zTVTJ2`+4bg+(S-qR)suYA)J~bUpu`|X1x5b=t;;eG@dy* z!acnrh4Yz_z{`@wvmC)yw7QVRLNHpmjHzj?@JJlj3`mx3D>(M`BKM3(Qcvryc~2y; z&M}|%Y;IpY*u=lmwBdaI#3eR5d-kbiyoqXXss**j;@jQ_;$;z~*9| zFl)*0Omc$J#$L=_1+jCwXFWq$&vr_ybY*1N(p|CU%d|y);GDvnTOSu(W-VFX(?m|( zZ)LC`^c1e#DYTw)%&}aFV_bY`VY$*l53nD0?I=#|OpzXc!W-u!!0mcMNl=xndhv8a zQ1iN_rSZcB`2Cmv@VY9o#19J`D z2YUDX-TVHJ@!|jeHwXS_ruaWU{NF;>|Cc9(6LWSg)zHh|pI}P(KP&UEr2GHnMfm@B z3;gHl|IZDF|E&9eHp725!@vK1p#OA)|L;1&{)xl98vOH6i0<3!EglC7sawoOo@{sV z9-gDoebA05dEtM&!1F!qGtSpj#Q*uD|1ldETk#3G40%su(CL$vmY2%H=z9GxmmNce literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..d9536734769999acd330cef87e066e8f9bdd0f53 GIT binary patch literal 18781 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

    430eZ6P{HH9|Jj`-;5+ev!zz}l*2drPOH21x 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.2.png new file mode 100644 index 0000000000000000000000000000000000000000..66e31db5ca89980d6b5ab01c94fb2000b42c56d8 GIT binary patch literal 18906 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 NjI!3ve5Jd8{|{cGP_6&~ literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.3.png b/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.3.png new file mode 100644 index 0000000000000000000000000000000000000000..c37f15a9b047ea240cfdf72c3f9c19b013bfca72 GIT binary patch literal 18759 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..c62b823bd7f1b96cc05c9e7333bb5375e72324a9 GIT binary patch literal 18688 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.2.png new file mode 100644 index 0000000000000000000000000000000000000000..bf703259b629af9fc1cc6729571ecbdd68ca56be GIT binary patch literal 18688 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..6e8592a7ae321a880a1a700e214494374d2800f7 GIT binary patch literal 17506 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 Jzw_|L{{qrLEx`Z) literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.2.png new file mode 100644 index 0000000000000000000000000000000000000000..ff151222c2075d45684d8eb2e1479857c172fcb6 GIT binary patch literal 17542 zcmeIaXH-<_x-N>^7*M2JMN|Y7$x)I>7L`zBfg%T$Bw2C>1(B90S&%9a$x$U|MFEM0 z9?= zGO}aO$jFYrIDHa+^7-0SCj4^DNnP$fSwYA31^CS$PWMror{N#()24ruk=-CeJ(SjT zPg)uEaKD5eJ3JCQAM8)3TYT$^LF!4?1Xi~FD>M&}x7>b_n)0pi#?*@&jEqr_UNuo_ zY6XbDcyapSt4DwQr5Pg~DSheNFX?-4eqI=?zfk(>&p0xXI;s2A*($6HD*o3o#HGq| z!Pp#)%$!_;vo<Lh#2e*794m(0DRmT(3sBU3$D+1GPz z2_F}_Gnc-;JaMzcX>UkFJ_*M0Wani3^_DqWWc zZZ=op0#?d;bG5Ax_jjrLf(q*n^+ITPdA5neW%m6e6;83y(Hc+1K6C_ibH+^X7Gok> z@KZlz@t6 z!4weUP`GwfjzP|AE z#x6%51GirBZA#iZT-(I%OjY5iwxJShMH|+h-RFC+JjIg>Ha>TKr;^Hp2cV&yP>kbx zG~F21x+Ql*SXg^&slPv3RWdt>-ZRU<_8`vCbHHG$+Ew|aGi7@ z9Zy=!E$pdqvZy&aB;{xqjum$Ge+mBk?@mLv(R8VU1=YH}N{jZy^tLu7pY1`5%9YY# zCY0Cq@-K|rShf>Rf~dVpR1v+Dq31wqnRkL@;}tbROyBLX;wIzp~yd| z8#RT~byvD9#l*&<38lWRi9*&H=Ix0d83+3tIIFR0kI$1_sKLQO1y$Al$XjwMZZw=f z^zCb0Wc!x9t8Ipgv&FdC*-`M1MVi!(&uMY2U;D;d9%n)MeV3&^^k}6Ey2xA^rEX)B zC$=);ghP9JR*JeVXJuuv$vk|>CMjt&-4sC-+-w&fpO2KIcfePyw{UK?^42|@tPiX_ z+*@Ks1)QW*_x7$nNkxCXSm4B|^P1fm%3BMW$?ymKc>94;&NFD9s5{2!JJW3us?AGeI)Qs5)5nGY<_4Umb z&cgXVeS7r7hp!lK-5T8vx!AI_bk}+Q?bP(NkW~*GO8x$Q|7(KJZlDqcEu-LvE<3*` zFZ9#Q!%12kud7?^%g_D$@6$3eGTHXywK78qx1adiRj)6!mz%Aq{*7`KuByZrp>wnN3gBBT!I4}D47E6BvnG)HfJ zJwe6B$XH)v{AbJV!a%WQH1arJ+cs{Ae6q^#@1S87`T6-r7Oq~s3SV(6tNFcV@E6`h zhr)Puez>eK()2uo7A2$$H? zquC8d>Q*yyJpp@j^Ng!vu6YSU)^S&*e5>Dtg$YHq!BJ3HiRNp})A?L4&rc`nd|SZ0 z?KX1IVQYLD_SMr%j5NJ);X>O|VV#}2hDJN3@W^APgk;}6_Ov_4scSRdVX=1V>gu65 zyujzrpIMlh8@zWWgJj~6ic&@8xxH-p`aGSL@6O~YVq!c~^t*S@WFzQpPoBFnu`QC+ zkGo=$`tjrF(!>FYC=7M&tjFNJU%!6M9gCv{L$#u50;=FuSB5aYxvJG#foK+GegTUP!GKd#$f`e-lY0+Ufz~7xe>Wl| zMBu^ebA0-xHfI+0xt4#tx!AZ$e1D3;yAxCDxn)(hQs4AlY_-~ise%|l!GN2UVEB%< zx+jLgNcq)Te0%lkkM42@6O&NBaqo?=NYlCY_cYGTB;qQR?Z5xJrLY`&u@k0YOUZt? zG(XaGdu3RA`3pQZYy`OIMfGGOkJ;D@!^^)u2Em^$4^FEL*Lamhnw~s={fav6OMlFB zWR2O`vwzIOr7f5eW?SQ{x|2?%dTh+i!B`2KP4r%E^86JQ6|e?IQYI?on|oVJqn00d zXxVP3=$F~qF7_81Pu+=Q`MpJ%piBqfAI#LkU>R~77M8~A z5}rMnrZeGc$M@c_Vt*?!K+mx7ix*0Hu;OoDjLw`ngY^w^^gZeM``f9oab#EVJ$(gDFvDTG5&Qmygy-h(HX3*F zc)a&H?ewm(Cx!ZDDp8x6QU|Dog@vIyzq;+!@xrq_-_E{d_6SC}j z2rZ8{f8pt z$?d?1Ej7~GI~P_dA|KSO^Yxj<BsNAO-8`{&3Mv;li1_Dm+$aNsB+0`albZeYR;%8e*n*mEJ!myC`m?+}FrFTG2#iyQ2;qz0{xk!+&iCMLpVPPx z(Zlz!)%cJRH3a9xa`h-Z-bl~10`^x8w3OhmuwL7;(YcDFn7B9=6w=Zy61)f%a?t-c zos40H3jr{&0Ro6Bc3ILwkw@fH_rZZcXR9|Hh{S9Nbi%hYgvY%#poMj!wGGoNobZbU zRfMjse&a%;8qWnd#G*(08~^k@!zx#^A#7Ji)AQJCogGDKE{i>DN<`9u6_iBM5gg_2 z7spQYl-lZ})Z=*!1?@)4Kb_zd71i_G`OYBTo%DDV7LjS8q~AybF3p6JOy+$imcEq@ z?erRK1=-LmACQqm2|^>1INX_zq9}Nfo|)-oH@537Y54pbIiZ#6FW_RGCUb}b`nObTmYyN>69nm z-^tf6>jRV#6B+s8=7ZPIT4LA%qk0a|QMg9`*j>7I^=kHmp!3;qXst#oOB@{?ohQB? zqr3b39;bEz*B1knkLXff(I&C($WY9U&;#8v>pY!kmTI!(-Y!0wnfiJgoW&vNl2COr zq23`dd8pDQ7wa*X@OJ2vbXK9U46nXUiB+$l@4ox&?5t&T6m!m_kFR0;1t}Y^H}f=xV{2eltn46vpNh%ht0ku_Y!==W)gMwYx0Khw)5+c>v^AE@ZqP5vJ{6js%c6 zB(Rz)6B5kDpukXOGsuVnv|Pb~&Y7{p%Q5&|QfK%sB6ZdpcE%hoxe!)g2YADCsbeEP z#L+}Plh>Dd46Gm_co;#)8F^LO^esyO{-VD7 z2b~V+eA0AcyT(34P%)_m2qbg+E?`)1wWKeAb!rmbBg`fCFrLUwGts006{Pq`s^Ys~8G- zYj>Wa$m8jeId5oaVCUgc0kEeHW#5^!QU=TC;tB9gV&(PFQ9aW6px0<4XQx91e?4EX zMB(gZF12JyA0`wq1_gQfPMGdX)YR!GDX%>P-Y3nA5A2e&pba{A6}cs+fPe-7olWsR z?I^Vl++&1n{dG(B+1};?VV8s2pa3=|jgZH>Y0b`52y;P|INoG{Vw~^X_N5@cMg&YT zF)@`#M9$CLy3gRV83_;%I;^{|+jmM~1nhYYJ*P)?H8*wnI}kD=t_n1E8x5D*N*IeA zO$1OWDJg}LNCN{p#EzVe9c@b=N5V6)BfU}^OY^pOlMAX=Rym}Bm@#;L zoUp-c`&l|rtSzDber~->@BLfaP2wV}iu39<)dcN*2VP9u6 z$!a}GVUr7sLHOcZH|{*&`7KYcBxhjufrn4P8F-Kl&3lto^Ucl8fGOX+;cL7C2>u$8 z{W6|C61IPZDi!r15%6in%FNuHrx8su&H^fiHG-S%$|qkxB`t_!`0U5TiEGj*WXV~$ zx!W#Nd{qEe0oVPofezmop)oM*(5|K8vCo}HPQhm*y#eG6@5 zgoVwh3DrDBn4{8dwE(HBQ1L~9?ErgBSLCVqaUlQb9iQxbVf#K0JSG!3a>^2CzG{a{ z?$4j{L=&ys0HifsT}u(-JF^g@Hk@@j=saB)P(GJ$MX-2M_%OI`ZCkhQk|m;|!ZR;+~sXu7r_nQJ2M- zg^W(vt>_BJ*_33qG=J+M;C)#9YZCkrnCKHj=U+0+V}Db@oYINAtFfviv8{=^!^pmT z`JxF&AAwJZR)I(zFglvSg04Mx=&EL>rdgmZnAL6@b?thH#uB~H$><#OsiZ#tjI55b zfn2a9Vp8_lS9PtLfU#2B_vf$QO(`!g7Z{}`ySXGt zp6t`5W0YiZj4ANFKLp8iIA&g)&yzg>>b*E@U+3bNaol0&em)DGUF>pjfqGaJd2Wxd zXe@rKy}cb;t^L5Kn|WrcAVo1N#v#X0rg3IR=s=imJeCPA;oAih7P8;9N?h_g#QSZ6 zuETcorgUNLUJhw=zre7HgOBeK$Pv++w?%uQG8dLu_g79cX~!h{9aj8Mzo%{FVfJ~v zrfhKFvx(xt({MgnfXLcUpQcJe8KtnMp}sXx;ZPa7wI$*HJLPbvNy-Y&E7WmM=yW0H z+BBm`gep}yA5l1bH^89$_jZ-+BL&*0ha*-IqG4jt4`mz?m14DxbW0mU`7$hUSee?Q z!e|r4D<*AF6_u9y@+GEu|IoU67sEV-;kzG9B6cbAqJMhUf)01_;zbj!0t4NfE3qs( z+3AmA?M+`((rQhvyyj$QZ+2c8D!D(rFSPBUpr8;*!9dOYui+{K@s4WqCqFx4d5Vf& z5E{u`nD7{z!yt@>7G(}V@JV4=DyCq%O7H`{+{@_zc@F+NkFvy#Zydp_icd4$oz^yh zTK6RENdFhsbCKO;b<8lT`1$1ht;JqN;Q5tnjkL_vbUW2~Hu+eej2zQb0J}{BBSO}! zmp4H3Dic+b5$ndw-c3^A1VorCwgNM(S7v9JZK7j8RE$#p_8fDRe_tZ}@L<(%DN2#& zQMy8GF(`QwI}-udZa;oO0N(jqYJt{ikr3Ae;Yy>6JPsraSm`1L7cbxw?9frNsz(&_e)R=xIf3e=Oe#`LGdF?ZsBRUln7eP zn~;!=P)*P&O`z48Ryr>{1rjzD{_HXxo$b`rl=!NkLKF~xV1oFBzCvTEi!?OaGZ=Kp zW$qETn;l{=-&|y^{8+Yo;4AQ~^MT;rxNb~;RtB$S4~1I0NsyT!pyMTy!p-4+`O_C zmZ(*r%Ls>+Xoq?b#z&rf1<=4FQ$uKl$P}6bc9O=0!I%Lp@4aKiy4SDx!Dtf%EW$G} zBO?aTUm1w+l;!1bC;MzCpx{?HP*$IZuPxKfG=|YCLs1*qf?k{ejI?42nakxMn5y?U zNm83(8R#l-6X_6oxB1QwKbMd*dBr4j7Yer$oD^}}%(OK3MZwoz;WYG9}PLb%bDg14_9gHZXd ztE=mN!KKN0Xc^EfGG=B>g`RcZv+ghGO_2^LhCWmFP!pu?%e+Q;4IwnSUPJ<-#n$V| zWi3^LbSvb&^W0@+2+gULrv~$~1tjwsAhBx#=Alq5i(Oarlrf@cL`?%id2L-=8?KxW zIu#|IsHXb2_86D~c2-uo)ml=4zKH`=2fa^^UuKT!s;GoVMMrPVe~ie1?%_!sb=!ib znGICYsyibrO~W|drFyGRe{rZJ7hz2R4szAg6%-T|H%8>orH|gL@gnxs2c8u&{rM+? z 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..8dd394e1549e75f1d3fd64821511ae4389a7f8a0 GIT binary patch literal 12037 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-007-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-duration-007-manual.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..f59ba0df8eeb0fb83b76ce30ca9b5129b8f44513 GIT binary patch literal 15606 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 + +CSS Animations Test: animation-direction - alternate + + + + + + + + + + + +

    + Test passes if there is a filled blue square with 'Filler Text', + which starts moving from right to left on the page load, + and then moves from left to right. This cycle gets repeated. +

    +
    Filler Text
    + + \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-002-manual.html b/integration_tests/specs/css/css-animations/animation-direction-002-manual.html new file mode 100644 index 0000000000..52d356b987 --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-direction-002-manual.html @@ -0,0 +1,51 @@ + + +CSS Animations Test: animation-direction - normal + + + + + + + + + + + +

    + Test passes if there is a filled color square with 'Filler Text', + which starts moving from right to left, then back to right and moves from + right to left again. This cycle gets repeated. +

    +
    Filler Text
    + + \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-003-manual.html b/integration_tests/specs/css/css-animations/animation-direction-003-manual.html new file mode 100644 index 0000000000..b736221d79 --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-direction-003-manual.html @@ -0,0 +1,53 @@ + + +CSS Animations Test: animation-direction - alternate-reverse + + + + + + + + + + +

    + Test passes if there is a filled blue square with 'Filler Text', + which starts moving from right to left on the page load, + and then moves from left to right. This cycle gets repeated. +

    +
    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 new file mode 100644 index 0000000000..8fdc331b0f --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-direction-004-manual.html @@ -0,0 +1,51 @@ + + +CSS Animations Test: animation-direction - reverse + + + + + + + + + + +

    + Test passes if there is a filled color square with 'Filler Text', + which starts moving from left to right, then back to left again and moves from + left to right. This cycle gets repeated. +

    +
    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 new file mode 100644 index 0000000000..ed243885be --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-duration-001-manual.html @@ -0,0 +1,46 @@ + + +CSS Animations Test: animation-duration - blank value + + + + + + + + + +

    + Test passes if there is a filled blue square with 'Filler Text', + which starts staying left for about 2 seconds upon page load, + then moves from right to left. +

    +
    Filler Text
    + + \ 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 new file mode 100644 index 0000000000..bb96604c5f --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-duration-002-manual.html @@ -0,0 +1,39 @@ + + +CSS Animations Test: animation-duration - finite value + + + + + + + + + +

    + 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
    + + 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 new file mode 100644 index 0000000000..72e1070f1f --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-duration-003-manual.html @@ -0,0 +1,48 @@ + + +CSS Animations Test: animation-duration - negative value + + + + + + + + + +

    + Test passes if there is a filled blue square with 'Filler Text', + which starts staying left for about 2 seconds upon page load, + then moves from right to left. +

    +
    Filler Text
    + + + 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 new file mode 100644 index 0000000000..19baf08b1f --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-duration-004-manual.html @@ -0,0 +1,48 @@ + + +CSS Animations Test: animation-duration - 0s + + + + + + + + + +

    + Test passes if there is a filled blue square with 'Filler Text', + which starts staying left for about 2 seconds upon page load, + then moves from right to left. +

    +
    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 new file mode 100644 index 0000000000..ea6a331c8e --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-duration-005-manual.html @@ -0,0 +1,50 @@ + + +CSS Animations Test: animation-duration - 0s, animation-fill-mode - forwards + + + + + + + + + +

    + Test passes if there is a filled blue with 'Filler Text', + and without animation, and if there is no red. +

    +
    +
    Filler Text
    + + 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 new file mode 100644 index 0000000000..2ad017b1fd --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-duration-006-manual.html @@ -0,0 +1,50 @@ + + +CSS Animations Test: animation-duration - 0s, animation-fill-mode - both + + + + + + + + + +

    + Test passes if there is a filled blue with 'Filler Text', + and without animation, and if there is no red. +

    +
    +
    Filler Text
    + + diff --git a/integration_tests/specs/css/css-animations/animation-duration-007-manual.html b/integration_tests/specs/css/css-animations/animation-duration-007-manual.html new file mode 100644 index 0000000000..94d71796dc --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-duration-007-manual.html @@ -0,0 +1,38 @@ + + +CSS Animations Test: animation-duration - ::before + + + + + + + + +

    + 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. +

    +
    + + diff --git a/integration_tests/specs/css/css-animations/animation-duration-008-manual.html b/integration_tests/specs/css/css-animations/animation-duration-008-manual.html new file mode 100644 index 0000000000..b5a8dba02a --- /dev/null +++ b/integration_tests/specs/css/css-animations/animation-duration-008-manual.html @@ -0,0 +1,38 @@ + + +CSS Animations Test: animation-duration - ::after + + + + + + + + +

    + 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. +

    +
    + + diff --git a/webf/lib/src/css/animation.dart b/webf/lib/src/css/animation.dart index 713937bfa5..0770c2716b 100644 --- a/webf/lib/src/css/animation.dart +++ b/webf/lib/src/css/animation.dart @@ -319,6 +319,9 @@ class Animation { } void play() { + if (_totalDuration <= 0) { + return; + } _isPaused = false; if (_isFinished || _isIdle) { _rewind(); @@ -770,7 +773,10 @@ class KeyframeEffect extends AnimationEffect { d = d! + 1; } currentDirection = PlaybackDirection.normal; - if (d != double.infinity && d! % 2 != 0) { + if (d == null) { + print('123'); + } + if (d != null && d != double.infinity && d % 2 != 0) { currentDirection = PlaybackDirection.reverse; } } diff --git a/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index 468cfc67eb..466db1fa50 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -238,7 +238,7 @@ mixin CSSAnimationMixin on RenderStyle { }, orElse: () { return FillMode.both; }), - iterations: iterationCount == 'infinite' ? -1 : (double.tryParse(iterationCount) ?? 1), + iterations: iterationCount == 'infinite' ? double.infinity : (double.tryParse(iterationCount) ?? 1), direction: PlaybackDirection.values.firstWhere((element) => element.toString().split('.').last == direction), ); diff --git a/webf/lib/src/css/values/calc.dart b/webf/lib/src/css/values/calc.dart index d11f82c62d..8a3d66fcf6 100644 --- a/webf/lib/src/css/values/calc.dart +++ b/webf/lib/src/css/values/calc.dart @@ -6,11 +6,8 @@ import 'package:source_span/source_span.dart'; import 'package:webf/css.dart'; class CSSCalcValue { - final RenderStyle _renderStyle; - final CalcExpressionNode? _expression; - - CSSCalcValue(this._renderStyle, this._expression); + CSSCalcValue(this._expression); // Get the lazy calculated CSS resolved value. dynamic computedValue(String propertyName) { @@ -27,7 +24,7 @@ class CSSCalcValue { final expression = fns.first.args.first; final _CSSCalcParser parser = _CSSCalcParser(propertyName, renderStyle, expression); CalcExpressionNode? node = parser.processCalcExpression(); - return CSSCalcValue(renderStyle, node); + return CSSCalcValue(node); } } return null; From 27c9f34d57e3b7614496b77de911009e1057a234 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 9 Sep 2022 03:20:17 +0800 Subject: [PATCH 289/498] fix: fix event.toRaw() doesn't match to struct memory align. --- bridge/core/dart_methods.h | 2 +- .../code_generator/bin/code_generator.js | 4 +- .../code_generator/src/idl/generateHeader.ts | 4 +- .../code_generator/src/idl/generateSource.ts | 4 +- .../idl_templates/base.cc.tpl | 0 .../idl_templates/base.h.tpl | 0 .../idl_templates/dictionary.cc.tpl | 0 .../idl_templates/dictionary.h.tpl | 0 .../idl_templates/global_function.cc.tpl | 0 .../idl_templates/global_function.h.tpl | 0 .../idl_templates/interface.cc.tpl | 0 .../idl_templates/interface.h.tpl | 0 .../json_templates/element_factory.cc.tpl | 0 .../json_templates/element_factory.h.tpl | 0 .../json_templates/element_type_helper.h.tpl | 0 .../json_templates/event_factory.cc.tpl | 2 + .../json_templates/event_factory.h.tpl | 0 .../json_templates/event_type_helper.h.tpl | 0 .../json_templates/make_names.cc.tpl | 0 .../json_templates/make_names.h.tpl | 0 .../json_templates/names_installer.cc.tpl | 0 .../json_templates/names_installer.h.tpl | 0 bridge/test/webf_test_context.cc | 5 +- integration_tests/lib/bridge/from_native.dart | 15 ++--- webf/lib/src/dom/event.dart | 61 +++++++++++++------ webf/lib/src/dom/event_target.dart | 8 ++- webf/lib/src/foundation/binding.dart | 2 +- 27 files changed, 70 insertions(+), 37 deletions(-) rename bridge/scripts/code_generator/{static => templates}/idl_templates/base.cc.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/idl_templates/base.h.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/idl_templates/dictionary.cc.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/idl_templates/dictionary.h.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/idl_templates/global_function.cc.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/idl_templates/global_function.h.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/idl_templates/interface.cc.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/idl_templates/interface.h.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/json_templates/element_factory.cc.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/json_templates/element_factory.h.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/json_templates/element_type_helper.h.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/json_templates/event_factory.cc.tpl (94%) rename bridge/scripts/code_generator/{static => templates}/json_templates/event_factory.h.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/json_templates/event_type_helper.h.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/json_templates/make_names.cc.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/json_templates/make_names.h.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/json_templates/names_installer.cc.tpl (100%) rename bridge/scripts/code_generator/{static => templates}/json_templates/names_installer.h.tpl (100%) diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index f26f975dee..4a4bf568ca 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -68,7 +68,7 @@ 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); struct DartMethodPointer { diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 7a2f4482b2..092a2c5a67 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -62,7 +62,7 @@ function genCodeFromJSONData() { cwd: source }); let templateFiles = glob.sync('**/*.tpl', { - cwd: path.join(__dirname, '../static/json_templates') + cwd: path.join(__dirname, '../templates/json_templates') }); let blobs = jsonFiles.map(file => { @@ -72,7 +72,7 @@ function genCodeFromJSONData() { let templates = templateFiles.map(template => { let filename = template.split('/').slice(-1)[0].replace('.tpl', ''); - return new JSONTemplate(path.join(path.join(__dirname, '../static/json_templates'), template), filename); + return new JSONTemplate(path.join(path.join(__dirname, '../templates/json_templates'), template), filename); }); let names_needs_install = new Set(); diff --git a/bridge/scripts/code_generator/src/idl/generateHeader.ts b/bridge/scripts/code_generator/src/idl/generateHeader.ts index 80e9e273a2..6c418998e7 100644 --- a/bridge/scripts/code_generator/src/idl/generateHeader.ts +++ b/bridge/scripts/code_generator/src/idl/generateHeader.ts @@ -29,11 +29,11 @@ export function getTemplateKind(object: ClassObject | FunctionObject | null): Te } function readTemplate(name: string) { - return fs.readFileSync(path.join(__dirname, '../../static/idl_templates/' + name + '.h.tpl'), {encoding: 'utf-8'}); + 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, '../../static/idl_templates/base.h.tpl'), {encoding: 'utf-8'}); + const baseTemplate = fs.readFileSync(path.join(__dirname, '../../templates/idl_templates/base.h.tpl'), {encoding: 'utf-8'}); let headerOptions = { interface: false, dictionary: false, diff --git a/bridge/scripts/code_generator/src/idl/generateSource.ts b/bridge/scripts/code_generator/src/idl/generateSource.ts index 4c91e450df..fb702367bd 100644 --- a/bridge/scripts/code_generator/src/idl/generateSource.ts +++ b/bridge/scripts/code_generator/src/idl/generateSource.ts @@ -409,11 +409,11 @@ ${addIndent(callBody, 4)} } function readTemplate(name: string) { - return fs.readFileSync(path.join(__dirname, '../../static/idl_templates/' + name + '.cc.tpl'), {encoding: 'utf-8'}); + 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, '../../static/idl_templates/base.cc.tpl'), {encoding: 'utf-8'}); + 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); diff --git a/bridge/scripts/code_generator/static/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl similarity index 100% rename from bridge/scripts/code_generator/static/idl_templates/base.cc.tpl rename to bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl diff --git a/bridge/scripts/code_generator/static/idl_templates/base.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/base.h.tpl similarity index 100% rename from bridge/scripts/code_generator/static/idl_templates/base.h.tpl rename to bridge/scripts/code_generator/templates/idl_templates/base.h.tpl diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/dictionary.cc.tpl similarity index 100% rename from bridge/scripts/code_generator/static/idl_templates/dictionary.cc.tpl rename to bridge/scripts/code_generator/templates/idl_templates/dictionary.cc.tpl diff --git a/bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/dictionary.h.tpl similarity index 100% rename from bridge/scripts/code_generator/static/idl_templates/dictionary.h.tpl rename to bridge/scripts/code_generator/templates/idl_templates/dictionary.h.tpl diff --git a/bridge/scripts/code_generator/static/idl_templates/global_function.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/global_function.cc.tpl similarity index 100% rename from bridge/scripts/code_generator/static/idl_templates/global_function.cc.tpl rename to bridge/scripts/code_generator/templates/idl_templates/global_function.cc.tpl diff --git a/bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/global_function.h.tpl similarity index 100% rename from bridge/scripts/code_generator/static/idl_templates/global_function.h.tpl rename to bridge/scripts/code_generator/templates/idl_templates/global_function.h.tpl diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl similarity index 100% rename from bridge/scripts/code_generator/static/idl_templates/interface.cc.tpl rename to bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl diff --git a/bridge/scripts/code_generator/static/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl similarity index 100% rename from bridge/scripts/code_generator/static/idl_templates/interface.h.tpl rename to bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl similarity index 100% rename from bridge/scripts/code_generator/static/json_templates/element_factory.cc.tpl rename to bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl diff --git a/bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl b/bridge/scripts/code_generator/templates/json_templates/element_factory.h.tpl similarity index 100% rename from bridge/scripts/code_generator/static/json_templates/element_factory.h.tpl rename to bridge/scripts/code_generator/templates/json_templates/element_factory.h.tpl diff --git a/bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl b/bridge/scripts/code_generator/templates/json_templates/element_type_helper.h.tpl similarity index 100% rename from bridge/scripts/code_generator/static/json_templates/element_type_helper.h.tpl rename to bridge/scripts/code_generator/templates/json_templates/element_type_helper.h.tpl diff --git a/bridge/scripts/code_generator/static/json_templates/event_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl similarity index 94% rename from bridge/scripts/code_generator/static/json_templates/event_factory.cc.tpl rename to bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl index 6d96432ed0..da72763bf3 100644 --- a/bridge/scripts/code_generator/static/json_templates/event_factory.cc.tpl +++ b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl @@ -39,10 +39,12 @@ struct CreateEventFunctionMapData { <% if (_.isString(item)) { %> static Event* <%= _.upperFirst(item) %>EventConstructor(ExecutingContext* context, const AtomicString& type, RawEvent* raw_event) { + assert(raw_event->length == sizeof(Native<%= _.upperFirst(item) %>Event) / sizeof(int64_t)); return MakeGarbageCollected<<%= _.upperFirst(_.camelCase(item)) %>Event>(context, type, toNativeEventEvent>(raw_event)); } <% } else if (_.isObject(item)) { %> static Event* <%= item.class %>Constructor(ExecutingContext* context, const AtomicString& type, RawEvent* raw_event) { + assert(raw_event->length == sizeof(Native<%= _.upperFirst(item.class) %>) / sizeof(int64_t)); return MakeGarbageCollected<<%= item.class %>>(context, type, toNativeEvent>(raw_event)); } <% } %> diff --git a/bridge/scripts/code_generator/static/json_templates/event_factory.h.tpl b/bridge/scripts/code_generator/templates/json_templates/event_factory.h.tpl similarity index 100% rename from bridge/scripts/code_generator/static/json_templates/event_factory.h.tpl rename to bridge/scripts/code_generator/templates/json_templates/event_factory.h.tpl diff --git a/bridge/scripts/code_generator/static/json_templates/event_type_helper.h.tpl b/bridge/scripts/code_generator/templates/json_templates/event_type_helper.h.tpl similarity index 100% rename from bridge/scripts/code_generator/static/json_templates/event_type_helper.h.tpl rename to bridge/scripts/code_generator/templates/json_templates/event_type_helper.h.tpl diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/make_names.cc.tpl similarity index 100% rename from bridge/scripts/code_generator/static/json_templates/make_names.cc.tpl rename to bridge/scripts/code_generator/templates/json_templates/make_names.cc.tpl diff --git a/bridge/scripts/code_generator/static/json_templates/make_names.h.tpl b/bridge/scripts/code_generator/templates/json_templates/make_names.h.tpl similarity index 100% rename from bridge/scripts/code_generator/static/json_templates/make_names.h.tpl rename to bridge/scripts/code_generator/templates/json_templates/make_names.h.tpl diff --git a/bridge/scripts/code_generator/static/json_templates/names_installer.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/names_installer.cc.tpl similarity index 100% rename from bridge/scripts/code_generator/static/json_templates/names_installer.cc.tpl rename to bridge/scripts/code_generator/templates/json_templates/names_installer.cc.tpl diff --git a/bridge/scripts/code_generator/static/json_templates/names_installer.h.tpl b/bridge/scripts/code_generator/templates/json_templates/names_installer.h.tpl similarity index 100% rename from bridge/scripts/code_generator/static/json_templates/names_installer.h.tpl rename to bridge/scripts/code_generator/templates/json_templates/names_installer.h.tpl diff --git a/bridge/test/webf_test_context.cc b/bridge/test/webf_test_context.cc index 4733e209a1..01144f5b2c 100644 --- a/bridge/test/webf_test_context.cc +++ b/bridge/test/webf_test_context.cc @@ -132,10 +132,10 @@ static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, JS_ToUint32(ctx, &length, lengthValue); JS_FreeValue(ctx, lengthValue); - auto** mousePointerList = new MousePointer*[length]; + auto* mousePointerList = new MousePointer[length]; for (int i = 0; i < length; i++) { - auto mouse = new MousePointer(); + MousePointer* mouse = &mousePointerList[i]; JSValue params = JS_GetPropertyUint32(ctx, inputArrayValue, i); mouse->contextId = context->contextId(); JSValue xValue = JS_GetPropertyUint32(ctx, params, 0); @@ -153,7 +153,6 @@ static JSValue simulatePointer(JSContext* ctx, JSValueConst this_val, int argc, mouse->x = x; mouse->y = y; mouse->change = change; - mousePointerList[i] = mouse; JS_FreeValue(ctx, params); JS_FreeValue(ctx, xValue); diff --git a/integration_tests/lib/bridge/from_native.dart b/integration_tests/lib/bridge/from_native.dart index 11f65cdf6c..e99577fdc6 100644 --- a/integration_tests/lib/bridge/from_native.dart +++ b/integration_tests/lib/bridge/from_native.dart @@ -92,7 +92,7 @@ final Pointer> _nativeEnvironment = Pointer.fromFunction(_environment); typedef NativeSimulatePointer = Void Function( - Pointer>, Int32 length, Int32 pointer); + Pointer, Int32 length, Int32 pointer); typedef NativeSimulateInputText = Void Function(Pointer); PointerChange _getPointerChange(double change) { @@ -114,15 +114,14 @@ class MousePointer extends Struct { } void _simulatePointer( - Pointer> mousePointerList, int length, int pointer) { + Pointer mousePointerList, int length, int pointer) { List 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, @@ -135,6 +134,8 @@ void _simulatePointer( pointerIdentifier: pointer)); } + print('simulate pointer data: $data'); + PointerDataPacket dataPacket = PointerDataPacket(data: data); window.onPointerDataPacket!(dataPacket); } diff --git a/webf/lib/src/dom/event.dart b/webf/lib/src/dom/event.dart index 6aea3d4866..ca473f6ff2 100644 --- a/webf/lib/src/dom/event.dart +++ b/webf/lib/src/dom/event.dart @@ -210,9 +210,10 @@ class Event { (_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; @@ -253,8 +254,10 @@ class PopStateEvent extends Event { List methods = [stringToNativeString(jsonEncode(_popStateEventInit.state)).address]; Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + 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; } @@ -315,12 +318,18 @@ class UIEvent extends Event { super(type, init); @override - Pointer toRaw([int methodLength = 0]) { - List methods = [doubleToUint64(detail), view?.pointer ?? nullptr, doubleToUint64(which)]; + Pointer toRaw([int extraMethodsLength = 0]) { + List methods = [ + doubleToUint64(detail), + view?.pointer?.address ?? nullptr.address, + doubleToUint64(which) + ]; - Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + Pointer rawEvent = super.toRaw(methods.length + extraMethodsLength).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; } @@ -349,9 +358,11 @@ class MouseEvent extends UIEvent { 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 + methodLength).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; } @@ -439,8 +450,10 @@ class GestureEvent extends Event { ]; Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + 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; } @@ -467,8 +480,10 @@ class CustomEvent extends Event { List methods = [detail.toNativeUtf8().address]; Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + 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; } @@ -487,8 +502,10 @@ class InputEvent extends Event { 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)); + 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; } @@ -541,8 +558,10 @@ class MediaError extends Event { List methods = [code, stringToNativeString(message).address]; Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + 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; } @@ -565,8 +584,10 @@ class MessageEvent extends Event { List methods = [(jsonEncode(data)).toNativeUtf8().address, stringToNativeString(origin).address]; Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + 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; } @@ -590,8 +611,10 @@ class CloseEvent extends Event { 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)); + 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; } @@ -606,8 +629,10 @@ class IntersectionChangeEvent extends Event { List methods = [doubleToUint64(intersectionRatio)]; Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + 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; } @@ -639,8 +664,10 @@ class TouchEvent extends Event { ]; Pointer rawEvent = super.toRaw(methods.length).cast(); - Uint64List bytes = rawEvent.ref.bytes.asTypedList((rawEvent.ref.length + methods.length)); + 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 9ba5c68aa3..d02f76a9b7 100644 --- a/webf/lib/src/dom/event_target.dart +++ b/webf/lib/src/dom/event_target.dart @@ -68,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/foundation/binding.dart b/webf/lib/src/foundation/binding.dart index 8c2a634d92..03a6539989 100644 --- a/webf/lib/src/foundation/binding.dart +++ b/webf/lib/src/foundation/binding.dart @@ -21,7 +21,7 @@ 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(); From 09e0b73ff893f6d12efd668184bf9f94ea98fcd0 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 9 Sep 2022 20:27:28 +0800 Subject: [PATCH 290/498] fix: fix override atomicString leaks. --- bridge/bindings/qjs/atomic_string.cc | 4 +++ .../core/dom/legacy/element_attribute_test.cc | 29 +++++++++++++++++++ bridge/core/dom/legacy/element_attributes.cc | 2 +- bridge/test/test.cmake | 1 + integration_tests/lib/bridge/from_native.dart | 2 -- .../specs/dom/elements/custom-element.ts | 8 +++-- 6 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 bridge/core/dom/legacy/element_attribute_test.cc diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index 783d4470be..dfc2a3bd92 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -129,6 +129,7 @@ StringView AtomicString::ToStringView() const { AtomicString::AtomicString(const AtomicString& value) { if (&value != this) { + JS_FreeAtom(ctx_, atom_); atom_ = JS_DupAtom(value.ctx_, value.atom_); } ctx_ = value.ctx_; @@ -139,6 +140,7 @@ AtomicString::AtomicString(const AtomicString& value) { AtomicString& AtomicString::operator=(const AtomicString& other) { if (&other != this) { + JS_FreeAtom(ctx_, atom_); atom_ = JS_DupAtom(other.ctx_, other.atom_); } runtime_ = other.runtime_; @@ -150,6 +152,7 @@ AtomicString& AtomicString::operator=(const AtomicString& other) { AtomicString::AtomicString(AtomicString&& value) noexcept { if (&value != this) { + JS_FreeAtom(ctx_, atom_); atom_ = JS_DupAtom(value.ctx_, value.atom_); } ctx_ = value.ctx_; @@ -160,6 +163,7 @@ AtomicString::AtomicString(AtomicString&& value) noexcept { AtomicString& AtomicString::operator=(AtomicString&& value) noexcept { if (&value != this) { + JS_FreeAtom(ctx_, atom_); atom_ = JS_DupAtom(value.ctx_, value.atom_); } ctx_ = value.ctx_; 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..5d1411a027 --- /dev/null +++ b/bridge/core/dom/legacy/element_attribute_test.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 "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 index 5ab0a2ef55..9c077e121d 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -24,7 +24,7 @@ AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { bool numberIndex = IsNumberIndex(name.ToStringView()); if (numberIndex) { - AtomicString::Empty(ctx()); + return AtomicString::Empty(ctx()); } return attributes_[name]; diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 83ad85da13..9cd85a90b2 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -23,6 +23,7 @@ list(APPEND WEBF_UNIT_TEST_SOURCEURCE ./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/dom/element_test.cc ./core/frame/dom_timer_test.cc diff --git a/integration_tests/lib/bridge/from_native.dart b/integration_tests/lib/bridge/from_native.dart index e99577fdc6..27bb2f7aa2 100644 --- a/integration_tests/lib/bridge/from_native.dart +++ b/integration_tests/lib/bridge/from_native.dart @@ -134,8 +134,6 @@ void _simulatePointer( pointerIdentifier: pointer)); } - print('simulate pointer data: $data'); - PointerDataPacket dataPacket = PointerDataPacket(data: data); window.onPointerDataPacket!(dataPacket); } diff --git a/integration_tests/specs/dom/elements/custom-element.ts b/integration_tests/specs/dom/elements/custom-element.ts index 65fb007351..01c7aad310 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 () => { From 4b04e5dea63d9bd5c7d6b5802bdf9b4ca78553e4 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Fri, 9 Sep 2022 12:28:29 +0000 Subject: [PATCH 291/498] Committing clang-format changes --- bridge/core/dom/legacy/element_attribute_test.cc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bridge/core/dom/legacy/element_attribute_test.cc b/bridge/core/dom/legacy/element_attribute_test.cc index 5d1411a027..c742f5bba3 100644 --- a/bridge/core/dom/legacy/element_attribute_test.cc +++ b/bridge/core/dom/legacy/element_attribute_test.cc @@ -10,14 +10,13 @@ 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::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 char* code = R"( const text = document.createElement('div'); text.setAttribute('value', 'Hello'); document.body.appendChild(text); From 2943321f7f9812d3b9ae66c98f31f3d5f34b08d2 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sat, 10 Sep 2022 08:07:24 +0800 Subject: [PATCH 292/498] fix: remove unused code --- webf/lib/src/css/animation.dart | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/webf/lib/src/css/animation.dart b/webf/lib/src/css/animation.dart index 0770c2716b..6e6811678a 100644 --- a/webf/lib/src/css/animation.dart +++ b/webf/lib/src/css/animation.dart @@ -769,13 +769,10 @@ class KeyframeEffect extends AnimationEffect { PlaybackDirection? currentDirection = playbackDirection; if (playbackDirection != PlaybackDirection.normal && playbackDirection != PlaybackDirection.reverse) { var d = currentIteration; - if (playbackDirection == PlaybackDirection.alternateReverse) { - d = d! + 1; + if (d != null && playbackDirection == PlaybackDirection.alternateReverse) { + d = d + 1; } currentDirection = PlaybackDirection.normal; - if (d == null) { - print('123'); - } if (d != null && d != double.infinity && d % 2 != 0) { currentDirection = PlaybackDirection.reverse; } From 1361fd56de0c87784d3c05066c04ef07b3e51090 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sat, 10 Sep 2022 10:16:44 +0800 Subject: [PATCH 293/498] fix(test): calc spec --- .../css/css-values/calc.ts.02d230c41.png | Bin 0 -> 3002 bytes .../css/css-values/calc.ts.192846f91.png | Bin 0 -> 3041 bytes .../css/css-values/calc.ts.2b6feae81.png | Bin 0 -> 3001 bytes .../css/css-values/calc.ts.62f32f6a1.png | Bin 0 -> 2886 bytes .../specs/css/css-values/calc.ts | 65 ++++++++++++++++++ webf/lib/src/css/values/calc.dart | 16 ++++- webf/lib/src/dom/element.dart | 3 + 7 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 integration_tests/snapshots/css/css-values/calc.ts.02d230c41.png create mode 100644 integration_tests/snapshots/css/css-values/calc.ts.192846f91.png create mode 100644 integration_tests/snapshots/css/css-values/calc.ts.2b6feae81.png create mode 100644 integration_tests/snapshots/css/css-values/calc.ts.62f32f6a1.png create mode 100644 integration_tests/specs/css/css-values/calc.ts diff --git a/integration_tests/snapshots/css/css-values/calc.ts.02d230c41.png b/integration_tests/snapshots/css/css-values/calc.ts.02d230c41.png new file mode 100644 index 0000000000000000000000000000000000000000..fda13f2f6197d661d918966ba08469d0dd9b0a6d GIT binary patch literal 3002 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_tgFPZ!6KiaBrZoL-%m%6R4W$4&ogaa-#x$i zdfa0D?&q=D@i(5AXEHpQv*(^T!-1)sCJs{^m|cL>71Q&wf2(ijZ~t3*yZ83HJKx&0 z{~UY2{cq*%t+(IZC_BCL=l$I3oZFXg>*ejPy*)er@oKgmTry05k8IP=t^RW64DW5b z-EX%&e(rr+?#{OlJB@3yZr@F}tN&TCZ?oO5w};oC{K^z#%GdbsAz$IYFWbuVe;?ka zpY!|Nw(~OK|Ek(8>I-hy-kx{s+tl3qH@=zsLil5aEL-naYh-raxl zq#MLbZ#4hAxNY~gz0ZG4-k$gS0AFC;pBbzwPe+=gs^Fw2MC7iT$|$+}Sy8KTG~S(_T~g&Ej4~tFir_ zx6!xX-7I^*dXHDb>IH)(v*%fCe|F{j_S*d0zqi%?G2H=D zb#mR^ve(<<=gj{3v~bT?i+da2_FLOnmA^7S{;{O~=gi#r+luGD=l?#kXEn!%c&j&B z_je!m*5H5lVa46Wx9fHtmi_SZ-pAkNH-i7Y;?tjNv-9oj+xND-l|62H|K7LHx$kd1 zbe%qb?oV&=@`^aAhVUl>FRIHb=DcIST~{+_XZ7vl=hoDm=F{I)Q+r#y-yRt8>firN zZ(Uw}rTb=${0d$R7#Fcf(Sdq(XsP#fy}kFu84o~j;d=0BeJi5J-ZX7F_Nb6Mw<&;$UHIK%1y literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-values/calc.ts.192846f91.png b/integration_tests/snapshots/css/css-values/calc.ts.192846f91.png new file mode 100644 index 0000000000000000000000000000000000000000..3478a9eec67fe6c6cfef0a94d68a65c90e2fea72 GIT binary patch literal 3041 zcmeH}?N3t&6vi)|LUEFkWf@Y|nT;(h6OjZAGc8O>9U~;g8K#p`MnqRoZdon1$fYk6 z%;*Rhllj6z+hhrnxP%UDZjrX!h8HhEq$=a3y``05iy*hR0^YXk+q!>ZU)J;GmvcTo zIp=wf@&u#=5+sJOI( z8mm_7xefq$yR(#itfILWw?EPf)&TUi^GIa{WT(?FE6Y9-rq`t=*N-V~C~Kx>%=TV{ zy)RXHdR$mTUW8z9A<=k&BjqYssun2>ikE|`XZ9ksAzhJvf z4)QX7;4KzyAV~$Yu8i<*V7TbV#Ey6B6raX0vaZoaV4RQ1^f(M2S|(>+g(eCynF)7; z5&`77>?W4UHSjcS+S0wJ$a+EDnFlovvQw0&7X+WfOb($1gJUpJ zc9?LGccW9j-ObaVQ^H*gp)3k7HUwR8xgagIc+jY-0p1G|y6wklgESU)J$e)#1R{%$YkDw??WuEdQcQ zuVRYNV?T-QZ-h=bCY&F>UiEE~xpu_JE!TRGToD<`zGrb{9@^oV{%{-1Fg1dE=7O3c zWn;4U-KHxAjRq-pJ!dB5&v(e^gndvOLPZy6e}m8umaQ$8Yzpf`bYP1sVYP2vW9V(+ zvzQMy32D6S)@`^EAJzsuAgv+ztUK&Fty-|(UoD+DfJH{|0&KLUa@!UqwG?mEMcv^s z1^vOlyP3K0>bhB?<_4}ZXhW?6te=Bc>u?kF^6ZL5FAa<#-k(9@*StDK*2`z(k(#Je zvU$KcgvvIEjrvIXVh>bzQs3jEG`S(o+N{@5c1I0C0{{dlDo{#_l literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-values/calc.ts.2b6feae81.png b/integration_tests/snapshots/css/css-values/calc.ts.2b6feae81.png new file mode 100644 index 0000000000000000000000000000000000000000..afc48ad22870b563b7a05739219d8c2a4ecc12eb GIT binary patch literal 3001 zcmeAS@N?(olHy`uVBq!ia0y~yV9a1(U~1rC1Bz^vdbAu!F%}28J29*~C-V}>VJUX< z4B-HR8jh3>1_thAPZ!6KiaBrZoISlXmGSt;`#i}@lfyhZrm49&K2cL*QZ`tTaZznm z=#@qGi+%rc{^Qo|Qt0x!!;*XJi<|ywr5wSCfGK+|GJ^cNfA<*M^(|iUaQdGbv+I`6 zcg_2K=fi>Nhrh-=x7@z|&8HJP{-o~bdrOMw>b^Z$&7qLhwcmXLro6j%oZ`bXA zn}0j+ZrS&3<}uIJ|801C{&wE2vg_N-?>~6upY!|iw(i^SDqP=*KQMjF@nval{QYk) zOpV32*CvJ^uByE4e7mmj8Q1)qW%obF{I$MaxA|@CZM*MXck~Zr9}|2b|GwjM`u4r) z$Cuw*pZELUw)6AD>^3Q%lfU)tz&7>mwaK@?Z`)ga`}Mh)pXt_*YQB{+#7#-&|D}HW z-Q92ZE^FuC{&r9{uzomul{*7bjT-$viQxAE=2@Go(Ualz-_UwV1* zrai~K8Yy}E-ujzmr)Qe3x8Iz0yKaN>jrp^Tl^FC7|FkWwD!Dv!ruK8|dAri~6r`Qq zdHKha%$k(j{o87b%b(L6ndupd3$X7*~`m6Uc3A_|NRZEo$1F5 z_rAUH>Bh^Sv&&3hG2FS(8f#-)_2?Qh9#`|>#tZfv_h_n39r8}s^)R_{Mt z=+mfAI)B%V@j<2R?o;32bG&r6?z?5X>uvb$d;3?{>-;&;=TKjKyS==|`@_TB{F~p- z$wrHRk9jVecT@1i?)Ap(2R>Ud{oN;f{AJyax6hZ){JpLA&8(^UcR#vLd!P6F*2}Hd z+iR=L?}cxB&huf?oa$@#Z|bX#%Y8aq{jRe&JJ#y#{(o%Q6`p?%*FES;Pyg{}-8=pJ zCyt88F&#Mlc(n>cAQP{E>V;8is0YDwz4(1KKdVJUX< z4B-HR8jh3>1_rJVo-U3d6?5L+^*Kf|sKK|oS?hVNUvhN-}TmESOyt6TFKa2hy43GSL$?#sibQxPg z`|cZGAHVoxboA-#(?9HXyzNiVo@Wm+Z1;BCEpOG+jqPoZwfB9Tn7jY(x8>T|b#*=b z{0-4>zRbAmUcQU-#}&Vef4^;$&-?u#ZeQe&g~>HV%Pan8+&15~xAOMx|LMP3cO=ir z`(2x`{P@d#Ti;e6KlAvuU1jRi=<;7mr>}3T{aNuo_qN^U>G@)DrEhas9_%lAD{6n^ z|0TccQ{UgVx7%2F_4DyRd;cf#GORzTZCtbQ|C{#Sb^m{>GVC#{%M(%XTEMu7MM~k- ztzK#KKjHZe3iBZXQ1MX;7DA&TF&Yw5qd_nl1i;dAv@946g3(k!os960Pa-t@zR@W^ R6JT4I!PC{xWt~$(695&Ts3ZUY literal 0 HcmV?d00001 diff --git a/integration_tests/specs/css/css-values/calc.ts b/integration_tests/specs/css/css-values/calc.ts new file mode 100644 index 0000000000..29dfda983f --- /dev/null +++ b/integration_tests/specs/css/css-values/calc.ts @@ -0,0 +1,65 @@ +fdescribe("calc", () => { + it("001", async () => { + let div; + div = createElement( + "div", + { + style: { + width: "calc(100px +100px)", + height: "calc(100px -50px)", + backgroundColor: "green" + } + }, [createText("001")] + ); + BODY.appendChild(div); + await snapshot(); + }); + + it("002", async () => { + let div; + div = createElement( + "div", + { + style: { + width: "calc(100px- 50px)", + height: "calc(20px+ 30px)", + backgroundColor: "green" + } + }, [createText("002")] + ); + BODY.appendChild(div); + await snapshot(); + }); + + it("003", async () => { + let div; + div = createElement( + "div", + { + style: { + width: "calc(100px + 100px)", + height: "calc(100px - 50px)", + backgroundColor: "green" + } + }, [createText("003")] + ); + BODY.appendChild(div); + await snapshot(); + }); + + it("004", async () => { + let div; + div = createElement( + "div", + { + style: { + width: "calc(20px*5)", + height: "calc(300px/2)", + backgroundColor: "green" + } + }, [createText("004")] + ); + BODY.appendChild(div); + await snapshot(); + }); +}); diff --git a/webf/lib/src/css/values/calc.dart b/webf/lib/src/css/values/calc.dart index 8a3d66fcf6..2c4ce16593 100644 --- a/webf/lib/src/css/values/calc.dart +++ b/webf/lib/src/css/values/calc.dart @@ -78,6 +78,14 @@ class CalcLengthNode extends CalcExpressionNode { double get computedValue => value.computedValue; } +class CalcNumberNode extends CalcExpressionNode { + final double value; + CalcNumberNode(this.value); + + @override + double get computedValue => value; +} + class CalcOperationExpressionNode extends CalcExpressionNode { int operator; final CalcExpressionNode leftNode; @@ -152,7 +160,7 @@ class _CSSCalcParser { while (_peek() != TokenKind.END_OF_FILE) { String operator = _peekToken.text; if (_peekToken.text != '+' && _peekToken.text != '-') { - break; + return null; } _next(); secondNode = processCalcProduct(); @@ -215,7 +223,11 @@ class _CSSCalcParser { String unit = _peekToken.text; // ignore unit type if (TokenKind.matchUnits(unit, 0, unit.length) == -1) { - return CalcLengthNode(CSSLengthValue(double.tryParse(value), CSSLengthType.PX, _renderStyle, propertyName)); + double? numberValue = double.tryParse(value); + if (numberValue == null) { + return null; + } + return CalcNumberNode(numberValue); } value += unit; CSSLengthValue lengthValue = CSSLength.parseLength(value, _renderStyle, propertyName); diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 70c6c2f8cc..079e8fdf65 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1065,6 +1065,9 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element value = CSSBackgroundPosition(calcValue: value); } else { value = value.computedValue(name); + if (value != null) { + value = CSSLengthValue(value, CSSLengthType.PX); + } } } From 082f1ff6776902b44e727a1448246dfe82c84745 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sat, 10 Sep 2022 10:29:22 +0800 Subject: [PATCH 294/498] fix: css animation code review --- webf/lib/src/css/css_animation.dart | 48 ++++++++++++++--------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index 466db1fa50..1b612f370c 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -29,76 +29,76 @@ const String _0s = '0s'; mixin CSSAnimationMixin on RenderStyle { List? _animationName; + @override + List get animationName => _animationName ?? [NONE]; + set animationName(List? value) { _animationName = value; } - @override - List get animationName => _animationName ?? [NONE]; - List? _animationDuration; + @override + List get animationDuration => _animationDuration ?? const [_0s]; + set animationDuration(List? value) { _animationDuration = value; } - @override - List get animationDuration => _animationDuration ?? const [_0s]; - List? _animationTimingFunction; + @override + List get animationTimingFunction => _animationTimingFunction ?? const [EASE]; + set animationTimingFunction(List? value) { _animationTimingFunction = value; } - @override - List get animationTimingFunction => _animationTimingFunction ?? const [EASE]; - List? _animationDelay; + @override + List get animationDelay => _animationDelay ?? const [_0s]; + set animationDelay(List? value) { _animationDelay = value; } - @override - List get animationDelay => _animationDelay ?? const [_0s]; - List? _animationIterationCount; + @override + List get animationIterationCount => _animationIterationCount ?? ['1']; + set animationIterationCount(List? value) { _animationIterationCount = value; } - @override - List get animationIterationCount => _animationIterationCount ?? ['1']; - List? _animationDirection; + @override + List get animationDirection => _animationDirection ?? ['normal']; + set animationDirection(List? value) { _animationDirection = value; } - @override - List get animationDirection => _animationDirection ?? ['normal']; - List? _animationFillMode; + @override + List get animationFillMode => _animationFillMode ?? ['none']; + set animationFillMode(List? value) { _animationFillMode = value; } - @override - List get animationFillMode => _animationFillMode ?? ['none']; - List? _animationPlayState; + @override + List get animationPlayState => _animationPlayState ?? ['running']; // paused + set animationPlayState(List? value) { _animationPlayState = value; } - @override - List get animationPlayState => _animationPlayState ?? ['running']; // paused - bool shouldAnimation(List properties) { if (renderBoxModel != null) { bool shouldAnimation = false; From 59b425cd248c2803e24f8578f1330dfb5842c97e Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 11 Sep 2022 20:03:14 +0800 Subject: [PATCH 295/498] feat: add widget element class and support async and sync function for nativeValue. --- bridge/CMakeLists.txt | 3 +- bridge/bindings/qjs/bom/window.cc | 0 bridge/bindings/qjs/dom/event_target.cc | 0 bridge/bindings/qjs/dom/event_target.h | 0 bridge/bindings/qjs/qjs_function.cc | 29 +++ bridge/bindings/qjs/qjs_function.h | 6 + bridge/bindings/qjs/script_promise.cc | 10 + bridge/bindings/qjs/script_promise.h | 2 + bridge/bindings/qjs/script_promise_resolver.h | 18 +- bridge/bindings/qjs/script_value.cc | 95 +++++++-- bridge/bindings/qjs/script_value.h | 9 + bridge/bindings/qjs/wrapper_type_info.h | 1 + bridge/core/binding_object.cc | 117 ++++++++++- bridge/core/binding_object.h | 26 ++- bridge/core/dom/events/event_target.cc | 4 +- bridge/core/dom/events/event_target.h | 2 +- .../core/dom/legacy/bounding_client_rect.cc | 2 +- bridge/core/dom/legacy/bounding_client_rect.h | 2 +- bridge/core/dom/node.h | 3 + bridge/core/extensions/widget_element.cc | 37 ++++ bridge/core/extensions/widget_element.d.ts | 6 + bridge/core/extensions/widget_element.h | 34 +++ .../canvas/canvas_rendering_context_2d.cc | 2 +- .../html/canvas/canvas_rendering_context_2d.h | 2 +- bridge/foundation/native_type.h | 7 + bridge/foundation/native_value.cc | 25 ++- bridge/foundation/native_value.h | 14 +- bridge/foundation/native_value_converter.cc | 137 ------------ bridge/foundation/native_value_converter.h | 34 ++- webf/lib/src/bridge/binding.dart | 198 ++++++++++++------ webf/lib/src/bridge/native_types.dart | 7 +- webf/lib/src/bridge/native_value.dart | 32 ++- webf/lib/src/foundation/binding.dart | 5 + 33 files changed, 610 insertions(+), 259 deletions(-) delete mode 100644 bridge/bindings/qjs/bom/window.cc delete mode 100644 bridge/bindings/qjs/dom/event_target.cc delete mode 100644 bridge/bindings/qjs/dom/event_target.h create mode 100644 bridge/core/extensions/widget_element.cc create mode 100644 bridge/core/extensions/widget_element.d.ts create mode 100644 bridge/core/extensions/widget_element.h delete mode 100644 bridge/foundation/native_value_converter.cc diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index ce5558a20c..e7c941e63c 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -78,7 +78,6 @@ list(APPEND BRIDGE_SOURCE foundation/task_queue.cc foundation/string_view.cc foundation/native_value.cc - foundation/native_value_converter.cc foundation/ui_command_buffer.cc polyfill/dist/polyfill.cc ) @@ -240,6 +239,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/dom/child_node_list.cc core/dom/empty_node_list.cc core/dom/container_node.cc + core/extensions/widget_element.cc core/events/error_event.cc core/events/message_event.cc core/events/animation_event.cc @@ -356,6 +356,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/qjs_html_html_element.cc out/qjs_html_image_element.cc out/qjs_html_canvas_element.cc + out/qjs_widget_element.cc out/qjs_canvas_rendering_context_2d.cc out/qjs_canvas_rendering_context.cc out/canvas_types.cc diff --git a/bridge/bindings/qjs/bom/window.cc b/bridge/bindings/qjs/bom/window.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/event_target.cc b/bridge/bindings/qjs/dom/event_target.cc deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/dom/event_target.h b/bridge/bindings/qjs/dom/event_target.h deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index a3d99f1e16..d2d5601859 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -4,10 +4,39 @@ */ #include "qjs_function.h" #include +#include #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* context = static_cast(JS_GetOpaque(opaque_object, JS_CLASS_OBJECT)); + std::vector arguments; + arguments.reserve(argc); + for(int i = 0; i < argc; i ++) { + arguments[i] = ScriptValue(ctx, argv[i]); + } + ScriptValue result = context->qjs_function_callback(ctx, ScriptValue(ctx, this_val), argc, arguments.data(), context->private_data); + delete context; + return JS_DupValue(ctx, result.QJSValue()); +} + +QJSFunction::QJSFunction(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, void* private_data) { + 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); +} + bool QJSFunction::IsFunction(JSContext* ctx) { return JS_IsFunction(ctx, function_); } diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index e702e3bffa..209b4466d7 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -9,6 +9,8 @@ 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 { @@ -16,7 +18,11 @@ class QJSFunction { 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), function_(JS_DupValue(ctx, function)){}; + explicit QJSFunction(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, void* private_data); // This safe to free function_ at GC stage. ~QJSFunction() { JS_FreeValue(ctx_, function_); } diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index c0ff4c76c0..9b7b87974f 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -18,6 +18,16 @@ ScriptPromise::ScriptPromise(JSContext* ctx, JSValue promise) : ctx_(ctx) { 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()); } diff --git a/bridge/bindings/qjs/script_promise.h b/bridge/bindings/qjs/script_promise.h index 418fefadb7..a21e788eac 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -7,6 +7,7 @@ #include #include "foundation/macros.h" +#include "qjs_function.h" #include "script_value.h" namespace webf { @@ -21,6 +22,7 @@ class ScriptPromise final { public: ScriptPromise() = default; ScriptPromise(JSContext* ctx, JSValue promise); + ScriptPromise(JSContext* ctx, std::shared_ptr* resolve_func, std::shared_ptr* reject_func); JSValue ToQuickJS(); diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 2c18d1a1f9..041a3358e4 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -29,12 +29,20 @@ class ScriptPromiseResolver { 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); + } + private: enum ResolutionState { kPending, @@ -47,13 +55,17 @@ class ScriptPromiseResolver { 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_->IsValid() || !context_) return; assert(new_state == kResolving || new_state == kRejecting); state_ = new_state; - JSValue qjs_value = toQuickJS(context_->ctx(), value); - ResolveOrRejectImmediately(qjs_value); - JS_FreeValue(context_->ctx(), qjs_value); + ResolveOrRejectImmediately(value); } void ResolveOrRejectImmediately(JSValue value); diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 34210c7035..8b7c4332da 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -5,14 +5,69 @@ #include "script_value.h" #include #include "core/executing_context.h" +#include "core/binding_object.h" +#include "bindings/qjs/converter_impl.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_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), 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); @@ -99,23 +154,21 @@ NativeValue ScriptValue::ToNative() const { } else if (JS_IsString(value_)) { // NativeString owned by NativeValue will be freed by users. return NativeValueConverter::ToNativeValue(ToString()); + } else 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(); + } + return Native_NewList(values.size(), result); } - - // else if (JS_IsFunction(ctx_, value_)) { - // auto* context = static_cast(JS_GetContextOpaque(ctx_)); - // auto* functionContext = new NativeFunctionContext{context, value_}; - // return Native_NewPtr(JSPointerType::NativeFunctionContext, functionContext); - // } - // else if (JS_IsObject(value_)) { - // auto* context = static_cast(JS_GetContextOpaque(ctx_)); - // auto* context = static_cast(JS_GetContextOpaque(ctx)); - // if (JS_IsInstanceOf(ctx, value, ImageElement::instance(context)->jsObject)) { - // auto* imageElementInstance = static_cast(JS_GetOpaque(value, Element::classId())); - // return Native_NewPtr(JSPointerType::NativeEventTarget, imageElementInstance->nativeEventTarget); - // } - - // return Native_NewJSON(context, value); + // TODO: needs a better way to convert bindingObject to pointers. + if (QJSEventTarget::HasInstance(ExecutingContext::From(ctx_), value_)) { + auto* event_target = toScriptWrappable(value_); + return Native_NewPtr(JSPointerType::Others, event_target->bindingObject()); + } + return NativeValueConverter::ToNativeValue(ScriptValue(ctx_, value_)); } return Native_NewNull(); @@ -137,6 +190,18 @@ bool ScriptValue::IsString() { return JS_IsString(value_); } +bool ScriptValue::IsNull() { + return JS_IsNull(value_); +} + +bool ScriptValue::IsUndefined() { + return JS_IsUndefined(value_); +} + +bool ScriptValue::IsBool() { + return JS_IsBool(value_); +} + void ScriptValue::Trace(GCVisitor* visitor) { visitor->Trace(value_); } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index ce522d0f97..87016fc33c 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -41,6 +41,7 @@ class ScriptValue final { : ctx_(ctx), value_(JS_NewUnicodeString(ctx, string->string(), string->length())) {} explicit ScriptValue(JSContext* ctx, double v) : ctx_(ctx), value_(JS_NewFloat64(ctx, v)) {} explicit ScriptValue(JSContext* ctx) : ctx_(ctx){}; + explicit ScriptValue(JSContext* ctx, const NativeValue& native_value); ScriptValue() = default; // Copy and assignment @@ -63,6 +64,9 @@ class ScriptValue final { bool IsEmpty(); bool IsObject(); bool IsString(); + bool IsNull(); + bool IsUndefined(); + bool IsBool(); void Trace(GCVisitor* visitor); @@ -71,6 +75,11 @@ class ScriptValue final { JSValue value_{JS_NULL}; }; +template +class ScriptValueConverter { + using ImplType = T; +}; + } // namespace webf #endif // BRIDGE_SCRIPT_VALUE_H diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 83cd79100a..988ef0b594 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -53,6 +53,7 @@ enum { 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, diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 2cf7234b9f..00b1d53ce0 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -6,14 +6,16 @@ #include "binding_object.h" #include "binding_call_methods.h" #include "bindings/qjs/exception_state.h" +#include "bindings/qjs/script_promise_resolver.h" #include "core/executing_context.h" #include "foundation/logging.h" +#include "foundation/native_value_converter.h" namespace webf { void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_object, NativeValue* return_value, - NativeString* method, + NativeValue* method, int32_t argc, NativeValue* argv) { NativeValue result = binding_object->binding_target_->HandleCallFromDartSide(method, argc, argv); @@ -45,15 +47,34 @@ NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, } NativeValue return_value = Native_NewNull(); + NativeValue native_method = NativeValueConverter::ToNativeValue(method); binding_object_->invoke_bindings_methods_from_native(binding_object_, &return_value, - method.ToNativeString().release(), argc, argv); + &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: invokeBindingMethod 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(binding_call_methods::kgetPropertyMagic, 1, argv, exception_state); + return InvokeBindingMethod(BindingMethodCallOperations::kGetProperty, 1, argv, exception_state); } NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, @@ -61,7 +82,95 @@ NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, ExceptionState& exception_state) const { context_->FlushUICommand(); const NativeValue argv[] = {Native_NewString(prop.ToNativeString().release()), value}; - return InvokeBindingMethod(binding_call_methods::ksetPropertyMagic, 2, argv, exception_state); + 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* binding_object = toScriptWrappable(this_val.QJSValue()); + + std::vector arguments; + arguments.reserve(argc + 1); + + arguments[0] = NativeValueConverter::ToNativeValue(id); + for(int i = 0; i < argc; i ++) { + arguments[i + 1] = argv[i].ToNative(); + } + + ExceptionState exception_state; + NativeValue result = binding_object->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, arguments.size(), arguments.data(), exception_state); + + if (exception_state.HasException()) { + JSValue error = JS_GetException(ctx); + binding_object->context_->ReportError(error); + JS_FreeValue(ctx, error); + return ScriptValue::Empty(ctx); + } + + return ScriptValue(ctx, result); +} + +struct BindingObjectPromiseContext { + BindingObject* binding_object; + ExecutingContext* context; + std::shared_ptr promise_resolver; +}; + +void HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, int32_t contextId, const char* errmsg) { + auto* promise_context = static_cast(ptr); + if (!promise_context->context->IsValid()) + 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); + } + + 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* binding_object = toScriptWrappable(this_val.QJSValue()); + + auto promise_resolver = ScriptPromiseResolver::Create(binding_object->context_); + + auto* promise_context = new BindingObjectPromiseContext{ + binding_object, + binding_object->context_, + promise_resolver + }; + + std::vector arguments; + arguments.reserve(argc + 4); + + arguments[0] = NativeValueConverter::ToNativeValue(id); + arguments[1] = NativeValueConverter::ToNativeValue(binding_object->context_->contextId()); + arguments[2] = NativeValueConverter>::ToNativeValue(promise_context); + arguments[3] = NativeValueConverter>::ToNativeValue(reinterpret_cast(HandleAnonymousAsyncCalledFromDart)); + + for(int i = 0; i < argc; i ++) { + arguments[i + 4] = argv[i].ToNative(); + } + + ExceptionState exception_state; + NativeValue result = binding_object->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, arguments.data(), exception_state); + return ScriptValue(ctx, result); +} + +NativeValue BindingObject::GetAllBindingPropertyNames(ExceptionState& exception_state) const { + context_->FlushUICommand(); + return InvokeBindingMethod(BindingMethodCallOperations::kGetAllPropertyNames, 0, nullptr, exception_state); } bool BindingObject::IsEventTarget() const { diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h index 69595094f6..669a1197a4 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -18,13 +18,13 @@ class ExceptionState; using InvokeBindingsMethodsFromNative = void (*)(const NativeBindingObject* binding_object, NativeValue* return_value, - NativeString* method, + NativeValue* method, int32_t argc, const NativeValue* argv); using InvokeBindingMethodsFromDart = void (*)(NativeBindingObject* binding_object, NativeValue* return_value, - NativeString* method, + NativeValue* method, int32_t argc, NativeValue* argv); @@ -35,7 +35,7 @@ struct NativeBindingObject { static void HandleCallFromDartSide(NativeBindingObject* binding_object, NativeValue* return_value, - NativeString* method, + NativeValue* method, int32_t argc, NativeValue* argv); @@ -44,15 +44,27 @@ struct NativeBindingObject { InvokeBindingsMethodsFromNative invoke_bindings_methods_from_native{nullptr}; }; +enum BindingMethodCallOperations { + kGetProperty, + kSetProperty, + kGetAllPropertyNames, + kAnonymousFunctionCall, + kAsyncAnonymousFunction, +}; + 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); + using ImplType = BindingObject*; BindingObject() = delete; ~BindingObject(); explicit BindingObject(ExecutingContext* context); // Handle call from dart side. - virtual NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) = 0; + 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, @@ -60,6 +72,7 @@ class BindingObject { 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_; } @@ -71,6 +84,11 @@ class BindingObject { virtual bool IsTouchList() const; protected: + 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); diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 06d04a09f7..01ebab9ca2 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -246,9 +246,9 @@ DispatchEventResult EventTarget::DispatchEventInternal(Event& event, ExceptionSt return dispatch_result; } -NativeValue EventTarget::HandleCallFromDartSide(NativeString* native_method, int32_t argc, const NativeValue* argv) { +NativeValue EventTarget::HandleCallFromDartSide(const NativeValue* native_method, int32_t argc, const NativeValue* argv) { MemberMutationScope mutation_scope{GetExecutingContext()}; - AtomicString method = AtomicString(ctx(), native_method); + AtomicString method = NativeValueConverter::FromNativeValue(ctx(), *native_method); if (method == binding_call_methods::kdispatchEvent) { return HandleDispatchEventFromDart(argc, argv); diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 7f0d8a6ecf..df33f56329 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -141,7 +141,7 @@ class EventTarget : public ScriptWrappable, public BindingObject { DispatchEventResult DispatchEventInternal(Event& event, ExceptionState& exception_state); - NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) override; + 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 diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc index 30d9c69f17..9879698624 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.cc +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -15,7 +15,7 @@ BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, Native BoundingClientRect::BoundingClientRect(ExecutingContext* context, NativeBindingObject* native_binding_object) : ScriptWrappable(context->ctx()), BindingObject(context, native_binding_object) {} -NativeValue BoundingClientRect::HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) { +NativeValue BoundingClientRect::HandleCallFromDartSide(const NativeValue* method, int32_t argc, const NativeValue* argv) { return Native_NewNull(); } diff --git a/bridge/core/dom/legacy/bounding_client_rect.h b/bridge/core/dom/legacy/bounding_client_rect.h index 91511bf9f6..7317fbbf5e 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.h +++ b/bridge/core/dom/legacy/bounding_client_rect.h @@ -22,7 +22,7 @@ class BoundingClientRect : public ScriptWrappable, public BindingObject { static BoundingClientRect* Create(ExecutingContext* context, NativeBindingObject* native_binding_object); explicit BoundingClientRect(ExecutingContext* context, NativeBindingObject* native_binding_object); - NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) override; + NativeValue HandleCallFromDartSide(const NativeValue* method, int32_t argc, const NativeValue* argv) override; double x() const { return x_; } double y() const { return y_; } diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 44f900f7f4..bff4df548f 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -249,6 +249,7 @@ class Node : public EventTarget { kHasEventTargetDataFlag = 1 << 21, kHasDuplicateAttributes = 1 << 24, + kIsWidgetElement = 1 << 25, kSelfOrAncestorHasDirAutoAttribute = 1 << 27, kDefaultNodeFlags = kIsFinishedParsingChildrenFlag, @@ -296,6 +297,8 @@ class Node : public EventTarget { static_cast(ElementNamespaceType::kOther), kCreateHTMLElement = kDefaultNodeFlags | kIsContainerFlag | static_cast(DOMNodeType::kElement) | static_cast(ElementNamespaceType::kHTML), + kCreateWidgetElement = kDefaultNodeFlags | kIsContainerFlag | static_cast(DOMNodeType::kElement) | + static_cast(ElementNamespaceType::kHTML) | kIsWidgetElement, kCreateMathMLElement = kDefaultNodeFlags | kIsContainerFlag | static_cast(DOMNodeType::kElement) | static_cast(ElementNamespaceType::kMathML), kCreateSVGElement = kDefaultNodeFlags | kIsContainerFlag | static_cast(DOMNodeType::kElement) | diff --git a/bridge/core/extensions/widget_element.cc b/bridge/core/extensions/widget_element.cc new file mode 100644 index 0000000000..17b83fefbb --- /dev/null +++ b/bridge/core/extensions/widget_element.cc @@ -0,0 +1,37 @@ +/* +* 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 "foundation/native_value_converter.h" + +namespace webf { + +WidgetElement::WidgetElement(const AtomicString& tag_name, Document* document): HTMLElement(tag_name, document, ConstructionType::kCreateWidgetElement) {} + +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& names, ExceptionState& exception_state) { + NativeValue result = GetAllBindingPropertyNames(exception_state); + assert(result.tag == NativeTag::TAG_LIST); + std::vector property_names = NativeValueConverter>::FromNativeValue(ctx(), result); + names.reserve(property_names.size()); + for(int i = 0; i < property_names.size(); i ++) { + names[i] = property_names[i]; + } +} + +ScriptValue WidgetElement::item(const AtomicString& key, ExceptionState& exception_state) { + return ScriptValue(ctx(), GetBindingProperty(key, exception_state)); +} + +bool WidgetElement::SetItem(const AtomicString& key, const ScriptValue& value, ExceptionState& exception_state) { + NativeValue result = SetBindingProperty(key, value.ToNative(), exception_state); + return NativeValueConverter::FromNativeValue(result); +} + +} \ No newline at end of file diff --git a/bridge/core/extensions/widget_element.d.ts b/bridge/core/extensions/widget_element.d.ts new file mode 100644 index 0000000000..f8dc2b34d1 --- /dev/null +++ b/bridge/core/extensions/widget_element.d.ts @@ -0,0 +1,6 @@ +import {HTMLElement} from "../html/html_element"; + +interface WidgetElement extends HTMLElement { + [key: string]: any; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/extensions/widget_element.h b/bridge/core/extensions/widget_element.h new file mode 100644 index 0000000000..b3e57e608c --- /dev/null +++ b/bridge/core/extensions/widget_element.h @@ -0,0 +1,34 @@ +/* +* 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 "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); + + bool NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state); + void NamedPropertyEnumerator(std::vector& names, ExceptionState&); + + ScriptValue item(const AtomicString& key, ExceptionState& exception_state); + bool SetItem(const AtomicString& key, const ScriptValue& value, ExceptionState& exception_state); + + private: + +}; + +} + +#endif // WEBF_CORE_DOM_WIDGET_ELEMENT_H_ diff --git a/bridge/core/html/canvas/canvas_rendering_context_2d.cc b/bridge/core/html/canvas/canvas_rendering_context_2d.cc index 71dfd9e7b0..83e22b4837 100644 --- a/bridge/core/html/canvas/canvas_rendering_context_2d.cc +++ b/bridge/core/html/canvas/canvas_rendering_context_2d.cc @@ -14,7 +14,7 @@ CanvasRenderingContext2D::CanvasRenderingContext2D(ExecutingContext* context, NativeBindingObject* native_binding_object) : BindingObject(context, native_binding_object), CanvasRenderingContext(context) {} -NativeValue CanvasRenderingContext2D::HandleCallFromDartSide(NativeString* method, +NativeValue CanvasRenderingContext2D::HandleCallFromDartSide(const NativeValue* method, int32_t argc, const NativeValue* argv) { return Native_NewNull(); diff --git a/bridge/core/html/canvas/canvas_rendering_context_2d.h b/bridge/core/html/canvas/canvas_rendering_context_2d.h index 4c23be5b64..4dc8947225 100644 --- a/bridge/core/html/canvas/canvas_rendering_context_2d.h +++ b/bridge/core/html/canvas/canvas_rendering_context_2d.h @@ -18,7 +18,7 @@ class CanvasRenderingContext2D : public CanvasRenderingContext, public BindingOb CanvasRenderingContext2D() = delete; explicit CanvasRenderingContext2D(ExecutingContext* context, NativeBindingObject* native_binding_object); - NativeValue HandleCallFromDartSide(NativeString* method, int32_t argc, const NativeValue* argv) override; + NativeValue HandleCallFromDartSide(const NativeValue* method, int32_t argc, const NativeValue* argv) override; bool IsCanvas2d() const override; }; diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h index bd20d1532d..e4f21a437c 100644 --- a/bridge/foundation/native_type.h +++ b/bridge/foundation/native_type.h @@ -6,6 +6,7 @@ #define BRIDGE_FOUNDATION_NATIVE_TYPE_H_ #include +#include #include "bindings/qjs/qjs_function.h" #include "bindings/qjs/script_value.h" #include "foundation/native_string.h" @@ -39,6 +40,12 @@ struct NativeTypeDouble final : public NativeTypeBaseHelper {}; // JSON struct NativeTypeJSON final : public NativeTypeBaseHelper {}; +// Array +template +struct NativeTypeArray final : public NativeTypeBase { + using ImplType = typename std::vector; +}; + // Pointer template struct NativeTypePointer final : public NativeTypeBaseHelper {}; diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index b460c364b7..6fef0deb03 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -10,17 +10,18 @@ namespace webf { NativeValue Native_NewNull() { - return (NativeValue){.u = {.int64 = 0}, NativeTag::TAG_NULL}; + return (NativeValue){.u = {.int64 = 0}, .uint32 = 0, .tag = NativeTag::TAG_NULL}; } NativeValue Native_NewString(NativeString* string) { return (NativeValue){ .u = {.ptr = static_cast(string)}, - NativeTag::TAG_STRING, + .uint32 = 0, + .tag = NativeTag::TAG_STRING, }; } -NativeValue Native_NewCString(std::string string) { +NativeValue Native_NewCString(const std::string& string) { std::unique_ptr nativeString = stringToNativeString(string); // NativeString owned by NativeValue will be freed by users. return Native_NewString(nativeString.release()); @@ -32,28 +33,35 @@ NativeValue Native_NewFloat64(double value) { return (NativeValue){ .u = {.int64 = result}, - NativeTag::TAG_FLOAT64, + .uint32 = 0, + .tag = NativeTag::TAG_FLOAT64, }; } NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr) { - return (NativeValue){.u = {.ptr = ptr}, NativeTag::TAG_POINTER}; + return (NativeValue){.u = {.ptr = ptr}, .uint32 = static_cast(pointerType), .tag = NativeTag::TAG_POINTER}; } NativeValue Native_NewBool(bool value) { return (NativeValue){ .u = {.int64 = value ? 1 : 0}, - NativeTag::TAG_BOOL, + .uint32 = 0, + .tag = NativeTag::TAG_BOOL, }; } NativeValue Native_NewInt64(int64_t value) { return (NativeValue){ .u = {.int64 = value}, - NativeTag::TAG_INT, + .uint32 = 0, + .tag = NativeTag::TAG_INT, }; } +NativeValue Native_NewList(uint32_t argc, NativeValue* argv) { + return (NativeValue){.u = {.ptr = reinterpret_cast(argv)}, .uint32 = argc, .tag = NativeTag::TAG_LIST}; +} + NativeValue Native_NewJSON(const ScriptValue& value) { ExceptionState exception_state; ScriptValue json = value.ToJSONStringify(&exception_state); @@ -65,7 +73,8 @@ NativeValue Native_NewJSON(const ScriptValue& value) { auto native_string = str.ToNativeString(); NativeValue result = (NativeValue){ .u = {.ptr = static_cast(native_string.release())}, - NativeTag::TAG_JSON, + .uint32 = 0, + .tag = NativeTag::TAG_JSON, }; return result; } diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index 0e6bc60e23..b49ef2ef94 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -21,9 +21,10 @@ enum NativeTag { TAG_NULL = 3, TAG_FLOAT64 = 4, TAG_JSON = 5, - TAG_POINTER = 6, - TAG_FUNCTION = 7, - TAG_ASYNC_FUNCTION = 8, + TAG_LIST = 6, + TAG_POINTER = 7, + TAG_FUNCTION = 8, + TAG_ASYNC_FUNCTION = 9, }; enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, Others = 2 }; @@ -35,9 +36,11 @@ class ScriptValue; struct NativeValue { union { int64_t int64; + double float64; void* ptr; } u; - int64_t tag; + uint32_t uint32; + int32_t tag; }; struct NativeFunctionContext; @@ -64,10 +67,11 @@ struct NativeFunctionContext { 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_NewInt64(int64_t value); +NativeValue Native_NewList(uint32_t argc, NativeValue* argv); NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr); NativeValue Native_NewJSON(const ScriptValue& value); diff --git a/bridge/foundation/native_value_converter.cc b/bridge/foundation/native_value_converter.cc deleted file mode 100644 index 3a5243fc5f..0000000000 --- a/bridge/foundation/native_value_converter.cc +++ /dev/null @@ -1,137 +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_converter.h" - -namespace webf { - -#define AnonymousFunctionCallPreFix "_anonymous_fn_" -#define AsyncAnonymousFunctionCallPreFix "_anonymous_async_fn_" - -// void call_native_function(NativeFunctionContext* functionContext, -// int32_t argc, -// NativeValue* argv, -// NativeValue* returnValue) { -// // auto* context = functionContext->context_; -// // auto* arguments = new JSValue[argc]; -// // for (int i = 0; i < argc; i++) { -// // arguments[i] = nativeValueToJSValue(context, argv[i]); -// // } -// // JSValue result = JS_Call(context->ctx(), functionContext->m_callback, context->Global(), argc, arguments); -// // context->DrainPendingPromiseJobs(); -// // if (context->HandleException(&result)) { -// // *returnValue = jsValueToNativeValue(context->ctx(), result); -// // } -// // -// // JS_FreeValue(context->ctx(), result); -// // -// // for (int i = 0; i < argc; i++) { -// // JS_FreeValue(context->ctx(), arguments[i]); -// // } -// // delete[] arguments; -// // delete functionContext; -//} - -static JSValue anonymousFunction(JSContext* ctx, - JSValueConst this_val, - int argc, - JSValueConst* argv, - int magic, - JSValue* func_data) { - auto id = magic; - // auto* eventTarget = static_cast(JS_GetOpaque(this_val, JSValueGetClassId(this_val))); - // - // std::string call_params = AnonymousFunctionCallPreFix + std::to_string(id); - // - // auto* arguments = new NativeValue[argc]; - // for (int i = 0; i < argc; i++) { - // arguments[i] = jsValueToNativeValue(ctx, argv[i]); - // } - // - // JSValue returnValue = eventTarget->callNativeMethods(call_params.c_str(), argc, arguments); - // delete[] arguments; - // return returnValue; -} - -void anonymousAsyncCallback(void* callbackContext, NativeValue* nativeValue, int32_t contextId, const char* errmsg) { - // auto* promiseContext = static_cast(callbackContext); - // if (!promiseContext->context->IsValid()) - // return; - // if (promiseContext->context->contextId() != contextId) - // return; - // - // auto* context = promiseContext->context; - // - // if (nativeValue != nullptr) { - // JSValue value = nativeValueToJSValue(promiseContext->context, *nativeValue); - // JSValue returnValue = JS_Call(context->ctx(), promiseContext->resolveFunc, context->Global(), 1, &value); - // context->DrainPendingPromiseJobs(); - // context->HandleException(&returnValue); - // JS_FreeValue(context->ctx(), value); - // JS_FreeValue(context->ctx(), returnValue); - // } else if (errmsg != nullptr) { - // JSValue error = JS_NewError(context->ctx()); - // JS_DefinePropertyValueStr(context->ctx(), error, "message", JS_NewString(context->ctx(), errmsg), - // JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE); JSValue returnValue = JS_Call(context->ctx(), - // promiseContext->rejectFunc, context->Global(), 1, &error); context->DrainPendingPromiseJobs(); - // context->HandleException(&returnValue); - // JS_FreeValue(context->ctx(), error); - // JS_FreeValue(context->ctx(), returnValue); - // } - // - // JS_FreeValue(context->ctx(), promiseContext->resolveFunc); - // JS_FreeValue(context->ctx(), promiseContext->rejectFunc); - // list_del(&promiseContext->link); -} - -static JSValue anonymousAsyncFunction(JSContext* ctx, - JSValueConst this_val, - int argc, - JSValueConst* argv, - int magic, - JSValue* func_data) { - // JSValue resolving_funcs[2]; - // JSValue promise = JS_NewPromiseCapability(ctx, resolving_funcs); - // - // auto id = magic; - // auto* eventTarget = static_cast(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->callNativeMethods(call_params.c_str(), argc + 3, arguments); - // delete[] arguments; - // - // return promise; - return JS_NULL; -} - -std::shared_ptr CreateSyncCallback(JSContext* ctx, int function_id) { - JSValue callback = JS_NewCFunctionData(ctx, anonymousFunction, 4, function_id, 0, nullptr); - auto result = QJSFunction::Create(ctx, callback); - JS_FreeValue(ctx, callback); - return result; -} - -std::shared_ptr CreateAsyncCallback(JSContext* ctx, int function_id) { - JSValue callback = JS_NewCFunctionData(ctx, anonymousAsyncFunction, 4, function_id, 0, nullptr); - auto result = QJSFunction::Create(ctx, callback); - JS_FreeValue(ctx, callback); - return result; -} - -} // namespace webf diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 252564a52b..be4ad61d6c 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -86,9 +86,6 @@ struct NativeValueConverter> : public NativeValueConverterB static T* FromNativeValue(NativeValue value) { return static_cast(value.u.ptr); } }; -std::shared_ptr CreateSyncCallback(JSContext* ctx, int function_id); -std::shared_ptr CreateAsyncCallback(JSContext* ctx, int function_id); - template <> struct NativeValueConverter : public NativeValueConverterBase { static NativeValue ToNativeValue(ImplType value) { @@ -96,7 +93,9 @@ struct NativeValueConverter : public NativeValueConverterBas assert(false); } - static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { return CreateSyncCallback(ctx, value.u.int64); }; + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + return QJSFunction::Create(ctx, BindingObject::AnonymousFunctionCallback, 4, value.u.ptr); + }; }; template <> @@ -106,7 +105,32 @@ struct NativeValueConverter : public NativeValueConvert assert(false); } - static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { return CreateAsyncCallback(ctx, value.u.int64); } + static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { + return QJSFunction::Create(ctx, BindingObject::AnonymousAsyncFunctionCallback, 4, value.u.ptr); + } +}; + +template +struct NativeValueConverter> : public NativeValueConverterBase> { + using ImplType = typename NativeTypeArray::ImplType>::ImplType; + static NativeValue ToNativeValue(ImplType value) { + auto* ptr = new NativeValue[value.size()]; + for(int i = 0; i < value.size(); i ++) { + ptr[i] = NativeValueConverter::ToNativeValue(value[i]); + } + return Native_NewList(value.size(), ptr); + } + + static ImplType FromNativeValue(JSContext* ctx, NativeValue native_value) { + size_t length = native_value.uint32; + auto* arr = static_cast(native_value.u.ptr); + std::vector vec; + vec.reserve(length); + for(int i = 0; i < length; i ++) { + vec[i] = NativeValueConverter::FromNativeValue(ctx, arr[i]); + } + return vec; + } }; } // namespace webf diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 9886f61d60..8c9ab708f2 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -15,83 +15,147 @@ 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]}'); + } + + 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) { + return bindingObject.getAllBindingPropertyNames(); +} + +List bindingCallMethodDispatchTable = [ + getterBindingCall, + setterBindingCall, + getPropertyNamesBindingCall, +]; + // This function receive calling from binding side. -void _invokeBindingMethodFromNativeImpl(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); - var result; - try { - if (method == GetPropertyMagic && argc == 1) { - if (isEnabledLog) { - print('$bindingObject getBindingProperty key: ${values[0]}'); - } + var result = null; + try { + // Method is binding call method operations from internal. + if (method is int) { + // Get and setter ops + if (method <= 2) { + BindingObject bindingObject = BindingBridge.getBindingObject(nativeBindingObject); + result = bindingCallMethodDispatchTable[method](bindingObject, values); + } else { + if (method == BindingMethodCallOperations.AnonymousFunctionCall) { + int id = values[0]; + List functionArguments = values.sublist(1); + AnonymousNativeFunction? fn = getAnonymousNativeFunctionFromId(id); + if (fn == null) { + print('WebF warning: can not find registered anonymous native function for id: $id'); + toNativeValue(returnValue, null); + return; + } + try { + if (isEnabledLog) { + String argsStr = functionArguments.map((e) => e.toString()).join(','); + print('Invoke AnonymousFunction ${debugFunctionMap[id]}($argsStr)'); + } - result = bindingObject.getBindingProperty(values[0]); - } else if (method == SetPropertyMagic && argc == 2) { - if (isEnabledLog) { - print('$bindingObject setBindingProperty key: ${values[0]} value: ${values[1]}'); - } + result = fn(functionArguments); + } catch (e, stack) { + print('$e\n$stack'); + } + removeAnonymousNativeFunctionFromId(id); + } else if (method == BindingMethodCallOperations.AsyncAnonymousFunction) { + int id = values[0]; + AsyncAnonymousNativeFunction? fn = getAsyncAnonymousNativeFunctionFromId(id); + if (fn == null) { + print('WebF warning: can not find registered anonymous native async function for id: $id'); + toNativeValue(returnValue, null); + 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 ${debugFunctionMap[id]}($argsStr)'); + } - bindingObject.setBindingProperty(values[0], values[1]); - result = null; - } else { - if (isEnabledLog) { - print('$bindingObject invokeBindingMethod method: $method args: $values'); + Future p = fn(functionArguments); + p.then((result) { + if (isEnabledLog) { + print('AsyncAnonymousFunction ${debugFunctionMap[id]} Resolved with $result'); + } + Pointer nativeValue = malloc.allocate(sizeOf()); + toNativeValue(nativeValue, result); + callback(callbackContext, nativeValue, contextId, nullptr); + removeAsyncAnonymousNativeFunctionFromId(id); + }).catchError((e, stack) { + String errorMessage = '$e\n$stack'; + if (isEnabledLog) { + print('AsyncAnonymousFunction ${debugFunctionMap[id]} Rejected with $errorMessage'); + } + callback(callbackContext, nullptr, contextId, errorMessage.toNativeUtf8()); + removeAsyncAnonymousNativeFunctionFromId(id); + }); } - result = bindingObject.invokeBindingMethod(method, values); } - } 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); + + // Record the id and method name for debug use. + assert(() { + if (result is AsyncAnonymousNativeFunction || result is AnonymousNativeFunction) { + int id = values[0]; + debugFunctionMap[id] = method; + } + return true; + }()); } } @@ -111,7 +175,8 @@ void _dispatchEventToNative(Event event) { print('dispatch event to native side: target: ${event.target} arguments: $dispatchEventArguments'); } - Pointer method = stringToNativeString('dispatchEvent'); + Pointer method = malloc.allocate(sizeOf()); + toNativeValue(method, 'dispatchEvent'); Pointer allocatedNativeArguments = makeNativeValueArguments(dispatchEventArguments); f(pointer, nullptr, method, dispatchEventArguments.length, allocatedNativeArguments); @@ -127,8 +192,11 @@ void _dispatchEventToNative(Event event) { } abstract class BindingBridge { - static final Pointer> _invokeBindingMethodFromNative = Pointer.fromFunction(_invokeBindingMethodFromNativeImpl); - static Pointer> get nativeInvokeBindingMethod => _invokeBindingMethodFromNative; + static final Pointer> _invokeBindingMethodFromNative = + Pointer.fromFunction(_invokeBindingMethodFromNativeImpl); + + static Pointer> get nativeInvokeBindingMethod => + _invokeBindingMethodFromNative; static final SplayTreeMap _nativeObjects = SplayTreeMap(); @@ -168,13 +236,13 @@ 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.'); + 'Failed to listen event \'$type\' for $target, for which is already bound.'); 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.'); + 'Failed to unlisten event \'$type\' for $target, for which is already unbound.'); target.removeEventListener(type, _dispatchEventToNative); } diff --git a/webf/lib/src/bridge/native_types.dart b/webf/lib/src/bridge/native_types.dart index 2516a9fd93..cbf17405dc 100644 --- a/webf/lib/src/bridge/native_types.dart +++ b/webf/lib/src/bridge/native_types.dart @@ -82,11 +82,12 @@ class NativeTouch extends Struct { } typedef InvokeBindingsMethodsFromNative = Void Function(Pointer binding_object, - Pointer return_value, Pointer method, Int32 argc, Pointer argv); + Pointer return_value, Pointer method, Int32 argc, Pointer argv); + typedef InvokeBindingMethodsFromDart = Void Function(Pointer binding_object, - Pointer return_value, Pointer method, Int32 argc, Pointer argv); + 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); + Pointer return_value, Pointer method, int argc, Pointer argv); class NativeBindingObject extends Struct { external Pointer instance; diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index 95dae7adc6..96daaf1c9c 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -14,7 +14,10 @@ class NativeValue extends Struct { @Int64() external int u; - @Int64() + @Uint32() + external int uint32; + + @Int32() external int tag; } @@ -25,6 +28,7 @@ enum JSValueType { TAG_NULL, TAG_FLOAT64, TAG_JSON, + TAG_LIST, TAG_POINTER, TAG_FUNCTION, TAG_ASYNC_FUNCTION @@ -43,6 +47,9 @@ int _functionId = 0; LinkedHashMap _functionMap = LinkedHashMap(); LinkedHashMap _asyncFunctionMap = LinkedHashMap(); +// Save the relationship between function name and functionId in debug mode. +LinkedHashMap debugFunctionMap = LinkedHashMap(); + AnonymousNativeFunction? getAnonymousNativeFunctionFromId(int id) { return _functionMap[id]; } @@ -53,10 +60,18 @@ AsyncAnonymousNativeFunction? getAsyncAnonymousNativeFunctionFromId(int id) { void removeAnonymousNativeFunctionFromId(int id) { _functionMap.remove(id); + assert(() { + debugFunctionMap.remove(id); + return true; + }()); } void removeAsyncAnonymousNativeFunctionFromId(int id) { _asyncFunctionMap.remove(id); + assert(() { + debugFunctionMap.remove(id); + return true; + }()); } dynamic fromNativeValue(Pointer nativeValue) { @@ -79,6 +94,11 @@ dynamic fromNativeValue(Pointer nativeValue) { return uInt64ToDouble(nativeValue.ref.u); case JSValueType.TAG_POINTER: 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: break; @@ -110,7 +130,15 @@ void toNativeValue(Pointer target, value) { target.ref.u = value.address; } else if (value is BindingObject) { target.ref.tag = JSValueType.TAG_POINTER.index; - target.ref.u = (value.pointer as Pointer?)!.address; + 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); + for(int i = 0; i < value.length; i ++) { + toNativeValue(lists.elementAt(i), value[i]); + } + target.ref.u = lists.address; } else if (value is AsyncAnonymousNativeFunction) { int id = _functionId++; _asyncFunctionMap[id] = value; diff --git a/webf/lib/src/foundation/binding.dart b/webf/lib/src/foundation/binding.dart index 03a6539989..6ac78565b5 100644 --- a/webf/lib/src/foundation/binding.dart +++ b/webf/lib/src/foundation/binding.dart @@ -48,6 +48,11 @@ abstract class BindingObject { // el.foo = 'bar'; void setBindingProperty(String key, value) {} + // Return a list contains all supported properties. + List getAllBindingPropertyNames() { + return []; + } + // Call a method, eg: // el.getContext('2x'); dynamic invokeBindingMethod(String method, List args) {} From 1ac6b60f0a6dce2e228d1d7143d64f268ae6b593 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Sun, 11 Sep 2022 12:04:31 +0000 Subject: [PATCH 296/498] Committing clang-format changes --- bridge/bindings/qjs/qjs_function.cc | 22 +++++++---- bridge/bindings/qjs/qjs_function.h | 11 +++++- bridge/bindings/qjs/script_promise.cc | 4 +- bridge/bindings/qjs/script_promise_resolver.h | 8 +--- bridge/bindings/qjs/script_value.cc | 15 ++++---- bridge/bindings/qjs/script_value.h | 2 +- bridge/core/binding_object.cc | 37 +++++++++++-------- bridge/core/binding_object.h | 12 +++++- bridge/core/dom/events/event_target.cc | 4 +- .../core/dom/legacy/bounding_client_rect.cc | 4 +- bridge/core/extensions/widget_element.cc | 16 ++++---- bridge/core/extensions/widget_element.h | 8 ++-- bridge/foundation/native_type.h | 2 +- bridge/foundation/native_value_converter.h | 6 +-- 14 files changed, 91 insertions(+), 60 deletions(-) diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index d2d5601859..3886fa28f6 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -14,25 +14,31 @@ struct QJSFunctionCallbackContext { void* private_data; }; -static JSValue HandleQJSFunctionCallback(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValue *func_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* context = static_cast(JS_GetOpaque(opaque_object, JS_CLASS_OBJECT)); std::vector arguments; arguments.reserve(argc); - for(int i = 0; i < argc; i ++) { + for (int i = 0; i < argc; i++) { arguments[i] = ScriptValue(ctx, argv[i]); } - ScriptValue result = context->qjs_function_callback(ctx, ScriptValue(ctx, this_val), argc, arguments.data(), context->private_data); + ScriptValue result = + context->qjs_function_callback(ctx, ScriptValue(ctx, this_val), argc, arguments.data(), context->private_data); delete context; return JS_DupValue(ctx, result.QJSValue()); } -QJSFunction::QJSFunction(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, void* private_data) { +QJSFunction::QJSFunction(JSContext* ctx, + QJSFunctionCallback qjs_function_callback, + int32_t length, + void* private_data) { JSValue opaque_object = JS_NewObject(ctx); - auto* context = new QJSFunctionCallbackContext{ - qjs_function_callback, - private_data - }; + auto* context = new QJSFunctionCallbackContext{qjs_function_callback, private_data}; JS_SetOpaque(opaque_object, context); function_ = JS_NewCFunctionData(ctx, HandleQJSFunctionCallback, length, 0, 1, &opaque_object); } diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 209b4466d7..8b9ef402e6 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -9,7 +9,11 @@ namespace webf { -using QJSFunctionCallback = ScriptValue(*)(JSContext* ctx, const ScriptValue& this_val, uint32_t argc, const ScriptValue* argv, void* private_data); +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. @@ -18,7 +22,10 @@ class QJSFunction { 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) { + 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), function_(JS_DupValue(ctx, function)){}; diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index 9b7b87974f..572aa676f1 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -18,7 +18,9 @@ ScriptPromise::ScriptPromise(JSContext* ctx, JSValue promise) : ctx_(ctx) { promise_ = ScriptValue(ctx, promise); } -ScriptPromise::ScriptPromise(JSContext* ctx, std::shared_ptr* resolve_func, std::shared_ptr* reject_func) { +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); diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 041a3358e4..1c8f9c89f9 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -29,9 +29,7 @@ class ScriptPromiseResolver { ResolveOrReject(value, kResolving); } - void Resolve(JSValue 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 @@ -39,9 +37,7 @@ class ScriptPromiseResolver { ResolveOrReject(value, kRejecting); } - void Reject(JSValue value) { - ResolveOrReject(value, kRejecting); - } + void Reject(JSValue value) { ResolveOrReject(value, kRejecting); } private: enum ResolutionState { diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 8b7c4332da..934875e359 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -4,9 +4,9 @@ */ #include "script_value.h" #include -#include "core/executing_context.h" -#include "core/binding_object.h" #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" @@ -49,7 +49,8 @@ static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& nat auto* binding_object = BindingObject::From(ptr); // Only eventTarget can be converted from nativeValue to JSValue. - auto* event_target = DynamicTo(binding_object);; + auto* event_target = DynamicTo(binding_object); + ; if (event_target) { return event_target->ToQuickJS(); } @@ -66,7 +67,8 @@ static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& nat return JS_NULL; } -ScriptValue::ScriptValue(JSContext* ctx, const NativeValue& native_value): ctx_(ctx), value_(FromNativeValue(ExecutingContext::From(ctx), native_value)) {} +ScriptValue::ScriptValue(JSContext* ctx, const NativeValue& native_value) + : ctx_(ctx), value_(FromNativeValue(ExecutingContext::From(ctx), native_value)) {} ScriptValue ScriptValue::CreateErrorObject(JSContext* ctx, const char* errmsg) { JS_ThrowInternalError(ctx, "%s", errmsg); @@ -157,12 +159,11 @@ NativeValue ScriptValue::ToNative() const { } else 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 ++) { + for (int i = 0; i < values.size(); i++) { result[i] = values[i].ToNative(); } return Native_NewList(values.size(), result); - } - else if (JS_IsObject(value_)) { + } else if (JS_IsObject(value_)) { // TODO: needs a better way to convert bindingObject to pointers. if (QJSEventTarget::HasInstance(ExecutingContext::From(ctx_), value_)) { auto* event_target = toScriptWrappable(value_); diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 87016fc33c..4fadc86116 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -75,7 +75,7 @@ class ScriptValue final { JSValue value_{JS_NULL}; }; -template +template class ScriptValueConverter { using ImplType = T; }; diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 00b1d53ce0..101dbf6ed9 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -48,8 +48,7 @@ NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, 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); + binding_object_->invoke_bindings_methods_from_native(binding_object_, &return_value, &native_method, argc, argv); return return_value; } @@ -66,8 +65,7 @@ NativeValue BindingObject::InvokeBindingMethod(BindingMethodCallOperations bindi 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); + binding_object_->invoke_bindings_methods_from_native(binding_object_, &return_value, &native_method, argc, argv); return return_value; } @@ -85,7 +83,11 @@ NativeValue BindingObject::SetBindingProperty(const AtomicString& prop, 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) { +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* binding_object = toScriptWrappable(this_val.QJSValue()); @@ -93,12 +95,13 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, const Scrip arguments.reserve(argc + 1); arguments[0] = NativeValueConverter::ToNativeValue(id); - for(int i = 0; i < argc; i ++) { + for (int i = 0; i < argc; i++) { arguments[i + 1] = argv[i].ToNative(); } ExceptionState exception_state; - NativeValue result = binding_object->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, arguments.size(), arguments.data(), exception_state); + NativeValue result = binding_object->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, + arguments.size(), arguments.data(), exception_state); if (exception_state.HasException()) { JSValue error = JS_GetException(ctx); @@ -139,17 +142,17 @@ void HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, in delete promise_context; } -ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, const ScriptValue& this_val, uint32_t argc, const ScriptValue* argv, void* private_data) { +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* binding_object = toScriptWrappable(this_val.QJSValue()); auto promise_resolver = ScriptPromiseResolver::Create(binding_object->context_); - auto* promise_context = new BindingObjectPromiseContext{ - binding_object, - binding_object->context_, - promise_resolver - }; + auto* promise_context = new BindingObjectPromiseContext{binding_object, binding_object->context_, promise_resolver}; std::vector arguments; arguments.reserve(argc + 4); @@ -157,14 +160,16 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, const arguments[0] = NativeValueConverter::ToNativeValue(id); arguments[1] = NativeValueConverter::ToNativeValue(binding_object->context_->contextId()); arguments[2] = NativeValueConverter>::ToNativeValue(promise_context); - arguments[3] = NativeValueConverter>::ToNativeValue(reinterpret_cast(HandleAnonymousAsyncCalledFromDart)); + arguments[3] = NativeValueConverter>::ToNativeValue( + reinterpret_cast(HandleAnonymousAsyncCalledFromDart)); - for(int i = 0; i < argc; i ++) { + for (int i = 0; i < argc; i++) { arguments[i + 4] = argv[i].ToNative(); } ExceptionState exception_state; - NativeValue result = binding_object->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, arguments.data(), exception_state); + NativeValue result = binding_object->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, + argc + 4, arguments.data(), exception_state); return ScriptValue(ctx, result); } diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h index 669a1197a4..4f80d12961 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -55,8 +55,16 @@ enum BindingMethodCallOperations { 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 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); using ImplType = BindingObject*; BindingObject() = delete; diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 01ebab9ca2..4c68c2c02d 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -246,7 +246,9 @@ DispatchEventResult EventTarget::DispatchEventInternal(Event& event, ExceptionSt return dispatch_result; } -NativeValue EventTarget::HandleCallFromDartSide(const NativeValue* native_method, int32_t argc, const NativeValue* argv) { +NativeValue EventTarget::HandleCallFromDartSide(const NativeValue* native_method, + int32_t argc, + const NativeValue* argv) { MemberMutationScope mutation_scope{GetExecutingContext()}; AtomicString method = NativeValueConverter::FromNativeValue(ctx(), *native_method); diff --git a/bridge/core/dom/legacy/bounding_client_rect.cc b/bridge/core/dom/legacy/bounding_client_rect.cc index 9879698624..ecbc5c54da 100644 --- a/bridge/core/dom/legacy/bounding_client_rect.cc +++ b/bridge/core/dom/legacy/bounding_client_rect.cc @@ -15,7 +15,9 @@ BoundingClientRect* BoundingClientRect::Create(ExecutingContext* context, Native 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) { +NativeValue BoundingClientRect::HandleCallFromDartSide(const NativeValue* method, + int32_t argc, + const NativeValue* argv) { return Native_NewNull(); } diff --git a/bridge/core/extensions/widget_element.cc b/bridge/core/extensions/widget_element.cc index 17b83fefbb..1109f74151 100644 --- a/bridge/core/extensions/widget_element.cc +++ b/bridge/core/extensions/widget_element.cc @@ -1,14 +1,15 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "widget_element.h" #include "foundation/native_value_converter.h" namespace webf { -WidgetElement::WidgetElement(const AtomicString& tag_name, Document* document): HTMLElement(tag_name, document, ConstructionType::kCreateWidgetElement) {} +WidgetElement::WidgetElement(const AtomicString& tag_name, Document* document) + : HTMLElement(tag_name, document, ConstructionType::kCreateWidgetElement) {} bool WidgetElement::NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) { NativeValue result = GetBindingProperty(key, exception_state); @@ -18,9 +19,10 @@ bool WidgetElement::NamedPropertyQuery(const AtomicString& key, ExceptionState& void WidgetElement::NamedPropertyEnumerator(std::vector& names, ExceptionState& exception_state) { NativeValue result = GetAllBindingPropertyNames(exception_state); assert(result.tag == NativeTag::TAG_LIST); - std::vector property_names = NativeValueConverter>::FromNativeValue(ctx(), result); + std::vector property_names = + NativeValueConverter>::FromNativeValue(ctx(), result); names.reserve(property_names.size()); - for(int i = 0; i < property_names.size(); i ++) { + for (int i = 0; i < property_names.size(); i++) { names[i] = property_names[i]; } } @@ -34,4 +36,4 @@ bool WidgetElement::SetItem(const AtomicString& key, const ScriptValue& value, E return NativeValueConverter::FromNativeValue(result); } -} \ No newline at end of file +} // namespace webf \ No newline at end of file diff --git a/bridge/core/extensions/widget_element.h b/bridge/core/extensions/widget_element.h index b3e57e608c..ea2e4e2285 100644 --- a/bridge/core/extensions/widget_element.h +++ b/bridge/core/extensions/widget_element.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #ifndef WEBF_CORE_DOM_WIDGET_ELEMENT_H_ #define WEBF_CORE_DOM_WIDGET_ELEMENT_H_ @@ -15,6 +15,7 @@ namespace webf { // 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); @@ -26,9 +27,8 @@ class WidgetElement : public HTMLElement { bool SetItem(const AtomicString& key, const ScriptValue& value, ExceptionState& exception_state); private: - }; -} +} // namespace webf #endif // WEBF_CORE_DOM_WIDGET_ELEMENT_H_ diff --git a/bridge/foundation/native_type.h b/bridge/foundation/native_type.h index e4f21a437c..71862071ab 100644 --- a/bridge/foundation/native_type.h +++ b/bridge/foundation/native_type.h @@ -41,7 +41,7 @@ struct NativeTypeDouble final : public NativeTypeBaseHelper {}; struct NativeTypeJSON final : public NativeTypeBaseHelper {}; // Array -template +template struct NativeTypeArray final : public NativeTypeBase { using ImplType = typename std::vector; }; diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index be4ad61d6c..4741f44c00 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -110,12 +110,12 @@ struct NativeValueConverter : public NativeValueConvert } }; -template +template struct NativeValueConverter> : public NativeValueConverterBase> { using ImplType = typename NativeTypeArray::ImplType>::ImplType; static NativeValue ToNativeValue(ImplType value) { auto* ptr = new NativeValue[value.size()]; - for(int i = 0; i < value.size(); i ++) { + for (int i = 0; i < value.size(); i++) { ptr[i] = NativeValueConverter::ToNativeValue(value[i]); } return Native_NewList(value.size(), ptr); @@ -126,7 +126,7 @@ struct NativeValueConverter> : public NativeValueConverterBas auto* arr = static_cast(native_value.u.ptr); std::vector vec; vec.reserve(length); - for(int i = 0; i < length; i ++) { + for (int i = 0; i < length; i++) { vec[i] = NativeValueConverter::FromNativeValue(ctx, arr[i]); } return vec; From 3b9bf884966e3f65788e0f6add4ed82ff70c24dd Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 11 Sep 2022 22:06:12 +0800 Subject: [PATCH 297/498] fix: add widget element creation. --- bridge/CMakeLists.txt | 2 +- bridge/bindings/qjs/binding_initializer.cc | 2 ++ bridge/core/dom/document.cc | 5 +++++ .../{extensions => html/custom}/widget_element.cc | 13 +++++++++++++ .../{extensions => html/custom}/widget_element.d.ts | 0 .../{extensions => html/custom}/widget_element.h | 2 ++ 6 files changed, 23 insertions(+), 1 deletion(-) rename bridge/core/{extensions => html/custom}/widget_element.cc (82%) rename bridge/core/{extensions => html/custom}/widget_element.d.ts (100%) rename bridge/core/{extensions => html/custom}/widget_element.h (95%) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index e7c941e63c..d13f7968c2 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -239,7 +239,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/dom/child_node_list.cc core/dom/empty_node_list.cc core/dom/container_node.cc - core/extensions/widget_element.cc + core/html/custom/widget_element.cc core/events/error_event.cc core/events/message_event.cc core/events/animation_event.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index bbff263126..8a0984642e 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -46,6 +46,7 @@ #include "qjs_mouse_event.h" #include "qjs_node.h" #include "qjs_node_list.h" +#include "qjs_widget_element.h" #include "qjs_pointer_event.h" #include "qjs_promise_rejection_event.h" #include "qjs_screen.h" @@ -94,6 +95,7 @@ void InstallBindings(ExecutingContext* context) { QJSComment::Install(context); QJSElement::Install(context); QJSHTMLElement::Install(context); + QJSWidgetElement::Install(context); QJSHTMLDivElement::Install(context); QJSHTMLHeadElement::Install(context); QJSHTMLBodyElement::Install(context); diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index 33b9e9c8a0..c2cc24d555 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -9,6 +9,7 @@ #include "core/dom/element.h" #include "core/dom/text.h" #include "core/frame/window.h" +#include "core/html/custom/widget_element.h" #include "core/html/html_body_element.h" #include "core/html/html_element.h" #include "core/html/html_head_element.h" @@ -42,6 +43,10 @@ Element* Document::createElement(const AtomicString& name, ExceptionState& excep return element; } + if (WidgetElement::IsValidName(name)) { + return MakeGarbageCollected(name, this); + } + return MakeGarbageCollected(name, *this); } diff --git a/bridge/core/extensions/widget_element.cc b/bridge/core/html/custom/widget_element.cc similarity index 82% rename from bridge/core/extensions/widget_element.cc rename to bridge/core/html/custom/widget_element.cc index 1109f74151..acff4e5c41 100644 --- a/bridge/core/extensions/widget_element.cc +++ b/bridge/core/html/custom/widget_element.cc @@ -4,6 +4,7 @@ */ #include "widget_element.h" +#include "core/dom/document.h" #include "foundation/native_value_converter.h" namespace webf { @@ -11,6 +12,18 @@ 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::NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state) { NativeValue result = GetBindingProperty(key, exception_state); return result.tag != NativeTag::TAG_NULL; diff --git a/bridge/core/extensions/widget_element.d.ts b/bridge/core/html/custom/widget_element.d.ts similarity index 100% rename from bridge/core/extensions/widget_element.d.ts rename to bridge/core/html/custom/widget_element.d.ts diff --git a/bridge/core/extensions/widget_element.h b/bridge/core/html/custom/widget_element.h similarity index 95% rename from bridge/core/extensions/widget_element.h rename to bridge/core/html/custom/widget_element.h index ea2e4e2285..c5484c356f 100644 --- a/bridge/core/extensions/widget_element.h +++ b/bridge/core/html/custom/widget_element.h @@ -20,6 +20,8 @@ class WidgetElement : public HTMLElement { using ImplType = WidgetElement*; WidgetElement(const AtomicString& tag_name, Document* document); + static bool IsValidName(const AtomicString& name); + bool NamedPropertyQuery(const AtomicString& key, ExceptionState& exception_state); void NamedPropertyEnumerator(std::vector& names, ExceptionState&); From 498a99bebb33557ab23733298c10816a9a3eac33 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Sun, 11 Sep 2022 14:07:16 +0000 Subject: [PATCH 298/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 2 +- bridge/core/html/custom/widget_element.cc | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 8a0984642e..1cdea9ef1f 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -46,7 +46,6 @@ #include "qjs_mouse_event.h" #include "qjs_node.h" #include "qjs_node_list.h" -#include "qjs_widget_element.h" #include "qjs_pointer_event.h" #include "qjs_promise_rejection_event.h" #include "qjs_screen.h" @@ -56,6 +55,7 @@ #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" diff --git a/bridge/core/html/custom/widget_element.cc b/bridge/core/html/custom/widget_element.cc index acff4e5c41..b7032da52b 100644 --- a/bridge/core/html/custom/widget_element.cc +++ b/bridge/core/html/custom/widget_element.cc @@ -17,8 +17,9 @@ bool WidgetElement::IsValidName(const AtomicString& 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; + for (int i = 0; i < string_view.length(); i++) { + if (string[i] == '-') + return true; } return false; From ed6ecc61d02bbcf32915ab882e7268d6834b5732 Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 11 Sep 2022 22:36:18 +0800 Subject: [PATCH 299/498] fix: fix TEST_DIR env. --- integration_tests/lib/main.dart | 9 ++++++--- integration_tests/scripts/core_integration_starter.js | 4 ++-- integration_tests/scripts/plugin_integration_starter.js | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/integration_tests/lib/main.dart b/integration_tests/lib/main.dart index 94ca82a922..595f96c32b 100644 --- a/integration_tests/lib/main.dart +++ b/integration_tests/lib/main.dart @@ -22,7 +22,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 { @@ -60,14 +60,17 @@ 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', CustomEventInit(detail: 'nativegesture')); webF.controller!.view.document.documentElement?.dispatchEvent(event); } }, diff --git a/integration_tests/scripts/core_integration_starter.js b/integration_tests/scripts/core_integration_starter.js index 7205b62884..64eeb8ff14 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' 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' From 35116846fcba96501f128e5e38f22f04a81cf22b Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 12 Sep 2022 13:34:47 +0800 Subject: [PATCH 300/498] fix: fix binding_object getBindingPropertyNames. --- bridge/bindings/qjs/atomic_string.cc | 3 --- bridge/bindings/qjs/qjs_function.cc | 2 +- bridge/core/binding_object.cc | 16 ++++++------ bridge/core/html/custom/widget_element.cc | 4 +-- bridge/foundation/native_value_converter.h | 3 +-- .../lib/custom/custom_element.dart | 26 ++++++++++++++----- webf/lib/src/bridge/binding.dart | 11 ++++++-- webf/lib/src/bridge/native_value.dart | 2 +- webf/lib/src/foundation/binding.dart | 3 +-- 9 files changed, 42 insertions(+), 28 deletions(-) diff --git a/bridge/bindings/qjs/atomic_string.cc b/bridge/bindings/qjs/atomic_string.cc index dfc2a3bd92..7c1655a4c7 100644 --- a/bridge/bindings/qjs/atomic_string.cc +++ b/bridge/bindings/qjs/atomic_string.cc @@ -129,7 +129,6 @@ StringView AtomicString::ToStringView() const { AtomicString::AtomicString(const AtomicString& value) { if (&value != this) { - JS_FreeAtom(ctx_, atom_); atom_ = JS_DupAtom(value.ctx_, value.atom_); } ctx_ = value.ctx_; @@ -152,7 +151,6 @@ AtomicString& AtomicString::operator=(const AtomicString& other) { AtomicString::AtomicString(AtomicString&& value) noexcept { if (&value != this) { - JS_FreeAtom(ctx_, atom_); atom_ = JS_DupAtom(value.ctx_, value.atom_); } ctx_ = value.ctx_; @@ -163,7 +161,6 @@ AtomicString::AtomicString(AtomicString&& value) noexcept { AtomicString& AtomicString::operator=(AtomicString&& value) noexcept { if (&value != this) { - JS_FreeAtom(ctx_, atom_); atom_ = JS_DupAtom(value.ctx_, value.atom_); } ctx_ = value.ctx_; diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 3886fa28f6..cdc54b705e 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -25,7 +25,7 @@ static JSValue HandleQJSFunctionCallback(JSContext* ctx, std::vector arguments; arguments.reserve(argc); for (int i = 0; i < argc; i++) { - arguments[i] = ScriptValue(ctx, argv[i]); + arguments.emplace_back(ScriptValue(ctx, argv[i])); } ScriptValue result = context->qjs_function_callback(ctx, ScriptValue(ctx, this_val), argc, arguments.data(), context->private_data); diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 101dbf6ed9..63d8caaec3 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -94,9 +94,9 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, std::vector arguments; arguments.reserve(argc + 1); - arguments[0] = NativeValueConverter::ToNativeValue(id); + arguments.emplace_back(NativeValueConverter::ToNativeValue(id)); for (int i = 0; i < argc; i++) { - arguments[i + 1] = argv[i].ToNative(); + arguments.emplace_back(argv[i].ToNative()); } ExceptionState exception_state; @@ -157,14 +157,14 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, std::vector arguments; arguments.reserve(argc + 4); - arguments[0] = NativeValueConverter::ToNativeValue(id); - arguments[1] = NativeValueConverter::ToNativeValue(binding_object->context_->contextId()); - arguments[2] = NativeValueConverter>::ToNativeValue(promise_context); - arguments[3] = NativeValueConverter>::ToNativeValue( - reinterpret_cast(HandleAnonymousAsyncCalledFromDart)); + arguments.emplace_back(NativeValueConverter::ToNativeValue(id)); + arguments.emplace_back(NativeValueConverter::ToNativeValue(binding_object->context_->contextId())); + arguments.emplace_back(NativeValueConverter>::ToNativeValue(promise_context)); + arguments.emplace_back(NativeValueConverter>::ToNativeValue( + reinterpret_cast(HandleAnonymousAsyncCalledFromDart))); for (int i = 0; i < argc; i++) { - arguments[i + 4] = argv[i].ToNative(); + arguments.emplace_back(argv[i].ToNative()); } ExceptionState exception_state; diff --git a/bridge/core/html/custom/widget_element.cc b/bridge/core/html/custom/widget_element.cc index b7032da52b..ed9be8d7fb 100644 --- a/bridge/core/html/custom/widget_element.cc +++ b/bridge/core/html/custom/widget_element.cc @@ -36,8 +36,8 @@ void WidgetElement::NamedPropertyEnumerator(std::vector& names, Ex std::vector property_names = NativeValueConverter>::FromNativeValue(ctx(), result); names.reserve(property_names.size()); - for (int i = 0; i < property_names.size(); i++) { - names[i] = property_names[i]; + for (auto & property_name : property_names) { + names.emplace_back(property_name); } } diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 4741f44c00..01f31a4ff8 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -36,7 +36,6 @@ struct NativeValueConverter : public NativeValueConverterBase< static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { return AtomicString(ctx, static_cast(value.u.ptr)); - ; } }; @@ -127,7 +126,7 @@ struct NativeValueConverter> : public NativeValueConverterBas std::vector vec; vec.reserve(length); for (int i = 0; i < length; i++) { - vec[i] = NativeValueConverter::FromNativeValue(ctx, arr[i]); + vec.emplace_back(NativeValueConverter::FromNativeValue(ctx, arr[i])); } return vec; } diff --git a/integration_tests/lib/custom/custom_element.dart b/integration_tests/lib/custom/custom_element.dart index e620da9e56..483e36ba49 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 properties, List children) { + Widget build(BuildContext context, Map properties, + List 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 properties, List children) { + Widget build(BuildContext context, Map properties, + List 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 properties, List children) { + Widget build(BuildContext context, Map properties, + List 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 properties, List children) { + Widget build(BuildContext context, Map properties, + List children) { return Container( width: 200, height: 200, @@ -95,6 +101,12 @@ class SampleElement extends dom.Element implements BindingObject { } } + @override + void getAllBindingPropertyNames(List properties) { + super.getAllBindingPropertyNames(properties); + properties.addAll(['ping', 'fake', 'fn', 'asyncFn', 'asyncFnFailed']); + } + String get ping => 'pong'; int get fake => 1234; diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 8c9ab708f2..84b97814aa 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -33,7 +33,7 @@ typedef BindingCallFunc = dynamic Function(BindingObject bindingObject, List args) { assert(args.length == 1); if (isEnabledLog) { - print('$bindingObject getBindingProperty key: ${args[0]}'); + print('$bindingObject getBindingProperty key: ${args[0]} result: ${bindingObject.getBindingProperty(args[0])}'); } return bindingObject.getBindingProperty(args[0]); @@ -50,7 +50,14 @@ dynamic setterBindingCall(BindingObject bindingObject, List args) { } dynamic getPropertyNamesBindingCall(BindingObject bindingObject, List args) { - return bindingObject.getAllBindingPropertyNames(); + List properties = List.empty(growable: true); + bindingObject.getAllBindingPropertyNames(properties); + + if (isEnabledLog) { + print('$bindingObject getPropertyNamesBindingCall value: $properties'); + } + + return properties; } List bindingCallMethodDispatchTable = [ diff --git a/webf/lib/src/bridge/native_value.dart b/webf/lib/src/bridge/native_value.dart index 96daaf1c9c..d38894fff5 100644 --- a/webf/lib/src/bridge/native_value.dart +++ b/webf/lib/src/bridge/native_value.dart @@ -135,10 +135,10 @@ void toNativeValue(Pointer target, value) { 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]); } - target.ref.u = lists.address; } else if (value is AsyncAnonymousNativeFunction) { int id = _functionId++; _asyncFunctionMap[id] = value; diff --git a/webf/lib/src/foundation/binding.dart b/webf/lib/src/foundation/binding.dart index 6ac78565b5..69643a2e0a 100644 --- a/webf/lib/src/foundation/binding.dart +++ b/webf/lib/src/foundation/binding.dart @@ -49,8 +49,7 @@ abstract class BindingObject { void setBindingProperty(String key, value) {} // Return a list contains all supported properties. - List getAllBindingPropertyNames() { - return []; + void getAllBindingPropertyNames(List properties) { } // Call a method, eg: From cced15fffa74a2e872edfcfa83f512e32ef0d787 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 12 Sep 2022 16:52:57 +0800 Subject: [PATCH 301/498] fix: fix binding call AnonymousFunctions. --- bridge/bindings/qjs/qjs_function.cc | 11 +++--- bridge/bindings/qjs/script_value.cc | 13 ++++++- bridge/core/binding_object.cc | 19 +++++----- bridge/core/executing_context.cc | 2 +- bridge/foundation/ui_command_buffer.cc | 36 ++++++++++++------- bridge/foundation/ui_command_buffer.h | 10 +++++- .../specs/dom/elements/custom-element.ts | 6 ++++ webf/lib/src/bridge/binding.dart | 4 +-- 8 files changed, 68 insertions(+), 33 deletions(-) diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index cdc54b705e..1017f8eb3b 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -6,6 +6,8 @@ #include #include #include "cppgc/gc_visitor.h" +#include "core/binding_object.h" +#include "core/dom/events/event_target.h" namespace webf { @@ -21,15 +23,16 @@ static JSValue HandleQJSFunctionCallback(JSContext* ctx, int magic, JSValue* func_data) { JSValue opaque_object = func_data[0]; - auto* context = static_cast(JS_GetOpaque(opaque_object, JS_CLASS_OBJECT)); + 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 = - context->qjs_function_callback(ctx, ScriptValue(ctx, this_val), argc, arguments.data(), context->private_data); - delete context; + ScriptValue result = callback_context->qjs_function_callback(ctx, ScriptValue(ctx, this_val), argc, arguments.data(), + callback_context->private_data); + delete callback_context; + JS_FreeValue(ctx, opaque_object); return JS_DupValue(ctx, result.QJSValue()); } diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 934875e359..9b28b985a9 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -38,6 +38,18 @@ static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& nat 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); + JS_FreeValue(context->ctx(), value); + } + return array; + } case NativeTag::TAG_JSON: { auto* str = static_cast(native_value.u.ptr); JSValue returnedValue = JS_ParseJSON(context->ctx(), str, strlen(str), ""); @@ -50,7 +62,6 @@ static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& nat // Only eventTarget can be converted from nativeValue to JSValue. auto* event_target = DynamicTo(binding_object); - ; if (event_target) { return event_target->ToQuickJS(); } diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 63d8caaec3..b9c98f5a0c 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -8,7 +8,6 @@ #include "bindings/qjs/exception_state.h" #include "bindings/qjs/script_promise_resolver.h" #include "core/executing_context.h" -#include "foundation/logging.h" #include "foundation/native_value_converter.h" namespace webf { @@ -89,7 +88,7 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, const ScriptValue* argv, void* private_data) { auto id = reinterpret_cast(private_data); - auto* binding_object = toScriptWrappable(this_val.QJSValue()); + auto* event_target = toScriptWrappable(this_val.QJSValue()); std::vector arguments; arguments.reserve(argc + 1); @@ -100,21 +99,19 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, } ExceptionState exception_state; - NativeValue result = binding_object->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, + NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, arguments.size(), arguments.data(), exception_state); if (exception_state.HasException()) { JSValue error = JS_GetException(ctx); - binding_object->context_->ReportError(error); + event_target->GetExecutingContext()->ReportError(error); JS_FreeValue(ctx, error); return ScriptValue::Empty(ctx); } - return ScriptValue(ctx, result); } struct BindingObjectPromiseContext { - BindingObject* binding_object; ExecutingContext* context; std::shared_ptr promise_resolver; }; @@ -148,17 +145,17 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, const ScriptValue* argv, void* private_data) { auto id = reinterpret_cast(private_data); - auto* binding_object = toScriptWrappable(this_val.QJSValue()); + auto* event_target = toScriptWrappable(this_val.QJSValue()); - auto promise_resolver = ScriptPromiseResolver::Create(binding_object->context_); + auto promise_resolver = ScriptPromiseResolver::Create(event_target->GetExecutingContext()); - auto* promise_context = new BindingObjectPromiseContext{binding_object, binding_object->context_, promise_resolver}; + auto* promise_context = new BindingObjectPromiseContext{event_target->GetExecutingContext(), promise_resolver}; std::vector arguments; arguments.reserve(argc + 4); arguments.emplace_back(NativeValueConverter::ToNativeValue(id)); - arguments.emplace_back(NativeValueConverter::ToNativeValue(binding_object->context_->contextId())); + arguments.emplace_back(NativeValueConverter::ToNativeValue(event_target->GetExecutingContext()->contextId())); arguments.emplace_back(NativeValueConverter>::ToNativeValue(promise_context)); arguments.emplace_back(NativeValueConverter>::ToNativeValue( reinterpret_cast(HandleAnonymousAsyncCalledFromDart))); @@ -168,7 +165,7 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, } ExceptionState exception_state; - NativeValue result = binding_object->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, + NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, arguments.data(), exception_state); return ScriptValue(ctx, result); } diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index b2201898df..d97070c510 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -279,7 +279,7 @@ static void DispatchPromiseRejectionEvent(const AtomicString& event_type, } void ExecutingContext::FlushUICommand() { - if (uiCommandBuffer()->size() > 0) { + if (!uiCommandBuffer()->empty()) { dartMethodPtr()->flushUICommand(context_id_); } } diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 9af3b0cd1f..7d29211211 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -11,20 +11,17 @@ namespace webf { -UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) { - // It's rare to store over 1024 commands in one frame. - queue.reserve(1024); -} +UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) {} void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { UICommandItem item{id, static_cast(type), nativePtr}; - queue.emplace_back(item); + addCommand(item); } void UICommandBuffer::addCommand(int32_t id, UICommand type, std::unique_ptr&& args_01, void* nativePtr) { assert(args_01 != nullptr); UICommandItem item{id, static_cast(type), args_01.release(), nativePtr}; - queue.emplace_back(item); + addCommand(item); } void UICommandBuffer::addCommand(int32_t id, @@ -35,23 +32,36 @@ void UICommandBuffer::addCommand(int32_t id, assert(args_01 != nullptr); assert(args_02 != nullptr); UICommandItem item{id, static_cast(type), args_01.release(), args_02.release(), nativePtr}; - queue.emplace_back(item); + addCommand(item); +} + +void UICommandBuffer::addCommand(const UICommandItem& item) { + if (size_ >= MAXIMUM_UI_COMMAND_SIZE) { + context_->FlushUICommand(); + assert(size_ == 0); + } + 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(command.string_01); - delete[] reinterpret_cast(command.string_02); + for (int i = 0; i < size_; i ++) { + delete[] reinterpret_cast(buffer_[i].string_01); + delete[] reinterpret_cast(buffer_[i].string_02); } - queue.clear(); + size_ = 0; } } // namespace webf diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 130a08e88e..f7d354bae8 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -33,7 +33,10 @@ enum class UICommand { kCreateDocumentFragment, }; +#define MAXIMUM_UI_COMMAND_SIZE 2056 + 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((new NativeString(args_01))->string())), @@ -72,11 +75,16 @@ class UICommandBuffer { void addCommand(int32_t id, UICommand type, std::unique_ptr&& args_01, void* nativePtr); UICommandItem* data(); int64_t size(); + bool empty(); void clear(); private: + + void addCommand(const UICommandItem& item); + ExecutingContext* context_{nullptr}; - std::vector queue; + UICommandItem buffer_[MAXIMUM_UI_COMMAND_SIZE]; + size_t size_{0}; }; } // namespace webf diff --git a/integration_tests/specs/dom/elements/custom-element.ts b/integration_tests/specs/dom/elements/custom-element.ts index 01c7aad310..78f2565abb 100644 --- a/integration_tests/specs/dom/elements/custom-element.ts +++ b/integration_tests/specs/dom/elements/custom-element.ts @@ -245,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']); + }); + it('support custom properties in dart directly', () => { let sampleElement = document.createElement('sample-element'); let text = document.createTextNode('helloworld'); diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 84b97814aa..db87d7ad9e 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -84,7 +84,7 @@ void _invokeBindingMethodFromNativeImpl(Pointer nativeBindi BindingObject bindingObject = BindingBridge.getBindingObject(nativeBindingObject); result = bindingCallMethodDispatchTable[method](bindingObject, values); } else { - if (method == BindingMethodCallOperations.AnonymousFunctionCall) { + if (method == BindingMethodCallOperations.AnonymousFunctionCall.index) { int id = values[0]; List functionArguments = values.sublist(1); AnonymousNativeFunction? fn = getAnonymousNativeFunctionFromId(id); @@ -104,7 +104,7 @@ void _invokeBindingMethodFromNativeImpl(Pointer nativeBindi print('$e\n$stack'); } removeAnonymousNativeFunctionFromId(id); - } else if (method == BindingMethodCallOperations.AsyncAnonymousFunction) { + } else if (method == BindingMethodCallOperations.AsyncAnonymousFunction.index) { int id = values[0]; AsyncAnonymousNativeFunction? fn = getAsyncAnonymousNativeFunctionFromId(id); if (fn == null) { From 51c4b968ef2c408f5c8055348661e07ae43326d9 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Mon, 12 Sep 2022 08:54:59 +0000 Subject: [PATCH 302/498] Committing clang-format changes --- bridge/bindings/qjs/qjs_function.cc | 2 +- bridge/bindings/qjs/script_value.cc | 2 +- bridge/core/binding_object.cc | 12 +++++++----- bridge/core/html/custom/widget_element.cc | 2 +- bridge/foundation/ui_command_buffer.cc | 2 +- bridge/foundation/ui_command_buffer.h | 1 - 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 1017f8eb3b..dcbbbd157e 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -5,9 +5,9 @@ #include "qjs_function.h" #include #include -#include "cppgc/gc_visitor.h" #include "core/binding_object.h" #include "core/dom/events/event_target.h" +#include "cppgc/gc_visitor.h" namespace webf { diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 9b28b985a9..417679518d 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -43,7 +43,7 @@ static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& nat 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 ++) { + for (int i = 0; i < length; i++) { JSValue value = FromNativeValue(context, arr[i]); JS_SetPropertyInt64(context->ctx(), array, i, value); JS_FreeValue(context->ctx(), value); diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index b9c98f5a0c..6b60afc923 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -100,7 +100,7 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, ExceptionState exception_state; NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, - arguments.size(), arguments.data(), exception_state); + arguments.size(), arguments.data(), exception_state); if (exception_state.HasException()) { JSValue error = JS_GetException(ctx); @@ -155,8 +155,10 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, 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(event_target->GetExecutingContext()->contextId())); + arguments.emplace_back( + NativeValueConverter>::ToNativeValue(promise_context)); arguments.emplace_back(NativeValueConverter>::ToNativeValue( reinterpret_cast(HandleAnonymousAsyncCalledFromDart))); @@ -165,8 +167,8 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, } ExceptionState exception_state; - NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, - argc + 4, arguments.data(), exception_state); + NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, + arguments.data(), exception_state); return ScriptValue(ctx, result); } diff --git a/bridge/core/html/custom/widget_element.cc b/bridge/core/html/custom/widget_element.cc index ed9be8d7fb..88c74a3516 100644 --- a/bridge/core/html/custom/widget_element.cc +++ b/bridge/core/html/custom/widget_element.cc @@ -36,7 +36,7 @@ void WidgetElement::NamedPropertyEnumerator(std::vector& names, Ex std::vector property_names = NativeValueConverter>::FromNativeValue(ctx(), result); names.reserve(property_names.size()); - for (auto & property_name : property_names) { + for (auto& property_name : property_names) { names.emplace_back(property_name); } } diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 7d29211211..cc6e6a5e43 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -57,7 +57,7 @@ bool UICommandBuffer::empty() { } void UICommandBuffer::clear() { - for (int i = 0; i < size_; i ++) { + for (int i = 0; i < size_; i++) { delete[] reinterpret_cast(buffer_[i].string_01); delete[] reinterpret_cast(buffer_[i].string_02); } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index f7d354bae8..36c46eb530 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -79,7 +79,6 @@ class UICommandBuffer { void clear(); private: - void addCommand(const UICommandItem& item); ExecutingContext* context_{nullptr}; From c6d84c20499a7b92b5d503edbb6ebf2212fdfc2a Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 13 Sep 2022 01:06:03 +0800 Subject: [PATCH 303/498] fix: fix arguments to array leaks. --- bridge/bindings/qjs/converter_impl.h | 5 +++- bridge/bindings/qjs/script_promise.cc | 4 +++ bridge/bindings/qjs/script_promise.h | 1 + bridge/bindings/qjs/script_promise_resolver.h | 1 - bridge/bindings/qjs/script_value.cc | 3 +- .../lib/custom/custom_element.dart | 29 ++++++++++--------- .../specs/dom/elements/custom-element.ts | 2 +- 7 files changed, 26 insertions(+), 19 deletions(-) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 5f7a447fd7..368068b955 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -263,7 +263,10 @@ struct Converter> : public ConverterBase> { v.reserve(length); for (uint32_t i = 0; i < length; i++) { - auto&& item = Converter::FromValue(ctx, JS_GetPropertyUint32(ctx, value, i), exception_state); + JSValue iv = JS_GetPropertyUint32(ctx, value, i); + auto&& item = Converter::FromValue(ctx, iv, exception_state); + JS_FreeValue(ctx, iv); + if (exception_state.HasException()) { return {}; } diff --git a/bridge/bindings/qjs/script_promise.cc b/bridge/bindings/qjs/script_promise.cc index 572aa676f1..e1f0d3cfb4 100644 --- a/bridge/bindings/qjs/script_promise.cc +++ b/bridge/bindings/qjs/script_promise.cc @@ -34,6 +34,10 @@ 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 index a21e788eac..9802c24ee5 100644 --- a/bridge/bindings/qjs/script_promise.h +++ b/bridge/bindings/qjs/script_promise.h @@ -25,6 +25,7 @@ class ScriptPromise final { ScriptPromise(JSContext* ctx, std::shared_ptr* resolve_func, std::shared_ptr* reject_func); JSValue ToQuickJS(); + ScriptValue ToValue() const; void Trace(GCVisitor* visitor); diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 1c8f9c89f9..423243f041 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -6,7 +6,6 @@ #define BRIDGE_BINDINGS_QJS_SCRIPT_PROMISE_RESOLVER_H_ #include "converter_impl.h" -#include "script_promise.h" #include "to_quickjs.h" namespace webf { diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 417679518d..3fa518fea5 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -46,7 +46,6 @@ static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& nat for (int i = 0; i < length; i++) { JSValue value = FromNativeValue(context, arr[i]); JS_SetPropertyInt64(context->ctx(), array, i, value); - JS_FreeValue(context->ctx(), value); } return array; } @@ -180,7 +179,7 @@ NativeValue ScriptValue::ToNative() const { auto* event_target = toScriptWrappable(value_); return Native_NewPtr(JSPointerType::Others, event_target->bindingObject()); } - return NativeValueConverter::ToNativeValue(ScriptValue(ctx_, value_)); + return NativeValueConverter::ToNativeValue(*this); } return Native_NewNull(); diff --git a/integration_tests/lib/custom/custom_element.dart b/integration_tests/lib/custom/custom_element.dart index 483e36ba49..ac5a3120df 100644 --- a/integration_tests/lib/custom/custom_element.dart +++ b/integration_tests/lib/custom/custom_element.dart @@ -19,8 +19,7 @@ class WaterfallFlowWidgetElement extends WidgetElement { } @override - Widget build(BuildContext context, Map properties, - List children) { + Widget build(BuildContext context, Map properties, List children) { _children = children; return WaterfallFlow.builder( @@ -30,9 +29,8 @@ 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, ), ); } @@ -42,11 +40,9 @@ class TextWidgetElement extends WidgetElement { TextWidgetElement(BindingContext? context) : super(context); @override - Widget build(BuildContext context, Map properties, - List children) { + Widget build(BuildContext context, Map properties, List 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))); } } @@ -54,8 +50,7 @@ class ImageWidgetElement extends WidgetElement { ImageWidgetElement(BindingContext? context) : super(context); @override - Widget build(BuildContext context, Map properties, - List children) { + Widget build(BuildContext context, Map properties, List children) { return Image(image: AssetImage(properties['src'])); } } @@ -64,8 +59,7 @@ class ContainerWidgetElement extends WidgetElement { ContainerWidgetElement(BindingContext? context) : super(context); @override - Widget build(BuildContext context, Map properties, - List children) { + Widget build(BuildContext context, Map properties, List children) { return Container( width: 200, height: 200, @@ -98,13 +92,15 @@ class SampleElement extends dom.Element implements BindingObject { return asyncFn; case 'asyncFnFailed': return asyncFnFailed; + case 'asyncFnNotComplete': + return asyncFnNotComplete; } } @override void getAllBindingPropertyNames(List properties) { super.getAllBindingPropertyNames(properties); - properties.addAll(['ping', 'fake', 'fn', 'asyncFn', 'asyncFnFailed']); + properties.addAll(['ping', 'fake', 'fn', 'asyncFn', 'asyncFnFailed', 'asyncFnNotComplete']); } String get ping => 'pong'; @@ -125,6 +121,11 @@ class SampleElement extends dom.Element implements BindingObject { return completer.future; }; + Function get asyncFnNotComplete => (List argv) async { + Completer completer = Completer(); + return completer.future; + }; + Function get asyncFnFailed => (List args) async { Completer completer = Completer(); Timer(Duration(milliseconds: 100), () { diff --git a/integration_tests/specs/dom/elements/custom-element.ts b/integration_tests/specs/dom/elements/custom-element.ts index 78f2565abb..4cfb9a8f59 100644 --- a/integration_tests/specs/dom/elements/custom-element.ts +++ b/integration_tests/specs/dom/elements/custom-element.ts @@ -248,7 +248,7 @@ describe('custom html element', () => { it('dart implements getAllBindingPropertyNames works', async () => { let sampleElement = document.createElement('sample-element'); let attributes = Object.keys(sampleElement); - expect(attributes).toEqual(['ping', 'fake', 'fn', 'asyncFn', 'asyncFnFailed']); + expect(attributes).toEqual(['ping', 'fake', 'fn', 'asyncFn', 'asyncFnFailed', 'asyncFnNotComplete']); }); it('support custom properties in dart directly', () => { From f881bbfe269734d589f0fd38f745155ca14269cd Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 13 Sep 2022 19:39:08 +0800 Subject: [PATCH 304/498] fix: fix pending promise cause memory leaks. --- bridge/CMakeLists.txt | 1 - bridge/bindings/qjs/converter_impl.h | 1 + bridge/bindings/qjs/pending_promises.cc | 14 --------- bridge/bindings/qjs/pending_promises.h | 25 --------------- .../bindings/qjs/script_promise_resolver.cc | 8 +++-- bridge/bindings/qjs/script_promise_resolver.h | 4 +++ bridge/core/binding_object.cc | 31 +++++++++++++------ bridge/core/binding_object.h | 15 +++++++++ bridge/core/dom/element.h | 1 + bridge/core/dom/events/event_target.cc | 6 ++++ bridge/core/dom/events/event_target.h | 2 ++ bridge/core/executing_context.cc | 8 ++--- bridge/core/executing_context.h | 5 --- bridge/core/script_state.cc | 1 + bridge/core/script_state.h | 7 ++++- .../specs/dom/elements/custom-element.ts | 18 +++++++++++ 16 files changed, 84 insertions(+), 63 deletions(-) delete mode 100644 bridge/bindings/qjs/pending_promises.cc delete mode 100644 bridge/bindings/qjs/pending_promises.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index d13f7968c2..7b1ac9fc00 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -192,7 +192,6 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") bindings/qjs/exception_state.cc bindings/qjs/exception_message.cc bindings/qjs/rejected_promises.cc - bindings/qjs/pending_promises.cc # Core sources core/executing_context.cc core/script_state.cc diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 368068b955..36b5b7d006 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -9,6 +9,7 @@ #include #include "atomic_string.h" #include "converter.h" +#include "script_promise.h" #include "core/dom/document.h" #include "core/dom/events/event.h" #include "core/dom/events/event_target.h" diff --git a/bridge/bindings/qjs/pending_promises.cc b/bridge/bindings/qjs/pending_promises.cc deleted file mode 100644 index 7cb0189cb3..0000000000 --- a/bridge/bindings/qjs/pending_promises.cc +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ -#include "pending_promises.h" -#include "script_promise.h" - -namespace webf { - -void PendingPromises::TrackPendingPromises(ScriptPromise&& promise) { - promises_.emplace_back(promise); -} - -} // namespace webf diff --git a/bridge/bindings/qjs/pending_promises.h b/bridge/bindings/qjs/pending_promises.h deleted file mode 100644 index ad0d33d56f..0000000000 --- a/bridge/bindings/qjs/pending_promises.h +++ /dev/null @@ -1,25 +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_PENDING_PROMISES_H_ -#define BRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ - -#include -#include -#include "script_promise.h" - -namespace webf { - -class PendingPromises { - public: - PendingPromises() = default; - void TrackPendingPromises(ScriptPromise&& promise); - - private: - std::vector promises_; -}; - -} // namespace webf - -#endif // BRIDGE_BINDINGS_QJS_PENDING_PROMISES_H_ diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index edff510c0d..da77686316 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -18,8 +18,6 @@ ScriptPromiseResolver::ScriptPromiseResolver(ExecutingContext* context) promise_ = JS_NewPromiseCapability(context->ctx(), resolving_funcs); resolve_func_ = resolving_funcs[0]; reject_func_ = resolving_funcs[1]; - - context->GetPendingPromises()->TrackPendingPromises(ScriptPromise(context_->ctx(), promise_)); } ScriptPromiseResolver::~ScriptPromiseResolver() { @@ -28,6 +26,12 @@ ScriptPromiseResolver::~ScriptPromiseResolver() { 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_); } diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 423243f041..2289bcbc17 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -10,6 +10,8 @@ namespace webf { +class GCVisitor; + class ScriptPromiseResolver { public: static std::shared_ptr Create(ExecutingContext* context); @@ -38,6 +40,8 @@ class ScriptPromiseResolver { void Reject(JSValue value) { ResolveOrReject(value, kRejecting); } + void Trace(GCVisitor* visitor) const; + private: enum ResolutionState { kPending, diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 6b60afc923..1a4162e6c1 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -6,6 +6,7 @@ #include "binding_object.h" #include "binding_call_methods.h" #include "bindings/qjs/exception_state.h" +#include "core/dom/events/event_target.h" #include "bindings/qjs/script_promise_resolver.h" #include "core/executing_context.h" #include "foundation/native_value_converter.h" @@ -51,6 +52,14 @@ NativeValue BindingObject::InvokeBindingMethod(const AtomicString& method, return return_value; } +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(BindingMethodCallOperations binding_method_call_operation, int32_t argc, const NativeValue* argv, @@ -111,12 +120,7 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, return ScriptValue(ctx, result); } -struct BindingObjectPromiseContext { - ExecutingContext* context; - std::shared_ptr promise_resolver; -}; - -void HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, int32_t contextId, const char* errmsg) { +void BindingObject::HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, int32_t contextId, const char* errmsg) { auto* promise_context = static_cast(ptr); if (!promise_context->context->IsValid()) return; @@ -136,6 +140,8 @@ void HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, in JS_FreeValue(context->ctx(), error_object); } + promise_context->binding_object->FullFillPendingPromise(promise_context); + delete promise_context; } @@ -149,7 +155,8 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, auto promise_resolver = ScriptPromiseResolver::Create(event_target->GetExecutingContext()); - auto* promise_context = new BindingObjectPromiseContext{event_target->GetExecutingContext(), promise_resolver}; + auto* promise_context = new BindingObjectPromiseContext{event_target->GetExecutingContext(), event_target, promise_resolver}; + event_target->TrackPendingPromiseBindingContext(promise_context); std::vector arguments; arguments.reserve(argc + 4); @@ -167,9 +174,9 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, } ExceptionState exception_state; - NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, + event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, arguments.data(), exception_state); - return ScriptValue(ctx, result); + return promise_resolver->Promise().ToValue(); } NativeValue BindingObject::GetAllBindingPropertyNames(ExceptionState& exception_state) const { @@ -177,6 +184,12 @@ NativeValue BindingObject::GetAllBindingPropertyNames(ExceptionState& exception_ 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; } diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h index 4f80d12961..37f15a99d8 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -7,6 +7,7 @@ #define BRIDGE_CORE_DOM_BINDING_OBJECT_H_ #include +#include #include "bindings/qjs/atomic_string.h" #include "foundation/native_value.h" @@ -15,6 +16,8 @@ namespace webf { class BindingObject; class NativeBindingObject; class ExceptionState; +class GCVisitor; +class ScriptPromiseResolver; using InvokeBindingsMethodsFromNative = void (*)(const NativeBindingObject* binding_object, NativeValue* return_value, @@ -52,6 +55,12 @@ enum BindingMethodCallOperations { kAsyncAnonymousFunction, }; +struct BindingObjectPromiseContext { + 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. @@ -65,6 +74,7 @@ class BindingObject { uint32_t argc, const ScriptValue* argv, void* private_data); + static void HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, int32_t contextId, const char* errmsg); using ImplType = BindingObject*; BindingObject() = delete; @@ -84,6 +94,8 @@ class BindingObject { NativeBindingObject* bindingObject() const { return binding_object_; } + void Trace(GCVisitor* visitor) const; + inline static BindingObject* From(NativeBindingObject* native_binding_object) { return native_binding_object->binding_target_; }; @@ -92,6 +104,8 @@ class BindingObject { 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, @@ -103,6 +117,7 @@ class BindingObject { private: ExecutingContext* context_{nullptr}; NativeBindingObject* binding_object_{new NativeBindingObject(this)}; + std::set pending_promise_contexts_; }; } // namespace webf diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 9355862d68..4aa5098c15 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -6,6 +6,7 @@ #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 "legacy/bounding_client_rect.h" diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 4c68c2c02d..6087ff92f6 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -180,6 +180,11 @@ bool EventTarget::IsEventTarget() const { return true; } +void EventTarget::Trace(GCVisitor* visitor) const { + ScriptWrappable::Trace(visitor); + BindingObject::Trace(visitor); +} + bool EventTarget::AddEventListenerInternal(const AtomicString& event_type, const std::shared_ptr& listener, const std::shared_ptr& options) { @@ -358,6 +363,7 @@ bool EventTarget::FireEventListeners(Event& event, } void EventTargetWithInlineData::Trace(GCVisitor* visitor) const { + EventTarget::Trace(visitor); data_.Trace(visitor); } diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index df33f56329..9bda1a5685 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -131,6 +131,8 @@ class EventTarget : public ScriptWrappable, public BindingObject { virtual bool IsWindowOrWorkerGlobalScope() const { return false; } bool IsEventTarget() const override; + void Trace(GCVisitor *visitor) const override; + protected: virtual bool AddEventListenerInternal(const AtomicString& event_type, const std::shared_ptr& listener, diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index d97070c510..6f286f60a9 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -26,7 +26,7 @@ std::unique_ptr createJSContext(int32_t contextId, const JSExc } ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) - : context_id_(contextId), handler_(handler), owner_(owner), ctx_invalid_(false), unique_id_(context_unique_id++) { + : context_id_(contextId), handler_(handler), owner_(owner), unique_id_(context_unique_id++) { //#if ENABLE_PROFILE // auto jsContextStartTime = // std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) @@ -92,8 +92,6 @@ ExecutingContext::~ExecutingContext() { for (auto& active_wrapper : active_wrappers_) { JS_FreeValue(ctx(), active_wrapper->ToQuickJSUnsafe()); } - - ctx_invalid_ = true; } ExecutingContext* ExecutingContext::From(JSContext* ctx) { @@ -142,11 +140,10 @@ bool ExecutingContext::EvaluateByteCode(uint8_t* bytes, size_t byteLength) { } bool ExecutingContext::IsValid() const { - return !ctx_invalid_; + return script_state_.Invalid(); } void* ExecutingContext::owner() { - assert(!ctx_invalid_ && "GetExecutingContext has been released"); return owner_; } @@ -172,7 +169,6 @@ JSValue ExecutingContext::Global() { } JSContext* ExecutingContext::ctx() { - assert(!ctx_invalid_ && "GetExecutingContext has been released"); return script_state_.ctx(); } diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 5162f3dca1..9b912ec621 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -92,9 +92,6 @@ class ExecutingContext { // Gets the ModuleCallbacks which from the 4th parameter of `webf.invokeModule` function. ModuleCallbackCoordinator* ModuleCallbacks(); - // Get all pending promises which are not resolved or rejected. - PendingPromises* GetPendingPromises() { return &pending_promises_; }; - // Get current script state. ScriptState* GetScriptState() { return &script_state_; } @@ -145,7 +142,6 @@ class ExecutingContext { JSExceptionHandler handler_; void* owner_; JSValue global_object_{JS_NULL}; - bool ctx_invalid_{false}; Document* document_{nullptr}; Window* window_{nullptr}; DOMTimerCoordinator timers_; @@ -156,7 +152,6 @@ class ExecutingContext { UICommandBuffer ui_command_buffer_{this}; std::unique_ptr dart_method_ptr_ = std::make_unique(); RejectedPromises rejected_promises_; - PendingPromises pending_promises_; MemberMutationScope* active_mutation_scope{nullptr}; std::vector active_wrappers_; }; diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 98ea95273b..5cdbe9ddbf 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -38,6 +38,7 @@ JSRuntime* ScriptState::runtime() { } ScriptState::~ScriptState() { + ctx_invalid_ = true; JS_FreeContext(ctx_); // Run GC to clean up remaining objects about m_ctx; diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h index 8859347e84..1e154e29b6 100644 --- a/bridge/core/script_state.h +++ b/bridge/core/script_state.h @@ -19,10 +19,15 @@ class ScriptState { ScriptState(); ~ScriptState(); - inline JSContext* ctx() { return ctx_; } + 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}; }; diff --git a/integration_tests/specs/dom/elements/custom-element.ts b/integration_tests/specs/dom/elements/custom-element.ts index 4cfb9a8f59..f2758add2a 100644 --- a/integration_tests/specs/dom/elements/custom-element.ts +++ b/integration_tests/specs/dom/elements/custom-element.ts @@ -295,6 +295,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'); From 2bc832913f867da8a9d3aa6e623387a443dc446e Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 13 Sep 2022 19:59:53 +0800 Subject: [PATCH 305/498] fix: fix documentFragment. --- bridge/bindings/qjs/binding_initializer.cc | 2 ++ bridge/bindings/qjs/script_promise_resolver.cc | 1 - bridge/core/dom/document_fragment.cc | 4 +++- bridge/core/executing_context.h | 1 - integration_tests/specs/dom/elements/custom-element.ts | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 1cdea9ef1f..ba342675d3 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -19,6 +19,7 @@ #include "qjs_custom_event.h" #include "qjs_document.h" #include "qjs_element.h" +#include "qjs_document_fragment.h" #include "qjs_element_attributes.h" #include "qjs_error_event.h" #include "qjs_event.h" @@ -90,6 +91,7 @@ void InstallBindings(ExecutingContext* context) { QJSNode::Install(context); QJSNodeList::Install(context); QJSDocument::Install(context); + QJSDocumentFragment::Install(context); QJSCharacterData::Install(context); QJSText::Install(context); QJSComment::Install(context); diff --git a/bridge/bindings/qjs/script_promise_resolver.cc b/bridge/bindings/qjs/script_promise_resolver.cc index da77686316..a8e70938fc 100644 --- a/bridge/bindings/qjs/script_promise_resolver.cc +++ b/bridge/bindings/qjs/script_promise_resolver.cc @@ -4,7 +4,6 @@ */ #include "script_promise_resolver.h" #include "core/executing_context.h" -#include "pending_promises.h" namespace webf { diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index 3c9084f3b5..f590c149cb 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -16,7 +16,9 @@ DocumentFragment* DocumentFragment::Create(ExecutingContext* context, ExceptionS return MakeGarbageCollected(context->document(), ConstructionType::kCreateDocumentFragment); } -DocumentFragment::DocumentFragment(Document* document, ConstructionType type) : ContainerNode(document, type) {} +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"; diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 9b912ec621..66ce0652b7 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -16,7 +16,6 @@ #include #include #include "bindings/qjs/binding_initializer.h" -#include "bindings/qjs/pending_promises.h" #include "bindings/qjs/rejected_promises.h" #include "bindings/qjs/script_value.h" #include "foundation/macros.h" diff --git a/integration_tests/specs/dom/elements/custom-element.ts b/integration_tests/specs/dom/elements/custom-element.ts index f2758add2a..20a118f143 100644 --- a/integration_tests/specs/dom/elements/custom-element.ts +++ b/integration_tests/specs/dom/elements/custom-element.ts @@ -325,7 +325,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"'); } }); From 66d7dffbcbd8f091c21ea582454df643451c13de Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 13 Sep 2022 21:47:46 +0800 Subject: [PATCH 306/498] fix: fix replaceChild uicommand. --- bridge/core/dom/container_node.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index d0408b6613..e6df12a2b4 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -398,7 +398,7 @@ void ContainerNode::InsertBeforeCommon(Node& next_child, Node& new_child) { std::unique_ptr args_01 = stringToNativeString(std::to_string(new_child.eventTargetId())); std::unique_ptr args_02 = stringToNativeString("beforebegin"); - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kInsertAdjacentNode, + GetExecutingContext()->uiCommandBuffer()->addCommand(next_child.eventTargetId(), UICommand::kInsertAdjacentNode, std::move(args_01), std::move(args_02), nullptr); } From 962097908d1808362fdbdd4f38f2ad5aa4209bd4 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Tue, 13 Sep 2022 14:11:55 +0000 Subject: [PATCH 307/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 2 +- bridge/bindings/qjs/converter_impl.h | 2 +- bridge/core/binding_object.cc | 16 ++++++++++------ bridge/core/binding_object.h | 5 ++++- bridge/core/dom/document_fragment.cc | 3 ++- bridge/core/dom/events/event_target.h | 2 +- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index ba342675d3..7c105ff333 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -18,8 +18,8 @@ #include "qjs_css_style_declaration.h" #include "qjs_custom_event.h" #include "qjs_document.h" -#include "qjs_element.h" #include "qjs_document_fragment.h" +#include "qjs_element.h" #include "qjs_element_attributes.h" #include "qjs_error_event.h" #include "qjs_event.h" diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 36b5b7d006..4dd964844c 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -9,7 +9,6 @@ #include #include "atomic_string.h" #include "converter.h" -#include "script_promise.h" #include "core/dom/document.h" #include "core/dom/events/event.h" #include "core/dom/events/event_target.h" @@ -27,6 +26,7 @@ #include "js_event_handler.h" #include "js_event_listener.h" #include "native_string_utils.h" +#include "script_promise.h" namespace webf { diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 1a4162e6c1..4174a66e52 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -6,8 +6,8 @@ #include "binding_object.h" #include "binding_call_methods.h" #include "bindings/qjs/exception_state.h" -#include "core/dom/events/event_target.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" @@ -120,7 +120,10 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, return ScriptValue(ctx, result); } -void BindingObject::HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, int32_t contextId, const char* errmsg) { +void BindingObject::HandleAnonymousAsyncCalledFromDart(void* ptr, + NativeValue* native_value, + int32_t contextId, + const char* errmsg) { auto* promise_context = static_cast(ptr); if (!promise_context->context->IsValid()) return; @@ -155,7 +158,8 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, auto promise_resolver = ScriptPromiseResolver::Create(event_target->GetExecutingContext()); - auto* promise_context = new BindingObjectPromiseContext{event_target->GetExecutingContext(), event_target, promise_resolver}; + auto* promise_context = + new BindingObjectPromiseContext{event_target->GetExecutingContext(), event_target, promise_resolver}; event_target->TrackPendingPromiseBindingContext(promise_context); std::vector arguments; @@ -174,8 +178,8 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, } ExceptionState exception_state; - event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, - arguments.data(), exception_state); + event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, arguments.data(), + exception_state); return promise_resolver->Promise().ToValue(); } @@ -185,7 +189,7 @@ NativeValue BindingObject::GetAllBindingPropertyNames(ExceptionState& exception_ } void BindingObject::Trace(GCVisitor* visitor) const { - for(auto&& promise_context : pending_promise_contexts_) { + for (auto&& promise_context : pending_promise_contexts_) { promise_context->promise_resolver->Trace(visitor); } } diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h index 37f15a99d8..87fb5bfee9 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -74,7 +74,10 @@ class BindingObject { uint32_t argc, const ScriptValue* argv, void* private_data); - static void HandleAnonymousAsyncCalledFromDart(void* ptr, NativeValue* native_value, int32_t contextId, const char* errmsg); + static void HandleAnonymousAsyncCalledFromDart(void* ptr, + NativeValue* native_value, + int32_t contextId, + const char* errmsg); using ImplType = BindingObject*; BindingObject() = delete; diff --git a/bridge/core/dom/document_fragment.cc b/bridge/core/dom/document_fragment.cc index f590c149cb..c0d9b1c6ee 100644 --- a/bridge/core/dom/document_fragment.cc +++ b/bridge/core/dom/document_fragment.cc @@ -17,7 +17,8 @@ DocumentFragment* DocumentFragment::Create(ExecutingContext* context, ExceptionS } DocumentFragment::DocumentFragment(Document* document, ConstructionType type) : ContainerNode(document, type) { - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateDocumentFragment, (void*)bindingObject()); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kCreateDocumentFragment, + (void*)bindingObject()); } std::string DocumentFragment::nodeName() const { diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index 9bda1a5685..52c3d86783 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -131,7 +131,7 @@ class EventTarget : public ScriptWrappable, public BindingObject { virtual bool IsWindowOrWorkerGlobalScope() const { return false; } bool IsEventTarget() const override; - void Trace(GCVisitor *visitor) const override; + void Trace(GCVisitor* visitor) const override; protected: virtual bool AddEventListenerInternal(const AtomicString& event_type, From 43c33afe4fd7b7ed2e62128738073c6a57782718 Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 13 Sep 2022 22:17:28 +0800 Subject: [PATCH 308/498] fix: fix spec test --- bridge/foundation/native_value_converter.h | 2 + bridge/test/webf_test_env.cc | 72 ++++++++++++---------- bridge/test/webf_test_env.h | 1 + 3 files changed, 44 insertions(+), 31 deletions(-) diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 01f31a4ff8..931f5fc5f0 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -90,6 +90,7 @@ struct NativeValueConverter : public NativeValueConverterBas static NativeValue ToNativeValue(ImplType value) { // Not supported. assert(false); + return Native_NewNull(); } static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { @@ -102,6 +103,7 @@ struct NativeValueConverter : public NativeValueConvert static NativeValue ToNativeValue(ImplType value) { // Not supported. assert(false); + return Native_NewNull(); } static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index 683652f9d1..fef942f430 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -202,6 +202,7 @@ std::unique_ptr TEST_init(OnJSError onJsError) { TEST_mockDartMethods(contextId, onJsError); initTestFramework(contextId); + TEST_mockTestEnvDartMethods(contextId); auto* page = static_cast(getPage(contextId)); auto* context = page->GetExecutingContext(); JSThreadState* th = new JSThreadState(); @@ -278,6 +279,34 @@ void TEST_runLoop(webf::ExecutingContext* context) { } } +void TEST_onJSError(int32_t contextId, const char*) { + +} + +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); +} + +const char* TEST_environment() { + return ""; +} + +void TEST_simulatePointer(MousePointer*, int32_t length, int32_t pointer) { + +} + +void TEST_simulateInputText(NativeString* nativeString) { + +} + void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { std::vector mockMethods{ reinterpret_cast(TEST_invokeModule), @@ -303,35 +332,16 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { registerDartMethods(contextId, mockMethods.data(), mockMethods.size()); } -} // namespace webf +void TEST_mockTestEnvDartMethods(int32_t contextId) { + std::vector mockMethods { + reinterpret_cast(TEST_onJSError), + reinterpret_cast(TEST_onMatchImageSnapshot), + reinterpret_cast(TEST_environment), + reinterpret_cast(TEST_simulatePointer), + reinterpret_cast(TEST_simulateInputText), + }; -// void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type) { -// NativeEventTarget* nativeEventTarget = new NativeEventTarget(eventTarget); -// auto nativeEventType = stringToNativeString(type); -// NativeString* rawEventType = nativeEventType.release(); -// -// NativeEvent* nativeEvent = new NativeEvent{rawEventType}; -// -// RawEvent* rawEvent = new RawEvent{reinterpret_cast(nativeEvent)}; -// -// NativeEventTarget::dispatchEventImpl(contextId, nativeEventTarget, rawEventType, rawEvent, false); -//} -// -// void TEST_callNativeMethod(void* nativePtr, void* returnValue, void* method, int32_t argc, void* argv) {} -// -// std::unordered_map> unitTestEnvMap; -// std::shared_ptr TEST_getEnv(int32_t contextUniqueId) { -// if (unitTestEnvMap.count(contextUniqueId) == 0) { -// unitTestEnvMap[contextUniqueId] = std::make_shared(); -// } -// -// return unitTestEnvMap[contextUniqueId]; -//} -// -// void TEST_registerEventTargetDisposedCallback(int32_t contextUniqueId, TEST_OnEventTargetDisposed callback) { -// if (unitTestEnvMap.count(contextUniqueId) == 0) { -// unitTestEnvMap[contextUniqueId] = std::make_shared(); -// } -// -// unitTestEnvMap[contextUniqueId]->onEventTargetDisposed = callback; -//} + registerTestEnvDartMethods(contextId, mockMethods.data(), mockMethods.size()); +} + +} // namespace webf diff --git a/bridge/test/webf_test_env.h b/bridge/test/webf_test_env.h index 07b6dc568f..020e862291 100644 --- a/bridge/test/webf_test_env.h +++ b/bridge/test/webf_test_env.h @@ -28,6 +28,7 @@ std::unique_ptr TEST_init(); std::unique_ptr TEST_allocateNewPage(OnJSError onJsError); void TEST_runLoop(ExecutingContext* context); void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); +void TEST_mockTestEnvDartMethods(int32_t contextId); } // namespace webf // void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); From 0bab3c173df62eafcafdcb538fbe80d238657302 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Tue, 13 Sep 2022 15:43:40 +0000 Subject: [PATCH 309/498] Committing clang-format changes --- bridge/test/webf_test_env.cc | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index fef942f430..2baaccbca9 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -279,13 +279,9 @@ void TEST_runLoop(webf::ExecutingContext* context) { } } -void TEST_onJSError(int32_t contextId, const char*) { +void TEST_onJSError(int32_t contextId, const char*) {} -} - -void TEST_onJSLog(int32_t contextId, int32_t level, const char*) { - -} +void TEST_onJSLog(int32_t contextId, int32_t level, const char*) {} void TEST_onMatchImageSnapshot(void* callbackContext, int32_t contextId, uint8_t* bytes, @@ -299,13 +295,9 @@ const char* TEST_environment() { return ""; } -void TEST_simulatePointer(MousePointer*, int32_t length, int32_t pointer) { +void TEST_simulatePointer(MousePointer*, int32_t length, int32_t pointer) {} -} - -void TEST_simulateInputText(NativeString* nativeString) { - -} +void TEST_simulateInputText(NativeString* nativeString) {} void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { std::vector mockMethods{ @@ -333,12 +325,10 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { } void TEST_mockTestEnvDartMethods(int32_t contextId) { - std::vector mockMethods { - reinterpret_cast(TEST_onJSError), - reinterpret_cast(TEST_onMatchImageSnapshot), - reinterpret_cast(TEST_environment), - reinterpret_cast(TEST_simulatePointer), - reinterpret_cast(TEST_simulateInputText), + std::vector mockMethods{ + reinterpret_cast(TEST_onJSError), reinterpret_cast(TEST_onMatchImageSnapshot), + reinterpret_cast(TEST_environment), reinterpret_cast(TEST_simulatePointer), + reinterpret_cast(TEST_simulateInputText), }; registerTestEnvDartMethods(contextId, mockMethods.data(), mockMethods.size()); From ec26aef959c33bb8bb30c35c2b589bc1fdf7c57e Mon Sep 17 00:00:00 2001 From: andycall Date: Thu, 15 Sep 2022 15:44:37 +0800 Subject: [PATCH 310/498] fix: add children api for element. --- bridge/bindings/qjs/binding_initializer.cc | 2 ++ bridge/bindings/qjs/converter_impl.h | 8 +++++ bridge/core/dom/container_node.cc | 15 +++++++-- bridge/core/dom/container_node.h | 4 +-- bridge/core/dom/element.d.ts | 3 +- bridge/core/dom/element.h | 1 + bridge/core/dom/parent_node.d.ts | 9 +++++ bridge/core/dom/parent_node.h | 24 ++++++++++++++ bridge/core/html/custom/widget_element.h | 2 ++ bridge/core/html/html_all_collection.cc | 6 +++- bridge/core/html/html_all_collection.d.ts | 2 +- bridge/core/html/html_all_collection.h | 5 ++- bridge/core/html/legacy/html_collection.cc | 26 ++++++++++++++- .../core/html/legacy/html_collection_test.cc | 33 +++++++++++++++++++ .../code_generator/bin/code_generator.js | 9 ++++- .../code_generator/src/idl/analyzer.ts | 1 - .../templates/idl_templates/base.cc.tpl | 1 + bridge/test/test.cmake | 1 + .../lib/custom/custom_element.dart | 2 ++ .../specs/dom/elements/custom-element.ts | 2 +- webf/lib/src/foundation/binding.dart | 9 +++-- 21 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 bridge/core/dom/parent_node.d.ts create mode 100644 bridge/core/dom/parent_node.h create mode 100644 bridge/core/html/legacy/html_collection_test.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 7c105ff333..5d2209a4d9 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -15,6 +15,7 @@ #include "qjs_close_event.h" #include "qjs_comment.h" #include "qjs_console.h" +#include "qjs_html_all_collection.h" #include "qjs_css_style_declaration.h" #include "qjs_custom_event.h" #include "qjs_document.h" @@ -112,6 +113,7 @@ void InstallBindings(ExecutingContext* context) { QJSCanvasRenderingContext2D::Install(context); QJSCSSStyleDeclaration::Install(context); QJSBoundingClientRect::Install(context); + QJSHTMLAllCollection::Install(context); QJSScreen::Install(context); QJSBlob::Install(context); QJSTouch::Install(context); diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 4dd964844c..37b38d29e3 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -277,6 +277,14 @@ struct Converter> : public ConverterBase> { 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 diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index e6df12a2b4..e13fcd1bf5 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -9,12 +9,21 @@ #include "document.h" #include "document_fragment.h" #include "node_traversal.h" +#include "core/html/html_all_collection.h" namespace webf { -HTMLCollection* ContainerNode::Children() { - // TODO: add children implements. - return nullptr; +// 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 { diff --git a/bridge/core/dom/container_node.h b/bridge/core/dom/container_node.h index e8379b36ab..d091c6f9b6 100644 --- a/bridge/core/dom/container_node.h +++ b/bridge/core/dom/container_node.h @@ -13,7 +13,7 @@ namespace webf { -class HTMLCollection; +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. @@ -31,7 +31,7 @@ class ContainerNode : public Node { bool HasOneTextChild() const { return HasOneChild() && first_child_->IsTextNode(); } bool HasChildCount(unsigned) const; - HTMLCollection* Children(); + std::vector Children(); unsigned CountChildren() const; diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index ed2a4a3921..ff12466b77 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -3,8 +3,9 @@ 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 { +interface Element extends Node, ParentNode { readonly attributes: ElementAttributes; readonly style: CSSStyleDeclaration; diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 4aa5098c15..068b0cd477 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -12,6 +12,7 @@ #include "legacy/bounding_client_rect.h" #include "legacy/element_attributes.h" #include "qjs_scroll_to_options.h" +#include "parent_node.h" namespace webf { diff --git a/bridge/core/dom/parent_node.d.ts b/bridge/core/dom/parent_node.d.ts new file mode 100644 index 0000000000..19625bebe1 --- /dev/null +++ b/bridge/core/dom/parent_node.d.ts @@ -0,0 +1,9 @@ +// @ts-ignore +import {HTMLAllCollection} from "../html/html_all_collection"; +import {Element} from "./element"; + +// @ts-ignore +@Mixin() +export interface ParentNode { + 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..a2d6afdb52 --- /dev/null +++ b/bridge/core/dom/parent_node.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_BINDINGS_QJS_BOM_PARENT_NODE_H_ +#define BRIDGE_BINDINGS_QJS_BOM_PARENT_NODE_H_ + +#include "foundation/macros.h" + +namespace webf { + +class ParentNode { + WEBF_STATIC_ONLY(ParentNode); + public: + + static std::vector children(ContainerNode& node) { + return node.Children(); + } +}; + +} + +#endif diff --git a/bridge/core/html/custom/widget_element.h b/bridge/core/html/custom/widget_element.h index c5484c356f..982f91fc9e 100644 --- a/bridge/core/html/custom/widget_element.h +++ b/bridge/core/html/custom/widget_element.h @@ -5,6 +5,7 @@ #ifndef WEBF_CORE_DOM_WIDGET_ELEMENT_H_ #define WEBF_CORE_DOM_WIDGET_ELEMENT_H_ +#include #include "core/html/html_element.h" namespace webf { @@ -29,6 +30,7 @@ class WidgetElement : public HTMLElement { bool SetItem(const AtomicString& key, const ScriptValue& value, ExceptionState& exception_state); private: + std::unordered_map unimplemented_properties_; }; } // namespace webf diff --git a/bridge/core/html/html_all_collection.cc b/bridge/core/html/html_all_collection.cc index 92d223b51b..33e9efecba 100644 --- a/bridge/core/html/html_all_collection.cc +++ b/bridge/core/html/html_all_collection.cc @@ -5,4 +5,8 @@ #include "html_all_collection.h" -namespace webf {} \ No newline at end of file +namespace webf { + +HTMLAllCollection::HTMLAllCollection(ContainerNode* base, CollectionType type): HTMLCollection(base, type) {} + +} \ 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 index 6cecec5461..b480adba11 100644 --- a/bridge/core/html/html_all_collection.d.ts +++ b/bridge/core/html/html_all_collection.d.ts @@ -1,6 +1,6 @@ import {Element} from "../dom/element"; -interface HTMLCollection { +interface HTMLAllCollection { readonly length: double; item(index: double): Element | null; readonly [key: number]: Element | null; diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h index cfbb212ca5..be0364e140 100644 --- a/bridge/core/html/html_all_collection.h +++ b/bridge/core/html/html_all_collection.h @@ -10,10 +10,13 @@ namespace webf { +class ContainerNode; + class HTMLAllCollection : public HTMLCollection { DEFINE_WRAPPERTYPEINFO(); - public: + using ImplType = HTMLAllCollection*; + HTMLAllCollection(ContainerNode* base, CollectionType); private: }; diff --git a/bridge/core/html/legacy/html_collection.cc b/bridge/core/html/legacy/html_collection.cc index fe9ce9fb03..b89d442c48 100644 --- a/bridge/core/html/legacy/html_collection.cc +++ b/bridge/core/html/legacy/html_collection.cc @@ -4,6 +4,7 @@ */ #include "html_collection.h" +#include "bindings/qjs/cppgc/gc_visitor.h" #include "core/dom/container_node.h" namespace webf { @@ -11,10 +12,32 @@ namespace webf { HTMLCollection::HTMLCollection(ContainerNode* base, CollectionType) : base_(base), ScriptWrappable(base->ctx()) {} unsigned int HTMLCollection::length() const { - return nodes_.size(); + std::vector elements; + NodeList* node_list = base_->childNodes(); + int32_t length = 0; + + for(int i = 0; i < node_list->length(); i ++) { + if (DynamicTo(node_list->item(i, ASSERT_NO_EXCEPTION()))) { + length++; + } + } + + return length; } Element* HTMLCollection::item(unsigned int offset, ExceptionState& exception_state) const { + std::vector elements; + NodeList* node_list = base_->childNodes(); + int32_t length = 0; + + + for(int i = 0; i < node_list->length(); i ++) { + auto* element = DynamicTo(node_list->item(i, ASSERT_NO_EXCEPTION())); + if (element) { + elements.emplace_back(element); + } + } + return nodes_.at(offset); } @@ -24,6 +47,7 @@ bool HTMLCollection::NamedPropertyQuery(const AtomicString& key, ExceptionState& } void HTMLCollection::NamedPropertyEnumerator(std::vector& 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))); } 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..ca7ba13b85 --- /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

    "); + 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/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index 092a2c5a67..ba6bcb3607 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -9,6 +9,7 @@ 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"); @@ -41,9 +42,15 @@ function genCodeFromTypeDefine() { return new IDLBlob(path.join(source, file), dist, filename, implement); }); + // Analyze all files first. for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; - let result = analyzer(b); + analyzer(b); + } + + 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}); diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 9952fec6e8..194a22b3b1 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -19,7 +19,6 @@ export function analyzer(blob: IDLBlob) { blob.objects = sourceFile.statements.map(statement => walkProgram(statement)).filter(o => { return o instanceof ClassObject || o instanceof FunctionObject; }) as (FunctionObject | ClassObject)[]; - return generatorSource(blob); } function getInterfaceName(statement: ts.Statement) { diff --git a/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl index 4aad0ab72b..9c2c2219e4 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl @@ -19,6 +19,7 @@ #include "core/dom/document_fragment.h" #include "core/dom/comment.h" #include "core/input/touch_list.h" +#include "core/html/html_all_collection.h" namespace webf { diff --git a/bridge/test/test.cmake b/bridge/test/test.cmake index 9cd85a90b2..a49f8ca5d7 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -25,6 +25,7 @@ list(APPEND WEBF_UNIT_TEST_SOURCEURCE ./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 diff --git a/integration_tests/lib/custom/custom_element.dart b/integration_tests/lib/custom/custom_element.dart index ac5a3120df..bfda59c3de 100644 --- a/integration_tests/lib/custom/custom_element.dart +++ b/integration_tests/lib/custom/custom_element.dart @@ -94,6 +94,8 @@ class SampleElement extends dom.Element implements BindingObject { return asyncFnFailed; case 'asyncFnNotComplete': return asyncFnNotComplete; + default: + return super.getBindingProperty(key); } } diff --git a/integration_tests/specs/dom/elements/custom-element.ts b/integration_tests/specs/dom/elements/custom-element.ts index 20a118f143..474cc11b2b 100644 --- a/integration_tests/specs/dom/elements/custom-element.ts +++ b/integration_tests/specs/dom/elements/custom-element.ts @@ -336,7 +336,7 @@ 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]; diff --git a/webf/lib/src/foundation/binding.dart b/webf/lib/src/foundation/binding.dart index 69643a2e0a..3ad08964ba 100644 --- a/webf/lib/src/foundation/binding.dart +++ b/webf/lib/src/foundation/binding.dart @@ -42,11 +42,16 @@ abstract class BindingObject { // Get a property, eg: // console.log(el.foo); - dynamic getBindingProperty(String key) {} + dynamic getBindingProperty(String key) { + return unimplemented_properties_[key]; + } + Map unimplemented_properties_ = {}; // Set a property, eg: // el.foo = 'bar'; - void setBindingProperty(String key, value) {} + void setBindingProperty(String key, value) { + unimplemented_properties_[key] = value; + } // Return a list contains all supported properties. void getAllBindingPropertyNames(List properties) { From 9b03bdbf1bbddfbabacbe308f0b17ed65fa913e0 Mon Sep 17 00:00:00 2001 From: andycall Date: Thu, 15 Sep 2022 16:25:48 +0800 Subject: [PATCH 311/498] fix: fix tagname not uppercase. --- bridge/core/dom/element.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 068b0cd477..2312c09ecb 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -55,7 +55,7 @@ class Element : public ContainerNode { bool HasTagName(const AtomicString&) const; std::string nodeValue() const override; - AtomicString tagName() const { return tag_name_; } + AtomicString tagName() const { return tag_name_.ToUpperSlow(); } std::string nodeName() const override; CSSStyleDeclaration* style(); From 2281ff27dc365790907e632aabfbf244df8ae78f Mon Sep 17 00:00:00 2001 From: andycall Date: Thu, 15 Sep 2022 17:32:33 +0800 Subject: [PATCH 312/498] feat: add Image class. --- bridge/CMakeLists.txt | 2 ++ bridge/bindings/qjs/binding_initializer.cc | 2 ++ bridge/bindings/qjs/wrapper_type_info.h | 1 + bridge/core/html/image.cc | 15 +++++++++++++ bridge/core/html/image.d.ts | 5 +++++ bridge/core/html/image.h | 25 ++++++++++++++++++++++ 6 files changed, 50 insertions(+) create mode 100644 bridge/core/html/image.cc create mode 100644 bridge/core/html/image.d.ts create mode 100644 bridge/core/html/image.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 7b1ac9fc00..e99aa42201 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -267,6 +267,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/html/html_image_element.cc core/html/html_script_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 @@ -355,6 +356,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/qjs_html_html_element.cc out/qjs_html_image_element.cc out/qjs_html_canvas_element.cc + out/qjs_image.cc out/qjs_widget_element.cc out/qjs_canvas_rendering_context_2d.cc out/qjs_canvas_rendering_context.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 5d2209a4d9..6faa3128a4 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -39,6 +39,7 @@ #include "qjs_html_script_element.h" #include "qjs_html_template_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" @@ -105,6 +106,7 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLHtmlElement::Install(context); QJSHTMLAnchorElement::Install(context); QJSHTMLImageElement::Install(context); + QJSImage::Install(context); QJSHTMLScriptElement::Install(context); QJSHTMLUnknownElement::Install(context); QJSHTMLTemplateElement::Install(context); diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 988ef0b594..2364a865c2 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -62,6 +62,7 @@ enum { JS_CLASS_HTML_SCRIPT_ELEMENT, JS_CLASS_HTML_ANCHOR_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, diff --git a/bridge/core/html/image.cc b/bridge/core/html/image.cc new file mode 100644 index 0000000000..7fae11aa82 --- /dev/null +++ b/bridge/core/html/image.cc @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "image.h" + +namespace webf { + +Image* Image::Create(ExecutingContext* context, ExceptionState& exception_state) { + return MakeGarbageCollected(context, exception_state); +} + +Image::Image(ExecutingContext* context, ExceptionState& exception_state) : HTMLImageElement(*context->document()) {} + +} // 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..26db46bdc2 --- /dev/null +++ b/bridge/core/html/image.h @@ -0,0 +1,25 @@ +/* +* 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); + + private: +}; + +} + +#endif // WEBF_CORE_HTML_IMAGE_H_ From 503500b4caefe82e3f0f47d77dceacb7c538d364 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 16 Sep 2022 01:24:50 +0800 Subject: [PATCH 313/498] fix: fix interface error. --- bridge/bindings/qjs/converter_impl.h | 1 - bridge/core/frame/window.d.ts | 2 ++ bridge/core/html/html_image_element.d.ts | 8 ++--- .../lib/custom/custom_element.dart | 31 +++++++++++++------ 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 37b38d29e3..1545c880f3 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -110,7 +110,6 @@ struct Converter, std::enable_if_t struct Converter : public ConverterBase { static ImplType FromValue(JSContext* ctx, JSValue value, ExceptionState& exception_state) { - assert(!JS_IsException(value)); return ScriptValue(ctx, value); } diff --git a/bridge/core/frame/window.d.ts b/bridge/core/frame/window.d.ts index 4ba04e7c40..ae4c449060 100644 --- a/bridge/core/frame/window.d.ts +++ b/bridge/core/frame/window.d.ts @@ -6,6 +6,8 @@ import {WindowEventHandlers} from "./window_event_handlers"; interface Window extends EventTarget, WindowEventHandlers { 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; diff --git a/bridge/core/html/html_image_element.d.ts b/bridge/core/html/html_image_element.d.ts index 8578787375..9ab275e70b 100644 --- a/bridge/core/html/html_image_element.d.ts +++ b/bridge/core/html/html_image_element.d.ts @@ -5,10 +5,10 @@ interface HTMLImageElement extends HTMLElement { src: DartImpl; srcset: DartImpl; sizes: DartImpl; - width: DartImpl; - height: DartImpl; - readonly naturalWidth: DartImpl; - readonly naturalHeight: DartImpl; + width: DartImpl; + height: DartImpl; + readonly naturalWidth: DartImpl; + readonly naturalHeight: DartImpl; readonly complete: DartImpl; readonly currentSrc: DartImpl; decoding: DartImpl; diff --git a/integration_tests/lib/custom/custom_element.dart b/integration_tests/lib/custom/custom_element.dart index bfda59c3de..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 properties, List children) { + Widget build(BuildContext context, Map properties, + List 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 properties, List children) { + Widget build(BuildContext context, Map properties, + List 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 properties, List children) { + Widget build(BuildContext context, Map properties, + List 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 properties, List children) { + Widget build(BuildContext context, Map properties, + List children) { return Container( width: 200, height: 200, @@ -102,7 +108,14 @@ class SampleElement extends dom.Element implements BindingObject { @override void getAllBindingPropertyNames(List properties) { super.getAllBindingPropertyNames(properties); - properties.addAll(['ping', 'fake', 'fn', 'asyncFn', 'asyncFnFailed', 'asyncFnNotComplete']); + properties.addAll([ + 'ping', + 'fake', + 'fn', + 'asyncFn', + 'asyncFnFailed', + 'asyncFnNotComplete' + ]); } String get ping => 'pong'; @@ -117,7 +130,7 @@ class SampleElement extends dom.Element implements BindingObject { Function get asyncFn => (List argv) async { Completer completer = Completer(); - Timer(Duration(seconds: 1), () { + Timer(Duration(milliseconds: 200), () { completer.complete(argv[0]); }); return completer.future; From 5788371d5987ea6d8975e748236844a89dfcc962 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 16 Sep 2022 01:25:09 +0800 Subject: [PATCH 314/498] fix: should batch update when incoming new uicommands. --- bridge/foundation/ui_command_buffer.cc | 9 +++++++++ bridge/foundation/ui_command_buffer.h | 1 + 2 files changed, 10 insertions(+) diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index cc6e6a5e43..cf7ee45182 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -40,6 +40,14 @@ void UICommandBuffer::addCommand(const UICommandItem& item) { context_->FlushUICommand(); assert(size_ == 0); } + + if (!update_batched_ && context_->dartMethodPtr()->requestBatchUpdate != nullptr) { +#if FLUTTER_BACKEND + context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); +#endif + update_batched_ = true; + } + buffer_[size_] = item; size_++; } @@ -62,6 +70,7 @@ void UICommandBuffer::clear() { delete[] reinterpret_cast(buffer_[i].string_02); } size_ = 0; + update_batched_ = false; } } // namespace webf diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index 36c46eb530..f28325ae6e 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -83,6 +83,7 @@ class UICommandBuffer { ExecutingContext* context_{nullptr}; UICommandItem buffer_[MAXIMUM_UI_COMMAND_SIZE]; + bool update_batched_{false}; size_t size_{0}; }; From dbb3f853548f706fbbe8214d1068f1172b58ba0c Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Thu, 15 Sep 2022 17:49:20 +0000 Subject: [PATCH 315/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 1 - bridge/bindings/qjs/converter_impl.h | 2 +- bridge/core/dom/container_node.cc | 4 +- bridge/core/dom/element.h | 2 +- bridge/core/dom/parent_node.h | 14 +++--- bridge/core/html/html_all_collection.cc | 4 +- bridge/core/html/html_all_collection.h | 2 + bridge/core/html/image.h | 6 +-- bridge/core/html/legacy/html_collection.cc | 5 +- .../core/html/legacy/html_collection_test.cc | 46 +++++++++---------- 10 files changed, 42 insertions(+), 44 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 6faa3128a4..6b607755be 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -15,7 +15,6 @@ #include "qjs_close_event.h" #include "qjs_comment.h" #include "qjs_console.h" -#include "qjs_html_all_collection.h" #include "qjs_css_style_declaration.h" #include "qjs_custom_event.h" #include "qjs_document.h" diff --git a/bridge/bindings/qjs/converter_impl.h b/bridge/bindings/qjs/converter_impl.h index 1545c880f3..57c7419ef9 100644 --- a/bridge/bindings/qjs/converter_impl.h +++ b/bridge/bindings/qjs/converter_impl.h @@ -279,7 +279,7 @@ struct Converter> : public ConverterBase> { 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 ++) { + for (int i = 0; i < value.size(); i++) { JS_SetPropertyUint32(ctx, array, i, Converter::ToValue(ctx, value[i])); } return array; diff --git a/bridge/core/dom/container_node.cc b/bridge/core/dom/container_node.cc index e13fcd1bf5..12e5c50d74 100644 --- a/bridge/core/dom/container_node.cc +++ b/bridge/core/dom/container_node.cc @@ -6,10 +6,10 @@ #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" -#include "core/html/html_all_collection.h" namespace webf { @@ -17,7 +17,7 @@ namespace webf { std::vector ContainerNode::Children() { std::vector elements; uint32_t length = childNodes()->length(); - for(int i = 0; i < length; i ++) { + for (int i = 0; i < length; i++) { auto* element = DynamicTo(childNodes()->item(i, ASSERT_NO_EXCEPTION())); if (element) { elements.emplace_back(element); diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 2312c09ecb..47103f0e3e 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -11,8 +11,8 @@ #include "core/css/legacy/css_style_declaration.h" #include "legacy/bounding_client_rect.h" #include "legacy/element_attributes.h" -#include "qjs_scroll_to_options.h" #include "parent_node.h" +#include "qjs_scroll_to_options.h" namespace webf { diff --git a/bridge/core/dom/parent_node.h b/bridge/core/dom/parent_node.h index a2d6afdb52..9f1e43f540 100644 --- a/bridge/core/dom/parent_node.h +++ b/bridge/core/dom/parent_node.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_BINDINGS_QJS_BOM_PARENT_NODE_H_ #define BRIDGE_BINDINGS_QJS_BOM_PARENT_NODE_H_ @@ -12,13 +12,11 @@ namespace webf { class ParentNode { WEBF_STATIC_ONLY(ParentNode); - public: - static std::vector children(ContainerNode& node) { - return node.Children(); - } + public: + static std::vector children(ContainerNode& node) { return node.Children(); } }; -} +} // namespace webf #endif diff --git a/bridge/core/html/html_all_collection.cc b/bridge/core/html/html_all_collection.cc index 33e9efecba..ab487898c4 100644 --- a/bridge/core/html/html_all_collection.cc +++ b/bridge/core/html/html_all_collection.cc @@ -7,6 +7,6 @@ namespace webf { -HTMLAllCollection::HTMLAllCollection(ContainerNode* base, CollectionType type): HTMLCollection(base, type) {} +HTMLAllCollection::HTMLAllCollection(ContainerNode* base, CollectionType type) : HTMLCollection(base, type) {} -} \ No newline at end of file +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/html_all_collection.h b/bridge/core/html/html_all_collection.h index be0364e140..4615ad1b4a 100644 --- a/bridge/core/html/html_all_collection.h +++ b/bridge/core/html/html_all_collection.h @@ -14,9 +14,11 @@ class ContainerNode; class HTMLAllCollection : public HTMLCollection { DEFINE_WRAPPERTYPEINFO(); + public: using ImplType = HTMLAllCollection*; HTMLAllCollection(ContainerNode* base, CollectionType); + private: }; diff --git a/bridge/core/html/image.h b/bridge/core/html/image.h index 26db46bdc2..d80200f9e0 100644 --- a/bridge/core/html/image.h +++ b/bridge/core/html/image.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #ifndef WEBF_CORE_HTML_IMAGE_H_ #define WEBF_CORE_HTML_IMAGE_H_ @@ -20,6 +20,6 @@ class Image : public HTMLImageElement { 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 index b89d442c48..2d7525b0a0 100644 --- a/bridge/core/html/legacy/html_collection.cc +++ b/bridge/core/html/legacy/html_collection.cc @@ -16,7 +16,7 @@ unsigned int HTMLCollection::length() const { NodeList* node_list = base_->childNodes(); int32_t length = 0; - for(int i = 0; i < node_list->length(); i ++) { + for (int i = 0; i < node_list->length(); i++) { if (DynamicTo(node_list->item(i, ASSERT_NO_EXCEPTION()))) { length++; } @@ -30,8 +30,7 @@ Element* HTMLCollection::item(unsigned int offset, ExceptionState& exception_sta NodeList* node_list = base_->childNodes(); int32_t length = 0; - - for(int i = 0; i < node_list->length(); i ++) { + for (int i = 0; i < node_list->length(); i++) { auto* element = DynamicTo(node_list->item(i, ASSERT_NO_EXCEPTION())); if (element) { elements.emplace_back(element); diff --git a/bridge/core/html/legacy/html_collection_test.cc b/bridge/core/html/legacy/html_collection_test.cc index ca7ba13b85..5fec8e8131 100644 --- a/bridge/core/html/legacy/html_collection_test.cc +++ b/bridge/core/html/legacy/html_collection_test.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #include "gtest/gtest.h" #include "webf_test_env.h" @@ -8,26 +8,26 @@ 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

    "); - 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); + 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

    "); + 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); + EXPECT_EQ(errorCalled, false); } From c077e87bee1632bab920b75f48f3f97574e9f34c Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 16 Sep 2022 04:55:14 +0800 Subject: [PATCH 316/498] fix: fix img specs. --- integration_tests/specs/dom/elements/img.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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); } From d904258ff6277ebf4db31bbf6dd331b5e2e1e7a7 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 16 Sep 2022 05:02:25 +0800 Subject: [PATCH 317/498] fix: fix form element initialize. --- bridge/bindings/qjs/binding_initializer.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 6b607755be..4e4fb95507 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -36,6 +36,9 @@ #include "qjs_html_html_element.h" #include "qjs_html_image_element.h" #include "qjs_html_script_element.h" +#include "qjs_html_input_element.h" +#include "qjs_html_textarea_element.h" +#include "qjs_html_button_element.h" #include "qjs_html_template_element.h" #include "qjs_html_unknown_element.h" #include "qjs_image.h" @@ -105,6 +108,9 @@ void InstallBindings(ExecutingContext* 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); QJSHTMLUnknownElement::Install(context); From 88ab06abf8715fa6d9e35d28b3701b5ae8ec0639 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 16 Sep 2022 20:38:49 +0800 Subject: [PATCH 318/498] feat: add popstate event and refactor dart event. --- bridge/CMakeLists.txt | 3 + bridge/bindings/qjs/binding_initializer.cc | 2 + bridge/bindings/qjs/wrapper_type_info.h | 1 + bridge/core/dom/events/event.cc | 4 + bridge/core/dom/events/event.h | 1 + bridge/core/events/dart_created_events.json5 | 1 + bridge/core/events/popstate_event.cc | 51 ++++++++ bridge/core/events/popstate_event.d.ts | 6 + bridge/core/events/popstate_event.h | 54 +++++++++ bridge/core/events/popstate_event_init.d.ts | 7 ++ bridge/foundation/ui_command_buffer.cc | 4 +- bridge/test/webf_test_env.cc | 12 +- bridge/test/webf_test_env.h | 2 +- webf/lib/src/dom/element.dart | 2 +- .../src/dom/elements/text_form_control.dart | 2 +- webf/lib/src/dom/event.dart | 114 ++++++++++-------- webf/lib/src/gesture/gesture_dispatcher.dart | 9 +- webf/lib/src/module/history.dart | 2 +- 18 files changed, 211 insertions(+), 66 deletions(-) create mode 100644 bridge/core/events/popstate_event.cc create mode 100644 bridge/core/events/popstate_event.d.ts create mode 100644 bridge/core/events/popstate_event.h create mode 100644 bridge/core/events/popstate_event_init.d.ts diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index e99aa42201..2d86c92671 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -249,6 +249,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/events/input_event.cc core/events/touch_event.cc core/events/mouse_event.cc + core/events/popstate_event.cc core/events/pointer_event.cc core/events/transition_event.cc core/events/intersection_change_event.cc @@ -303,6 +304,8 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/qjs_focus_event_init.cc out/qjs_input_event.cc out/qjs_input_event_init.cc + out/qjs_popstate_event.cc + out/qjs_popstate_event_init.cc out/qjs_ui_event.cc out/qjs_ui_event_init.cc out/qjs_gesture_event.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 4e4fb95507..9c73237228 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -43,6 +43,7 @@ #include "qjs_html_unknown_element.h" #include "qjs_image.h" #include "qjs_input_event.h" +#include "qjs_popstate_event.h" #include "qjs_intersection_change_event.h" #include "qjs_keyboard_event.h" #include "qjs_location.h" @@ -89,6 +90,7 @@ void InstallBindings(ExecutingContext* context) { QJSMouseEvent::Install(context); QJSPointerEvent::Install(context); QJSTouchEvent::Install(context); + QJSPopstateEvent::Install(context); QJSTransitionEvent::Install(context); QJSIntersectionChangeEvent::Install(context); QJSKeyboardEvent::Install(context); diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 2364a865c2..75452fc1ca 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -32,6 +32,7 @@ enum { JS_CLASS_ANIMATION_EVENT, JS_CLASS_FOCUS_EVENT, JS_CLASS_GESTURE_EVENT, + JS_CLASS_POPSTATE_EVENT, JS_CLASS_INTERSECTION_CHANGE_EVENT, JS_CLASS_KEYBOARD_EVENT, JS_CLASS_PROMISE_REJECTION_EVENT, diff --git a/bridge/core/dom/events/event.cc b/bridge/core/dom/events/event.cc index ead73f0047..591f0cea3d 100644 --- a/bridge/core/dom/events/event.cc +++ b/bridge/core/dom/events/event.cc @@ -147,6 +147,10 @@ bool Event::IsMessageEvent() const { return false; } +bool Event::IsPopstateEvent() const { + return false; +} + bool Event::IsIntersectionchangeEvent() const { return false; } diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index e5561638e7..9a3f4791e0 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -150,6 +150,7 @@ class Event : public ScriptWrappable { 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. diff --git a/bridge/core/events/dart_created_events.json5 b/bridge/core/events/dart_created_events.json5 index b2e39ef264..936ede01e7 100644 --- a/bridge/core/events/dart_created_events.json5 +++ b/bridge/core/events/dart_created_events.json5 @@ -24,6 +24,7 @@ "close", // "error", // "focus", + "popstate", { "class": "GestureEvent", "types": [ diff --git a/bridge/core/events/popstate_event.cc b/bridge/core/events/popstate_event.cc new file mode 100644 index 0000000000..be5233d504 --- /dev/null +++ b/bridge/core/events/popstate_event.cc @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ + +#include "bindings/qjs/cppgc/gc_visitor.h" +#include "event_type_names.h" +#include "popstate_event.h" +#include "qjs_popstate_event.h" + +namespace webf { + +PopstateEvent* PopstateEvent::Create(ExecutingContext* context, + ExceptionState& exception_state) { + return MakeGarbageCollected(context, event_type_names::kpopstate, exception_state); +} + +PopstateEvent* PopstateEvent::Create(ExecutingContext* context, + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state) { + return MakeGarbageCollected(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& initializer, + ExceptionState& exception_state) + : Event(context, type), state_(initializer->state()) {} + +PopstateEvent::PopstateEvent(ExecutingContext* context, const AtomicString& type, NativePopstateEvent* native_ui_event) + : Event(context, type, &native_ui_event->native_event), + state_(ScriptValue::CreateJsonObject(context->ctx(), + static_cast(native_ui_event->state), + strlen(static_cast(native_ui_event->state)))){} + +ScriptValue PopstateEvent::state() const { + return state_; +} + +bool PopstateEvent::IsPopstateEvent() const { + return true; +} + +void PopstateEvent::Trace(GCVisitor* visitor) const { + Event::Trace(visitor); +} + +} // namespace webf \ No newline at end of file diff --git a/bridge/core/events/popstate_event.d.ts b/bridge/core/events/popstate_event.d.ts new file mode 100644 index 0000000000..a49bb362f5 --- /dev/null +++ b/bridge/core/events/popstate_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/popstate_event.h b/bridge/core/events/popstate_event.h new file mode 100644 index 0000000000..9044896569 --- /dev/null +++ b/bridge/core/events/popstate_event.h @@ -0,0 +1,54 @@ +/* +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ + +#ifndef WEBF_CORE_EVENTS_POPSTATE_EVENT_H_ +#define WEBF_CORE_EVENTS_POPSTATE_EVENT_H_ + +#include "core/dom/events/event.h" +#include "qjs_popstate_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& 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& 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 { + static bool AllowFrom(const Event& event) { return event.IsPopstateEvent(); } +}; + +} + +#endif // WEBF_CORE_EVENTS_POPSTATE_EVENT_H_ diff --git a/bridge/core/events/popstate_event_init.d.ts b/bridge/core/events/popstate_event_init.d.ts new file mode 100644 index 0000000000..93ec0576ab --- /dev/null +++ b/bridge/core/events/popstate_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/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index cf7ee45182..2442fe5c09 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -41,12 +41,12 @@ void UICommandBuffer::addCommand(const UICommandItem& item) { assert(size_ == 0); } - if (!update_batched_ && context_->dartMethodPtr()->requestBatchUpdate != nullptr) { #if FLUTTER_BACKEND + if (!update_batched_ && context_->dartMethodPtr()->requestBatchUpdate != nullptr) { context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); -#endif update_batched_ = true; } +#endif buffer_[size_] = item; size_++; diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index 2baaccbca9..81dc537577 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -202,7 +202,7 @@ std::unique_ptr TEST_init(OnJSError onJsError) { TEST_mockDartMethods(contextId, onJsError); initTestFramework(contextId); - TEST_mockTestEnvDartMethods(contextId); + TEST_mockTestEnvDartMethods(contextId, onJsError); auto* page = static_cast(getPage(contextId)); auto* context = page->GetExecutingContext(); JSThreadState* th = new JSThreadState(); @@ -279,8 +279,6 @@ void TEST_runLoop(webf::ExecutingContext* context) { } } -void TEST_onJSError(int32_t contextId, const char*) {} - void TEST_onJSLog(int32_t contextId, int32_t level, const char*) {} void TEST_onMatchImageSnapshot(void* callbackContext, int32_t contextId, @@ -324,10 +322,12 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { registerDartMethods(contextId, mockMethods.data(), mockMethods.size()); } -void TEST_mockTestEnvDartMethods(int32_t contextId) { +void TEST_mockTestEnvDartMethods(int32_t contextId, OnJSError onJSError) { std::vector mockMethods{ - reinterpret_cast(TEST_onJSError), reinterpret_cast(TEST_onMatchImageSnapshot), - reinterpret_cast(TEST_environment), reinterpret_cast(TEST_simulatePointer), + reinterpret_cast(onJSError), + reinterpret_cast(TEST_onMatchImageSnapshot), + reinterpret_cast(TEST_environment), + reinterpret_cast(TEST_simulatePointer), reinterpret_cast(TEST_simulateInputText), }; diff --git a/bridge/test/webf_test_env.h b/bridge/test/webf_test_env.h index 020e862291..19df93db27 100644 --- a/bridge/test/webf_test_env.h +++ b/bridge/test/webf_test_env.h @@ -28,7 +28,7 @@ std::unique_ptr TEST_init(); std::unique_ptr TEST_allocateNewPage(OnJSError onJsError); void TEST_runLoop(ExecutingContext* context); void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); -void TEST_mockTestEnvDartMethods(int32_t contextId); +void TEST_mockTestEnvDartMethods(int32_t contextId, OnJSError onJSError); } // namespace webf // void TEST_dispatchEvent(int32_t contextId, EventTarget* eventTarget, const std::string type); diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index c2053f1b64..4c7e456571 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1589,7 +1589,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element void click() { flushLayout(); Event clickEvent = MouseEvent( - EVENT_CLICK, MouseEventInit(bubbles: true, cancelable: true, detail: 1, view: ownerDocument.defaultView)); + EVENT_CLICK, MouseEventInit(eventInit: UIEventInit(detail: 1, view: ownerDocument.defaultView, eventInit: EventInit(bubbles: true, cancelable: true)))); // If element not in tree, click is fired and only response to itself. dispatchEvent(clickEvent); } diff --git a/webf/lib/src/dom/elements/text_form_control.dart b/webf/lib/src/dom/elements/text_form_control.dart index a3e393f770..f024024756 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(InputEventInit(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 ca473f6ff2..9538dcc647 100644 --- a/webf/lib/src/dom/event.dart +++ b/webf/lib/src/dom/event.dart @@ -242,16 +242,23 @@ class EventInit { this.cancelable = false, this.composed = false, }); + + EventInit.fromEventInit([EventInit? eventInit]) + : bubbles = eventInit?.bubbles ?? false, + cancelable = eventInit?.cancelable ?? false, + composed = eventInit?.composed ?? false; } class PopStateEvent extends Event { - final PopStateEventInit _popStateEventInit; + final dynamic state; - PopStateEvent(this._popStateEventInit) : super('popstate', _popStateEventInit); + PopStateEvent(PopStateEventInit init) + : state = init.state, + super(EVENT_POP_STATE); @override Pointer toRaw([int methodLength = 0]) { - List methods = [stringToNativeString(jsonEncode(_popStateEventInit.state)).address]; + List methods = [jsonEncode(state).toNativeUtf8().address]; Pointer rawEvent = super.toRaw(methods.length).cast(); int currentStructSize = rawEvent.ref.length + methods.length; @@ -266,7 +273,7 @@ class PopStateEvent extends Event { class PopStateEventInit extends EventInit { final dynamic state; - PopStateEventInit(this.state); + PopStateEventInit({this.state, EventInit? init}) : super.fromEventInit(init); } class UIEventInit extends EventInit { @@ -285,14 +292,7 @@ class UIEventInit extends EventInit { // 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; - UIEventInit( - {bool bubbles = false, - bool cancelable = false, - bool composed = false, - this.detail = 0, - this.view, - this.which = 0}) - : super(bubbles: bubbles, cancelable: cancelable, composed: composed); + UIEventInit({this.detail = 0, this.view, this.which = 0, EventInit? eventInit}) : super.fromEventInit(eventInit); } class UIEvent extends Event { @@ -319,11 +319,7 @@ class UIEvent extends Event { @override Pointer toRaw([int extraMethodsLength = 0]) { - List methods = [ - doubleToUint64(detail), - view?.pointer?.address ?? nullptr.address, - doubleToUint64(which) - ]; + List methods = [doubleToUint64(detail), view?.pointer?.address ?? nullptr.address, doubleToUint64(which)]; Pointer rawEvent = super.toRaw(methods.length + extraMethodsLength).cast(); int currentStructSize = rawEvent.ref.length + methods.length; @@ -375,17 +371,8 @@ class MouseEventInit extends UIEventInit { final double offsetY; MouseEventInit( - {bool bubbles = false, - bool cancelable = false, - bool composed = false, - this.clientX = 0.0, - this.clientY = 0.0, - this.offsetX = 0.0, - this.offsetY = 0.0, - required EventTarget view, - double detail = 0.0, - double which = 0.0}) - : super(view: view, detail: detail, which: which, bubbles: bubbles, cancelable: cancelable, composed: composed); + {this.clientX = 0.0, this.clientY = 0.0, this.offsetX = 0.0, this.offsetY = 0.0, UIEventInit? eventInit}) + : super(eventInit: eventInit); } class GestureEventInit extends EventInit { @@ -398,18 +385,17 @@ class GestureEventInit extends EventInit { final double velocityY; final double scale; - GestureEventInit({ - bool bubbles = false, - bool cancelable = false, - this.state = '', - this.direction = '', - this.rotation = 0.0, - this.deltaX = 0.0, - this.deltaY = 0.0, - this.velocityX = 0.0, - this.velocityY = 0.0, - this.scale = 0.0, - }) : super(bubbles: bubbles, cancelable: cancelable); + GestureEventInit( + {this.state = '', + this.direction = '', + this.rotation = 0.0, + this.deltaX = 0.0, + this.deltaY = 0.0, + this.velocityX = 0.0, + this.velocityY = 0.0, + this.scale = 0.0, + EventInit? eventInit}) + : super.fromEventInit(eventInit); } /// reference: https://developer.mozilla.org/en-US/docs/Web/API/GestureEvent @@ -462,8 +448,7 @@ class GestureEvent extends Event { class CustomEventInit extends EventInit { final String detail; - CustomEventInit({bool bubbles = false, bool cancelable = false, required this.detail}) - : super(bubbles: bubbles, cancelable: cancelable); + CustomEventInit({required this.detail, EventInit? eventInit}) : super.fromEventInit(eventInit); } /// reference: http://dev.w3.org/2006/webapi/DOM-Level-3-Events/html/DOM3-Events.html#interface-CustomEvent @@ -489,8 +474,15 @@ class CustomEvent extends Event { } } +class InputEventInit extends UIEventInit { + String? inputType; + String? data; + + InputEventInit({this.inputType, this.data, UIEventInit? uiEventInit}) : super(eventInit: uiEventInit); +} + // 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. @@ -510,10 +502,10 @@ class InputEvent extends Event { return rawEvent; } - InputEvent( - this.data, { - this.inputType = '', - }) : super(EVENT_INPUT, EventInit(cancelable: true)); + InputEvent(InputEventInit init) + : inputType = init.inputType ?? '', + data = init.data ?? '', + super(EVENT_INPUT); } class AppearEvent extends Event { @@ -638,9 +630,30 @@ class IntersectionChangeEvent extends Event { } } +class TouchEventInit extends UIEventInit { + TouchList? touches; + TouchList? targetTouches; + TouchList? changedTouches; + bool? altKey; + bool? metaKey; + bool? ctrlKey; + bool? shiftKey; + + TouchEventInit( + {this.touches, + this.targetTouches, + this.changedTouches, + this.altKey, + this.metaKey, + this.ctrlKey, + this.shiftKey, + UIEventInit? eventInit}) + : super(eventInit: eventInit); +} + /// reference: https://w3c.github.io/touch-events/#touchevent-interface -class TouchEvent extends Event { - TouchEvent(String type) : super(type, EventInit(bubbles: true, cancelable: true)); +class TouchEvent extends UIEvent { + TouchEvent(String type, [TouchEventInit? eventInit]) : super(type, eventInit); TouchList touches = TouchList(); TouchList targetTouches = TouchList(); @@ -751,8 +764,7 @@ class TouchList { Pointer toNative() { Pointer touchList = malloc.allocate(sizeOf()); - Pointer touches = - malloc.allocate(sizeOf() * _items.length); + Pointer touches = malloc.allocate(sizeOf() * _items.length); for (int i = 0; i < _items.length; i++) { _items[i].toNative(touches.elementAt(i)); } diff --git a/webf/lib/src/gesture/gesture_dispatcher.dart b/webf/lib/src/gesture/gesture_dispatcher.dart index 97e63f7d25..1a372371d3 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; } @@ -336,13 +339,13 @@ class GestureDispatcher { Event event = MouseEvent( type, MouseEventInit( - bubbles: bubbles, - cancelable: cancelable, clientX: clientX, clientY: clientY, offsetX: localPosition.dx, offsetY: localPosition.dy, - view: (_target as Node).ownerDocument.defaultView + eventInit: UIEventInit( + view: (_target as Node).ownerDocument.defaultView, + eventInit: EventInit(bubbles: bubbles, cancelable: cancelable)), )); _target?.dispatchEvent(event); } diff --git a/webf/lib/src/module/history.dart b/webf/lib/src/module/history.dart index fa0c2a4379..c2e1512fdb 100644 --- a/webf/lib/src/module/history.dart +++ b/webf/lib/src/module/history.dart @@ -107,7 +107,7 @@ class HistoryModule extends BaseModule { } void _dispatchPopStateEvent(state) { - PopStateEventInit init = PopStateEventInit(state); + PopStateEventInit init = PopStateEventInit(state: state); PopStateEvent popStateEvent = PopStateEvent(init); moduleManager!.controller.view.window.dispatchEvent(popStateEvent); } From 61f12a9cf5884f5b12eed79729c78f34e94b5cc8 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Fri, 16 Sep 2022 12:39:53 +0000 Subject: [PATCH 319/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 8 ++++---- bridge/core/events/popstate_event.cc | 7 +++---- bridge/core/events/popstate_event.h | 18 +++++++++--------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index 9c73237228..ed0eba884d 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -29,21 +29,20 @@ #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_script_element.h" #include "qjs_html_input_element.h" -#include "qjs_html_textarea_element.h" -#include "qjs_html_button_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_popstate_event.h" #include "qjs_intersection_change_event.h" #include "qjs_keyboard_event.h" #include "qjs_location.h" @@ -53,6 +52,7 @@ #include "qjs_node.h" #include "qjs_node_list.h" #include "qjs_pointer_event.h" +#include "qjs_popstate_event.h" #include "qjs_promise_rejection_event.h" #include "qjs_screen.h" #include "qjs_text.h" diff --git a/bridge/core/events/popstate_event.cc b/bridge/core/events/popstate_event.cc index be5233d504..fe604b72c1 100644 --- a/bridge/core/events/popstate_event.cc +++ b/bridge/core/events/popstate_event.cc @@ -2,15 +2,14 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +#include "popstate_event.h" #include "bindings/qjs/cppgc/gc_visitor.h" #include "event_type_names.h" -#include "popstate_event.h" #include "qjs_popstate_event.h" namespace webf { -PopstateEvent* PopstateEvent::Create(ExecutingContext* context, - ExceptionState& exception_state) { +PopstateEvent* PopstateEvent::Create(ExecutingContext* context, ExceptionState& exception_state) { return MakeGarbageCollected(context, event_type_names::kpopstate, exception_state); } @@ -34,7 +33,7 @@ PopstateEvent::PopstateEvent(ExecutingContext* context, const AtomicString& type : Event(context, type, &native_ui_event->native_event), state_(ScriptValue::CreateJsonObject(context->ctx(), static_cast(native_ui_event->state), - strlen(static_cast(native_ui_event->state)))){} + strlen(static_cast(native_ui_event->state)))) {} ScriptValue PopstateEvent::state() const { return state_; diff --git a/bridge/core/events/popstate_event.h b/bridge/core/events/popstate_event.h index 9044896569..7d53d6368a 100644 --- a/bridge/core/events/popstate_event.h +++ b/bridge/core/events/popstate_event.h @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #ifndef WEBF_CORE_EVENTS_POPSTATE_EVENT_H_ #define WEBF_CORE_EVENTS_POPSTATE_EVENT_H_ @@ -21,16 +21,16 @@ class PopstateEvent : public Event { static PopstateEvent* Create(ExecutingContext* context, ExceptionState& exception_state); static PopstateEvent* Create(ExecutingContext* context, - const AtomicString& type, - const std::shared_ptr& initializer, - ExceptionState& exception_state); + const AtomicString& type, + const std::shared_ptr& 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& initializer, - ExceptionState& exception_state); + const AtomicString& type, + const std::shared_ptr& initializer, + ExceptionState& exception_state); explicit PopstateEvent(ExecutingContext* context, const AtomicString& type, NativePopstateEvent* native_ui_event); @@ -49,6 +49,6 @@ struct DowncastTraits { static bool AllowFrom(const Event& event) { return event.IsPopstateEvent(); } }; -} +} // namespace webf #endif // WEBF_CORE_EVENTS_POPSTATE_EVENT_H_ From e942e2ac44e6d74c088d95bca5589de4fb21e490 Mon Sep 17 00:00:00 2001 From: andycall Date: Sat, 17 Sep 2022 12:45:19 +0800 Subject: [PATCH 320/498] chore: move animation event to dom/event.dart --- webf/lib/src/css/css_animation.dart | 33 ----------------------------- webf/lib/src/dom/event.dart | 32 +++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index 1b612f370c..49b6b67789 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -7,23 +7,6 @@ import 'package:webf/dom.dart'; // CSS Animation: https://drafts.csswg.org/css-animations/ -const String EVENT_ANIMATION_CANCEL = 'animationcancel'; -const String EVENT_ANIMATION_START = 'animationstart'; -const String EVENT_ANIMATION_END = 'animationend'; -const String EVENT_ANIMATION_ITERATION = 'animationiteration'; - -class AnimationEvent extends Event { - AnimationEvent(String type, {String? animationName, double? elapsedTime, String? pseudoElement}) - : animationName = animationName ?? '', - elapsedTime = elapsedTime ?? 0.0, - pseudoElement = pseudoElement ?? '', - super(type) {} - - String animationName; - double elapsedTime; - String pseudoElement; -} - const String _0s = '0s'; mixin CSSAnimationMixin on RenderStyle { @@ -185,22 +168,6 @@ mixin CSSAnimationMixin on RenderStyle { } target.setInlineStyle(property, value); }); - - // for (var i = 0; i < keyframes.length; i++) { - // Keyframe keyframe = keyframes[i]; - // String property = keyframe.property; - // String? originStyle = target.inlineStyle[property]; - // if (originStyle == keyframe.value) { - // continue; - // } - // if (originStyle != null) { - // _cacheOriginProperties.putIfAbsent(property, () => originStyle); - // } - // if (keyframe.offset == 0) { - // target.setInlineStyle(property, keyframe.value); - // target.style.flushPendingProperties(); - // } - // } } } target.style.flushPendingProperties(); diff --git a/webf/lib/src/dom/event.dart b/webf/lib/src/dom/event.dart index 45839178bb..ab2f775190 100644 --- a/webf/lib/src/dom/event.dart +++ b/webf/lib/src/dom/event.dart @@ -58,7 +58,10 @@ const String EVENT_LONG_PRESS = 'longpress'; const String EVENT_DOUBLE_CLICK = 'dblclick'; const String EVENT_DRAG = 'drag'; const String EVENT_RESIZE = 'resize'; - +const String EVENT_ANIMATION_CANCEL = 'animationcancel'; +const String EVENT_ANIMATION_START = 'animationstart'; +const String EVENT_ANIMATION_END = 'animationend'; +const String EVENT_ANIMATION_ITERATION = 'animationiteration'; const String EVENT_STATE_START = 'start'; const String EVENT_STATE_UPDATE = 'update'; const String EVENT_STATE_END = 'end'; @@ -646,3 +649,30 @@ class TouchList { return touchList; } } + +class AnimationEvent extends Event { + AnimationEvent(String type, {String? animationName, double? elapsedTime, String? pseudoElement}) + : animationName = animationName ?? '', + elapsedTime = elapsedTime ?? 0.0, + pseudoElement = pseudoElement ?? '', + super(type) {} + + String animationName; + double elapsedTime; + String pseudoElement; + + @override + Pointer toRaw([int methodLength = 0]) { + 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)); + bytes.setAll(rawEvent.ref.length, methods); + + return rawEvent; + } +} From 896d1c3bfe60dbc6acd92360d727e2aa6f52c6c4 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sat, 17 Sep 2022 22:10:03 +0800 Subject: [PATCH 321/498] feat: support matched style for devtools --- webf/lib/src/css/css_animation.dart | 8 +++--- webf/lib/src/css/style_declaration.dart | 19 +++++++++++--- webf/lib/src/devtools/modules/css.dart | 33 ++++++++++++++++++++++++- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index 49b6b67789..0cb0426d63 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -128,10 +128,10 @@ mixin CSSAnimationMixin on RenderStyle { } else { offset = CSSPercentage.parsePercentage(keyText); } - rule.declarations.sheetStyle.forEach((key, value) { - final property = camelize(key); - keyframes.add(Keyframe(property, value, offset ?? 0, LINEAR)); - }); + for (MapEntry entry in rule.declarations) { + final property = camelize(entry.key); + keyframes.add(Keyframe(property, entry.value, offset ?? 0, LINEAR)); + } return; }); return keyframes; diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index fd281281c7..2f4cfd7480 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -3,6 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +import 'dart:collection'; + import 'package:webf/css.dart'; import 'package:webf/dom.dart'; import 'package:webf/rendering.dart'; @@ -64,7 +66,7 @@ final LinkedLruHashMap> _cachedExpandedShorthand = /// object on the first CSS rule in the document's first stylesheet. /// 3. Via [Window.getComputedStyle()], which exposes the [CSSStyleDeclaration] /// object as a read-only interface. -class CSSStyleDeclaration { +class CSSStyleDeclaration with IterableMixin { Element? target; // TODO(yuanyan): defaultStyle should be longhand properties. @@ -88,7 +90,6 @@ class CSSStyleDeclaration { Map _pendingProperties = {}; final Map _importants = {}; final Map _sheetStyle = {}; - Map get sheetStyle => _sheetStyle; /// Textual representation of the declaration block. /// Setting this attribute changes the style. @@ -108,6 +109,7 @@ class CSSStyleDeclaration { // @TODO: Impl the cssText setter. /// The number of properties. + @override int get length => _properties.length; /// Returns a property name. @@ -530,8 +532,12 @@ class CSSStyleDeclaration { } /// Check a css property is valid. - bool contains(String property) { - return getPropertyValue(property).isNotEmpty; + @override + bool contains(Object? property) { + if (property != null && property is String) { + return getPropertyValue(property).isNotEmpty; + } + return super.contains(property); } void addStyleChangeListener(StyleChangeListener listener) { @@ -582,6 +588,11 @@ class CSSStyleDeclaration { bool operator ==(Object other) { return hashCode == other.hashCode; } + + @override + Iterator> get iterator { + return _properties.entries.followedBy(_pendingProperties.entries).iterator; + } } // aB to a-b diff --git a/webf/lib/src/devtools/modules/css.dart b/webf/lib/src/devtools/modules/css.dart index 14369326df..dfd55f8b54 100644 --- a/webf/lib/src/devtools/modules/css.dart +++ b/webf/lib/src/devtools/modules/css.dart @@ -42,7 +42,7 @@ class InspectCSSModule extends UIInspectorModule { Element? element = document.controller.view.getEventTargetById(nodeId); if (element != null) { MatchedStyles matchedStyles = MatchedStyles( - inlineStyle: buildInlineStyle(element), + inlineStyle: buildMatchedStyle(element), ); sendToFrontend(id, matchedStyles); } @@ -110,6 +110,37 @@ class InspectCSSModule extends UIInspectorModule { })); } + static CSSStyle? buildMatchedStyle(Element element) { + List cssProperties = []; + String cssText = ''; + for (MapEntry entry in element.style) { + String kebabName = _kebabize(entry.key); + String propertyValue = entry.value.toString(); + String _cssText = '$kebabName: $propertyValue'; + CSSProperty cssProperty = CSSProperty( + name: kebabName, + value: entry.value, + range: SourceRange( + startLine: 0, + startColumn: cssText.length, + endLine: 0, + endColumn: cssText.length + _cssText.length + 1, + ), + ); + cssText += '$_cssText; '; + cssProperties.add(cssProperty); + } + + return CSSStyle( + // Absent for user agent stylesheet and user-specified stylesheet rules. + // Use hash code id to identity which element the rule belongs to. + styleSheetId: element.ownerDocument.controller.view.getTargetIdByEventTarget(element), + cssProperties: cssProperties, + shorthandEntries: [], + cssText: cssText, + range: SourceRange(startLine: 0, startColumn: 0, endLine: 0, endColumn: cssText.length)); + } + static CSSStyle? buildInlineStyle(Element element) { List cssProperties = []; String cssText = ''; From 8e0bfb75da894b45c3678d485eb8728f84b4c9a1 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sat, 17 Sep 2022 23:35:54 +0800 Subject: [PATCH 322/498] fix code review --- integration_tests/scripts/html_loader.js | 4 ++-- .../animation-delay-001-manual.html.1.png | Bin 19046 -> 19087 bytes .../animation-delay-001-manual.html.2.png | Bin 19087 -> 0 bytes ...mation-delay-001-manual.html.84ab03931.png | Bin 0 -> 19046 bytes .../animation-delay-002-manual.html.1.png | Bin 16699 -> 16721 bytes .../animation-delay-002-manual.html.2.png | Bin 16721 -> 0 bytes ...mation-delay-002-manual.html.509a86541.png | Bin 0 -> 16725 bytes .../animation-delay-003-manual.html.1.png | Bin 14580 -> 14571 bytes ...ation-delay-003-manual.html.606cda041.png} | Bin .../animation-direction-001-manual.html.1.png | Bin 18774 -> 18896 bytes .../animation-direction-001-manual.html.2.png | Bin 18896 -> 18790 bytes .../animation-direction-001-manual.html.3.png | Bin 18790 -> 0 bytes ...on-direction-001-manual.html.7c8951471.png | Bin 0 -> 18774 bytes .../animation-direction-002-manual.html.1.png | Bin 19352 -> 19450 bytes .../animation-direction-002-manual.html.2.png | Bin 19450 -> 19352 bytes ...n-direction-002-manual.html.b1a170001.png} | Bin .../animation-direction-003-manual.html.1.png | Bin 18781 -> 18794 bytes .../animation-direction-003-manual.html.2.png | Bin 18906 -> 18897 bytes .../animation-direction-003-manual.html.3.png | Bin 18759 -> 0 bytes ...on-direction-003-manual.html.4c0b13381.png | Bin 0 -> 18781 bytes .../animation-direction-004-manual.html.1.png | Bin 18688 -> 18688 bytes .../animation-direction-004-manual.html.2.png | Bin 18688 -> 18688 bytes ...n-direction-004-manual.html.f2f6e6921.png} | Bin .../animation-duration-001-manual.html.1.png | Bin 17506 -> 17542 bytes .../animation-duration-001-manual.html.2.png | Bin 17542 -> 0 bytes ...ion-duration-001-manual.html.bb303e7e1.png | Bin 0 -> 17506 bytes ...on-duration-002-manual.html.0556ccf91.png} | Bin ...on-duration-005-manual.html.a1b1d58d1.png} | Bin ...on-duration-006-manual.html.03a816fc1.png} | Bin ...on-duration-007-manual.html.8475f5701.png} | Bin ...on-duration-008-manual.html.d23881b91.png} | Bin webf/lib/src/css/render_style.dart | 2 +- webf/lib/src/css/style_declaration.dart | 4 ---- webf/lib/src/dom/document.dart | 4 ++-- 34 files changed, 5 insertions(+), 9 deletions(-) delete mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.84ab03931.png delete mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.509a86541.png rename integration_tests/snapshots/css/css-animations/{animation-delay-003-manual.html.2.png => animation-delay-003-manual.html.606cda041.png} (100%) delete mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.3.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.7c8951471.png rename integration_tests/snapshots/css/css-animations/{animation-direction-002-manual.html.3.png => animation-direction-002-manual.html.b1a170001.png} (100%) delete mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.3.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.4c0b13381.png rename integration_tests/snapshots/css/css-animations/{animation-direction-004-manual.html.3.png => animation-direction-004-manual.html.f2f6e6921.png} (100%) delete mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.2.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.bb303e7e1.png rename integration_tests/snapshots/css/css-animations/{animation-duration-002-manual.html.1.png => animation-duration-002-manual.html.0556ccf91.png} (100%) rename integration_tests/snapshots/css/css-animations/{animation-duration-005-manual.html.1.png => animation-duration-005-manual.html.a1b1d58d1.png} (100%) rename integration_tests/snapshots/css/css-animations/{animation-duration-006-manual.html.1.png => animation-duration-006-manual.html.03a816fc1.png} (100%) rename integration_tests/snapshots/css/css-animations/{animation-duration-007-manual.html.1.png => animation-duration-007-manual.html.8475f5701.png} (100%) rename integration_tests/snapshots/css/css-animations/{animation-duration-008-manual.html.1.png => animation-duration-008-manual.html.d23881b91.png} (100%) diff --git a/integration_tests/scripts/html_loader.js b/integration_tests/scripts/html_loader.js index d91f88f09d..3b5c336a5a 100644 --- a/integration_tests/scripts/html_loader.js +++ b/integration_tests/scripts/html_loader.js @@ -47,7 +47,7 @@ const loader = function(source) { } }) - const htmlString = root.toString().replace(/['\n]/g,function(c){ + const htmlString = root.toString().replace(/['\n]/g, function(c){ return {'\n': '','\'': '\\'}[c]; }); @@ -56,7 +56,7 @@ const loader = function(source) { // Use html_parse to parser html in html file. const html_parse = () => __webf_parse_html__('${htmlString}'); var index = 0; - const snapshotAction = async () => { index++; await snapshot(null, '${snapshotFilepath}', index); }; + const snapshotAction = async () => { await snapshot(null, '${snapshotFilepath}', index++); }; ${isFit ? 'fit' : 'it'}("should work", async (done) => {\ html_parse();\ requestAnimationFrame(async () => { diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.1.png index 9a3736f7351e53fafc6250db148787e390f98f14..cee60af353536de862acce5c701d95da6eb33751 100644 GIT binary patch literal 19087 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 KoTF&=@c#gDcdl0e literal 19046 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 KoTF&=@c#gDcdl0e diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.84ab03931.png b/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.84ab03931.png new file mode 100644 index 0000000000000000000000000000000000000000..9a3736f7351e53fafc6250db148787e390f98f14 GIT binary patch literal 19046 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 literal 16699 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.2.png deleted file mode 100644 index 9a6b072d6fc000dd1b64a4acb64dd7a1a72050f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16721 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.509a86541.png b/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.509a86541.png new file mode 100644 index 0000000000000000000000000000000000000000..43f0dab602b58aa0b375154f499d09e60df14551 GIT binary patch literal 16725 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.1.png index 7b11c12730ae6d3dff08003fc9917c20a9b5d5d1..844dc66d8b2041aebe7285bc875b0ef580a3a7da 100644 GIT binary patch literal 14571 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 N7-g;71vei&{~z^z1epK; diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.2.png index dd647b5bc35d08ae95cd8ad6f00d7e3170231145..32f962b9a72ba81cb61b2b7a6ce22dfe74a32934 100644 GIT binary patch literal 18790 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 N7-g;71vei&{~z^z1epK; literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.1.png index be1086d8fbd7f9391d0d9958b9dee0695ce83624..b3607f02057a194ce6a1516f115fbc9d2d3a6ab5 100644 GIT binary patch literal 19450 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%0Al7kFRB3tNt7Mi$Y^@^QT0hp;XPkyHc39T zWMqCYr!{QyvgDe>xiFpuG4kSjr%!epC2oq+(+(9dK7CxAly1%$W*-?IP}H!Uz6}lN z%C&1}#=Li8Jv>{+P5~MI^5u)l0RNvhTV>UrpSqEJkLUjW{v5S>z&2Ki={3@h9rZo& z2F8htxU7!8&&}oj+0gLqUH}QQGKP=&_SB}`M}cD?Ed5)lMz4vUS})yDM${kXKb4+4 zma(?dH{xqEf(nLIRQc`IZ!-D2&BnT(MiiBljIhBnX=!P-2Hb%;)yG=&rUN2oS$3;X z=0>&tXKS0AA6I@Jh~P1n^6{zKkGP?nOaR+=y;We00J>ATuB~LmiWAa5FdZE>-gSM3lRuCwD?ygVq*ENJNauMU* zODnIc1ioejGfpz_z;#u-%B$34=QeOGK&{E8R+`YbO%GvO8r26;)&jSx{2T#zC}a)4e;y2W?P(CMrmSXPL5q#UslCy zctuikSPve<+SBFDhX8AhcGo5r=xb4W3CdK@o?3A`6wVtzKLp^6KU`~fE-^$3IDi|r zN4=-~9RPHBopP5an}@K?*di>s|}fu1FduE_dSV?8h>{~rbl zvx<=^Q0$f~xihn~*HM((>c**4av6wNcOM-Y+2z}|!>q)3FLyz`k$ED(#U)QejMsX2 zGd85j%X+Bz2_P^{giOG0l)b1GWbaNGM0qC7RIMeUNA`Og(+gGIK&0%K`gvNn|4vVv zU>N`O3R6o40>JsF^%{dx#Bq3Z)Z>0p+^a;R(EKBS1k3pEpLhVDP*hb_EeO*KKKtr1 zD{8iF{9vdcaXCR)h_a(s=6vkgx#y?w2@&Wml3wfe)RT3j5*HAe5n%;Qg@lBJBtkBZ z0YdW!UC?5~*0#1T;Jv(C>HO6}`zP$zAV4pHP!=FE#JY}>^P06?xGop-%#sNYAK%K> zS3^f9Gh1iV>8J0Xt3IjRdJZMMqL9aj@}k}ZGiZS z@!@fCoapxg)8`6f*Zj89KZDU!aiN2cV3CVqg&#F{j2tU-ehfu2vADjA=| z{4%Qzlkbg72E+G)<4lX9>mB$xVdsUbuy?M5+TY#C3OZgIUK>pH`gGL{&=zxNZik{* zM}AH>hx2z~cXp^fD)lZ8x6b}f2{t6Uz4gtD-}-QR4Npk`CcR5*zhbyp%#tnzay zHtEy4mm}7TJ2+rb-`JS3({uEXN3#xoMOp}^H*Wb|f4>IKu-#8_r@0TvWf)r5oP=Zd z!_`Z#XKx5j7p;zztJC)Z#X5tm|G;NVtz!=_N=ZpUU!AnHwAyUrz`7RhWwaSaS7Um0 ztlt;m*;-{yNNVWM1P^}axG|LCzNQDe(tB1{LnD=BXM4N%kVh4l6&eJu+_jd~;nGA~ z{h6#SXnRqyy=r%~i?c&$cq9v+_&`l3g@uwy0)g)~y~CC{a-tO(0uZ%zEuZg|Hr^_@ z4QCK?YU&h{p?s6K$Yp?|CXUC}S&q$qBvU+KbP)L%S%4iHl8k~ zo~d$C%}by}zW(rm3_Aw9NI@@*qa3`W2VPzk$tLQC10C9`T??T70<~R(ryhSVFzN@& z%g;9y&0@N*lvvWbFL;Ce3~&L2xSIfLdI-Nm*8$F=?3k~b(q$3F$pGvN*;!S$eq`aj zGEk5*@|oR)o(ln~%XqpeR3pzwI?}Qnr5Pxb0Mx&`znHTQk{`l4Qe!hfXZzMYdJBrz zW_w5TmR&*bA{H(-*PoX-BlCPV&Qne~;md~)$jcuHNVTmcUrgVFfQ0g$wVj;@*4CN( z2Y`!lkQXmsF1LNI>~%&S%F3ShQ)W{$)Am%|YLJfmC-5pJ18Nn5V#-Z$tg_$yC=X(&{Y9Hk}6KVD#KHkoiuFc_}#S}XP zL>2YfpK|^yn^m)|rk#`CsKt9m35K^+9JQ$OL_wiE(`#>~RcMYQFE5V@_MoQrm)Ku{ z4IEH?IL)=TwpIfT88vN|28)PtDmG+ZB`0n5^8@Rt-L#p{$f=S9?uDwy$lP!#uUd|t z&CdCTe7RL`mTxe{N8(^*MeP;#jA2UkM1AG?ZmJXD9iZ8K;9gdA1; z>Y6Jr@e8wg`-EZjDRrx7n$wt{O!#4^Q{Rm#?!Dk$gpo6{Ae>u7eDlwz<-mfWd(NE| z`Or+vk5&KPir6S#Qo|O03Fd{F>jm^LbUPdaUA({4F=d~m3Pu}vGTKva#+9$};FzOkbK5J|?d%bd)(;f&9P> z9e`dadULZsR`C;o3QD=bX&NZy-NRekmAv%cU?BIsHfa(sniqX{6+y$Ky zy?s%DSb{Fm8KgY_nFUy)L7lBu{*18JdU|1})MuwKMJ7P(Y%x#DZrwOH1b5yD@CtPp z4!64GQSL2y?;gMJ5`0G z7%~)7@MMCt7H$$Xc&PQg$PEn*Q8He&J4qBHjZ70yy+DZx7FpBpm zYBM~xdku_AuBLz?eU2>Wz0V|n((d8 z4FUC}0*n~dI8y1U?wu9Z*Q<5Acql3b;2Vzl#TSyZZ@4Jj-@=W=Xf+ zaNz2)wQ@<3P2=FT!Y0*QZ>hP8g?x*9OC7w+OF6WQQ^SvL4C%SKl^~Y^c-ZU|XsiV}<43!%l=Mvf3F*mH1`0bl>#d0^A zo`V2o4DdtH_t-1RJ1OSZF!T+3u)_fXfp=vL9x{xPGa#kx-LcD4hy@@P0MB0w8dvr4 zK3moCz7@KCz=4vMlKS~Ft`h$>&a$6Fcs0N;v2mdUiS#j_gLA&eoALa0uSw{Gpi>{W zSKQDjbGiYdQUWc!k2ipF#Mk6`Nzb2(dWhSXqu&TTckS1ekea%>L>bakO>^M=K79PR z4(<#k*&zF=FJN1%)rrlSnIEmoU-(bn`LC!7kkK@{JM~&$Gs;voUbBoX#NpHG6>v;j z7<00+*1)*42bt_-ZXN&ZVm}`s2EdKT94w{=PR0Tnd0KfBBbMw6nwXCL|FGCRh zkSTpNbrKcCzL~#<#X4(TP$2KY3jeACTztVVEszO*1n=bpPWwk=G}^FS)Yl#TS@jjj z2tpMnt|oSMl!INFMTFFploOyR{c3CD+}!n{<|sd({_7Uk+jLvw?Cid~+p*%_bl_fv zdbDpZ07QC26iq`H0EdXwHsN$ETuF%1F+cS%V}jX9su?@!kdSpg;zAg0+u2xC{j#U>92T zk-%M}X%%o(`2>2apr+L_HJ-!VObu~|Oa;Gy)AbM**X@kI>j+!o@%-*O}olU zN-|=Q%LFXb~uoZ?Jb3%9K>ZUm_aBiK`xp0)rOTh%6@rw5^$LQOY=sN;-mO7O&h)qr#IZ*>ADCGlW7_Dqe8y(PkOw;T&mEuym=6?clmxhl+Va;g+ONnApmU1xh0H$BB zI^1~7W7*B>>_M0$)DK=w_Vs>-c@|UDXsH+Qhco4V`8JLe14%0y(1s@)IIrpP{5E^XH zP+jd09&?jJ@G{R6QL&Q&%vQlr09uB4NSeiOwcivJ+P<*{?U#c3Vh0zo$@d z^tP$?xjqnovXK=59rdR}c|BLNyXN_J^nk5pnWku9Y-5qH$M!0$z9sZ$3gKNR&kq=I z6WkekN>5G>#Q>#!mT6^Gzll-29!!)oNS^PB4`^7-fSw;OlY-0gO*us_YQ29*zIpIN zw(J#F)`z^A-)ttPb=7+NEr~G1=I}Bmrl*tKPdRTdX-4BLvl^q~(@JGZQCj|vbVlRX z&?)wI@dNfxpFVwnc>qwNJ4gTIx$aGqtl#Q;B{_vu_;`4eO`&YiOYf_xg?a2wKqii) z(PWRt3Pn$??K0J0-6@qpdGQUjQ7rzyJpQz!&v)io*1?#vSWzGS zCQXDWEHf-PgjDy@vEyxl6k)QX!$ZCuarLO_*62(WiQ|@xrDiYb&v5*m*?e|< z=`X9~m05dxAcPg0MRii^+uw;-dtffW$>zlrI8b$&@#w@?BbB(9&DAQ(9-)nvXd^$iG%}uPwLlZ^k8}pr9a0 z6_qc-Iw@U0y1EKfI*r}6pL5t9rC#tEEwMj^eE9hBISlkEK&lH{&kyQj2(?>(gnhaB z4QO}DOuLSBbPP^o^Yq;!o^aTL4s>BT>hp00?5wP-XKz_0oeit|a@3rEEE|bGz4PP8 zj|f3~=G)f&erS{p+A`j=YNF1_qr!Z;Ka`;ZR4Xm{`Spt0B}yNxvW(<%`mTtHRd%nX zTN35`a>y6Pmmveo0Yw}S&}<2(bi>6!wyb){eLVON@moPj=>=r)03cV@J{crzR(m>~ zQ;}~Bynz6Sd?QQ@-mA75K3=pGOO?S_; zZw_#?p9n6GRWsHcnVL@L*MYZu*IO1o08+R7?aEOvIoffx_q*bc{Q+zlog5z}*_zt6 z4c^KLW|>X5G%g-?X;HoZxkSiBTLLs2(_I*LP5f2)vYY?q+8zWLBIB#RCn9K652hElNzR$e6$A6&ELYj>dR0k1?v~+XouGU77%q(Z&AUQ<*reT1)HaSCQgJJYM7(% z4>Gxzxw#d@@JfMCqVzmkz^*~!c%3vL{1vF1)DVuic-#68XeS=;U^TtXhl;aCa$bTa zdI|M^0Ux8FwY)2j55!@V{m=+LyNu3syXqqN5@YHU_{!>OB5AE`M$4SzczyGp`gy-|p?ldaiz(S;|hioK%<>zi2svb2sj6%6K(GT&H00GWRj3>O*Ok8S}=}J%5VXqW>Kcv zpm`SCj-tPj`FtKdxMr7+nbxpjLKnZ298aeAi5;$ zwVk;G`Gr8pZ(;309$*#})ZjgWEzjd~xU0m`&>Qs}wdybGH`0pmMdzGVj48 zZq~?cLx}J}6b%Ai!YzZxsCD^dv0_gUClDel;rp?PM}?W^<|QMt_2&9=;!QQAhicjK zGua1Vc3kp-C@VV1R|-HQH|qQ*4Jv?o<@ttDtAdtS;5QI3GBRTM!LFIP>8P)AQPrOe zl66uq@y@Mv+#^lHi3<-8*xGVJ;}1`w`0V#<+#zCux|6`q!62`jH?{VJ? z-VgLH=ElfLNB`tVN=ljl&9%tmR!TY;0spcLT}rY9=|ZxO3We$Z_;T4y2-ptafkhz) zL)4@>EY#`}Y$7QbUtm-kY&xs;R%vjAs#`Z4PZ?e0{k;SXo)u^lM2dnyd_=JvIygw^ zg}?hO%A59_@A%K}%=A4uj{4jStX`w!S%SyPT^IKENrZ;*8JR$2)w6Ye04#(AQWv53 zquIL#{~Rl@a}Er=TJOig{OQ7}sKAFx|1i2oV+*3 za|nZu|C2Thoz`|9DRb@`E_xTUS=~>#D%-_(WB}~ z>>to1*WqP;gLa4}qShd&@D+p<;3Ejcl)F6#AQw6Q@d3k+&+_7L#>Wk0BX6C7Qz4>g z))-)KGzE}raX1wLXB8l_PK?^ha7qN#j=Q5Zs`qB1`wJ~7(4-5z;0d@dG~*BXfsE~r zP!lh(t~yk1{VN!bhL%9weglwq0#xH#sIWkE{Y5-XJV3GMdHRbCjrazTGTztGxfI1` z@vYa{#LNtW(~6bzJOA5oxOMOeGzv5dE-EU@?Y{A}q_h+be7emg1fYubJ9-f+eQ#{2 z$EFLiI}>dOK`Hw}yD585DQM={4#+vsOioUck&{1%wvQg0K@W1=;^obQN!jX1 zK|!$&LFYs`vl18*()i*G(X*+BbJ#9z%`nLEC3ck98(+9^!2m*!Gy=Alwmihpe6+fr z`x%h!(3BPY@pO%f$Ar22IoeyN5pKx4!YLLK@FHnMT-Z@@3ck61CO-lH4uQK?B}a5* z=m7yKqqxpy2^2mNf?C`rbn#?KuuD9b0fws6hkg14XM%tqer?IQOVH(Tt9mT6c3|6J zQ_YUaKSIRSHtm3AXeyO|82o3{mgNRJyl7rS^8|7TYVkRl;ZS2b^fR&U>vRN@u)6k@9`c7-Afv0}M zx>Q2LD$2#w9f}On-4M`UyjtuKggQ6@*r3WP7wG27&ye{_(G_X5b$45NKMne ztJ9>iVy~L>tAea?$^nLRc&Cc(#)9GW3djx&yUqH?lWPnWVee;}N~w}w3A3>gfy}rt z@g6o#t5vQS^&o^Xtp^LwLQv;Eu-_OxH8e*Dnhyl@vwfm~`X}vEa0nGtKp-9m!NI|a znuRTmX*su{;>@6tf#Gd!X?kGUJf>Z)=%X1GN1wJ3{XV=%tmMCEWMlOHm66Q|aqXsK z0EYm)G!)c8W7f|ztiwZ+V<}3ik7Fx?zGr4F5W@1a<%`Kh@trL#EsYR$zr`XSM-2Qg zQ>&fpZV|xC7f%xejfNmP{%3ZJ&;>i`}3HKNkBloKupB( zTqSSwW1(5Y(qQ-L4>}AFGcW~k*Y;A)@BeJX+L#E4H77&d4=p^%->$&{0Q9^EEMKv^ zMQCF0GYBD*U%8JSuK*KMqtb&Pwfk>dXlHd5f5E7Z45+OIS%;wg|I@X{kVy{&9Sfp^ zSuK4(*qX21zD)%#k2ECeg^&Ita+(pMI(jx*xfqV~sKdSfw+!h>oTGySUsry8+78(4f|Z$&zjzCMz>*{ua+@c2Jv=VTf$ARsBIF`ht+y28mx0%1{rYgEu3oxZu~3;QjC--;^EW8nsf`@k{jfog~CfAj`@&U~bdU(apg4Dfxr z&n!=BYo4D%tuB}tG~h;~#~?xWGISOX0I~o_KG2Sg9?^kOy;e`H|D`OuZ(unO5V}US zj~Kx2ASk#52nC?wQg93=W@cW4eaEg-LIx*ZOyj@vqi4n7m<&o}AY6pTh{4{h0j#SC z1_TfWD~2%$OoA##Brm#tdV6&&1en|DGiTBQxMDQbpakpTc*1LlVNJr}sGk#c0dNXn z3zV`^zLDf77eT;nOPe;~qk|OQd(8sVA0s2WBbXMhyQ(OsAfo1!0$I-E41p>IQ+i6z z&?ne0aD0>b>QyP58fZe{^}vZvBRnQeUv7o8=kN8Yz2rxDpe16>l3I7*Y-NG#A60^h z-J2v2mVY#9x=q_R_24#DLmDT~Wm&6y(;$amC(moo0p|{Mx^R!GBCMTHy_A%cKXMra zA=GF}*PfDPE6Li+8%Eo3G=_T->p_0$k~M~Buy^~zr%&JX-WwjTLW-qZR2>2*prctb zP|(n@!oi%LMxlaC$Zy<$loDT#GXyJYA)WyPP{TVWE7`&MwvL}Swr3K2C}!7Db!Fbpe!&hYEkua2ds zVUTY}PaaBXX=z28?n5w@%lU?ibi?dBPz+PUmBk34X`oU%q&re&+sonLS;@1B7aqT( zgr*%tryaym?0XG8`U>`H*6|9U;3z|Ih$*3}&{XNPmL7Nxs5%BBbluEh{}2PvJvQ6n z5+>jg-G-2Xqyi#92H#{7$M_gv#3W39=4;oaQBlFi%%{{6@?UUC$+Sj|wv0p*XNVMj zjEPx#R%vEt_O_*^MJWe_76B0vZOH%D&y>7<3wD`=3f6}@R2@BB0cis?K$r-vUp;eZ!k0CuCK4t2s@dl)rWZp$$Nj`(Gqi-d-sZ7u!XnM62Hb5W81=CFDEc({&phGDI7~?QgZc6x`rHy z!VfOV^PP-8S2qi;l6RgU|CMRHc@I(}va7hyelE?jA-7MBgTTlQ-w7yy|gu1 zm$!IYf?GpiaF6zZFLe!J#OV7nv$hr!I9>y#_o=9d2eH^#F) z(rwi&+_U{G{?}nYuYt<>OSdkrav<$>P9>%+eiJ*dcPe)@mc3Ko1g3xfeEsZObRFq-^Zs=*61LNNW=hqm)!=-6Yuq;=%wyVO(?F=biHS<3wFN zy3#}ul3Qcku}@X}YqvNtqMUV3`3}r}w5D}-ry@>utz*l$E5)ll>pYM^+%QPTG zaams#ydM|(rKCuMrSFgy>|zPONPHzS5RSV@(8cu08qqBA<$rC`wfW0I4u2>46k=m2 z?Dgr)Wr`hAyQeiy55E)1u7*kY-RL;S{YsepqZd2wK2_oB*Vt8N_9ydoZ_9S%6Hbkv zt;wI?`$#Cuwmj;4phetwY7KWSbs%@0TervoFWI%5GLn;Au_sRuPq%SWU)q*>*=4o5 z&d{ST=G51}qrUB@a!0px#NV$T2~Z`w&bV_I?drUor?cE-d79MMN|fBZS$L4WB!VDB z@6g{1kkue1m7hAUUdd6dOAwJgjL72T;D12I?IT8oL~K1c8K;|qo4Iw*n=f9S;zSm!{A3l0sz zWrE~;ENKWyjgwb-ej)BSY1i56^AU2gy?*m$E6fojR!>KwN%fJ{A6tK}7ziK!!B3|6 z_GI-v7u^1M9QTnu6LY!`Ki`S{7I*8^=&Sj+^kN3WXLoS@4YySuV?-9o#jDBS z_U5&xS&8p1xcz=3=+(UcUzG^Xfkhl;ge`!Z1$h`SPx^Ah)q)Tqjd)Q`kepLQ`h32B zi-_C%?We%GAF1Q#4&E9F=k<43Yuxr5?1=MXdp<9vM=m=C(d?%5TF)*IcntQUC5hp5 zuZV0UEo33v=I^?*a+foCQ(je?J8^*g`nqN!* zyw^7{>RZC*cYmj)QaFE}OiDUpWrb9vL9q6wUytRMdz@d2$nRL&nVVd9W@81jgUB$R z{{CN-CKTI{^XCVCdR}LGPk|ThfBy;5oJ~YbjHXe4IJ;_&^M*mjvvSHymoAn4{P5}1 z8xGB)@gKf~$)Z8b5s{I8hlhtUx0inZez@45i)Y%GRbOb@hZsecW==aC?piMnm*#iR zOniTZ=N}NjR=75xG`R6-e_${)^I0YKqGplVT>}GyH5R+o@w>S$I~L{QfB*jVdLZ-k zX&Ax~az!wa~b{N>B(rdm{7+7w6~OT6n7m3+;cdwY97bZ_}{J}V15 zD7bUyj&Fw%1>yt$fR)Zg{SpV(cSa=>#Pt8z2y`ub??1Q+?$)Z`T!O8PVS zk~a<>ewdL~wLbA>J}C(a#H2Ul8;NF#6(cgfzqN3S<_%x`PW;K?8MlM&`mc{-+6#=k zY@+%mZLFCdclMx)6Rwu`9$oJq&cXF^>CRc~aYTst;4W+8z0JyWxojGjK!WoJ2j z>t;mYWAUTScMnws%{sE43EQhFD3E!1dC@f?F zbaZsof;KM`tb`nwuV_7cHZeJgOGQP+V>w#Uj>+eL90*I0>anM#rM22A$7LK>?YN>{ zVA?0YaR8HVOgCkACBk8`A18kxn2?Hd9CpI3m=*!MIkKvQW#Nx41{E8v!oG2LY-b)4 z(TPwa?UgQ$@YBV?Lb;=(`XH*cnHbBo=7eYE4*qZ6^aM+`?JsnHBZpD@8FziHX=x#z zY6$H+(lTu-q1ag&YZxxI?MRS3Q&Lg_OQlR(EsE{Uo`e;IXR77s;vnCLhAy{6^Wq_{ z>y0e%!xERBtlGco&A)zqhuzjuVy%b%RruPZdv9*=#OXIjB*3qEJ%hHm%!S>9Dz}4n z`1&p0veMG$J!#4aLCA!Rv~+@czJZm!y>EItyGD^&fca37Ag!gVs|a>ynV6K6)INru z&y3n)xFkVr26hzsE~PfiFyDAEkL3+{pOz$H7u1g(Z}Z!Wovej==yoJZqc3N_KB(Ta2M!6cc`7qhLO;CXX{h3PY; z@G8<9Gc7&l)sN%F?#s#L3qJ0>A>{JC(q*T;$Xs){%%1SW^+%Ya{jIg7;;|QV?Fp$* zk_q6{`DJ9>v|Ady2>YQfLp@)i5^G;qUtgnpYdp#@?yi;9t?Q5Df+MxD=io(ri;Ig} zjGvX-oaW}{_74iG{e0KEJxPYNuC7kE#_M#6^TyYiPwH=IcyH%OfHK2Pt0zzf!WWjkvpV92a+=TE zw`6AhIXHeKw3z$%{rAtU!VZUNVPBVR&dIq2vxz@3F=5o3aS0BkFC17VUS4J5>ez zy^y zho`W5?%K6$r*Lp^&Jq$z!n846z1mS|s@gJf*>qrF0R6%@m-%m53=8m>^^@i1=E95P z!ysSNdmNtUk2**HS(d+=h7X6evxa_X7oZ1i`3AQ#{d2eU{m_~`9*!8 zTE4-XGS@KuIA~raB?4c*el_{&u{Ez8jZf>4JTsKK@Kiz@B*-fTP}f*y}(@4nH|(ZokFe3-M0B=ll85q~Jj zLn7^;A|kFM{(`nM0wN4zR^t*l5@rK=(sx8|-BLivzJ2@V5MSWbsDig*fwYMsrujpXXye`2W5|Uisp_%u)zS<+4qiU>zpeW@wl6CA@X+-+4~=xS`%(9E*rPwj#&iHA%(N)^xnQwr zq4|Zxd%L^-o}Ng{ZurqrEva}n(a!q%1rCh@tKG7T@?&@7q4y%IKM1)P#Kh>}q(K>z zgksViD%2*jLFDNbdQ(tP5UmF$J!#X2+1|9-mD|xXWAK(gXIf&c_ty38EBdl^WX#NN zc6N3$_-hhGK*pa6%xAlw)sr&g8ruq!PApy7jg8edLe7r2J z(nQlM(~PXFXD{9msnB@hJOV?NhK^yMDK1@XF~Y+l7Pzh@^YA)3^u|}v$`X`PgH4hO+Q9OH18tIsSN) zWp{U1;ED&lEo`4|NArlh{QUEPEzV!PXVhy7PesAU6%nWQWvB419w_21R@1aIdmFPI zY0B*OPDVzoX)2i}j;^iS2~hqnX=`gcEtk%AKUYqBdN*6U@|J1u&GISxKhQ@7u$!!C zBctoRMNQ;Oq6C$0b8$fFs5s7kd$a4Y=}$lhUVS(Tb9Jc#PRw0 zK?CrwUG@v^KC;p3TfrzUU`8cwm2MhNF-dMv1W1VS06I4BKy z`0*A_R8$mNF|W(VqUQ^GKw0}aCEvI$&4~B!-y;-PHi}>J&`eBE`vOqJh)zI*Si-br zLl=4f;lonM3G^FiDOD|FYz=`N*_(<0Y0oq^Hj-ib5AjkmxKveDVg9ccue*FJ`nWKhvGAN-9xfU4NP1f*L~5xp7+Jr`4(mzRf7m^nEv58qW&qXwuK47B3q%a;sSui{l7uFECN z3Pz_8T)d$R?e_kI2S2i_j|0cYb%Uw7aZ?`0<16I6sI08CUYnGL63@iXPX&|(*7-a1 zKKnP4_}D%jWAVNxfV^_;{yrJ5_MituEV(pTxc2k1WxuPLVy~t8H`v$+gOCtSU)D28 zC~*5_fIo;~{Q)xJu{!nXw+!?=xR=ziEX%~a8cuE&ar@34s1i%=`wG30TOw=+q3dOV zKRU#orpVLAOc<{;tNdtm8@Lxr_XbvW97R3YwqL*6FYclf%2j=M+tW)964dwGPAo<9C|>4Vx!DL9#~zZJ!L)!k$Bli^d;ye1S-Kb}g|$X-F_ zr(IhQcc((5J=n*ZO!{*Yuue5?ZI4G}J-xhlrK4DUZS=itPW#%tUx7Z-0FVJa1sa7W z7qP0@hF6lM)!m^CNxNGp!t{vtMLY<J$52W2pqgPXm)PGx(O4mWu=VXJYuwpODG`C`*tIqU#sR{|WC>{l_7)&cniQEG8@H>W|RPr^^U7&xk1-R|!pQdbl zHq(6d>eX3NQW+SmHGrq?40Up7i`3jNPN6S6QR|naS!_Xu(cPNwx&QEC9V}AMnp#_N zUKK`Ck{jm2$;oLPm=U$G!+$l1Agth!A7CC|Xlv+Sr545VpePhz($ri>QuG?@1OS?c zB3s=`sTzehR5H|F!Co;X(YIR}rG@g#t&*wX3-g2z7_QfCSan4xKN-pk3K9_p1_lY* zV_327Y7=d4dHFLQEgSERaJi3rPIG;EDk z^mBH04ggBu?-r7%Q|&%)2ct}glMM+B>=dy$KHT%ox@lc>?87P`H&X7ng6Znn&@%0v zT3=gJj5h^rVr7*B)%4@Y?1FlAQGeMDUEpTEt&@I!euT)6t}dmsj8bN18QAGpHsSYQ zyhy}uy`66RBKx6x?WMlHac>X+C_oVxB>hP2u@R%aMaf#Uc00?@xS_XVx{gdp8&R00xocd*9ul*5A{x@NO!~K7{%e2z$V^x3BR7i*I~< zyn!>PT8_l--d+H}-?Y%n0#=`)Ciu>%wh86y?<|kZSDF7sNr?a)s}z!I*FBqrX-80j z?A>l;%a#V#+IUc5P+8|J9C*KtFIML^$4n)hcGjQ}De$u-E!U`ThWy znrh0*%1Tx1IQ9$Z?tY4LOI`pQcJ_gz)?u%O9hcF+p9;q-_v^ILQTVj&E3b4efY9Dh zLZInh@fH26nzBt@#V`kI0h8X zrD%hKrUq&gr=1NK97Sl&=uw0=V`y7C5VHa_7Cw_`F;eC$e@y8ccO(QK0$P!(T`4r* z{mt8XV}?;gB?tva`JcY-r;;`+)Op}^$%p@;2?$v z-imst@7}+%BcBHHV4|Oszc;E68Zj}+a(@o+zgCZmhGEN|MHaaug{F}+!Y9d{x(8>H zio;7&IR?yJ+<7*6L?eq}IED3xaQvsmjwWxk=IviKcqsk(v*;ffu&=u@56#-F_{k*} z`B+bsF1N*_s1ZSc52G{}Rs%uYcPGwN9BmH?*w2Sy~9*!IF>4j>k^j{QUt>$U6G zK|IujgBE?@+?=MGt=(T_eiY2C|sG`(YW(w0%(*bRV&%9JEJP_&DfR79WolwkozlL_<_2R(bB#F zjStt!iGKL~`$EwJ#ln}G>_Iax)FYi02TaVkOdkZ@2Y%LMU&fs5O2EaAE&i5gGRdio zIm>M~r+_2zXLfd|BB>K}T%X357j)eBl7)VkIku{dje_)7S7JTo6B!waWyybcRVEO& zdTMBth;tsu7UY1P!&leWZThFLOVxa8Mf_K)Pqgn04-~-bTz`1|gMicO=lxi~XB{xl z9n~Hl+#r%{wdw-V|MBv6fMxYj??eSF5b?VHTs_{}pCPpD53egFlvmj=_A3Iuy?b_T zb=i$wB|~9l)PRSk9`GrzBM0Hxv+cp+{QRmJTBT2bP^3Cu*Z@)`6H3R?>6FQQ?VA7E zR71L>H|vUYzvrQJpc@(p<%c{V`a-7E|5TkVjcKbO?O z3Iu)DDvDmn?mh^7YL${7Q&Lj=Lqm%N$C|cFoz`BQCnIAZ!uN&&U!ZqgeFdnI3>Lc# z8<`8$*?*xg+w>nv5v4F&yzs>H=g%iN5IZdNw#-;^7TR8P2H>mGF0}#lLRs~`1|BYM9S{$6LBx-D^%DLX=R(D)@vCwW6LDio+j z5a?%Tk;&dnP2ZwR-Zcyw1xAcT79(kwiu1&?)N(N+k%PsSD3AR*5C?4Q%C_X7<}56^ zeiOyS>}-njB2+Bj>gsACOYbwJltENnlrTR_AD-Se014_xU!Ur;6j0JsR$@~O#T|-7 zg@@>xK4f_oDZfu;`3%w<2jaiFYPB|C7#|?};rcnm*Mc_nW@>wTJH|J%cky&VzVS$v znw}60}?LqDEu$KL_24|9w z&nbJneAgxDAHkRTt~4Ie3rJ{A5pCS)PV-R;%6v57yMNt$0&GZP3kxICKeXh6O)qaUlU)h3u9BN1IT37 zTAb&GO9oR#X*=^<(NO|jied{600%6%xVYU7tN@+9;kN?wtAj#@_Tw48aP;X&9YAH? zuC1LNs(VR_e`aPV2ah>s8v&Q2;|-=4*GAfhO>VQZ%MqhYF%UkT-gSlnB%}pQ_6|rG zAQ)i)p<)m+*lI7O`*v*MJALk6WVZe(o8gtMq#1+O@> z*NV}M%hu4mvo~z1>$Vdx-K?sODvy&Yp*y`1UT0t%D?wXsD zuF!NXJUY>`Y7hTj(s`w?55X7vs`07-d<-~eCh|ozMEH-o>cNFMH$HI`n8}ve20X^W z>3wqQ)M*4TQiet$0lR9}%_ru40H|{f+vv=OOU^^VXGZmD#P@PZre^I@uGxptJpG`N zV=>bR^Imuo{5Mmhuv3d|bux&{`-*T@*Rm>;f%T#?G!_ z{{YkzJjBb}o3`~sQm;J%A}5|FrXkzg%PY1*qXCZ2*W>EE7Q6ZGMgygiyebMf7v`Z$l|z!w9Tj}K12fKM@awiDRrch?qmPNPVPDuPM4$;b%q(!U=zoqJT}r43n|mCZX^F>X z-dGPSKvLNSQ%ME=|21eplVc~x{HTISThy~uIhk2TL{x$}1D#Y$(EWy_uH*@$2V{9(+rQ`+>S}-;`Lu zaD6DcZLpLasf~-E#s@3w{Uf6kqIgjkK4|>}Tdf!UI`>6%zaw@Fy~L9md$x<)0;ItUX(+{{@V$QMg=g&(& z^Uh>h(#WJINaMn-6qf_{D5pnIt@o`^7)Dr;NcT}kLER9ZDP-6xOtNZl`#zC-Cm>Z%+z&z zThFv8x+Ex1G>njupcTKiM&F@G!>lh;GesL~3tF``*w9-~vTD4~5HfBSE)JT`6*i3O zJG6dHj%6z{<8S@_n*z>b^qr&Lr2#-H?uSk-J=EK**<o)D3`FaT!DKIRIqGbxTDf>*^%rouv$}61%{r$;2i`Mp`q0KAf zLW4#he^WiLZpOi)u|xSv@1ss^%nx&O;Udli<*fJWc`pM-_VSBc!xx4)%ty+UD;FOR zY~OoB13(36xsa=Qkw%{RQbVtqc^hVKjIK#CfJWKI#>NUvjuznn@c5p7dm?T7K2b!| zK~7I2Vrc+q>+0(2ck$yj(%-Ry`9w1%yJ5Yvi;HF;XbqS>;7S`Rw!F&4MapZ^og@5c zVdjM*0r7C}n`!Le7dcH6lN$i<&Lnm0{z{JqoyUh%{E!TMluoS?g(ukPANmYF5>N)M z{&q6AB!njBjD9tg(H{0e}+^8)BEXh9y``8&UEeg34dyxs@isf-BA zrpp#&7bZdZ%QsLvIX(gu_*?vUj2CE6&%q>&etyT`_jgct==V~hWCCy=U3&%c@HpTp zCm>l9%gfqd{c!ZdAXNfP871N%#Cbsw<(u}{q2@nSlj20`{CU|xps&~1@OjR^`$F9= zCG|cH3ClQLmZ9mZZ?w*_;{0}kcStFgZw4k5e6Z70oLa2jf;TRM2h$JD51Uda9u2Kh z7gP*2P|YpBx(dKt!eW{a~s_m6^kGPDjJct)-wMd`INhYP9(| z5)w+ZtKqqdnq3t8wG6(IyyeFem*g;Y)&qMpv&E!1=9Kk~4TzewKUMw9yl{Nor-%3; zs3}ib02n_MCIoYH+xtnW?-czb*$8Jvpoc;n1grt;SlU1R5U|0apx_4e z;`__nqLYF)t&oDStXO-Ewav>zrNxw;6V4^`5-FGHOqGd$Cq1O;#w-){RWZ!!Gt!E^ z=D(-6gns4zSKy=i>B@jM9W=SF+q53gjJkvu!?*d&c4+O;l)%>fX?}iwGpL!iyRbUUrWE8k(*;PqPB+ScYgukD^8sz(binyx4-OAFO3Gzo9{YhY-Y1g+6% zw8A;za)W@z45(`Ufq|HOgBDb#2CJjMY~TV&f3~|qDG#H(Kq`I?ZZ9f^M)O-{h3I_S zdE$C#7l57T9fl#kfts%yC4fdzTgvTsdUjQsilSgAv{H*Qo6dIcfqo&2l9a?x1d9ql%{1`SAx*v`&0jsa~x=tAf!=2^PdDvLnU$dAa{#< z0AOeks6zgr-D?rii)np|!x)n|I66|nl3xH3>z~iYyYGK9z&3GnCrj9Ye(O8a(s8?Q z=#N>8b{E!q=%YEW)?<(%5Yo$8L1jx6$+E+snEw9AE%`f6RI~EU9~ihV83rUP8CpKz z)wyN^x3{))tZaxhE_uN2srNcfCQX}{4JIi7uwmMp zF_2m)p}g<~Ir@^lfXL1Kf?ZKO0b^kITRM7rE}(j-mkshUO8U&8znoQEFIt`CWS40k zDdQO4-_8S-{{kWfRzW?QqgogP2JaRd7aR~A{M3>~1ei9;w%^6YWkqG@^;4PS7uZ-1 zL76t8TU=V=7%VVOIv(mhGV1E=JoVXJqPJP4IcxN~4v5w*7!X;3k^pz}qj$Fw;6#5` zlEKz?S~>v6A1#Coel(|zv+A~b*6kt&%0Df{TOc2xbinfJ$D>KGr8kJgp6r4|{RokG@PI(bZq5^|8xT0?C~a>n@}ALDO7jO- z$i5{$`8}T?UTb7?l8GMYgAXNJJQ+IKouY=%6cmQZUlu(e&+ubAh_pY7x=q@&;_dB? z!bX(V0_Up*C~|K6IaOlZQ9%Js=f&g!$}B_mfvF4*Ew*2U%G`zkXkufvjGWX9eIs8A z4#eUy`^(1)pm^gSJ35kSinuPkcM<^WRTwZ7#b0f0GWYJ`L()WN^18XNJ$v4|s`fxG z=tsfu_EQHd)nFExmoAT0SI;@kG~gunp}eNc!>AIxY<=XRKY+kN&q~DcCDsDF40A3v>uAriwGE%8~|}3F=oJ#1l|41eHf%*LZgr z`BYbndwNZMLRP^A9Y=Z7`gAjz*W@j^2yQ7Z0&n&LpkY5Ot=P$-ZH!JaDU<||%5zM5 zZj_r3Y)93-|Ac^;7}X-JfT^SL zYf$riL8_l2t*|Nm>xqMh=F|WRVbEfD4Bk5+Sq_pb0-mBS+q%$=kO$d}l^M|Hsl{9c z(5OzMEtB6}w*o*3bDXS2Cj9jf_&k{54CU)YELK0fj49Fdo#tL#t&k{W;=g|Sw#?uNV zl|%0{*M`c>e}JqGX|Kh8Zu^g!N2qNX-Rs0y;ybCRP(tuVvCu>rf-jGEUTwfJyL#siIRu|1eF-kw@#_f# z^tlQyo~*1a8eOc!ZZ&;5ukr1A-l4zwUy%Bt{(Hy7#B_bQ1g_q%U%w;(NtwsBvl2Ax zTR7(>i^RlAvlVa;xzP4@u^E3TDFlinvQ|gV_jo2O{NHw=eDOuYsodiD2pP$J%Vds2 z;8ti_3(9S->)!f{H&gH=w9>(mTB>U89HeSaUJq>PuhucsyBJq71xf^jnmOko zzLv=KSA^gnzi=&+9o;F8K zYb$p_bp*Dt?`Lv4p5Kx#f=ed^XqPzp00!lBM`zZcnK4jT(5nUW)c~M;(BD7+M#=Bu zw5B}_t#7z6cysS+(T@e==&xg0GK2G(DN%dCH6ZwY4N~?*DB&<4qyWX?9!jA8Qt7+) zwp2gxwtxBZTAa(WVBzxvG==Dvu@C85NvNMa=+%KF2=qihkBH?BV<&EkXmH{;>;GIO z`E)RpHn)$4?)-Vk#Al4Es#{O}Bu2koQBe^gD3&iTD-)_nPKi^o*W4`f5+0cNppRn3mm=nDtz2tnhmuVdmThj*}> zbFZLeO5MAMX3{{Y$rC=S|Aqd5C2Xn0|*Gr+I+tjF94QAR@5LcCga9We*?la{vIa>V`zIqQt$pTb2&U1I|+ey0u1C|Ipyw%7zi}M zy%Kz|sDHu?yp4#M$x)FVpnLJ+1$fkHbY%pDgtf4Z$>``pz{pOBkZ_1;At~B4983YPVhGsDdoU&IPMpBTDUL7>eMsxO1 zJ<+fp?o9uLRUtY{P1rq%RX{#iqbKvP`RTrL(Lod66}aNG zy0(@p=%#7L8?94jCyS5)VC)ldfIb|pVl!O|*PJ>;I3bLke;k;f;lo;zlCyQJn{KSp zwq9m49f&YMJ|Wu~l8#@p+=7}Ku9|N-^#0C>-OX91c-q7jBqZhn_Sx5ZOzA>gzQM%g z4HK6f8~>LeH_QzcbHQbgzd5U2>ZZG}Mr0Q*c%kW8kD~>hcOO6cgDbLiH7D=*)2H31 z6otyq5{fTYTpLPieE9OsE9=k_>`N?ANJ&m+glNBL#k=?K z6P<~BS1^za2nNnQ1xN|9SZT}p_Oq?DS46QCX!uY35jD|k%uzt^=FM!_^@xOogy&2t zii~Z%MZ!PH^rD4Keeb^ck&7mO6W4>T{5$$?`D=+qXB^?-AQ0BTEv4?zF0eMggXC_y zvDT_l3yu;x7T{i!*&avid=|rJKkLq(qRaBARDJjFkxcoVBS-UXaOdI0WL*Gyo}lJ^ zit{PdVu3!3&~ef?|8;S8HYSNGDK9rA;6hRz73AdPx+*hJkwjI;?IPrCqnFg%csH>T zkv6ThSlW#y#!`{@DwdTkzniS37T$F3e=;}b7j6EE@g+b*YS2DbLF8l-5TFK{l;dI? zp5F`f#}kAY20&PVX;4YyJO#yIbDB?zK)g~NX*Pi~gkEq#FP2!eK@W;H9!N5DX0x=( zaM_Bj6i!)Jw#Xa>8UK#>!D2o_z<*PdsxQK!E1#pZv^4av^X{}+g$=K7CAlwFqxK9y zRmg3C5lsdtNcr(?9MZlM$ZpZLhOXz9|JTcIS%ijYOt3>74{Y|&5R5^?z+3W96Dr6n zUH5Ls!S%D0LPba~B+ZOHNOu8bZO@esYEL3)RMC_BCgZGiVt%uddzXl-V4$ zL;*v4e;?*xF^6QDoP8vEoz9i?Z1PUSTgGTa!%WQPah2=quvX+U9b&&Ac{Q&zff^b zB}>cLRt#MMK#t?#s@iDK8BIO@V@%suVvA*fj6S_nu#(*?Y4(8e3|VP>U@v*0Sdr@?<=eTIT5G;Hw5Cq zoA`DRG0mKc2L1Ga-S~%usAMAvHcV1`1z63lq2IA_ah#CZ>c(G$>JkV63IgQC`kzK= zx_JAI>`Hu7VRlI4BtrEe=%@c3ZA{2k9-$P?9N(U4z`7Ll5LgcUF#E4aWbIEQ zmmx_s@5uZ5pS&Ya8!u>+XlAnkxLqLlFS4ZBr17CZQF>U9E5T?;MRdj$JIHNV_)GHH8L_r=Nym!PZ2B*P-HIbi(`?2^iN&^8ZB_}5VKpE}T9w!)`NG?mjyhQ^97AqGpiQ@0a z>V)S2;=rX@)FOkMq4I5sAM{{2knAN%d-`oX)o|LehagLf&+v8>-Bw2XN}VMWWEGG7 z-D513-6bhGaJ95{uQs=>PK08E7&=4$H#6ylgj{`I&_rCh@A?`%F@Wy0FI zllR>{*&e~m8Eak9KvMBPQ+b*r7PzUW#?K&@cmk&-zWpY^89$@@h^gXRPC_07ViW-PcHm+4NsSl_Z^)G{ZuJy2uMOp62{4VE`5KJ-e8A&i{yL3nb3TTLigV$acSZoxtgE1ehtSNXGTa@*q`Z6 zdpFSHtm1oy(i8FH84-Lx7EKR0p4%nSpb0?SZ%o=Q;}{W+Q+moG^LV?I-xVlMW!S8Y zk|Pedw~(`V3b9{pM7!n*87NMBkR2b7^AaOuK3vMCD z2ac5bVL5?k@q=4c9IR(+J|V~3x@ufV1Kq=h{h4_g9G)As#=eM}pVwRW)8DNq4i(il z$Jg+XSjoE*rj|C3rF^;JTV~FP6I^{j+1`{pOr>D8%eYkYm>B6b?;BA#wCWYkBtV#O zMEtxQhpZ7XnHt9POgC=e+m%M+>e{h|tPFaeVAY8E6UX0rCBGjnm0u|k!6CxWS*%&X zhFO(Kd1%39cjZeG#-le1N4-ztkkxC5YS;+(v&t?ZKQAl3gmGFs3IY;yGDXF;8zg_H z)sKeOhhDdd>{=3dmaz_zTVTJ2`+4bg+(S-qR)suYA)J~bUpu`|X1x5b=t;;eG@dy* z!acnrh4Yz_z{`@wvmC)yw7QVRLNHpmjHzj?@JJlj3`mx3D>(M`BKM3(Qcvryc~2y; z&M}|%Y;IpY*u=lmwBdaI#3eR5d-kbiyoqXXss**j;@jQ_;$;z~*9| zFl)*0Omc$J#$L=_1+jCwXFWq$&vr_ybY*1N(p|CU%d|y);GDvnTOSu(W-VFX(?m|( zZ)LC`^c1e#DYTw)%&}aFV_bY`VY$*l53nD0?I=#|OpzXc!W-u!!0mcMNl=xndhv8a zQ1iN_rSZcB`2Cmv@VY9o#19J`D z2YUDX-TVHJ@!|jeHwXS_ruaWU{NF;>|Cc9(6LWSg)zHh|pI}P(KP&UEr2GHnMfm@B z3;gHl|IZDF|E&9eHp725!@vK1p#OA)|L;1&{)xl98vOH6i0<3!EglC7sawoOo@{sV z9-gDoebA05dEtM&!1F!qGtSpj#Q*uD|1ldETk#3G40%su(CL$vmY2%H=z9GxmmNce diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.2.png index b3607f02057a194ce6a1516f115fbc9d2d3a6ab5..be1086d8fbd7f9391d0d9958b9dee0695ce83624 100644 GIT binary patch literal 19352 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 literal 19450 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%0Al7kFRB3tNt7Mi$Y^@^QT0hp;XPkyHc39T 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

    r0w2kgfI@YLAB0 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 literal 18781 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

    430eZ6P{HH9|Jj`-;5+ev!zz}l*2drPOH21x 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.2.png index 66e31db5ca89980d6b5ab01c94fb2000b42c56d8..49f5fb2c5faa61dc6d3c30723b0795d3212ee447 100644 GIT binary patch literal 18897 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 K+4r9Q{r>vdx1OAa|M<;A38S|M{ui+4 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 NjI!3ve5Jd8{|{cGP_6&~ diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.3.png b/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.3.png deleted file mode 100644 index c37f15a9b047ea240cfdf72c3f9c19b013bfca72..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18759 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.4c0b13381.png b/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.4c0b13381.png new file mode 100644 index 0000000000000000000000000000000000000000..d9536734769999acd330cef87e066e8f9bdd0f53 GIT binary patch literal 18781 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

    430eZ6P{HH9|Jj`-;5+ev!zz}l*2drPOH21x zA-(O=rPQxRE)A#Odh$1IEv4)%tBX1tgU}}q1%!(vz*+n*TsmX{gy``B7OuH)yEDM1 z0ll;;!u_8b9DkspAqhQyRh=?iS&C9uza}|r49)8yOtiORS5)YU*MI+07Gr$*`Xs`Q z`7GzdpPI)?;aB{W`Emdue`I>Ej*)`53x7NCYzpKe;e~gi#wFkLj^SB(NH4+_&NP=R zz<_av3Y$7J9ys=bT^$hOI+Gqu50~t%@Nz8lAZw5o->{2HB63b83*55J+WeSH-e(|0 zQsWPT0ozF--vmJw;l2D~V(rdpotXgO>ct5r0JYYEUdU0_w>Rkz9UKUiL}&uyAUfh; zA#SDwETTSAr^saF@-_XF6CyNZr`nPoIi*~jc;lIy4E9Q!J%2p4|MC9L$Gi1y6gOl( zIL@pNx=IP@=gymG0%hgv2?_IVdn=Y1yt9pLnK=E={YTVQRYM0$K=QUG#D+eCQwYSoJ{ErF zM}{-y(;s`03CGLNp8(RwIZKV>vgHBmi&@GW=M&(VE*recj< z#CVj$uEVN&klhf8=vb1NWy3oG-OP8A&I7(Lr_SH7dKIa0=7tx5hxiGaZq_N^j+O$TMiegMfud&K}bgd6RSxniG%bqdw7^wJ-7q zi8UL9b-??xw8W)@`~jm}L2JJwIu65Y6sEAH`Z8fKRkZ@gG-1IUJ)%oa-6LF#w%+~#=wn_(~K(VF20HxSK9R(R_X5Q{{>yB>3WA74=y zDo38^EEC$`v{QzFAY+fe5aV~>PkVr_d;2=zyIRgSuC{W8d(V78tf9%$fi+ch0;Qv_ zTh8v&f}>-btnrRpA)v3i=59`hESiwa?p*ZrIB{_(Lyeszft-aivic2YJ7Lsi?WVrE zxn0~Du8}O`mx){jAZATtU|A1ADFFoRg_rKtu$y0HW%&la(;W@6rWo`Mz zZ#=Zx`9(?VlV*m#aA?8%h{PBC&2!?=G#pXI$-xS0$zT^SPH+M|FhBn|82#>D7POma z|H|+m7SY&cuslpb2!O+BU}lChZnj$(uMdrgV8}Tg+qVaS)x#;tf}Cx>^?7_D>>F7qqQI?3R}CXt$tW6Y}0qPMQ3I1nFcdg>K*uE<5DPNo{Lpq zSkB-2>G$AKB&Vl!-ZhiR*;4C%efmhX1e7-d@}sV3ig1r|WxxQ2=Wv}TUcUa`+pX%p6N(D*uDsT=!@mGs04eZf4 zIHN+S`U4c=^!hP*jW!9^>=0D3Lw9s?vaqFk*WJUB;c1229igIY(qvGtPm=BwS!(si z6y95}=F(s%;Y^==-j54rn>nC6()}#-@L$_yZ)?6d|f89juEyzon^i`cHZV zMFi}V>6%Si=kiQvF^H?yHa7ZYveCNVZ&Y3Ow7!~6@ZiN4?D4TizZQK6NOA4>aCQ&% z`E%Xi1+W1S87)@WcxwAn&LQ#(ZnFzRbo>ED)e0LJxTlXE2t=1lqY-h(Em&yj?Ajy) zbIVk48Gogt^p7Of8aCL6GeM;i!P^0#IcgBA7DKmJOKSzh#XDJVw-Z)i<~Xh|PV&86 zzu+^JIydLUgiVOlTc+s>9!>y!ShimwCe!XIQ)na5=R<)%k*4EUd4Wmu3HYiYj{a2I>CAY8( z=+H541rx@IKM`Qo>5oQq1il3#&LJq5P$FX^{zn+ln2oSE1J<5oWM)=yQ4%9>5s7;<>0#&C`I_#YYg!QF+USScTO{cwn9W+L}wj^mp z0|4CiXzcJ%WVjy)Z?D<j;`-ujrS9+hbTr&8F3wRT@ZZjxqIrhU-fVy&G>F(u`TtY8Mm?VFZa~W~touC51fbP6egN)tf#-jVHc; z|7o~)zNUDLZ7{9a9*`I^!9i*+9#p#smIdhii7@`}SGC=X>3YLc^d?QLofwZy1dR9n z3h&Easa}|-Fb;8khs_Vk9{6DYEZ`26!`n%Y#Q$=~v|ev|gV60)A;QVJ?sZ9 zsl2T54t4kc$^vL9#Q=br0bGRhITa~*BCfdLiIQGJ$=#flGiS~a04Z$Lac{rBbnj)- zpJ(FCfw6)(;q~j=NkqD^$XG5A%p-WfcM(=sNKzOK?6VO}-Q(l?!b%Z6E;Kk&?eDcV z|NH)DpLNYrt8bDNcd8JH5ayg8MoKT*qg5|kM+w;QI!86bbE$9nhj5mm)(zMoy0=~Ou<>6BGOjNY4yuv|X1SUA+c+0_s+5e8D%2c6$dY|umt9!8QDEzv*i+Vm#rFhJ~K zT-v7Z@wP{dWuR*@e&zFr8CfP^3w3{w7Y9k%1%2rk069B&A#wy;imQms8<}gn27;u` zV|rc_RbL*Tc1?R=Zpi#7NS7vN0X)GR_`MVb=SWSs{0u6c7@g*R>xD!TBB^x8yI_TM zF0s~b(F|U=xINcrYin)o=82H|u(cHc;u|H*pcxcxuZ*Jyf4il?T-G%OdjRyT`goV- zhz4*XdD+-rd;>As!eX4mMSJp9kvTxvtCBUoFKRxpW)tQ=S~eDWQ;i_4LA;!k|2Oyj zdb6$fTSUE!JC}f%z6Bjr&}+;0-2DOK+}xZ6&ze%;!DJBpN}LA?n3Y+?xYp6Zf~YN& zZFd5+Gv2r7rFC?)*AwA~_MiXUUjZga<1YLZ%tR`4gs^^QW2TPp6AJD;IDsaGpVA<3_K}7J= z%0eSIdtxnY9&#s;VIUX~^RM81zvShQ^?|Gef*FCqtv^5+r+#ER-1*0WfKu7; zI0mf$sI}nG4woD|e@hGKI?fB3oyX2edXoJ>33$`W>HtMP3+`R|hh}+54Ioz0=H}Mc zmJU%{k(`~|>p$%d2Cxob40JWd3)XS=kAqx4pbK{a#)rQmSBB`a#lW5{sDd+9EaxQE zk)qcZab|9;Aejdk2Z8ia(CP9)Wi4AWf!aAq9}_VwxDOu)j#IqC#xxJyb~Y>!;>SP( zgB3NZn8{4!MU|JAZ*-}ksxPijz1D%$mxZSuo5Ht+>I|if< zUU*MrTQu4iq%zl--#}oE2PB5%c@W1EHpf_AN?2If0X+uiES}vwp#6bT_p^|r9H)w> zGe!mL+T>{fxDfLgNk&xO+wB6%N*_jz&_}@w{w>y^o^Z42!3_WuH{nAh5YHTf1u#35 zgu7;kBET0zC~Md`skX4QVqctt*W@$f#cdPpJ2T@;M=&j(^pRobs5;kTaz`8b<`{Wv zeyk25qM$V{LA!`wUiK_KyaM8bj?>kFj)5I$TIFQtudh!2!#vl~()2qKT=D}}R#pfh zEUWW)c}94}xHSMGLoRoEp+s6Ca`IKar=4~uPES~7i}M1^?2nFJbU#7L3Rle#B5Npz zpSK5+Fvj%;VR~?*n)NPZnN^Pw9=6?*4e#zeJ5XL1ywP7XKjsqr+X^*KHzq3t zR){r}4N^cMjhA#z-~cNCL<#;AhkQ%4M=aSyI^?H8|L%F96f+3Yhbtl(Cz(JIbulrZ zlnpz2LFpmc0UMA__$_YBS`M})N|8F>BRPqNx92JqRX8IguUu(-Fmm`?647nHvCgRN-aW^+N9HA2^ z1`P6UT6azyxoS&H8U``9dm!04Ju?%NDNSAnv(FPR9n%YV z$c+>g~sLW$O_*;5ApycjyMj| z($WX-RLF)JYRvsSKfK}$Ptk7&5=xwOVb5gCubE@ zH|~liZf@JeklbvjX=m+(ssI+l$2Q#Q+LaYaUk!#v17g>Y;$Aa~hV0JPQa8LUxJ+F> zmFE#ndU3J^fSd@UL>V0W981|RIq|Y-CuF!uEL?I~@Dl>CahydKqIn{zhbeFAFGOwI zqqbyD;TAtZ`U);7X^tagINP1u2)$`gyAhKf)mTUOql2>pS%@@n`mz8U8kM_eKxmDk zue?|ooZ3AQaX~h7(4{hO2yG8~6b%F&hAr9eU8jo*SR-;&k7)9v^^XwE>drGZfW0$< zDdm5D&mXtT1^-kzYIo)N7m6!@8C3yUy&l23u{0yJ=U)q0!X8=Ya|!`vFIl*08Z7(v z0E-qawzEnP;Xs6R;|V8dqezOY2#KIncQlPBAFu7k2~kX>DZhy}O&%ShZ;iNamb#8| zeUx$c<66B2d9vE|R@dmbI5LOcux9Y;ykoteFZHy}F)wh;uFA?n9xFfvCb_V1DJ9aXh8{LmM@Wsfrz(QjUMae!r&Nm-tb9G5+IZ@dmrC(sz+258=E5l z`3nM%@<=|oXSZp_ZWHGzi?G4B7EbV(R_)e-3p)YnCt5X9(&Ov67s8Vo;v7AKL9t}^qSQ}a3bPv0+7*3Wj-0; zm`RZJoc>`?*?;Qj0k3u?c6*2n(CQXawtiiiG&BEdcD_v|5~Np3L5qk`=Zjxqmi`d+ z%34Ya7p#jET-_Kea)znCF{2qwfM92);u%EsB$GiAv9`6XWS&cout?>u^P!KJ&Yg1# z%8@ttWuj!Hxh6Hfy*auA4z}2!>t^t3VTwJdH1!zo=ZucXP|V(Fy^sEYtpF3?t|TGa zR~%*71=bdd?c*wKpjZupMTW0iHuBC~Kv^f_o2M96Y8~9$^9O~^YfM0h=mD101q0cF zvQQZuW12VY=ffG~?+H89+|r^8KHH1ZlMn4QKL8~)L$Zn%mX=;K8F8@!Ny!wUW#bZb z`iEyo1cO7|#ttBiLw`Yre}pXz6a)(Z?$1zU&S;)Af1E1v-<~m;)E%%%tZp@sk(dVS zL<=gr7%&N}{(}gGjV#2SKoAZ?LrJ&L=Ap1jnU=@Y_jW9wT62*TOt9sqD=DDNcqhpF zmuzn{Y8?Sg)oeG5!s~+9>bMaHyc(2x)rSvb^gLaS1S@K(#d zwr?bdMPcps5}9NGx6b2j>@Ij!#REFJ{YaG`5@;!cyaM#9qQ2^ef@$a|v7+V(im?Oh z8w0t5mBG>NlCH_SIZDBcO;@Zd@Q)&UyRv`54!Kw60IW$neBc>QQ!8kd-XdD72jBz2 z^DmCD&&J#0BK4%Z2OJ^s8XbKaG2Me9a#Up1bPA}drt?5iB(MDPk9%w*%D2ed>qF%p z!!DkPwG2{87m#UdD=WM0t#!m1gsg87 z>veytzDDH+P=MF*1%&!^!2bjVzyvl83s)9-**yc-%Cau==jqUa-Dc~nY@`FPcs zsS9)|8+av$*EUSsc<>ZP&dd4!t?rBgIvK`W2$0dDR)|tAIFsZGy3W^j3L0j1(Z*?^jnQQ`yLmtROo$f35YKqg@AZ?LK@&cvx4jt`ZUqv;y-9K0mp@`MJ!KpHHQq{Ji~f2E1D_u&{9=Q9{i~un7 zfSg8vz7%cW4??^E56?t&w23(kaM>OYUX2-EMf1Da-xP2nNU{X%EQsrRXD0rsx(a7m zAF=@5NFrW*__Q8Y5PMlaD$fZq=@GolaiX#y29ISxA$yXOeUzDu(pqLKqe7!mVOvz@ zLT`#QDE&z01QOT_>Qej9$f|{!JS-`I0Xh*KEMG1cQ?&yzR)m~j(;=9pCO-&b) zDtv4&@>Bj8%jhhO*mS-2%^;E~L==$lX?D>NP{UF^G^%$L>AZm1{0G=t4{`^*2j-6Cnh;LR z20`|rAro`9fn@9COWMh*BnWT{dT+M4?q5NOlKeJ&dIbQN;Zz@XNIrowV~D-^_|q;V z5DxBPO?X^dF1l%9FI!%MlVOgWY?AgcZ#?*d|BC%iz1n{!IdylX=Zu9}Fw?8E(#O@u zw;6I~J;}r(hgLrHqR1O0Hr1zv8{3RdN^$~>Xbp;}qvr>V>smmYLTpCpN3knH*JL*p zk(@CYVqlDNdrbehgk+6j0Lrx>=G=oDhzHq_lj=vSo52NxbWaQZGCpJWV4Q-34cZ9~YzH{3X#LWf+R7&d^Vd-}N( z3^2J)rko^fYwh6Z32ol z0P&hlw?OEwxEuPZNg*$x%l}sx+5Cmly_|*U;=7xBS>%4M_xCH}d36m1J&6B1&+9`Z z{U9vB;~Qpirr!Mm*{a87m5$lfb_b;iO4$QFa^&x5O1JkJz)n~Q-|LCJP@KBz&AibgYF(4F)=Y2 zkO5XQX`q*T3`E7oW`b5I1aqa~ED|S#0ENKE4)+*A^fGV+N0@pWU>kNpUJT-ob+8XA zb}v@{6#@db2D#X7xa-isJ%@VKGcB>I8J4o6U!s(y|vpJ5M)DSF?cgw zXkBm-63{bs1}S9UauC@ zaXkLHhng)2vKb{L?ViKbF;m`T!BLG`Z^f7#Dc6x}C{+}y-O!dT@V4;Pt5?09k^+xY zO()2{F~x%A$9ff->DQW!AC|y~LzJ?ve?>tNy!+}Tj~;;TJo8!wi{jD6GRSOtA<;p= z^oW53Z5s(2-eKiezsit-rOk$1wF)31ZD3*$aNt2wp-2vWpwyKfq^Dq7v$d#(#OqFt)Cp4fQVk9|Nbi zKL#pB^(qLyM%pr`Ziz$faq@3FkR6xrghDWJ+NcTK{0=O3-1{W5G6?#Pv@#1=Q1St+ zoWO%##L4>P51QuO%vc#eG>V4>?(Xco7{J}4TYsxVjD>#b#DIi|NqTbw_B&MWTd@Yv z56gzflDPH<5l)0G`O`iWlc9`#$ge$_((>#{!N}+V<4k*yHP50lIn4FnC>d~V!RwW4E*-T5Nx*tQ|S$}ZHC4+fR{_s?e_(l zsh;4?Eswcr!6;tTzvLb5NrM!E1t=U=z%j>Hg^sfaF#(Bxp97+xL-38$OCA1gnqITn zcGmJcK=z0KJp3t?cY^^BRkcD>P|dp*2(}gSR1OUDREthES+gJlHl@jto6V;|%?zPm z@YGiJac+fNWX0TENG*-TPH)3c(a%tzi*-v~6c%;ii9;6w_`n1FI8*a*VhT}uK z@dF&e2b1A>Jut6_q9U`MNVlRr!v(yz0CrYkYX~E^pAMEYzW0v6m4@z3(PsJ~h~%QsDX;fJ&XH zIv;scO=l*i^AGMLT&F&q%8e}&{<8c&-Tc9Zk&ZKpsOnBgR=g}=h}AU>2uY8{^5fP_ zt(z6vU)m8=H_&{ptXa4AaK=0?O7Fd_?#e%N4;828aA`&zEo;s{!{FQ6_D1dGKBxXt zBM8lKpsaI5Snl7wopvfO_OwhAeyKi&6WeB0yu^>KkzLr4#&W7+OmrBmog%sE)bmkA z`llvtq`5y~;f%@B?^DDtev?F@ur~J%MH2R)qIkQtwdWgE9ry%yoHRmT4aB4Rlwb zWn$=v0Qtdy`#57v=}n5(`ucFsq~HL)N3bF^rcc7ye>g@LWg;+?L)O za8z-Dbn>s$qS;1$TWlOJ@~;i5%8OBr*@g#h>B{TQTYgFj())1a%QpHKJ-C~7hnoAr zr=T2rSaZwgS^DUGLzEKVL+Y?7a^_(kyehn7Y($;)~#s(2S z_!PDaxf_IyIy>vOSJTd5W0M|bR%_52`>$;K7p;A`Eq&t0t$X?U?RBMo*yAYo&=cQd zmKR>{&@aufvJI=g6P}?9Rva|xAHBI;nb(G0l>4-4^L7q*+=^N zybT@ax+!?aKA4>IoN0iqK+RswE>CY2oE0BdQ~JGBRjKq0PN&`Zao&e z8Lb8bB_kb8E?yctv`Rfs%?(4gmG~O7j_Yju{aD$&DmN2J-n;9ywfD2>eB8Ymkv;v{ z!tOWX7VFM2rnh-&Xv6HS%n!KJ_<|yRYbQ!p=!m`f)tRbh#{`z6eLJ-;e16<|))p(3 zl=02HGXL^Sv+ih;>R^ww;7H59tFp8^z)=s`?fUyPV)g#piTiiE_a7qr|Kf{I{LmcR zwd)Wa`F8>SU%bBmt820d6Pr?(+OtdRJpI2L-v5Z@|G&Du|Gemb?)m!9-u-9q{s)hT l|CH~4%J=_&<;x5{5W}vwDqT_n|3uO*l#<5%{JW3;{9l4o(;)x= literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.1.png index c62b823bd7f1b96cc05c9e7333bb5375e72324a9..bf703259b629af9fc1cc6729571ecbdd68ca56be 100644 GIT binary patch literal 18688 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.2.png index bf703259b629af9fc1cc6729571ecbdd68ca56be..c62b823bd7f1b96cc05c9e7333bb5375e72324a9 100644 GIT binary patch literal 18688 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 literal 18688 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 z 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 Jzw_|L{{qrLEx`Z) diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.2.png deleted file mode 100644 index ff151222c2075d45684d8eb2e1479857c172fcb6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17542 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 z 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 Jzw_|L{{qrLEx`Z) literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-002-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-duration-002-manual.html.0556ccf91.png similarity index 100% rename from integration_tests/snapshots/css/css-animations/animation-duration-002-manual.html.1.png rename to integration_tests/snapshots/css/css-animations/animation-duration-002-manual.html.0556ccf91.png diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-005-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-duration-005-manual.html.a1b1d58d1.png similarity index 100% rename from integration_tests/snapshots/css/css-animations/animation-duration-005-manual.html.1.png rename to integration_tests/snapshots/css/css-animations/animation-duration-005-manual.html.a1b1d58d1.png diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.03a816fc1.png similarity index 100% rename from integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.1.png rename to integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.03a816fc1.png diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-007-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-duration-007-manual.html.8475f5701.png similarity index 100% rename from integration_tests/snapshots/css/css-animations/animation-duration-007-manual.html.1.png rename to integration_tests/snapshots/css/css-animations/animation-duration-007-manual.html.8475f5701.png diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-008-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-duration-008-manual.html.d23881b91.png similarity index 100% rename from integration_tests/snapshots/css/css-animations/animation-duration-008-manual.html.1.png rename to integration_tests/snapshots/css/css-animations/animation-duration-008-manual.html.d23881b91.png diff --git a/webf/lib/src/css/render_style.dart b/webf/lib/src/css/render_style.dart index b2f1d5741c..a71b7f17ed 100644 --- a/webf/lib/src/css/render_style.dart +++ b/webf/lib/src/css/render_style.dart @@ -411,7 +411,7 @@ class CSSRenderStyle extends RenderStyle dynamic resolveValue(String propertyName, String propertyValue) { RenderStyle renderStyle = this; - // Process CSSVariable. + // Process CSSCalcValue. dynamic value = CSSCalcValue.tryParse(renderStyle, propertyName, propertyValue); if (value != null && value is CSSCalcValue) { return value; diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index 2f4cfd7480..6e61712d47 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -99,10 +99,6 @@ class CSSStyleDeclaration with IterableMixin { if (css.isNotEmpty) css += ' '; css += '${_kebabize(property)}: $value ${_importants.containsKey(property) ? '!important' : ''};'; }); - _sheetStyle.forEach((property, value) { - if (css.isNotEmpty) css += ' '; - css += '${_kebabize(property)}: $value ${_importants.containsKey(property) ? '!important' : ''};'; - }); return css; } diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 9773bbbee2..3feb33d409 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -171,9 +171,9 @@ class Document extends Node { Node removeChild(Node child) { if (documentElement == child) { documentElement = null; + ruleSet.reset(); + styleSheets.clear(); } - ruleSet.reset(); - styleSheets.clear(); return super.removeChild(child); } From ff66fd9653656d3ebf2c7049f9e5e0bd3dc20a9c Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 18 Sep 2022 16:27:32 +0800 Subject: [PATCH 323/498] fix: rule set isEmpty --- integration_tests/scripts/html_loader.js | 2 +- ... => animation-delay-001-manual.html.0.png} | Bin .../animation-delay-002-manual.html.0.png | Bin 0 -> 16699 bytes ...mation-delay-002-manual.html.509a86541.png | Bin 16725 -> 0 bytes ... => animation-delay-003-manual.html.0.png} | Bin ...animation-direction-001-manual.html.0.png} | Bin ...animation-direction-002-manual.html.0.png} | Bin ...animation-direction-003-manual.html.0.png} | Bin .../animation-direction-003-manual.html.2.png | Bin 18897 -> 18782 bytes ...animation-direction-004-manual.html.0.png} | Bin ... animation-duration-001-manual.html.0.png} | Bin .../animation-duration-001-manual.html.1.png | Bin 17542 -> 17535 bytes .../animation-duration-002-manual.html.0.png | Bin 0 -> 16421 bytes ...ion-duration-002-manual.html.0556ccf91.png | Bin 16406 -> 0 bytes .../animation-duration-002-manual.html.1.png | Bin 0 -> 16555 bytes .../animation-duration-003-manual.html.0.png | Bin 0 -> 17652 bytes .../animation-duration-004-manual.html.0.png | Bin 0 -> 17542 bytes ... animation-duration-005-manual.html.0.png} | Bin ... animation-duration-005-manual.html.1.png} | Bin .../animation-duration-006-manual.html.0.png | Bin 0 -> 12037 bytes .../animation-duration-006-manual.html.1.png | Bin 0 -> 12037 bytes ...ion-duration-007-manual.html.8475f5701.png | Bin 15606 -> 0 bytes ...ion-duration-008-manual.html.d23881b91.png | Bin 15606 -> 0 bytes .../animation-duration-001-manual.html | 3 +- .../animation-duration-002-manual.html | 7 ++- .../animation-duration-003-manual.html | 9 ++- .../animation-duration-004-manual.html | 9 ++- .../animation-duration-005-manual.html | 5 ++ .../animation-duration-006-manual.html | 5 ++ .../animation-duration-007-manual.html | 38 ------------ .../animation-duration-008-manual.html | 38 ------------ webf/example/assets/bundle.html | 58 +++++++++--------- webf/lib/src/css/rule_set.dart | 7 ++- 33 files changed, 63 insertions(+), 118 deletions(-) rename integration_tests/snapshots/css/css-animations/{animation-delay-001-manual.html.84ab03931.png => animation-delay-001-manual.html.0.png} (100%) create mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.0.png delete mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.509a86541.png rename integration_tests/snapshots/css/css-animations/{animation-delay-003-manual.html.606cda041.png => animation-delay-003-manual.html.0.png} (100%) rename integration_tests/snapshots/css/css-animations/{animation-direction-001-manual.html.7c8951471.png => animation-direction-001-manual.html.0.png} (100%) rename integration_tests/snapshots/css/css-animations/{animation-direction-002-manual.html.b1a170001.png => animation-direction-002-manual.html.0.png} (100%) rename integration_tests/snapshots/css/css-animations/{animation-direction-003-manual.html.4c0b13381.png => animation-direction-003-manual.html.0.png} (100%) rename integration_tests/snapshots/css/css-animations/{animation-direction-004-manual.html.f2f6e6921.png => animation-direction-004-manual.html.0.png} (100%) rename integration_tests/snapshots/css/css-animations/{animation-duration-001-manual.html.bb303e7e1.png => animation-duration-001-manual.html.0.png} (100%) create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-002-manual.html.0.png delete mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-002-manual.html.0556ccf91.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-002-manual.html.1.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-003-manual.html.0.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-004-manual.html.0.png rename integration_tests/snapshots/css/css-animations/{animation-duration-005-manual.html.a1b1d58d1.png => animation-duration-005-manual.html.0.png} (100%) rename integration_tests/snapshots/css/css-animations/{animation-duration-006-manual.html.03a816fc1.png => animation-duration-005-manual.html.1.png} (100%) create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.0.png create mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.1.png delete mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-007-manual.html.8475f5701.png delete mode 100644 integration_tests/snapshots/css/css-animations/animation-duration-008-manual.html.d23881b91.png delete mode 100644 integration_tests/specs/css/css-animations/animation-duration-007-manual.html delete mode 100644 integration_tests/specs/css/css-animations/animation-duration-008-manual.html diff --git a/integration_tests/scripts/html_loader.js b/integration_tests/scripts/html_loader.js index 3b5c336a5a..e9998c3863 100644 --- a/integration_tests/scripts/html_loader.js +++ b/integration_tests/scripts/html_loader.js @@ -56,7 +56,7 @@ const loader = function(source) { // Use html_parse to parser html in html file. const html_parse = () => __webf_parse_html__('${htmlString}'); var index = 0; - const snapshotAction = async () => { await snapshot(null, '${snapshotFilepath}', index++); }; + const snapshotAction = async () => { await snapshot(null, '${snapshotFilepath}', index.toString()); index++; }; ${isFit ? 'fit' : 'it'}("should work", async (done) => {\ html_parse();\ requestAnimationFrame(async () => { diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.84ab03931.png b/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.0.png similarity index 100% rename from integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.84ab03931.png rename to integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.0.png diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.0.png b/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.0.png new file mode 100644 index 0000000000000000000000000000000000000000..fcf3b7f188fdf02b4ad2adbfd47f23af35f88173 GIT binary patch literal 16699 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.509a86541.png b/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.509a86541.png deleted file mode 100644 index 43f0dab602b58aa0b375154f499d09e60df14551..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16725 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.606cda041.png b/integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.0.png similarity index 100% rename from integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.606cda041.png rename to integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.0.png diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.7c8951471.png b/integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.0.png similarity index 100% rename from integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.7c8951471.png rename to integration_tests/snapshots/css/css-animations/animation-direction-001-manual.html.0.png diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.b1a170001.png b/integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.0.png similarity index 100% rename from integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.b1a170001.png rename to integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.0.png diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.4c0b13381.png b/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.0.png similarity index 100% rename from integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.4c0b13381.png rename to integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.0.png diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.2.png index 49f5fb2c5faa61dc6d3c30723b0795d3212ee447..76fb652a4442110723df963ed8f421c1b5be5671 100644 GIT binary patch literal 18782 zcmeHvcT`jBwl8+YUJz8u-U2E?K$=LmAqJ3|fOG}vO$aqWu%k%PARtJU5(rJY^zBAL zYUsU44J|=>34u4)x#zxe$GzjdKkpmk?BTXRvhuC3%=v4JH=63ohgr|EvaqlmM&G@y z&BC(7nuTTOll^<)D>-LJNbtuFoVM~!maHbuDfq{3+)XrQKm6yj|Iup}mOog~w{KuP zqiF+PK9~;X_Pobsdd#KIyLx|}knH~c>U6~=)wCP;1T-`M_@!}7>(uT&N}9(54r=P( zes})TAIIKfbWYv4Zu4II@s123)_|X8k4*nQad-bi)uy=+PZ8Wj@!H0{%D(bEN4cXu zl}4AAi{q#b>)$!d16f!)rD+%`7M5dYb{u92KOev2FYVn+X!p&vd9$`chrSg3JWBz2 zd1Gm5>55L=*w5pQvGRsPzN^L-QaOLKva(|C-}h80jQR8_eRa@VV`^%uH^WAbEX}s%LO6T-ccW<1M=~let3clk{G(AD@^A|H@stm|(kJ9GgB%FeC;iy|9Ie7JRTleLF?}~I(cYs5Fh;3_*v;~LmoxJjxUndS9H$&JWo-UaVfl3(}rd} zSXOM)9Dk#Ih(sctLr;8ist|CNCs1#7%zMl0bujPKp6=srCS%h-?_KlfA*80<`=mP= za^1uvcDf^-RKw$Ev^wPH_p)>OmV1vyM4Hmu(=yweivu?1HNjzgwB7>MZx61FxbXOG z=E!)^Gdu>pl-*`J zvb97~oXW^+l9{4?J8GIVv;Jr>q{EUDo0+H_qC$Li_*{xjTZ+wiU6{;bgE&9iOBE$9 zS$DOW&djzgCPT0+izoM-Q<;|3pl700Z*PHJSX9(m$!Pwmzn<=@YiMX6uJTVWc@s9W z+-0iB-{&@Ha@DT0A*M6SShm2fi~b>hpW>0>uEp9i9*xqG06`2%ovR~FM_JOg{T&}a zAK%xuCYhPb7M7M|xK*-#UW%ES*@q7wlJrcxe&l`DP-J4{@k@`X#S3+U%L5)mHp73v zEZN$a;YUB;cT`tfoA~Mo*V!D==X(!p=UF!9Klrw@z`jRYR8*8~Tw2KOyVPQ~&%El@ z;eenZteRRNT-$ZPt;=Sx%&Vez%yTBQsJ+71+e5yRNpq5~7tznX-54dgxVcqzRB@?6 zr&s1?Icz}{6_r+xAzxCmY6MC^09_v;(zTDrCq5~OZyDCETdd0esNO1lfFJG9SBQlX zoxXhk*A`*4I>@#yCCHmIO42R`cA0nQFm)3dn}))AQmi*!x+6nhhsju7Ew{gBWgHAE zG5wO=r1_rIKoYFzkLv1@nUHGAx*MmA>s>S&eOBBukWWBbT7P+_D}wJ{teif4v5rC! zarPc|adBy{^z%bz6}fSk!;PgW9eeqc7t}Np?*#RG#rkb|4N-==vQ6i>`?|aD7udAj zF)VTva*nLl2^o(!oJN9hpUgy>>6|W1 zKEvy29HTAAdGroHKfldHLnK|oJP?^3m`NBzV@!oZ-&OclF@qZ~uUE~ye<6V)3wL79 z7rD9V<6%G`RJ?inR{ZJpzXCa>Ta!qnPwFu;aEgV58ad1xzCGV}?+S;+;jb)ygTU%>6^8F1mBnkWO=DT8cof-25!H*Ltr^Lm@8&kCs)&4kk<-mah zmN0XcP0`Xn!u9j5-+Le8_W6B7+-L3a;&_B<38joL^23Ky;_kEe#UK2|Qq*W6?N`ll zlAWEZuy1atvQi&P+nhz!j~_oGOX(%`Ot1d0&Uc7jD?L_?4UyuFW3{38@OXUtI5T=k zQ#piN683-#g#sVm@YQ)VP_+6GmrVOzv3*CmC92o__R?mphAMm;;e~jeOYVnzMP}yb<-yn>Uu- zUmjX6PBd1~DVlrs?76^kD9W2&ToY4h+kx^JNa(79Ds0179JlYK35Bhkr=HyY#Qm(S z-lodaK_FJnc? zcqLRrL6q147oAGKCsOwN=9`(>!Sd4gk5*=TM%DYuy^G#I+I#rym>t*f`GLo<$|=UB z?)<`1PJ?p1wUpFd^1MTTz&Tv9H;>sLd0Qq z?&eH`0x>8qg3dguCkX`Jsh&KmoOmIP>rTD-wRgpks*y1>U&FJ?+4thb?vp$`PQjeg zBTb{b{o|exmRgkM+iHTjW~2wl4_jB#j$W*loBw|DxF6L%Q8hw1b_&j??aQ4q{a+qd z!M^rdv@~ycvltc~J!?}G#}MzAU+p)4_AQ>V`13t4vTXN0U0wFs-(BwQLGc;x!YR>f za>=^Ag0s+n(8R3r6$7Z($0ao>wo;J;6gcYUA$H5PIK+GxrkSlVZ_A6W#gKp zYr58P{R9TBWSX4bA6K7ik3MnoO zsc7)6x~8U1sEhqIyS!IsTc9YX!mXxRQDS9A1I7C-{MPGD$tLCaib5PU#sg2-V$S)A zpf^_foYKpf5hi$>oN;1k=yI=Jw!oi%-jEvd$)hfkpgb55+dnHEq=+G1qASa|exMUd zxgGSTy88N1K8K!MC2g&u0?mOEw>D{N^yt}i2GE@#*lSFI)HORL;{}z8%!ur?NcA3|r z1Qe43#{n2fvMPJ|=0d&2kqkId%vmeNMK#=Ou;o7NhRm|gQ z`uN+PAK{g2BX7gP!m!is!X=aEDHQd&*mCdH@brNrinNfiIlt{v*de1U`L@0JX(oP5 zH)F#3IK7;`cb7mbg}`?DGy*8oaH1olC#ve+1btwy@P*aJaD0(8TCyPT%<- z>_5T<)9#UC+@rX?XGLlK#X6?G%V9FElkqSPJ6JDbfbhiF*dvGjuUb$X+vZ1WkT(k% z7Fz%M>uE*j!(Els?A(aMZd>crnJ_(*mX<{21PLpn;nVWWNPv`gt?*hFH8!oudPkKjyhy)bU5A}J~9D|--MIdpFe z+t8|YiQcIjIW}z<(5)SMrkQgU4E3Cx9OPW)=q(dEwe+J$J!ZRe#6%1`7xnb(@3?GC zk3T_v!WcZmGQ}NO9rCO#U|jE1~$be|0ur{qbt??xU&J-PrGLGJpj?>HBU~~`6a|Lb;d58pg^Kz=Qxvz2 z()F@+k#F%RWB^vxBlZ`4qsDq??R@$KU`877o0-24id>$5dkD5|$Lr&~u2abo)6maa zVEc9eRPRXDy2Wnv_$eI1rRnr+D4SWWaFba;L~NEOTVmayDtj$XOs&n2mW&Zqky;(Z zqiCY7#eNHo|MhgAgzttctg?~s#!|~3cB6P`m^Lk+mCjyy@I<(Bb3waHOJBHR%?z!S z{W?%IAS7h9JR@#VkFEX{ez`*r=Fq_y&OE{-?*ws5YXkG8Gf`~vS)5{zUZYW!lNDaI~P)Uq=}KXynG@FxQF@{W*Hi4}9>6-!`lQ)(2TOIJY z2U~{001UAjiX}1hY#dw^THLn^04YCu^vJU}G(3F5{gcZUf;5B9No|-YUn1$9yR)4RlVV|j+^yqm!JVVyY zjxDtZh|VSBdPPt#E3l+2Zt)x%ZbLo$NwY=^6g(i-Yipk7fugXR-Mw?JmEQ&)GQGmh z%3NJ)(8ZPL2=KiZB_T>w z18x>1T0PrWL`h0X!S{|uFTOc>v7~;(#xE5r>qmeQ(i8IDD-r-;=yKnSSl; zCowUx4^Zua>mYo;a_xsmf6=LgK+)cOo6sf&hCL8Q@sSl3yU@jc)YYq;Z)^Prp0sAHY;nzX^4-GXqT8SsHO$VCKcGyNb)Ibg6Ps;P-pi%%KQxWWK(y%jD={eE+LOO3G{Kd z>N8fC;HW|G{R(V!-Hd@XI2Xbx{Ta$p+MS@YLqpV@jBW*bnh&MStn$#_=Nlc_gk)et+Q1#~&=?2iCWfHas-V#=OH*z9 zY(eU7UB(_$Ko6X2&{>zE=m_PUYKAs_X1R3rDr_vHG}8vr>QwylH1eKkX&lO-JkU3-JTLs3fICE&5IfU++fGJ$AH{H?(?t%T|So2$viu?BQ{O8qR zD!GBf5E~lcIEeu6ZV|(L>jV&yOxcZH-y0Ozy49&~C80xAI4@I0{MO%YLGRb|l)b7; zQ3%84R!JAIU(|>-vYE-B{}`d16hOn$XhlPvotcJ6^=jjq-S&^a@q91p^5u-&Y-L7y z3V8&dWgp9%Z`pX3a_vCV8XL4rRNGo*7!7!vW|89nANnA>FoqH<9~K^-0!xZUDg5^G z>M@XKV6e4ER^jTp8(%pJxfgQ-FgfL9=`bC~lBtx?l7Rr1!Iye$m0cLD5hbjN;~xi` zw-!{mJDgfJz$7n-inf$7SNiDrzh#WyPm>VXBDX#kN`Gzq?Lo8L#$@6|bHXjU9JIs? z5Jf}*3uK}1-o2C){z+7cHwG=Xz0p-E*QVOn`QpGyH8Zo%R!PXoYfD0(Yy%)HZr^>y z<8~eZ;n0sCt+&=Ge&OAxl~q&%rl5CN8B`LO@E)@qt)VC>C&#^qI35H_i{gi1X8T+E zfG3>+G)2nNe|uU;Zbp%?`886^A|MS)o$OA%n>U~7=a?x)cAj$tT9M+ny+xmNgCUnK zHpzneCXd`0SGgeku2baz-6dA57O=XeePxXA8Z5sVT-^&mld9YVXigq>Ra1=j)SJpY z2gi-#5H2lbkpKALyaGsemT>tA4Zn?0*9!jcae_qTyvqv2 zaZ4!lDagnYr2UWbI3}HZsm29r0itS*)`YN^Zt-Hh=pg^RFT=(zE-dsv;D)V-Oi!sV zdICVZNMR*ER@9?+csKK-sCh$4v3M`!EbbA^iusHto{rznpy(Gr&qOfoiB83Sx zO&d{QNX^gBPXNL@-Z$z|KAbd(jR_Js+f@wr%g@DXt~U0M z6*zZt{bPjgd--Ub@2kCYa4h=u1_2njkuF}m=)Lt(W=Fn7{b`C!%oRnSwJ+YjFLjeW z$QW5Ehx{i;vKRUjm5)*+*Q?Jtos!k9=73rek-CHST4Jl9Ef~~5uauganood)Gz=FO z7LXkel7q_X>TW@2*%INyCHG7%5%@e@(gfwztBXDkm3T>NaD^7YV9LaT}uA zR2UpRI*EXc&WLyKex32_oAc$I!mr@uqt#*^-JzaWf_$p5YU=6rDR+PNl{i0lvh}r5Hh)={vDaea;y_r^PbfYTn87EX#Kw@+ zBDGU;LCd1Gf&(Ifn@}r zk)21YV`F!AnfjHJ;%7j-3H$gl6$&k-Bzn37DtlXUgbBI59h01#TwLja@^b~5@B7s- z%(%XcmXPe`x*L3?N>3kAeyRBueOtCm4eFe{1E?c7*U=!U5^6n6fm}Hs$SwS}I+wa5 zbqyk<6sbNidKERbCCphx5NwvVw}!UgmNaSVnfkir7`z!Dr$XtF^H_r>E6;O6H)*f2 zzTiQvP*}MqWEr;4|J5*6lV9uBdf|dcy zMCYHz19LvaFQd88eQr<)jWCm!eOy^d&$6S;)u1-8&vg z)Kqs0jFI&~DJYm2m$r%CNEuG-S4<#G!BgW_^c)&+duS2o)t`3Fx6D4 zLxDq|uDE4GGq9qt$VmNR zx~95l{qKP0umrfF_ZW*k^g8kh)2IhmIOf?(bTa!693X9t0PslzKr948C-au>@unai z6R!Tb(!Gi*C6++B*$(aX#TKWb!1#InwKB}>(B^`-eDr0ujkc3heb?3bhXQm@3m=3s z@#VqO3u+(Z*-boe_%Zs1K14=N9f-H?z(dW;H~SHAqG~hclcP4M^jpx^RZ#e40k%Dj z09*!khBYlM$iU5>I<&XoQ7w@*lf?I)ETgKForIjUEu%ARUscoJ8EGr6)IA2;IWv=8 zxgzHhs*Xu;&&mWmtOw$oj)6fG;)19Wp&aWB(`22ttJ2LGLb22SxW;QCzc@i?HURZH zt72nXrBBz(2-hgNu`!cf0F}g}7r20=d3B&mCdH|P_IpNu!4Gb6obbj6OuZ>ko;o+z z7sH6e6FEDbVb?`O#G$Hzc0hp+h0Y(3$ZD{8bwH1YS-La;0;Vl!``>NL5jL;{N)Oqh zKHLDhC}UQr-^q_*h;9X0za9AdYG#Z$bDbKe2f)S5++47%5(sGLlO4NUx0$OmATM-5 zwM_%XaR$z3GjM60EVppo4t-R~HN-M(jFHuYk3{Sul+jb!R#8e_9V+L%w}lJ&){t`z z7s8{PJU}pHZ!qqK9r^d&0PYGNPOUkppv(YKV5ZL`pFwI*(5vHUNl5V zN3bgoVJ!jiJBQu;U9uv<{k#v~DGd}wE(8P*U#E&4@Y`laDfpH+R;*hhm5dZeC>it| zJq(yX%V_4UJCFat%2)(Kr_2l|T}hd2dl1&#=m>}J&Vp`6SZug>Xg zeq^Mb;6WKEjsG4LsXn!QcmLe_u{0T1YEuk2+um}EY^P3rWt<%>x>{!DoqsjxtYqEY zK@UFu1~`RXE(u(OmM zO!IN{C8wj3R!y8meQ`M2@>Cl%m-Y2^etI7q-I1vhYqc@PHKU}N9**sdpuOH3gvOHdnrCyY_n++dQ%$2w#FT1_$Re zkf1vmA53(Uv2MbbFSmmy>;`IDDNkXfN-C89KYKL7*6IqKh7`CIeC~gH@TE>LKVkyJ zqfQVA^FgB1n0plf3*V+Ldk?rti-7Nn(Ft(=t~!vR3wm+;es~#2?t7nQ<$t(q7k{>j zC?Y}64VexR7!Jw4$(;M$fZ_@nmtej<&@?QxZ$Xr7i@H<5Kc+t%$$2d1(Ea5GY>jqA zb`q&ry9pq-aDoz-jT8_lobnxwvKIR%wXY>^Y8^X?9S3OJnoU?1q~r@5msrEyDn_#J zL9EgcZh1LGXiupDb@oH$C+V>L)_?5;1F;hbmm8oJ(5|yocy{%s#>Zbrm@>#mm~y-u z@bWxuhSav^@n)6>0$aCt{73#tLbIjsWZoj9Hgz&sIUCc+o6tV+us0YVFnH3lDyqg4VzrQ{nd!yT-6(&eqHB0Hll;C(Or~1Rg7tO(A@g;v63&8yRE>g+BCMa_z zU|-@~TwEb`Apl^_ZMt20Jj|u9$jMZo>c-AsL?Q|gS18VPqN_bKCK*f#Y>9etYeCNK!vfO zt0xEZI6c41Ks0^(-Ib}?@!*MeUGh|dmw>-jY6V??logU*QG(bii3gt#*Q znd$G$gi0I*k`vUXY%sOKGDo^@l&nXVRYkav;dF~K4>nH0x4#ftxiX-W;IFWbtB8dU z9&R&Ih{2yWbSm!Y{Tt^xVA1IZaw!uCx= z(k}u6wc>?NXe#+K0=A%vg2rM9Q&57W9GY((EoPIh>)it2J9*S^?YTE$u5x>emF?`~ z;Iy_v*w){;{EUd;G$w(OHU;u`2Z#()g$_U8{Rw=1DZ>uDTif>3_p+2lilV7~tmeNb z6xgtIG)yumA^k(t@$oQx(1x^zMfu>Zh?%~kY;MBZLu4tC832@nm>y7Hl;NKNZ{D;Z z-6r#X&gc83Dl&6m2w{LXybV+B`||KPkw{#E4#*g+{6FFvyb4tSYSQ4l`9A9|2=_py z4n!!vm}?Z=M1xk0j2klRXd-N@C92|fPr15nXGR(<3D%UbZjsC-s2F}(|1;dASLf)X z>-kK?`dR|nM-{F~#DM1TdDKa5p86gN;02&GyLavY@AhtJY%E=ua!*zDtpU~(MmYgo zMp9dA`4KQsS)_hz+$?K z!gFp2Ak_Z+c?Ea_&YyZn$Y8oCY8~W@{%2YL?#Fv)B+5S*PBU;_lav9++N>*eXc5yn zBkxmu;a(IF!0L~XmMX3}k8nM>QJtK=;$UX<>RqKLL86GCPX8T8SjoQJ(fgk#6Ji%L z=H8Arfl|O>-cN&`+@=<%Fc8Qs-wQ44B;fA8OLIA_>{Dl!0Ny(!eXgUOyr?b#VwR(N zmVVxaX+rtNbUM9220{%;up!THn{ZMzJ|@S3N{O4Z(R6XiwE^FbflHdV&I8wdEb|HH zn;}$H6Z$BZq7*N0;{A92Z!tMtPS{^-Yinjw)BVM`AihO#{bWa0qC-K;l&2gp&M|xW zK0uH2Xawl9Fu?29&kIrXXyq|BDu~l5)u%q!m#lr&r~| zR`DT_L2-6=jwQE&7y|I4gAQkhTDB%V_wwb-3knJq-OLZ~-aUwtuo>~*t~1CS=(z)c zM6NA*aTBQI*S6@onWGA8@m7NX<%$!i|CmLA_!#o91asxN!E)e@Ac~-zE1~XLVV0tL z*yhjVFLI87_lmW&Oo4<#SWHYNoX_ljW0jlI*jJF;;mWD|ooi7qw>acdFBB_E5-FV( zL!&(8zum69?Htmx$zy4B%H0QU7k24=$9U^s45IKl2Bgbg~3k)E@oL{*1guua9kx%D}ATX}Ay6`|COQ zWm_ zxV9m!ORxvK8d46iGjhlFPl5s|s3%|bdIv4=1_L5TWGEF7kqf$yFE6vt5}VD>gk5#^ z+T(bj63dHm+x;Hi=V?e7@E@`?QNpqR8Zz1c5LUB;=RlyK{F+VZh`l8k@IsF>D+4#4 zLH+gZdF;V9ujOAD_0_(uvy!&A4>d3NwEW7c;ckP9oQcH5bc~ba%3Y%sJ%_RR{8$@G zBDsQ8S3l=fxzwr(DMNCa4r%(^J{}BkGJ463@T$ymsULi>He$j1GTh4{2sjOj*jXvZ z4CvX}HyOkE;;oVqadCdF_@_{(5W~9*g!cZUHGV63abe!R$Gx4eIVnMmBJrR|*%vi- zqIfV3T=c{yoEZ6#GP!%kzpW7V!wh@n+Dq69kz4l>wKg^miX`Bc@f9c6!GM<)hlld4 zG;*B2tY@wmy~nZH$PW*_MpC60r@r^uSJEmW=a>)fznQ%3-`-PqmCARhkh>Ag2Li*; z$);`4nCq5prT)!nzV8i2b0(4s{BFV2eH)yV+oWT$=sn0k1#y)QP%I_frZEl!C0J;e zs6y^ahF(ucZ;T#kI7C^>zB?Y2>>o8XdYlSTF# zp#ev9VS^!FFw}u`2(-|}V-K9XU=6{MD0%Nf#2rvB`*sttqgZ$EjFQHsoE9<3fHgvm z_#jLYg0m?Ib5jk!+%odxtsAgPJ2-3j$pn9&IgA7(Jv$L?$b)VKNIhSz9uzP`9^a++ zkmN~{#!XCuaq5paXfxTH@H$t7Xl!k+fQE`x9>C@RF zX4BRnkZ3%791;x$14Y!Vh@bS#MFO$G0RkeC7MuBzA7@3)Zs%A9S+o?`=^@n$GN;Wj z&Lz;^rU8Q8N~?dV`*sUPR=1>4-HBS5f%B6~@Zu?qBC5PV_?S#==q zT^kKiB}d!R09Y4aeH1b zE-pr50pJHBgSL3|C>4yLUdR}{nZYTDoKYl{!P*!tMLrj;&M+zOWx5VbNq+gK5u9d! zZvXBEXqE#S9K6dTS0-*tT|gtM@^Qr-%Z3)$v@L#Z!&=`Vl7xoOV`rBQ*{&J%X=`hL zcBHJ|_W1qlTM9%d=wi^|ERlq!5BOrSbeD~abWJcq06RI*hL56JeI;NRg%XJ)Jw*s4fmkCFwe(P^>}UWz zM-Cge>*!BW1(Jj=+i? z;gxJ|P&-#Yfiu+vdEQht2P9Mj;X4SVm{khS6r-l1onXbk7p^<_-MI0z2OM9Ls!l4B zfxasSRP%B{Ikf95Kp)=piWFoZe#TYD{`)C2y&pe6L26NPpVpu^nY=DNN9a1~unj5t z2KYqP@;KB81h)`>$P#)#wlGK&iG9NVm#_K)*tj4r-qvQ(8iEWCCNC1Qb+>BMRdRu} zBWbEAIWL!&ioYLkcBjh&Ul0J9@~HgMJBW!+=?rlGC8IdtQmn`VqtZfz*|um~isD{q zdu>3cPt|=@dOn1aSjT75>mgTZit>FJ|)9#jFxj&t> z*VRiaz zib_^C!~!Aqc!K0gj1x(~JmS#^hqSsNKfe+nbH$E(H~1M5TwWyNWxLTrKt+2%r6F5V;3I_s-oLta|^&x-S z`4$^`UJu?6Vgz|1`(gyV^`wSe0*nbwM8Z6U76@hA~C4U?DZ z*T)IdotZPbhmNvk8sx>P47RibcSC9o^of2^3$SgpJq#v`c1k(+r(SjF{Ti11>EDv* z?0u`vzDtHbx{nhY%+1Y_J^_xxeXvfC`=^uv#cBuR1N}qrT+R3IsRZT*e=x;Zn{C8; zGqx0BY@o~x>DUy5|KpfOCT3L@4dPXyrL5))Mj?-Dz*UfHyHq;FR0l#^S6e%eO&HMc zJaeKWZK)qrK(mm_O~fe1e^=+@t-jK=^0PyI)gAyMa>p+ZFoUlUe%e7 zdqlLFngqZ%@lR4t!S^4uoH_di44nxe!ID7raQ-A8a7+i*Rnj|+wE5?9J)yz8Jv@G< zLIXIrB?Q7O|9Z0H&XC^R;W$c9O)f_C>cs7r7FOHfh1Fwj}8H zlNH04*FFs4sQDMmKCgqyO_z3jpH+zn6?YfpF^MO0L04Z?Th)P$umY;ga)T7;NxU(s z7~?_K>1qzdf#GF@c7@bd$a_56EX(WH!@J zDY#vuK7?{l+{9*qf{TI56ZZc7u%qgN-qU`{Jef=$`=ZnS;y~ugIaFD%lb7@j9>Q>J zgWmUxxBkkj0D1-@sS)}>LvB#!UVVIt4;0vYxmnqic5%b>k=#`}8 zWE&U;l6@9M_;D*#YSoNkRF0&k58j5rSeNLt zB=r0A+VEt!XCvnyY{Ydn6LE3kr{a{UxydDh?c)Cedw(pCx8dk%$FAQ6y{( zmSq}nB>MVW-JWYklR1|(*vu-aO*Ry;%z%q#riPTAH}*S1BE49=YXvG!av?>lla)4X z%ol?iQm=GZ3`>XS0xqLf?%l%{*eW?`9%N+=;5ug&cgwqLP1?jFQ^m2WAY>u^f>(Mi z&~-gX7R4Ye9(rCn%mNZ;wC&2ugpkGUj1@y}FgFX3+#sOh8;FAh8*v#Jp)J_{<0e)h z?E^e^vVjCDNTjb2x`d=B0Rw}qlE=_pf3yx}ZUInXpb|5`hpP}=&aaehHp{t)h~#i~ zN)So|8V*>FNSu-wz|AGU_Uk#|<`;Z(xVGlHY9s=lRZ5ouKr{*6xtTXa?z1>WPA9_H zJyKz7&Ac&A(G-gC|0OJXP9<60*Ot^QTBe{WQ0qJ!s+wLz3@EG7}kHLpkucnX} zgCkg>dgG-kq3=f1Dd(#Rkg7qNYFNt5WUY=~Z1yipB#B1+S zZX^34jq!pI6*ILl+uZ68nZ4On>o5FZq_I=b%Y?7`0zjL9ii#`U4?)`ghG^+F@S`Bp zBpWwm+xQ;kML{x-S=cB*K`-XoV2UTFY>rigjHj7#PJQ%WJ+_~AmR(re!W=yT59Yyx zEx;f8>5tVzb}C|DSFOu?wDc|_Days$C0o(42b(HS3mJS23=B-Az0u7X&_KEm5Wq6uiQjUtI0u-Bs?@M>0xEAf80B5;(7 zxjI2sD*3toH^gFJ~LZq?Kd(NZKCIfLwF&?|iFdKb;5JrGCQ z^`9}n_1d5VJ4<60$b*QsM3TA==UmdU|0_@XVM4Cjr>pmnJO=21pSw)FX&+wYeJ|cc z?iKLc69csiGQV(8Qh^)r6N<-4j!?M}5hij&S}&QWzPKJ#I`A-%hb2u-U+qv2*Wqqv zdGeFc&Yx}B^uf$*&8oigi^$I%mwtrE#P9g!ANXW1i|Rl8r=7iQGA7@Sqx1M*9{ICa zr8O=6@{wO8>W9_m3ew>!bs<;EgOjeIx%eBy^7UG$i0&%3LN>X4q=40H=rcrAW$xE} zBW-lz8Z$0wk^G>P#eU`Ffk}0u7_SGW*1J1>iD%JuMlS+se?v~tZtsm0R*dXASre|5y%j22_CyDl9OO;~6xcjY# ziAV0@4O)K?w*;ncpsiF(sEqXrOpE{99cX#}0fXR`^`1^%6zjka6f1ocg?G)>Q2J(a zuaz4ecejMWSU%13nEQ!5;pUEVbZh$;8wb8Hj|Xu*DAtvA;?{3welQ9zA&R}Yvp45v zD;E1T^ATJ5Nl$~;7}Q49@5rR1n5CLw0{R^0R5P=#=LpNiHjPX1T~oJ14k@1uQ;d(X zD()gr&fi*D-EImk@{Z*j$Xf8nK@3HswGYKQmq{#94KVtX^9d6^-ZwFzr1d4Lc)_za znmjQami-(x&31zPx$4=*K<610zMA&o+qB^~+RkfX@M@+wI!u~#LTPAwRTW*p_k%ng zI|Z+Mh`z=5BYiS#MVz>$tK_g#vx9K6{90XN{EH#!ADGqD)KU{H;n#$R;VnMj%+H$K zyiynL&@~S`y^Gqag`_mo6>*^8;*Cb{sAR9LYgi9 zUVggs$^vZTR)%pZXUU&y&K~BMPx@TLV~lMNt?Jo_J}Y9AlXNSrU!5iF&69A<(i-oQ zv~xzlGsmnX@5;DN6D8AwPU)YUv$8(NXYi-_CrzMOTO%;-8a>W;x+&vYy@kPPsWV1p z0eF|YN*dWF*p>*L*fl~g7jZgm@~w0uraf=ZlT1Q88`iQZWAcyrrZo;KVcRtEvtA-K z{ZjiaYCVY7^tYG-zg3jEA#CH0l|}5lBKOt+p>%1b@1g_lucLz+lehj}xF$7A<%n5J zFHmMay??#vzva>S*RuR;S^j$#^?&Wmf9=eF i?acogaZPXUY9HB0yfGra1V1B*1+AohJL~3yXa55u;0gl( literal 18897 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 literal 17542 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 z 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-002-manual.html.0556ccf91.png b/integration_tests/snapshots/css/css-animations/animation-duration-002-manual.html.0556ccf91.png deleted file mode 100644 index 68070a83ba365306a5617d894c8385a6f0613b65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16406 zcmeHucUV(-_a}D6f{vmTkx@_t1eD&f0zm{R0qH8zrS}$e97a)!1VV4p2_;fOFNz~b z4IP01A|=v7NvNUhx$}GX*?o7P{qMK`y*@KKqi}QY_nvb;?HKe>L*+O#2Qw2B({YsQ zU2P_&J=RQ2dtV$r2)|Rv@iPm4+2g9Ma+|53{roii&wkh2DBZ*G!|(93H%v^wGokL@ z()CK1ANKV-)4RR9d0fYfavr71F+S>sH46y8i`@Yf6o@ zBXZo)B4!SXo4?YScjrgzMpHamlN5$jMa?i98C7#;yPMJB&v;{R{{FZx&tN<^U&yGG z4P`mi8q0^T>eV(hwB7si#dKf(5X#K+)~%OE4!%;x!cUgdT^UoID%=v-*46o;kQ<$& zWpdQ=FZ=htG@Na8?>Dg;EV0oU8=sz@R>p2ns}=<88y9F!Ki3tKG@pZNp_uchIrJlNgfST>qp~~fEK}|KYTtQ)B4XI(bh=TH;ZzF1{Qlox46%`em zi(goJb9DGnT}cYQZsQ?Rc(^`ajnX>l+`N}!EMN zJqz`!H)nFQp4GG`M{~)#IYqC`^xm)WS&_ZG{W~OgBnRDl|c6Szb^^}$WA}Gp?_|YD$P=|-?Df)GrILb;gW-~|D?bi=)zkwo) zB+B2mJY>c~9U(oP_n*(+qLFZLZ>)lPzrL_2>1u0ZF}sY>Xy3MprU;2=-%g^mNl6OG zS6sdyRUYOzP;?L8MlnOt?N`SwyS}`ok}<1Y_vx;CEDH4ZSJY!tEZgF2da|`@%{ZHE zTC|Lf^Z1hewCi?O2&{0M${`#T^ZI|2cHGa)x~Za2D4(fVD_&GnQyLURpmTlsG>x(Y)>dA^G`X=r;Mgm`wSN+<`S_r39 z<$`^ha)=wPP0DVl)E-xB`S+VsGXuq0Rs$n7zR2B6jd-OsZ?lxy4@rhxP%zXt_0C|# z1s*+D8OE*X_lQS1*l9=|J{gYCu*@MpM<+|KA`DC4oF!B-%Hbv|$Nu7^&zFr>{?nXr z<8cypQJA%=QGQ|J_8PMvlr6XCa5V4-h94iltZHRTJHaJ;72c$bj0|--A>sVx%a<#L zE@0&Q8#W$Y9_yF;i-p^PH#Rl(lBa=|R(McYnAFRgpVVVTzoutp#bdTN*-=-7gxc85 zTR*=#^x!(1xcy+s4I3MqsWz!m@$rTbu`166NlqyTH^M#-DtSOXNq)d@@$(@qLK)e) zk&C+WrO>=FuB1e~8ybtJi9Xv;qLImDV=?skV5luI}!(KaZRyul5(1*zv|rOgy7?-4}^Pu2^bs#M6{#RMxJ~8D3A| zx3sj3gX4;W!Smbc(``LDI&D=8wMJBl`_aPgqp#qT4t&nh7t7EvN*R0c8p=lFP=ecABTjy&8hPm?_Khvnx#4j{wq zih@E-eLrKMZVXBS`LN+_G4~l=w`SXag(ujK33uSt|Z zvscvK+-Q`#I=v)1BEp24iztg}z@o?cDmEGptkY+l|qGm2F?7W>=jV+Y%y^r*r{L|fdD$NJJDlqDyMx}CB(UXe*L zM}IwLomKqqoXBeRbs3p~635XIs2jM+C%m!2!NJ*rO)V{A*w#pX9fA4a(;YA)m#;01 z)KrB(gORU4W~aoM6XR*B-r1b3-K0-MXzC44y}x)Tn77!9bis3gI$RaPE9O9!HnYF` z)}tQATbyZ)4|=mQIQSUU!K1=@ld1D3b8>Pr)6@Sp*}4n4kf5OXub7;M{zjZWNDRMzi_@mv9hn4GWE5 zu(QpW#pf2d5UTEX!650uqXltMQMTKgD^!m|d-tr7K0LBdI0jIoohYjm9OYJ8m7T46 zoKuoc+&!=0L+{9lHl5iq#`Wu6Im0}S@C<0xo{anFwtt?hduVKIoIwa8256=O0->~L z`|@##iTTyl^7BJwq#F5;@KzIjsTG4CpS=3(1Q*_CWu}6#JwU=_>g5}57?Om_$UiqW zr0O<8cx`A?ZG6?c=SMUK27Xx(i6-N-X8;+jxY&v2T$5{$T38qon1>*Rv(<2g`Sp z!^1yxrm5umu2He+d-fgt6?o1x+vV}s#>Ux_ZVlQs*FPjgL^QE?l5+#Ql`eybV^{DgVP<9)w`^q#VHZ^S=oL}tTwI1;rKYpBB zk<4Y&TkG#z1Jt8(0(+nAD&n_ayCivAKuD-Jzib5WO`Ae(K2g#apOPygVcJQm?QP=$ zTjSiea$oRW)GyS5DCSi216e9ijLcLKt~eMJ^6J?W^^!*1OVw33Z*+rFs@ zP_Ni@0H68qg?C`KP(27{b*7&mKeS48kK&#F@%h!H>4IlT*UNo}%pevRU9&N>J~Xo_ULg0U~J?{AK25-Z(j)v$62B2nRh3@f*H&fYG-)Te{~AfmEswM zQd7IPJj51kZPXd0e8~dYhxC|OeNogiI6JDS8?wRxMM4kW54S6_r^*WbSVam@I)4QL(( zl1ZhTO+`Or+FVaBikDwP0-cqNsjNy|s98%QDC$ja9qT9B=?j@wKU%2Utu3gc%C3pL zxS-%;JyPwBaNC|z`=RUrb{Sh2MLl%u{qt}i{U4uv>B-XMh8e1H12f-U(N#A}_^~l#~w;m1EPLY1@?^1gvCbxkY3a_zN1YO@z)+Sn1J8l6<;?yhe}! zIxy$piG>w*w`ef1*wnfZT~pyonW>yD=w2NA(hKDX(^C^DP4BPHH20><(SJWrD>Zid zd;n_V6uWuk`}aQ>M9k`Fke7Wq)SLlKwTBT`9z@2*=1`J+H9kE2cKTE3eUX};{+)bb zii-j8OyRmM7ZcYu38ctinv^{Lyw}9N7i~LGl%cJov+|86EW4yc7kHmmqO5y~!>}xu zf{!kbN@yhSFi%L+=7X=O z$^De%fZFt;`z-#ObgiprwBa8gpGxJ>^EtDGI#WQr4tU^cn|{aLFO!p#(;-qL9h1>! z1po~;lg+rzS+iX{v@;3|KOz$n@*24VjN9TQbO2h2&^Fo8v@Lq)Km)Lr=_FsOmV-mV z6e)Ruy-%MVfL9!JRu6EG$fP7{5R!8XqmvFar@mj>p>Es5Q$ftK)oFnPWX}+-8mV;hH|!$@iOu}C-+pU8Zb4@Z zs)pMWZF`&j?SYv4Ggc)8^$YSAS1_xN;+@LQ(rJ6^7gEF6Zq%!aLTNX!nInQc^r^FQ z(DR|vHZFxTI}2m4xjlyXuU(_hMFSNcMuYNEiEMD6+u z+>`Zq0+eq%?;w+PQrUTZetdmtTwzxI!2=0UQ_I0u@V}&`uB>jbKLAUBK?c6yxd9WJ zT>B@Bkoc9&3atoQp}^1JC8Z|;Po8`L^{VLS{v zYO+D+9CX;B6X!c*C@d5(j!z)OSQW9K4DWu7tKc6CKl$Gca_s8eQJ?2Z_!g&y6HY~B$%8$Pd zoB<8PFKy@EMVv{u;&`x_yc8KKDHjwO@_@LJH*4LyPKQ`7=z^xp=>NgX9`ldx;W7yEX~JIKgpoF!oO9FI9g~aFC#rea>*xHX)G1J!Yz+YEz)FKV zNu;mn>${KS?!IGpt>n!&D$^J$a}-$v*QJLPu;U6t(h!ul&*pU2@WK>4;6uQqI62RP z^rC?rhpKr=f3O;;@=ZhwI4NAz-py&v0K+lQ1$$s$B{62LHyX0Rp-@AiIuXHVH=pVO zHDUAV#UA9|R(5wrcjfqJ<$=y&R*REupn;k9??pbWoqQM&HZ*j!Bil>0scal6zMAB5C^TK|j}GWKT7^=8HANAPQJj6fUD> zrQ6)pbOW%UdZo{ZTJ%lUq;n<f`h`*g&c z!W9t{^T%DmZ9HsYD$&bm2i`O$2gqJQO)z}rc&ctDTVUVStBgU8q&gZi0hw>}Gk z5{SzNOm0IPv-9v?EhoN(Cs{z1ihp>RP0n)!=tx9(?2W0{0}RX7n6dVev!F8(7|F5g zgVWb!05Xcfm_VkGi`5S`b#-y@2;6alA(4rR_Tc19wws=VZ$YH*>fC@ie4d=I{?#+f zJx)XA&S_S9MNlV^Lk?D)JK*reJ-%*z!ROZpwNV;aP36QfH@~Th#ev3pnMMk{8+Dk{ z!?&x$Ik{+mDdh zLGgkk$tzKOc8@`3Gly|xrLC&j< zrVg`B9{9PH&{!BsdDQr&X+kh1jdhTIx&tF{R4(voMv?7jAghFuFRTaL^4z)Py*hY# zd1D@4eRhId@pjr$rZ(n^NqlE z$)CJ;HYjZgGOp^Qwf+h^t{Pyms4ATibw@lGr~)uL^r-7$dXwH@6Dlw3c?(~==Jsl% zuha9EAMRg@HfUI+x3lvmx6%>sRkd~Z8z4!Wn*<=YAMODi3^LF1m=xFhT^1^GL z*ze~qZFrMm(hs)gQ{GtMGV#caQO0^tgsYG#D!F~LDu#w}Rwss_%-K+xMr96HP983= z^G@7INux!;O1pb>4TD;xjEx1k8@Qcoovd<@4&p0>w-`{} zb0C~Yg{(OnzDEJG<)!Hx6cm)SQMR4b*coc2gcLcurIdBY@5j$ecWj?C7|9g4BzTly zNK}K&BPgrrBhSgh*_z3Ue10a#JtEEoFp-%kBL^U?UD;Y`_!?6cuN!KpO)$z+!2&?c z@syNLLKbv5Q2{tU*J6p2hkwP{6*vcY;H{-;tSX4W1^wF#_@U-lq)RlJevNO3iH6r6 zfT`kP1K9`S)*@Kp56&^RS^r099*1f8;@dee$y|;`HMqbFuiWK6c98Yh!QKKBIg;v2 zvz_JWq(2IP!6HoB&#yX5D9guPLnBnU^4Fz|%uLYfZT{q(tg<|2{=iapP{;D5h!z!?1^-z^HEhtH>E{ zZUT%?cQEzuM_ub&={GY=bDtlnF~TBO;aHQ?wm>HeYU7y6;pi`d+D7P~X&A}sGN*4Dgj5q#CsB_rm3k96V&{~+FSIbH_QaAu0zpI z9CdVYtx3i*HRQU@U`Y;eON-!~kMD1ntaaKb=`9^lV#rHLNzEu`|5C`FZyp%iy1>Hs z&Z-5mkv8-ro?P$Ezvh?PFY%;)Kgz;=ACe*U4LXi+I+Wi6Bh*9bv*&Ez17~MvBm)33 zLI5>nVQJatB^{~LDq3{O%|1I1?<&q$wAa%f9Taw%?U?fRpH$shkK6S*b?acS3W|y8 z2H1Se@6Z}U{}nfmOAJ^pNUB;qIpHcWtdPnY;>Lyb$u%!&16z77-(cCx z1hra;{A^wp!4Gnv;j3F@5Qw*&)7m~{P8(Oahu-FLDY%0Mki(zX=)>uXh1;-Gr3TJS6?r@ zM$*`0z{0S~BcFner{7_d&)lEaebw1^3T3DK zmXHR?WJ|P2zeTi(p4}`yv_+)O7}N&Z!=tncEO7HUr2q_ZkY?mAx9kjoUg|8fdUg zd!XgAdwoQz7jJ_qQ3p$*vl%BiZ08t`S;;j+VKX%n@u1o(0tRJTlD&Q&0qlmZSC$wV zL7XJ8J|I(j??eCf0rEUMa2YFC2kqR!^3~66dxV_KE9l_S9^ZwUtjq`TGr-vJ&IhR0 z3*HV64jHTib`d&Ul`vj7WL(nQ>hT zKGTDO?{fsm!wmXJ!5)}B}*!$AmevG4`nu<(3^Zn|CtwaMcx*w-E}MdI>_ zz~NYldkd4B>pjWNuPFsS*I17izat7$G?ri#ko(V^ITHhbM@roJW?R$1CT81nm1G_K zR(gmVQ#QnA?z2V(lt*&0;_6iU=xQ{UCzlxk_SYADvdN{g3qhkEUp8`Psv)QF^uoEG z8*w~9eT)B#Q)fqEP3N+r@jX@?k4hbzj^w3_E)#c3W;#IhNcpbLEy+V-`1f$%w?bk# z>yY3~H~6?A6Zy;4K%qS&Z-Nl6@m-UI4BM|~T;A9sO}hTIG>6X84Kct1(zj6SMvan- zCGA&Z5htR42VFu+T$&#n@n19X{GRMOhxneVqWA9I!#Mz84$I#;7ZQZIpz_v3o^yQH zOPbwQS8EcC7c)j4KNAyo39<`-_{5ZmyCuwjqoLhS6AKF#ZEK+9Z97u~cPIfnDiM5| zihC3aYP-6$C#R-nK>z)tIn}1othsns8=IWfg;4?!mP)B1bs=4t{O{+o)cov0A|(UO z%6oj>t6s3AaTzjaJMaYuE8TOg-0nP(X;cS0#tm3`VwW8W2O0^&*LMvp)s;w%LMMb8 zqKrzPYk_Sbt5{1oy@>E2;Ei^IRz5)5{(z6m-#2^_W;QE{Xqu)eLA zCz^Jx6we^hsOPYIwADa5D#8t-<>ci$OO4BWIX->@gM;X~VA@MCGXuk#k1m=L0%l-_B|Kk?Mi%RU3E2($*f(B}x}==_#}9-;0bE7-AeNOJGP^cck319j&cF zwjBQWWA89XX&*?DhTp37K2@jRKOZmU=<)7@ng!0@%xmm`x%yXhpwiajowCQ)kVAq( zRFAoSaYkhgh-o~`7_TUqUJpuJJVn@5VBhO=Sal5z55~_$BVsv_GRyBDpG?&+q2b%%B~cSw~u%n?<3)Jx0Iu^yrE~*_uU5KP8&s z-hS_$SGgB0ov^0TILod9`Sp1@k5>O`SsV$d_)~~ko+9kVp;%{3RvWOt1)bqB!l~4o znTRKYv=4<;6^XWU+|2$S98oPS%_t>B%p?&CL)V%G()6ct3ALiwR`_|wV5k152wRiU zA#o8AlBy`QQt*Joz0`wl|5tim)$7{b)g_Q8Q0+XfOanE>k(1}M2-^&T@+gpi7T^af zv?Sb47-LOK#6b2OkEGYpran!HePVtBDs||0iLaD^PpZd>H_0#keVQ5y_w5_#7&(ez z8z(dqPs<}6<_*x(1~X;&)#a@hp7ohvC{2L|mjFQ_209+DtN%#6w1{~YU_b)ik_4iX zVBrSDKxA&zHhgI;u;{oA8YMpHG+*V=d_wu_6I?E%#lUN0r@OPNbnu%;em$CJa|Vq4R~6m7v1e{9*FU<1L8Alb8Q*dspt8MT6P z0ZGRr;^Swl21|74XaT|VasfMAYHRoM=0jGun*WdU&P;uQ0n;Uf*Y#J&Mba_^g0Zl- z5;L#IYkO19bx7E>`a5j`{Ml|ekTr@mty8v6qF(TK6&>7*sELZJ3z2x1UAG@)wuP=t zfuu$`$B}B6TNj!fl~RN9T;}(`7jrBfhX)rr*8z%Waxeh#cX?@Lb=yKSn*#Wc7_ggs zlPF~MczJJ^jO}D`8*KOl#x@yfNYAUDBT@Jf-r(n>j`!x`=wC<=>QhrwT^K05&m;{1 zb#v2g%BfUvi@;e2we~Cd&|YR2|5>k!PpNO z9B~X_7@4{6Gbb)&=Uwsuc40>jQ-%8J0$Q1m#5@^8U|blzG}`>I?ESCA_JSM^>>^|X z4Pnj5$__&O@$xLL7QLD00BB%Kr7Xtg_mdBk75$%%J}Z zXw;N`TYbv9cXgNYkuK=Yv?50!KrnX9^8uCpRPxCd+KZTi=9X1XBrA+pE>ckfqb zyaQf)*A^OjbUtLl%E)$BK1aB&(bwK(=jj{rYF=L6Ad5An`xCMw2NM7x)wP^%Q-wmp z!Uc5imD&D&aA3$v`RUvO6lKG5rw^2LIgh!RsA~qP(C{c(Z}dC!l`oFC+2pMWK5TPs z+gSt-A+3Ji58&wr_N>6k!@ASw9y|+N`F39+UeX>G6<;!%1O)}}V`oz`M`(B`s_3f+oVn$8)X_F@njjbB)Rhz<47d z#xRI1^k$}&y?%Vb%&Ic}`TzEeZ&9zq!XQ^H24O!uUl7`zQA8Jk{OgYcN8NhS`Z>^A zpb=v>>C z1z=;$fCTLS>o}(ycxQUw9kMhM>0mkELJki|2&9yfkg7KOZ#_c`736UPc;pZS`lC%e zVgc;3VS3MkU#b6J@t9>>%#AeiX2SeTKOq_LCj}-HOF=$;IaAaW6W!%~ucsi1d9u=uhfo->Y(Z()8wd+5RK8U&gnHD#r ztLO;NZi?BkKz0fr#EENJT4q9eClh%4gG+RYfX+~F=Js<(aZFi;WP8L6l1dWBYb1xg z#zR2=a>%-6tKGlv-e+Jn4Ovzc@9_)r<(tPj&0C>F$W?@Pt{#Z;s>~G<1Hgs{<7SX8X^9GLk$k7!scw zi0o<5ty}ia)ypUMgCz=gZdU9v+04vX?K5&@M@=^PF>)kLwC=5`VoIgr9t~*^QBdwA zedt8Ait`pY@4#H3SXvZH!3ZHeS_kVBD8FABSBRW2>@xJsfi*$?edX05mN*FBih+oe zIM260@xfZJFBv<{I#6sC24=X&>m&D=xla%sM-@RFTEMXjK1Z||Nc1mrgT&k}3L1^x z)PfD)+T9&rxK2BeZSY3(oZdio6$BLe2FMEM*Rfx*(b4);e+6*G5RaX)wm?{Phj?8Q z$Y=pw2Bw0C*WTtZe93~o`zFN5b4Pr$66faTm>Xj_btD7sSop)z!VPFJ2^e|QB1p=L zJr_m{Wv)POW(Ql)0`qBRa1`1WLoTB25%@*d>i$v?N8T8lzCTwk6Cio}5E*%D~Z$(<^;0$yS4-z-S>(b98D*@(cVgTI|L}Vz~!^03x$-p+StAuNrb9tF8kxhj{LkT?}n4+ue zzlm)1dA;^b^gsh@Nel!ECfjVHofHT*kkOd1iFg%c)0PzK2RWH(xM@mZn z<;|2X_YLuXRv%LNsh>>$%I6X5 zgq6?v_~4bf<4h+MZ~gCs2Y>q}_q%oPpmdQhm=R%{@bu-=m-aB>sp}8w-35+*SG|Q& ziY_tKE=te3d|Ynl(NPpXcIv|J1_|pBK~GGNB9hxyu%+nbb`wJ_uAPE8Nz$ z!g!2I!q3N2TyPVf@bT%w)ElkQ#KDRBNY!TBpu@`Xk*mR0+sr@J_j%WHH zZhNn_XCe4IoJ%6yyd~n>wGAGJV#RMf_YP0z7pR)zQrxE4VI5>%&dTao z3`M*68Jt7uTvoU=T^Fjf`@VVarY9C-fLvz&&Ea3SPO*@*SN6%$)&q?0H%CQg6=@c? zUw*A1z=#w;?LqnDE0XYevxYeTt_!(EH(7G7_3ZhicxsRT!x7Dn726Ja`}poWd!hVQ zel6`+Zzd%++SSp0651ANdXm|5r`@+X^3E}7B)rjm&bCCjf>v%<*clZ^$?>w{^Ok#r zaUpgEhL;xxjr#u_-+iDvuqf|n;l%Xu-toE_u8sNIC;^!rGpmw&QO$o%<3yy;zFP%$ zLFv=jB;A2?cA{vHiZ5f4i>b|0FLVe8xx+>D2F}Tyh3Pl6(HLF#p=h@0A}U9q>WIET zPDnF`J5R@eDKu{f-(oXuZsLvI>q1Dq& zTe;&Knt55!+rBve!nAkI4)rEH)3&(d_P*40*DhXn{eee-zDk~dHk;{5r^)(6N$$|5K_ZyQ&2#3b^09^~Ivz)Jmh6qKUj@NDI z01Hmy|!V##kiO@lw|byGs?9fTkL2Q6N(u>6As zed~svcl->+&!1ztdS~A2?)8q_yG%Hq4Cp=4u%CWhSMcKY9;xMBFM^L}U#Q>nVlpK- z`7DQCx-o|~G5f{!y*jr!ZiIEF>SPGt%R6!WLfOGXqrZzjetP_O)tXw=TW-&*aTcz- zYL=@KBUH}3>Q9*Ux3Zc@pv26!34T3kzL({_X0) z+e^y$7;*EjFX@h9Z=#}NuK(jtlE;*1XSB)PyMO-mDLK*telZ}q=lE>RH-rfk1#Ehn zt1tD<TUI`rU&uWI#>y%a@y3 zrzS-U%nX#=`Sj;yx_?sOcE!f>RF>DUj|$erqX$j1)2RRQW%8|&V@DL$)Tc9=*=yt^ zBrT2Q3s{QGuif@VcP1+Mxi7X#PlZafU86BS1=d(^E;h=a(K0Kxj#N`q6F{>IYIaZe z7nQh%ws`%hM8N6e;5w_khEyU_`<9t-M5wYX{>9Ub8#R%lu0 zACIm`NO-Kxjj~hfWyP)9;&ls+sbiK6;k*=^oSYmr4Go2J%uf+Fe{K6;iD>Xetv70Q z6EGqrwmoXCu~M%E>Khs=yV|ae9kG1)kiIhLgk79y=cm|b>Z?ABE*bJTNdL90|!?>9pJklF0PH*ntM{SwQ%R$`SZ5*!JHeDX%TPUy~`fi z*^6x?`J!65l!ze%KDw1y~LvN_tp7JMx~SRG(60nLkZi$9VcGhvac02Ax%cA zoatDbAM2^|aSJ;w)45s|8DDz(+_`hU8?>zDDYG3zadC0mq4E!dxZ7LyW(_GfpCg|I z1)+(5^X5(ZRfhpp{KJ-aDR5AnoSYa^mFoKX`Y9RLbi)$c47f!pr(xr|XGcZ)+KO$u z+?RW>m1G}!P8O8UNV!Ye=D=0Qp?h%aM5JJ8@^#PIu;fGqg{~C_g@~N}`b1n0Be2|I zu+*kITbo2D5QltDh#F5S;@0NwALUaVa*%)%;pXPH?aB>XajF`1gj433 z%8YaDTpbCVRjh$FLC$NUC1!&%i_3y5TlIaMG}iUOviSt^g-?I&V@R5Z%6d*`!{?6w z`c>ZL=IRRfI+^dcwr$MSU|#YxY4+q*`*)ER!NI{pHn(rzK7aLU@7~?JBzzVuUMD6x zZmi7M3B(i^!`p}-U^^rE>Cg6Jhr!e4%Hk|^C zfH%qtBd&cWUKLcfhEQ(v&dyHtS{yS}psUQujO+$KTDx;}TaBEmElqLjmh60dP69DU zd9Ld93z~7hbY*GrNjMG5BUkY?f^W98)^o;?zfs`axftlgdgKZ`X9l>@4=E&% znt>M_I8AX0i7xe+EA?>s$Q#1RJs9?zt#)0!XhP}^ILfGAHASwj$amRxH9lJ4b$q;i zV&Jxqgl+eQGICEp?(0c*9-gVh1AOqL4Ql)=W_|eUzkZFCW0co11L)m3%6J5xTL0R2 zxFwWZLfzNbH)wIQzh)G=yycCM?$C^cWfO;l2VD=&iv7I$Wqix0CkMjBx5+o;reyrr zoEJBy3gT4w0=iYNFgKRnk|Z1kB>5ZD)6?HYoBB4Y$4WLT9AHpSyZ__(W#;Cpw2h4o z6rs2>{$*I06jW~_H7MzcriO;p{QP|4imR(@sKDYvvly1R6G`%#b=>{5gtJ8BkyG^>b%+uV;&%J}{w zs|Y!VRR%>F&n@SfTX6O4ZMh#m$Hp{lZEZ6aEiEigq8|ckFeI&>tD@2385tRjB?LN@ z@j@3bN`74%#C<)@HW|hvP3qce;}N=&TpE)gpc6}2CshIXCGqO|`ftT2)>fcM7M2Fd^W3yHxml!*m;npF8j%?Z zqsm)!_pvkb-xsUCF)OaZF(#Py$W22#sN(ah7SsT~g~i0Qe0+Rlsqt<@UHhP1a?;yn zrukLECrMNJy06b`5Nt_G(!<_q)YW{GO54%TK}}6fu7i{kyXf9rgS4tDd2aJ!yFO90 zt5%R+PnLe3AsYYeC{HGixj~~73p~k{cRFLG?7B%Fc2QALHc*}n-vhgMt&ZKrIK&@; zVMD;qu34k4szZW<0kinc2FXw*Y$l%lmW%3jNRxifE z&aO+7$2g3z3rR@m;=407cr2}~MBq*~S7zL_*kC5AK(QuXwf~;{$IWNiH=gWgRG~Ke z$;efhHCN1(4$9Z=Oi0&83OEc`tv_Ae`57W6Ds85tgE#e?k%H+q1r4=o9B5eSnJ19r zMr}*<@m-nDsbr9dUjrzB!~o>!pFb<1bv!*w)7#&dI6v8cR3}Y2Y-ogeI_dB6UA;13 zH&ATzTC{Td$|b{M)v_Vi5ZrpRneDe%f5>{zCATSV+Fvp#yaiZi7uK6a82dmX9I>{# zXakf+q3g+~{c%$O{rLJnY~?V^Id`H(W95Ac&ub;-_L=%=;FpHn$h7Uv*=P<~wy2mGT7@^) z!M*NnGSu>d+sK0$mAhFy!n8*<#8@R7jQNNl(8bBYVZAijaZ16r7@AkgZ)Mt*OPv>2 zI{UKNjjD+j_BtbV@`$jaqN!E9%QL@El{Q_O;}f-WpASdH#TkvewxM?X$+K;fiPe6q zz1btjP|S!4z=iJj_v?lt^?7C8+v8>2+-Vunqz2QqgXz|_n;3ol%t5DG6JQgs-TmdT zk`r=;WL!8|wXJ>(+QL{PR)TL z;5E4pdJnP0Z`F|#nrqhjv)F+9%m79No3E#-8Gc?fKHH$^p^$l?%quXmODpkUf-NBeCc30}{!+;{{o%0d4%Ts;P0$$h5Fx%U!wSG9S znkbLPbJ68|X7EBvf-)X1uFiJ3anv}>Q~9nvg@nPo!!b+&C%g-+qbD8x+I5!BEY`-x z_ss%(?MW;n4Ho22gdq?@Z%=vRSj+T;QoI#VruTZrh#er9Q*okdPjgdK=RPjuEY>ci@1tA`A=+@ab>fa3tV1Xbj()o$bwC3wvapMt6K^XE?(SbqdC zm-}q{@)JOyWX{%XYTEVYY8xBJALCQZ^%@Dl;)fzWStWS9g>if0;=N>;TT@kwZN#OJ zBMN>i-LJJ2b3(*w?jeKhVQXwgcefr$Aa#wns~CU(D${^v3<8-1v)Y=s&=*#TK3}_( zF#%2AzO@6K_Q2ypq=;C}T55Xx@tbJUCZkfyi(yQ0t~+`PSX%5=2joO-Xq_3Sq-@`a zRxfo54`z#SnR=(Bx67S14~1LsCr?>zCcN;etA;6L+h3T0yr`u`y&5`RO;vSn@B6XB zX3@ox0J|m`ledwPDI)phN&r52hQ-z?APXy?-7W3E)Ps=)r^Td=oiKOG{eK zCPVk~Nq3Z~ne(T8c%`f}zN?J9r%(5u6A-wdpkTtU9NHcxJ)8}dI?1S%rP!MWCJhjbG~Pxy?fX zO~d4Z>$%SrIt*U*TDrSNPK!{am+IcTH`y=x`1;2qJXc$WfG=l4n{cjlaVJEAh&#sj z#sY|v1KjwOVnKTP7h|_pDPUpp-tWb~zJp?nfg;gLig(y<5-BKO+@J-PPfy#kAKWWn zunwb69p>I~#-?3m&t9^K`6ZVjlQ%^@gO zuMKDiO3hQYOOod|K4`~{eUN<+Iv-fK4byylVuGNZrYyU&wWuKY5%5J1<^YIx>{=ut zJ^j|cefwmma*I+;vrkG|zk>3}f*AoS&IX`?G?^MQ6u~hXDn0-_uT6eY9h96reStuK z?KVts8T-m;$hC%!&H}Gs5=QYQY1w)w9*eqP6W-M7 z!Ya*dBUMH1w^=*3-{&;g+&u@hj(z-)rPvq3T07UMjIBbSW;jNGNa(0XPyy%zNxWM8_XD6e@1oT*y1h1%>1NE$?lC?huN%I zcDRLEt-H^Z@$6%{W3YV87n-_>OvWlHea>O9vLxvjnC1a!<$&LlMG`^D>hF_e`EgCt zh2>GkL2H)x9J`LPygi$|>!VK19@)ZE%iskpE%%vK*6Kbk-`LWBo$nYmP0H6f8kmZ) zM|B|6QFsEZ0a;peK~8Ly{tlPKggY8)DWTM9#FPPkOLo%E7S*uYw*-&JFHIMbNh7U% zm;ZbO0|@~=bV{Wz#ifCFW3_f?W>~NyKNnE5Q|CI0Nz)S9VpO+gY9<(tbGe0z+jgeE zgcmJ?*y}N<%r=6vqL0+p)@*OsF;@e3R?!9%FqxN&l9=jn$GZPG-VQ#55kvXTRT6V$ za2a@l9f%MNu1I|7Wy?vU1ks>llL*BgdIHa`7bal#A{k`{U7=vf6S%QNiyFR2jR@RI z+n#%l>h_hns!vvo!W8n$lyF(KywkRF7+8z#yHy-KBRBvwDGy{mtssy|^efug*$aQb z7#Q>)+Vm|R383GaCVR9lvI3~Kxh3tTZgX-OJKrc_>=^Z(!L8jtdP-6ikIXGQAfsw` z?sWBF%`W9^g^}%=dg=XH1U1j});Q_D^#F1EfL5Z2Vf_kwYfk(ELWa^s%Sz+5Mg@ca z`5|p5%@Os>-w5X0?dmig;8$o>)yqz`%u=~ag__vn4VweUPG63YUwGlykpJgp>oeXz zcDcbYT$PDHZI`1pHLt=b%zqgXQBeFgPCP{}0evOOf8nK-FQyshODw$E+)xf7Ir$?3 z51D~G+Yf(!dCA~y1xBg?EM*-rU2M!wH#Rmt{PNs{T%Rp$x85^F6vj_dmZ5}>S z?F6UGpbP?`^7;8vP~M|e){8N1voqO|_7F^tOpmEv25&j&zSgo4oKc=9Kj?|LNPZQP zSHa=^PbNT_iUa)>hc{+WDu4g|cX_EHuwo`FL=51$H|LcV9ShtVDyCk&di5uG3_p8MO#@nW%Q0J%P$L4MnK49tP`gK7zk2m!rGwVhrG=WQAR4>6 z7Oq37Da-)TVmmJ-)nn2JpA-jHV*~P{+8yRB5Y2n{tdR?6de|z;GQWNMb{PmS7zscq zn`R^cq(Q{x8>c*=ddDjUko+!!aoCt^T+vyCg6V-Z zS$A_e2q)wNx3|>fjJ2<0OLCtaV2dp;zXmt=Cr~2$P#N4{BT`47ZL4uhw5|qSiz{2b zL8ArQpt-1TL)u6nBNxB%N4%pZ1rApyj3GAdsGWjc??wK<{eddrB@^S(W>=8|0O8@U z3(pm!*He+5{Y`%T*X#2qk076i7<S}4x)G|bV0NvQ(DS-#9o@^h% zyvVPZI0Ya|euq3lvP&-X)zNs6A{{M>3WLbSx!JX$U!UiudH%2sEh$K7gY5oNqQV)xph*d$cTw396OiFb9Aa3(W14 zi8Pf>{TGP>#?3=8nHVl8`|p>{)$1{3b(m6+!+e_OwXT!jiPyEn%XBeanxgvzl9~D^ zJUw54+fKngPuo{KVi^Et2~1^a$N2TX-S?P=Gkz@rxMFxHV3heV?pHpjXN3cw>TLyg zY*nnl4P{OBX(I&!uW&Sy^PGkndnax_WD{B70c7`ONQeZS$IAh}+8JfMa6a8$eX<4) z)hmBG*n;mccB8ZGz44N z_!OrZW}_Hdf^bjU4I}OAD*8S(V7AI{{+cSNHD#1s`T7og6YR4t&kb?w_J5F< z;F*JoH%q$%7XG2VyC1GIVkIu<599CAlGthiW6Hs5tDl<51h+Qq1fpZ#!S{dDegQ^tHQt_8)N$ zJli~|r($6_3XMyHZe7+p;R&a-%%E&O6`KNsr3;vhx`0c8URlLR3 z&>PKW+o>?hKA1Nk@OQ~_3GhJ(3?sJ>QpXQY4%iiV1bPvyudRxN>lqv80=Mx1ir*P> zvBv=`Yue)ju+}3ySOV6}B@>d~;S(3zk(q($4xM$@p-4=f$nU0+0g&N1e(EF`1LJ1w z0NVqA94K>|#XmJRt`G_5U9ymafJgQUwE0GE`VO( zVD-$zNd0OhUo`{U4}qtsuRN00$s^kfj#Ha%Q;N2;U8K*l<-&#}bnuqLc6+C!~YL{7jpvMf{DdBpDeQ z$3tn(ZPuYsuEL_Cnus6cP)5m&62XGgp05M>(LjkEcHFsuK#-pWD*)d_$*nCNl-}}i zU5T;gR|g*~b7;tz+g$i!`tcbL_kjY!+FwZyiE(i}9z1$Wr@B4Hr=4-D&tKOAp*|(G zu+{zWq`HR4;iO5+Fg}#2PPXF@zMZuO+0ACZE)8j$`JeR+xiv=$N$C7}Wke%xtuKPd zmeJDEV#xN~HP}E||7rf=D2zw=#4Y_F`9$G)4B(RXNQzt!DdCkfU94vQD^F8POE)sC zP!=Vlr`IBTJ~45&b7_SEnaRo5!ArzO#j$4qzt`?76E)jYyTxD=ypy-RbZhhKE8WJj z(x@O}-E868iw3D+XV7R_Nxi_ltiV?E-JP9kTOAY>v;mg8$k9ch4t-6ntvyNGOM>VW zuOyud){tRLoXB?1?9&5>i{r@tWvK5SLPFCmBK=~}FdDILYWr^QV-SP|u=!IQUzRK*QLZ$XB zj5m9L>(JLD}=v3H$*M07yHC(qgYz2Kz2erU1)V$?Ns& zxXon)T%6vS5u%@OOiT0&St6P-)$o$wjjyjpUR^xv$&;3#bne zHy{Y3nIPq0>$7xZ%zq@5Dwmd=Zah}iCXfQky5 zB~6KvG0Ox!$(DSAr3^M0n`b!QrVH^cP&@_f20wB6lz9sPqe7av@Xy@9wH`o3LER`S)B3t73n%fuR4z#JW9g#<&T2X7xoEW|E)656z*0+>?eS2=*ld`!yWd#Q{=3{DOlT(==X>P|aQi|=)64K2) z*=RKGLm_!EE<6ST>$^<1H1(ga&))PYZ&G%7%H|@O`7_`lB0M8`J5~~`Z(1j4Uje7( z!QYcZZfvj)877SPD60{ke+6Q~CS}mKKF>r?u`{oQb2tQE{kj9AqnnW06lu(~@BP;< z0Kba++>aY%xtG?QFNj74K1G6bs z+UZP*j;{v>(^HX~s}3q*a zT)H6g)yn;*gUqHn5L5SUNU=NJ$Wh{7vu{2>hJ2CQt>UqkL*jJ^^!)xFb$5ZlikM1} zYvdr3py*ck|aJCgIm@B+mS8HzQ`BKkndlu&j3=SB!N&pY%N-Ue@_2k|PaK`9wl zd4EX6ZP-9Wxuxed`{s{i;*X*6h`-)l(*_I9kGVxft*TFf@`9UP3Y^gRgruWng;noN zjreQYY>I0?gmxs|gnw&)w9j*PI1dbG!u`5u;F2q(WbJa$gXIK7D}Q{v2N85|Jf>M= zaXY%dwX^&ww0&#We-)w2=>wR@+gf?TTjrhw0v~mJ+y=%ZgR)PH6UBUytL793ffdpu zG#aghG$TfM=<$qOcYFT;PR1;%_d|i&vypbbEY0GAiNqtY%}}s#ezA0kf1vN913osE zeg+FOiJHS`i_rIwxF{mhjAV$ve*L-?;NO+0@o;-<{UKwh!toGkAiGB>e2(xLNr*wb zg6!>q44x=5zFSbOz_i*nfTt7Akd{|A+?lPN_9iK5R*2&fiStrjD$*Trm-&ca2;qOH zV3NNMLq@`?t=lr%BomyV9H)^QTMDTJI)B@6xj=sxZysR|=2Fg zGgoXhkcXu+sb6yK!+uq%$#2-8bb@Kc2Jj@(xKmx?1g}^ieJU$AzJibsglwdWf z2kJ|@r{koZJVR2{QkqJ|s+aKMnLi1ikp%~%%aV{!fm0`e+gi(ZqC7v!^ANyuG-g6? z@Na-<%{g+~RP_p2WiuglPCY*nK?ANx?13}hA5F^E*1U_HM?b5fY}aOi5|*tWtx}ZLCp&}PhwyvG zm{g9xsj;y=4#p-icUm5XV42!gYHW`cA?z|($-Dq?`QcR4jdIeZyb|EGB*3n6DZXiUq zL&8`HcF98CBR(yTO|QZ?tXl|!nPl|FIdlc z7J~|`iMf3Lugc-J$9{svxft z;rbQ1wZb3!O}I~!MP{|t6G|t`_u%<@{jR#fIWK1i@&NgJ8}^6d7y+`V=Lm&Y)mU zOBzSiklTeQzD?6a{(%i;f~^UC;hM_;kuh(Wz*I1NhJm~ET&YkM5)wjkX0Of2)Zz0@ zITp_2QCMNfO(FGdYHscV?=g)JxAw=tML|7qG8MdD+jVD0;uhmZ%BAS>gnGdJFvAjW zFs>6Rc)olt+W0IQ(<_)u;0p)y$_9uxypx+9zgh#)Sk5y2>=e7~J{{gRUys>g zlfg2lJ`iD1z?z@|(0EUv-Zp`}Z9b*I7=|UP;SKP5`L6ZgmY2Y2DBE4`hBD2PZ-+$` zzLIL>&}!)W^?Pd43WL7`aUpo1&LUCW2yQbEuzu#PUJD_@Em*-{4IIdSIq{<2s-MLx)8 zSXo&mAy^DMqhR>q!-rS}zfuHvDg%4+uA1~D{MgEO(&){|4}Trpn?V@sncX&x{_E49 z-F*z0ekBC+lO7_ntH~x{jUt2c=g$YLVnaFQkIQvq72|a+%OEKi%(HdDFz1ZM2l`#k zN@p*IcaG-vfyo2Z?L|2q?k={`rMNa<>YS5;>huh z@o7F0{oFbA2;(GG+~6uPlj-~vn}Mus!3w~bkk~Sqx;HB-DzvG~M(gs%z{Yw~ZXTfE zHfMw{T*&T~R+u6UxO?i|$;0jSm~BU5-?byyUWZn8avPN^*W8qP}uTLb9}vp{AEh?^{+mzeKA46G6|uKy!>-{BM4;AhD~$m|Z? z|JKhG+`7)LE~y7!4pO|w!jO$Y1`vJL!RqaespaRWts4lHAF6Oeo;tGW7O-W3xs;#F z7j5DhQy|<2^sWNRE*(*Q5FKN|jW70De5jG6SUt25zf2!2D}WKylYC!q)UD`+mnDDB#YXaE-85PLMh6zr6Bh~%e^#kp*Vga1S3#KAO4!<^b~ zKyBX_h~jy|-R6W;=5TFbEjO?~Ig3f~sV>8_cYb~uQ(_u*lrnF0U*YQ2(jo6o$d`P+ zWn=mdJhYg!r$KdX6n+Nw!^_825-@Rsm@_FlaWe<+9e5g9dvDo1G6~HbGC52wf5I zG#2aFQ15`#>)UYyozxDxnGP|A2AESw?kU%jD-6SJ+_Vqy4RP9$W!O=FP&-$Fj^;1P zz*eWH_wJ?$dNGvGhPIB*&qID$3DeWltPSsY*hzsmEd#_ZU6S%!Wk>-!F^YkD6+x6Q z+b;+anjP%N3D2aN;d+sFb-fb)nm$)l+gAb??Xzv&`)v7KfnlkA&aYn{PO)qkr2gXk-%Hz| zYj&SvxwxEmcoL*1x%%iimMy`}j(tmDgXf}l*2!5U9+tayDAJ?-kN6HF{}iKz2R|kg z;u0ESWvQ!&f`DLD?6ZImMZ_KKbyY;LL5}N|wgl=2CjK3VfnsfB^8%tm_$m$TcelZ{ zI=<~6He<*lDQw^<@FMmHlK23csDnQA8Z?02WvUN6ir%@=I?75ui7t%H>2duzq+u0S zhsH{myTBxR^JZrEBf6{!9&&rd006D^>IrLgu;LhfV zFXTGKP9uuQ;uSt!#%;p-+x|#&26{6YSpb8@*^VMt z@VH2{%s6-0d6{e!OzH*~D-T>(eS-%FSaO}GiNl=w!k)sv!U04jn8@az-aXi`k#KQw z(RMPrmmpk;Bdp2x)PhQ%wV4H6U)HPN2CNEAHynA*$)XwL+c~dRk7zewsS3Lodfyyi zcNeycU>RE&G3ObyFlevxo-BkNzP%FM`)owG5`&osUX_=A_x6}Ge06Dy@#YqCf+sE+ za+GB9uzV*ZatMbZi*tzGghZ10)hmXG((9aOt$}G?n$jQf70Bh)V zd5R|5qfK=E{yai(0ef*|*?8!d0&Ic7o*M8JI&54bTOqsl?AN;|ag4=YM~hIU^TEyW z+NDbxkOkZY%iUG{NI+0|Gk*u@H6uoh-GDi14n z?XYMj5JZ@f1zSHI78>>J)>)jd#G7I=9%LyoVC-Z9kRVeCyx}fjw(h^a{f78vo%8fA zi0Spf^nzWrn+`+e+OTe%kYD~YST?UP4)(dhyHBo}+goE*8!GWvNY& zL<2UQT2@L-gKY&lFGe@42xfu_11k*CewtAF2zo$N0F(mxo`MT~%8ritpwlv%7p8z6 z0`|PJ8uGLV1dgKz<({2o1*@ak@YMO~lk6dAfaWO&EL^x&zUtQooCVU?fV~*oTsPUrZ`x? ztKLAPqKgf6@ab0Pj>0|=3%U_Mab}b0glD{3azf|P%j2fbIS_x%C#jK&!=n$eWc7Y{ zlqgKgxWMN1b}1!&@tjALdw(Mx`;>-H%=tBsQro!6dy(pfW(rjE(+INGY%I2-KlgJ? z2?ULFv{rc4@55iw)Qm{=z1Nv+zO!?$^ElcDpRagzW=qzvZ5KKQf2>d2kL9l0Z`5R? z+gru*R|6{3Z=nwp`u%g#V_2(s!Xmrxb>`s<>k=0~eJDk}a-aD?9akytl2=O-Jr>uy z^j)4mf_cWxKJh+2K5nhn)m}#A^MJwy8kRL7KSsc2?gLFBLct3y)yKs`4=0B_RT0RG zDz1%fCBK!to8tDxO+e$#Qc(JeEUv?4=R>K}8~4(5s{2-gpT`mH3l^1VUz3#g0s5T^ z4QFa^rg(kbBOr6G;0&ra=?^?kh}PGTIU+#w!W(uc^Re8OKC9`{f?wrO@k3*~Q`hJ5 z1H$f_eJ(GR{@AEc;^O9g!r>cj#BRW9l6zXnM(O*~S@dg8*;?FNrKxCtdqXa?u7JXM zT5Y((&MT!rJzUn^KlgmhVgIdt5C56OoY({IAC#zWl_KuJ7PaZ}ynJCF_kf2~*0_S`e7&n(yNAv;o zz@6TOcEO(&c_f`%U+8&ev&`ztQgXUCdi3@lXO&1Zur0AZP!Pn?b zZh4y6oD;g8au{dHYVs~OtG%4T&zjKtO|Cbh6wkm~_dFMI-^h5b#C0oy^vs5}dQXJz zWW-^Xc>iVj9ld2q@*=LFolA~%!TmReA-Yf1SE=)GL*nA2bQOV&J0yb?r}M|U=RBQvEy}v@y&LP;Z|DkE=6&aFQ3X*U{gfYysqK9OvwD+5OC_uUA-}U| zh8YgO-+7_Lbt3Mm#-JEY-AAk@O33|>*%wM98^p$IR<&y3m!>3xG(Ws4kT)&4$r{EY zqf#QrW)?8?p6}6*?S1^j`(+{{%GuT~Gh6 wr~mKk36*yi5C2bq0`mU?{P#e%d*^`C?Vgu^WswfS8?m5oYu?IN`tz^<1!!;%?*IS* literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-003-manual.html.0.png b/integration_tests/snapshots/css/css-animations/animation-duration-003-manual.html.0.png new file mode 100644 index 0000000000000000000000000000000000000000..2391950c7419ce3fe2c3d8db90414974aa78e1b1 GIT binary patch literal 17652 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-004-manual.html.0.png b/integration_tests/snapshots/css/css-animations/animation-duration-004-manual.html.0.png new file mode 100644 index 0000000000000000000000000000000000000000..ff151222c2075d45684d8eb2e1479857c172fcb6 GIT binary patch literal 17542 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 z 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-duration-006-manual.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..8dd394e1549e75f1d3fd64821511ae4389a7f8a0 GIT binary patch literal 12037 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-007-manual.html.8475f5701.png b/integration_tests/snapshots/css/css-animations/animation-duration-007-manual.html.8475f5701.png deleted file mode 100644 index f59ba0df8eeb0fb83b76ce30ca9b5129b8f44513..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15606 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 + - -

    - 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. -

    -
    - - diff --git a/integration_tests/specs/css/css-animations/animation-duration-008-manual.html b/integration_tests/specs/css/css-animations/animation-duration-008-manual.html deleted file mode 100644 index b5a8dba02a..0000000000 --- a/integration_tests/specs/css/css-animations/animation-duration-008-manual.html +++ /dev/null @@ -1,38 +0,0 @@ - - -CSS Animations Test: animation-duration - ::after - - - - - - - - -

    - 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. -

    -
    - - diff --git a/webf/example/assets/bundle.html b/webf/example/assets/bundle.html index 26dea2b7d9..6dce13c8a3 100644 --- a/webf/example/assets/bundle.html +++ b/webf/example/assets/bundle.html @@ -1,37 +1,39 @@ - - - - -
    - -
    - - - - + + + +
    A
    +
    B
    +
    C
    + + \ No newline at end of file diff --git a/webf/lib/src/css/rule_set.dart b/webf/lib/src/css/rule_set.dart index 17bd435bb0..4ddea6fe37 100644 --- a/webf/lib/src/css/rule_set.dart +++ b/webf/lib/src/css/rule_set.dart @@ -10,7 +10,12 @@ typedef CSSMap = HashMap>; class RuleSet { bool get isEmpty => - idRules.isEmpty && classRules.isEmpty && attributeRules.isEmpty && tagRules.isEmpty && universalRules.isEmpty; + idRules.isEmpty && + classRules.isEmpty && + attributeRules.isEmpty && + tagRules.isEmpty && + universalRules.isEmpty && + keyframesRules.isEmpty; final CSSMap idRules = HashMap(); final CSSMap classRules = HashMap(); From 169fa5f596881a118ba176abd3038e05d5b4d1bd Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 18 Sep 2022 16:40:20 +0800 Subject: [PATCH 324/498] fix: rule set isEmpty --- .../animation-delay-002-manual.html.0.png | Bin 16699 -> 16725 bytes .../animation-delay-002-manual.html.1.png | Bin 16721 -> 16874 bytes .../animation-direction-003-manual.html.1.png | Bin 18794 -> 18906 bytes .../animation-direction-004-manual.html.1.png | Bin 18688 -> 18677 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.0.png b/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.0.png index fcf3b7f188fdf02b4ad2adbfd47f23af35f88173..43f0dab602b58aa0b375154f499d09e60df14551 100644 GIT binary patch literal 16725 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 literal 16699 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.1.png index 9a6b072d6fc000dd1b64a4acb64dd7a1a72050f1..75d843c067699937c9b81425f57b49701e17f469 100644 GIT binary patch literal 16874 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 literal 16721 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-direction-003-manual.html.1.png index c48d4b17ebec2361d09245f4ee5d8151b0a5d740..66e31db5ca89980d6b5ab01c94fb2000b42c56d8 100644 GIT binary patch literal 18906 zcmeIacUV*D+AoYcmcfDz1r(W)ra@6eL`pzWgisX-y*MDf2}o}`4i=vdx1OAa|M<;A38S|M{ui+4 z-ZKu4KR7Tquj~0F(kT7`M|;>?KfTX5)Hvz2?{sdz(tG#SG2_*ghsuv%>m(are@Fhq z^3K7dM^i#?S98R?*rRiE=WnN9I@~;TH2T=tofjUKC|^HRp}dEA|97E(OjKLxk=9S} zUtKO$a5Ste2gv)_iAWaNy3prZ6VXXmt-=m*yp!!|eXxUrqvKFJ$KA8r`8dugUrVxS zh!nUW;XGLC{2J|Fdg;=o=(sqlBU&Z$R8OLe#}dVR1nYT3EH5id_4DV?w&NuX24i`X zMHdqkydmc`_i~&`uPE1HJOis&Z?SC03G8FA^hH>zp?!?N&Z+Hm-F- z+9grQzAxX5bJycLajfxI+T9uI$7>j^awLnoO?!JsWmvB1l|k3Ow%|<$+^=iRa;&?% z`_kxBftgqnmnb1xf00Vv7Ms5OQDAeSxNP@Ga7Qb(%NMPmBxxHF!7nF*;Zq1G&(wUA z86##eljzYJb(Axv33cvVcctfSaq!lr8hMsAm-)t}Q&XBUo>1yVGtS7!_`J^+BYXe* z!@|PC2jgF&bSa-u5`pVWl8cqC4b78Z-&A!Cl(~{uXoE>nE8pvGjIRaAk{cWp*BZ6; z%3REP@=Oe11)_z7g@r4+f>u8Ypv1(~@v@#edB!EM{>13$qcw|7Vg&BMm44fq{vx7W z8`i)qgI>$h+Iw8Q+q2J16vKDv-ZLE?oy$(&-e!gK%KN;1E$y1WbMLV%Y>s~Dix;|Z zxuvPJ$k=bZC$2c%4hX3J@#6<_ZAWsAK4bDLqerB{#I+{l0KaVc?AkIpm}V5zh!b`g zuCAam1p=3%Yzc+EhvwPL5d$@~&|22K+_9$MQ;8 z{^Pea^(V%mFCPzgRDAdSb|&-96=xl&b_%<_+Sen`yk?;2fMNp~?#=JVbJ3-_qBas# z1K-8F59!u|GPCX{<-9c76t}8fYS(OFOV27O44Va?3yL6*wWr)r!(ckT6<7&lmS=i& zKR($njCm!htq-?#KtRECW>&eWxw-4%wjIdQ!tZ9og=e5bpD$~~U%amQ`m!6w*Hk6Z(s6tLhZp$DhHcsriz^!pg9K$c;$BbGw(_!43P2?Pu~dG&IbX4!U&pSp?HI zgIFsxON>gGtY>Nw+5Pjr>;M68YsrV7rmBpjFtP!lLo~i1{Z{-@*uhnmTJNu z&6hq+YQwsrW7SvL5w(FVIJgU=ALoldKIMAz&p*E*=N68d#;sdVriUtuqV9Eeb%jgR zpOp7$|LyU0IE8|*2n3g5zuEJWJ_~n?{FyFAL2N&!!rkWB^IxAG5^UKm5O8ogNWIL3 zGQ@hSt+1Ak(bkqWt9&4V)1bEE(TvIQ@%W!(4R6YZ{hF(x3`NeKZB)H9+nXm=q*>2X zWcis>u<^m%w~l)@Hm}3N_Ay{HD`&SR-?)Ye>5|RSl5Rh4B6q+i?cyYq0Cm18e^=hYH+3O#^UNK=WtGS(0&UC$%w^zEWxBeG&VV_T<+i}39AWl3_~ zW~J{;HZe@73wn$<;rAmVJQ$Jl=f75OEH>MGdUj|YeP5B8yyMZs>4B0QWX+JZi@JCC z@ZpvMs|I;S$jD=tk=h!Jn%ZJ6Y^C!pobN=8K~vABs`Egx#G`BP!UPnDtD$$<%zZ0B z9$}s?_h9O-P{KT=>J8?VNJ4`^@DO^pfd5>9dszpM)h3%B{HF1h=$iyd+t+`6e3Afd z8Tm4JSlDk5f~ctIo6(O?-^|T9S@hYKr>34hvTxVd8s_kDcP_#$qI zdVQ4YVNGsWL`f#&>syl)M~d#&_$_O|VueZ62SAg7{x9un-hI6B>sKipQeJA#VMp3M zCR=`LxOQcRN8N*7cHE<1Tan$Zddao==M(Z3^wtI=Bfkhn*ex8KkVrXa<~-#V4rxVX z&zqrM@1eUTz^}zs4psXy!i0(f*4({cdnl!6WF)}7di&9bYr=(eZ{NNf_S1VKOoq38 zwvX&hTr<0(bMG1y=fGvV_T+BobL!(i!!r%Y=E8rMp59jD%din_+{bhAT~U6+DgTxt z*H<{4hNWeyjNek0T;!3HLEG0mR0Nha1YmO51{+*XW|u1Bgcz%1kt%FFgU_tWYgr?B zxyL|^yFKa&mw<~<0xX|ZOXC<8N>{szZJHD{U&Azqm82x8g!5T%fArA4H#dR$=iSIt zXnBS*p)IE3RCA7>5&dmno{1=CeXT7xKR5T>#N;GOTH4^axlf9J)%OO)&4ToH^xTIJ zN)(^5@XFO6FPe=#HR@Oty1XJhn{knCo4%JjOI?!Gdu6UaOq zAMe^9P}6GWO_^9*ldf9}7qTC#4<}Z$4NTpXpHBd=AoiL2Yho~%%86JL`L?9mwaF70 z9q2*m=3>V_Er1R=_f7i97G(I&tmNctxMJ((W0({d$I(h}dOn4Jz?@UM;DiT zq3Mh`a~*hAA*NYvy}^9sjeOa;;C}14v!bHJmcELmPIa`RT#f(A zJjEo%OKziB@wN0wfG+Hkr@t{YS&c5Dc3))hh81&QFa6O`nr$6`fSzn!QEM9;HCf+8 ztL3!_r=p};NgQKg^_8|l$LHe~xp18I0JgYoC5syutJbaekfUQ_GK!0fIh_q)6)XA- zdiS+rmU;{2LN%1QPpAQ$)r6tc*PX2!6BTu5ewVVp>W>Z2@W{x% zlp9aGa||w&K&iu%!+_Q`Hz(9WBR$+4)0^qgLz;$rP4k)`%7(UU3_V&eNg=Qpz!fIt zRfTzt-$1ebE9f0W=+|NwE@+#SI&~ype;knaaaT~J1@^*;`P3KUod6Y6tr{Yx(;^jg zV7(r&Xk{w+GWiCkUd1?0T?jKhJv~rh^%PL%5Y4>GEAQ|3$>QbTw_`frKi;i=j$T&Q z`Dn*(c||$uTF7y8!-PqeL-6H3Q;%?k)$cp_uin2VY1`TzXAzVwniQae8=|2fOue*C zSX2n`++ZYa>Dkz1J~_Zof+Nnn%A#!>r>=)RfQk~6kuiF4{6g=_hs6kL1$y6gDu`OR;Yrq*0lJu zLBK!At2V|HrbwlL1q?*Wt6Mm@xySA$jid76UVgWohO3Qm51^gRR?%y90py9-uKqYo zpJC^wk*>1HZ{NNhYK~>DVtWcZ54QDC+Bb5g=;0s0Xi^GUr-+xkg=^;7}SVzMpF|#ime(Bv=z50uyTiJ7c}83 z5u^+FxzhDYqRDCXIPv9{D!Y!%pvS~bv+Bc7sIc#iXSFOFnp+S5u!G~ezBHXCN3Yg}v#t-l z9x!Vr0fN=Vxw%sV;5B9w^?P+CcxFTq9j*6SsAAaOQ!_SoRW8vB$_3p8=%D zz9n9Q;2GVM2@Pa=_2*b6lyQk;pLh+NNt}||KH=#{yHB-;@k(|B3qrOLuG0>B0h7*Gs##ADYQDoJ&1- z_d?N!{7B~xSQx_bJ(Sd<;*`|eOd9GiVHdtm56jb|UAMcN<^U%U|OidF3 zsO7>CL~ks71e!J9Fbl&)os;5M4ox^FF)??1{GRo_ds)DR)+zKLqUUTc^1utZeXgvJ zF!pA?N{@6)^_iv>wJDg08RU15Y;AfkPj{){qH(xXXwmk-kbt_z@?CMtf-SpMgQpV1 zTdyGCol9}!Ezq^g9+Mi{Wwo8F6$)t;)0uCOZVO*o-j{Q<(oE*T)NRd#OUgH%9yq^T zBIbdPHCa9K(XQrdm;?f%GND_zNFT;eoxbcnZ)z1L0i$(gBj0VT-saoezX+aJ&2SG= zO^h$Mb}b#Z2C;Un6GqhhD=d*^VC3vhu#?*|E_JfMBq@oe z0mSpJ(v;akFc%b109i1xOYC%Yjv>^@G|-Y3>yyl3e=V&}3qBj5K8EbbB4~j3W&3I|U8na1^96j}Uv@9GJeYx7k*!XqACF@Y4^qpMB zP<$b|&2Evwp!gOQ77k5=u;D#;c(|lJeyZ=5b$-L*Gs*g`hwPm@cQ!RPcKA~9MD+_b z-5xut%gYU66v)oz7FhxV5ySu(|htuF2KLj1JKe_)6&mo|q(FId!_NVFD(JJ@6j{ z3nbsY$g^xJ5AzP0P|z{;rc&C6Yy3T1WvDmVH~$Qow@>n)uORH6_h-)cAm0a|A$I+b zVQtn&etDnU%F4QB!wOCsu6lT{R(xZ8=wKG$}v20i0$&ys&~Vkd{X(eeR5=$gnNb$4G(aN*=g zs$HKEL%J4BsI>%_reOXY(3%(!a^YqG$Vy!4BWH!q@oYraZ5J3ITegw>Pu_9;DRy=ArR6%+5oaZ(}hY8~}$ zW}ZD*i8_DRBA_jCYoAYAg~5!n%*f1~0j82uxSZ;cK_un?97Aqyd24fIOYS_`cj5bW zHlWO|-aHfTJ-;n|*n1(G$FA`f^vTYl3Xc*H9xm1O^UUkngyoUcQ}(V;#94^{1aN6G z^O#Ga9S|G|elw}yv7&uvy3X$AH5k~rNI#7U;y;mNo79Nvk4hYP0Yovs&~u=?P3*Ms z=M`NVt!$(hes7ssw?*Nf+hQf(NxKDTTfy2Qa>ZW@AIhj8`L47Bb(bF#x;kdi4TWkt zwxwSJ#Lbja* z1(9T8bX@=ycdb{(OayQ$VmAo7#2*3q?)5y3?~l3oFq!4$S7)B70h5#j1ThIW=S5WR zoetGkqyZ_Od@f43uQCT6dlCqb6coNY)fXtRhJypimr3!~1SuxFWGL+S@CPD|8)E`n z3mO+@OGGr{?*b1%3t}gz)Hk!_y5R@3fu4N=3Pb!I=l=Zjy%2CpL@vuH{T%iBwZl4- zPEY8CA@V9Q(bTfL?AUqZc)(12R$39m()OJ~-b+_5FH25-2kYfOFxwsQU_b({(v@o@3A)17`^leVK&H_EMx=fe<(1z6?7pH8Q4sIx z=bKK;B;jy4wLC7~+4vJyE#Kci)`UH?9S+%h{B?2h`}20($2O-nWFAp4V+Vj z=Cg2VWTYUQDyR~{W2JRKuEO$jXnc`PT9bWM>JxXsFIeS z_GtgqR^%>E!|snPX=^5ZId!%=)g5~4R}dD4_FjY$hEyIPrut%7ReVE%%~w9@`{sVr z!jjGdm%9qARNC4T1#g92>}*uZjDR&kgbnB)>WNz&BzZ1i|Ej~NHl$@;bNh9%U zM_?dk5{~|vf|?Ahg!jPcZ){KuHUIi6#lIE#4h(Inht>u=6?ZJGKwd z9nYIre^b2#Q&nmUgo7|4#!{Dd6+@hV8z=iRL&^vU*KirC>6%n`gsz(^{xb zES#|VkSFb1!AI3gi0UMF-rYh}L6KHju}iCTsNOkcSg08RwOOf3#)I66gIqAigpWquGWMzPa) zR8On&39)3s9C_wkf0u_=?dJNd@JftCJv0j`n`?5Z;lZ@xG?;ya>F3X%Bl=)h?B9QH zM@s(1vGYUrtrbU3RRgh%J^rnK)!*^^a1%cfAEby=$yY^)*%*uXvaYwClWOGG&=i}@9 zPst-N#N})nG>0>AI2doG;2KX(xZDr+BH3LX<5odIS2myd>p5y|2-m?@07Za@`{YdWcg0@W_P%k$}NF?ySVr5>M6F@ z)z*GAn_tzc+wKWc4>UV{P%>@Jw3cUpK~IN@<=+N?1u8COL@q`VkddLo8?~IpbNJj? ze&4N1=swm?&eNy-rv7AZ;XN`zyCIY&dJp?n0#Y6to7nT)c7pj~xbQ0>?UcK=#ztnt z8z&j?DOiDiUHc7?XM4BUBU_(MZOe1WwUsyXK7}rNC?skT#5SNo5T5=MlYQM_xamVo zc9037Z)T*fu0B$L3^3n*raOBgU-s137z6WcC^aZvLj>`Hy2S4})qcHhW8y;0%a_T( zi?ZP#j>r9M7BZ=uU* zfL8ArQ=_kJ>hV=-9Si_O!t)-0MlA)(F=t+zYY*)U!)P%WHh??mMF8j+Gl=xGvBrpb z{hF_2yPL1rUW4c)RDDH7#SC~EFyzuzPWj#hy}+O@hC8G{WJTX?RICple+ir$5HV#A<4w2Xz2e^4wxGXF+0U zm!T%6L1E3-pKwJet8R*CEVjpp6Q79qhLf%M94IIf4C|Qucl^#t)ba6XxOiv0F zkJd7M!~$6$h0Rqf5b!hs@i8|F=y2a6ny=FV7LkBLV0E_g;Z!5?-~;GFh$vbKydSx5 zdw7f(bMLFvMBmlXr=AV`-d}jq?7K4U0odDC>-B)wM&^~YnF7W04x+X88re<*hwFnr zgetBz>~l;1!l;1HPr%^cYo(<97^@~-1;ky@SFG2@#-`HDwXmotP5%$T0_9K&1mcYK z^PaEyxqa@0qh#ZfCNmL5#Ss{LK7=I|{aLB{_-#9O`S@9V`r#D3+T~&u2AuYB)IEa# zyiD7Y;h?y0j*yBzw?e?`TVJs3)SPmPv^Vml6O%}-02e%}3}m!N+0gm0Fkx`4A?=ve z`voYJ%1V%gNVnjhh146xGwq0}5| zfC&zqlArPwJ(Tya0=$K-*7qqH{_(>S^iq{zuwyS6y-!x4?7-a?6Ko`dvx1=j#!ciHlK5enN3>Qvc!HjY}K2f!;Lw%Cd$~RZ}&2a z6*nV&esMg`zv2rgGs_qzL)Tn?5va<4VgDQlAIQjq+m;8I>3pM?^R*U`eoC=6T7{gCJd_cn=)D9{@n2a)y*s>^{*XNr~R#g`@oH zD2g)*er0@oTyiLtbjZ1?K8#n_GT^6Lpt7hjb`->5&}_6=Ogg!RBu4YUQ*eAlZ0kJt zmpW}wHF7{%NAW8T@9HQO16BXio*;>l>$cX`fUva?feZu!L^3ll_f^)hJBWi6Ksdjg zzE0MLCV0&y_eYh$ZSlmV|9Rud8L*7uXD=!@p%ZK3WwEgTdWgpl-Zp50U-5E%k`{=^ z0|@{U91s_incln{KqoiNU?2Ts;9I!G^zAk`))0@{xr_qD5#0aEl>tX?$Ox>i`cORD z(1h!iFG52vTQ?o;AzO^#iO|gJ}&x2TlBb5zIu&&5@Dcfw5WS#wCEK-1i*)M#5k0cfl#TL;S zTm}(E9u9I_B@o?YXv^gcdFGIZ@Z|5(z)F^xobmm;Mh6C@0+#B$;mAq-ED3t z`r4^lQ_VQVs3_I{XoOU^bG907MW7Z6DVA4s3xiUcMDU#mMR* zi3}X%fxz9G)}Hs($Xq{ap1 z?2}`bA31X5D{!~3u+`K9JIeuR9-cj)XGu;Xy#tRNwq3BX*uJY?BT0dRSl5s)c=ht- z!J^wa9*QX7aNXT{lF&0GL0T?jM=|4V+6c}zE%ARLX$S@^WRz!jrcQBkA%DG0An-=}P%&#Hr_#Vij8Mi_QNx)ohZ`i`Cg9Py^5MFXLP z-af4hsTn~z3EJ@&-=CjaUu?ec1|e@O*59V2-x}(bT($rtEReAlLA(bR2hsAmow~5JlUzu7|)r zm7`h^QQ=W2hs<-q#sIJfCLHP{>sO&zhwKrx=t4x3f{;9n_b9Ch^t{un@jRPa&slvK z%n734+{2aHR)vmNPM6fNKkD*TIJU= zZlme#z}UOmxJ+NNe}EG3fShZw-8z1ZcgfsYwSG3-sz=DsQV!pX$NHm+PJ{a0=ktA?3G}drO4`UgH+sO_p;_gz7eukSLLX7z7e|0ZA>_DZCB^C zVpt2L0mQ5o!Vd`qMfxN5&fRw$h>r}mR4+`DVi$^R*(F>OZ7;dk+|V%Vgll!d2dq7q zVe^1vnYL8l_%0U}m}h93hu%GCF@DPqP)ygLZ5xA6S|zak(X|lN-zxj$>yt zd(>Guy+tPHhBq zB@hT004Rh%|G*!BqRg2wl$m{gu5n`D5Pf6VmE}G6?NEuVyYi`(a~9Bk*S@+&CneE& z^(flGD_=Z5`})w1>g@Oaw+B8%d)1VG*^Drzho$-#Z$A*#wQ-6GW{Lykfe<>5@M8q_ z;Iw+Jmx=s%v(-6-{j6i)v8qR33Gc+gQTS4j9{ZWfSdj=^vXQw5!UlNesHOf)`&U*@03c?B{iJr87#YW}8~h zw;G6|NjZIMKhnCj-dBfOa*c%=-M?v z=bb-3zd$xg1y9yaQut+2z+`g-tb_beq`Zk$VjdTso+c*s zf~k=E{D|lb@M^EeZ5^e~CXh0rfVAZYAsi|2q$vLMn%5Wq`rEcP^i+;vLpA7epj?z8 zNaNprEp#LI9sRB>JgsIdLT(8Vlq0x6pz7#$yQpjJoG=8Zm$=5D&X)9V}KGM5bo_%x_*J#g!Tm*V!4kFg<_5I0M(w9e1gZKS)!je8j&Mm|Vg z=%nNgrx9>jQy>|2?6itRi)1^HN+2zT_|$wd?&+J0admdnof!y20lDi8rjknV zN3r>$5?Rz=R%Qo!;oT0{DFe7bbV5Qls3B)0C3OLf^?`_}W|qXL9tV#H*_<0~C&34X zVx8~uB=)^4v~Jb_14cf^8FG3FFfLjDNPx#`rR8OXy9eLJv;2G32vXxawq`B1F5%>z zpC1LpGeUY-Fc1rx1YR7xC?ZB-xkQ;*ZNzc3@=vOO;99@v}V5~BX>Fn+l!HYQ-#TS0;Y zJ$ME5y;oqZR6dyg(Mm0Q?R)+DBjSw59|tc2{*vNSSWPBMYhUgFOe3cPMoe(rRTcJ0 zVkm*$5&L4ra}#vG2GvVov!5$5i)ugz;BhT5E7UgfbAOFlE2SDh*;BVu_jg1vph5|!p!WfZfEY6}K^x_^ zDbT-O!=W$oplWH~kDUOW1ulbjX20JS$JNAq6^$}6tJra!zV(E1ttjJdfv zox#a6Hdv@OPzwZc%~5mT32wngkO+%fR^~wh2dhWermlNY781GrpsrT5I$9}htFvt* zaF(T|LKPE**;!Rc)~46I>45 z#cxsED@JyJn|=qlgM3$_L)+GVkfQTJVfI;9+i~@|cRO|*!00*z)j=;kSQ6>1%qAQ7 z@7j#}<iqPYem3BPFzXv5$plT@WJcXAr_OF)Ft0|vXY@0nQ-N~h7kGyPY7&vKIm$7ROf zjA(UY>jXZ6(RSruj5%LKn-GxsX8Q|lT&lsVJO=W%;Q|&X7}qoLHr<>@KO=Ez;*)k(@=Wuh-R{JR&F@V6#I>x*@GXE)b0J`Dz48 zLE>vhe>Bgct`rOcfVAl%?%;+>W)x-BYK$awX z%p*=e zq_q~dzRc3fpaQ0hEoV=Z{k;yzY;=AL;oHFY=G&|OH}U?97Odtw!=&+5LMT?Vv>Hp` zZ_RSSX+TDZ7v5ZGY;BD&zT3ge_ZI4Z60$^6P~qNBi25D#K<*DzR z;H}UEv)*Q?+|34pkherh%v-HgCo5kP&k2vh4~0c+#J)m&H+Z;<03fb_2L)Jipr{29 zR+tcy?1rdpQ$3>VL5k!uAV^6BlE}R_1xKR-gcQm|GfC;>ixDeoF%jbSlQ24^;haPd zDp+Z>IMizuP>}o!usR@se*qEfkEi`k{x%HOu_W|G@Uo_Il&XrHpc{hIA_e?)KI;)Q zs!2FK_3?|?+go5eIz!Dl*|a9w1C^Q74^x0nWdznSVtx;mM({{{IKSPrck@!FCARS= zTL^5hS9x|Zwxa0}tD*AhT)b4BnDjB{5;(6*(_L1(9{7|@LjI7a(Iig>S*kN)$z7eD z@*hPZX*sCsUMA^&qS@s0w|**|ooDO*)T_=TwG{l7dtVMhPoh-yHXwAO)SJgmBi)=N852m_3%!PHHm7_ERFyxbxU0UkkouAW&3 z=L`^1&@crX@7da(0DJv08ZuDcr|MWasmNm-@wi;IsOKAziI{H2eo>$%iDA~ix(1CU z%pZLfcGM`Z0^%09CIh#!LWF3Y)tio9LqyST=ku=IR<=nEq+^Q++yH&78~9Bg^n7)k zxj#(`F>)ZVc7JaL;&3ysVADAZ;2HFc+)yIa$w>!z!)U%?8L*;HlVOO*DL!cB2C)Bk zAoLW#eQZCuUU!(22D(%n)z+4kh|`$JZb3kv^S8I}(TX2)>KlI82AjDz3KP>&GIK{I zliB*L@QmEhaxJ%>1=LraMpW;WvpMb_>5Fg>SL}-&s5)ngVNgo>F5W{U}EemnsAFX|J?ERPaKv*{4JOZ*&u6@-oCvJpGRJofzoewA}6suGfB9ymMjrv zvWN-6p^p3KtE7#aI#X|)M^U=hB=v>qJ9ZyP{OP*7mTfsV#{g~X+@xR77HdT8;2t|j zWvXdw8$d38XzX}Sp_=gD@|q>s~QM~6E5;YFYXnn!e* zlK;Kq7-Cad7n!0xF$15(=z#x8GT;ilb{>U76}TokAE29!*#^gyp(^%M6;Sz=<9Q;G z4hb~#rI^gCzs5%BF$KJ%}lK86Z8x`OQES?S{gUf(#HR zxd^~H0*{fDAQGE`aRC1+f((zG=XB?1c2+=T0FVXa8ov_A?Rk!WIaCAv3}i3b%*q^%35Qse5^|UD9GysX7;&>eHIK-z zUQ`PNK^d82P~FS#c5vB&bd025*H>scV0_uZK|{#Ht^+5hp6FafilYtksd=EM!0}W? z3Idw29&||k#R#v>^AOmE=v^NKp8!~SA)$2~@;+J2ED!9h3T&GmkPgHZ!$X%v>K2jM z&+2CSO!7kx9i--NC>!uLXW>;j{fj($um#lBA;|0T!3!M73QV>C83;P7!%%qvybP%C zJq$UwUmpfX1gUdCiT>uZ5ZW^M<6t|ok^se!gO0orG4B`*>{JsV@g2a6$Xx(KbNur1 zPvKIw27#4zy8b3v#8G37#FkAI-GFnrtkKrmDlQSYwaK1@m(4t?_LSIle1Ngjta_5tQ_1!=R1DZ*xCv+ z3ASH7j}-(tDf9QWBDX7Ip9!v^y$S{x-MZQpjGJ;>iG;nab&wc?Y19%=dyXxjXoq;& z_QPvG$oom&1URJSeMz>2NaGVS%)dmn zM=}>23P(w#0kVKV&}vH5J@a7pGUs`LJe;?SiwjnYtVGLJ#TdBtVO(8hWixd3$TcnImo#hdqTF6zc?Aj@AZ zX};(H;y)6JL@J#Pa8MUIQTlCze8U>vO#_I8C?o6_)M3vK<>S+c5C+DofBgVs#C>iP zsQV04+S ze(Y7RciCR`BiE@D6Yy$-3gCP=!dlrDE^8KLppL=0tubXMYG-W4TE)R@D1h0s;HcHs z?%RjiaNY>}sp5dTLcVnsaZ)jP1s*qYovYzy9a{dD#|hC)C0AC~4AhRZao2 zRaLqSZiDOF4vsU8w9kk9RLgIrJPbb?y|fo&fgy=b@5E^J%*f(&gk`X)Tc~Ll%!nFk zMr+Ga_;dcd^Jc=?UJ8*?U{Mo6?+pevBF5hh?{Ub^Dx@A@RQA|1R1>PJr)pAv-Vq{9laf zU`q;Jy>j$ZfbXH%Z0-zAgMT!0+$m}(dq41a8 zW1p71L?@Rc{P%GXZ^#H7KBl&QOUGACTI1D&Iwj^aqB@`Wv@S-|b1`LX^T_NbK7FrH zcu%AE{6vR(%F7yT=PYLkZ7XF;j$H*TqThYU~X-z-()65FlHckBxL zZTcNfiiM&@3QZj?kr$KRfg{8&ry-ib@hUr##zlYEKa9lyAMKI+$(D4M|vcZL7T@jY*mJg?Y=*aoZQWgr(bk{LYPErTcR39CN@Z>P+E1=9-#f#+K&3=x3?l z&3ziSS4i{aQnivrZllhW*VD1wBU|U?cRK&Z5ls4C~Hir>skm*cLi z8&v7MhQSM$7)-@BUcsotPt340YHGSuk1Pt5xI}W!c9W}arwsB{hz@k9b_#OR{tn`# z1XbKf*_|4PH?*i!qQ5)By*{>`gQ#7i6C5VvzZy`(y>6)@7INwX<&SAU%nhL$A4`IY zF#6B4H#+Iu>oz~wvTL`=^3G?}E0wr}c8hgelVg;&%?*9)&BVNf{q4D7%xWuSY{b=YMdLjlvk%~bb9*R z^S<>*N@a$vsKAx1182K;j4R>i7sJCoZ%Yo{&W8-3*>`v1Pa@qg%O|0$LKl*)ff1;q0I9EAU0F2R<-BuSVPH*b9i{(dG7 NjI!3ve5Jd8{|{cGP_6&~ literal 18794 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

    r0w2kgfI@YLAB0 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.1.png index bf703259b629af9fc1cc6729571ecbdd68ca56be..d9fadaceba45aabd1347280eb5080aa863573e70 100644 GIT binary patch literal 18677 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 Bv9SOE literal 18688 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 Date: Sun, 18 Sep 2022 16:46:41 +0800 Subject: [PATCH 325/498] fix: animation snapshot --- .../animation-delay-001-manual.html.0.png | Bin 19046 -> 19050 bytes .../animation-delay-002-manual.html.1.png | Bin 16874 -> 16721 bytes .../animation-delay-003-manual.html.0.png | Bin 14571 -> 14580 bytes .../animation-direction-003-manual.html.2.png | Bin 18782 -> 18759 bytes .../animation-direction-004-manual.html.1.png | Bin 18677 -> 18688 bytes .../animation-duration-001-manual.html.1.png | Bin 17535 -> 17542 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.0.png b/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.0.png index 9a3736f7351e53fafc6250db148787e390f98f14..d183054892c8c274379e01c68b65864157dd47ae 100644 GIT binary patch literal 19050 zcmeIaWkXeaw>La#DG?A5DaAkmK|(@WKtvD>x*MdsVPPR9p(3IZqS7JVp~OOIknWOP zbmueXzRtOybI$z%&i&%rFE+B2Ip@E|_|+u%zKQ}R*;z6KK`0gP$UQ_5JSzmj4>&;# zpX8pMNP#bSE)NxMA=$0WbMOtJ%PmE<6Y!t!39}#sIfp39$*6gL!VPng-UJ*WV z#Y#a)tdTG#;FT(tX*tn~kdghv10$ob6IU8%1J0S$1(7$U280osNBwB5Iw2=RPmrfi zZ~lPLTuwSTkBIQ;`RC6sUOXQ;RgMu#s zrti-jvR{5WI)!bw-@WdTZ0{$+5&<$ zJrtV@y(CDVt&m?oJj2U^0>O>h4gzCi;}fK$GG=BMFRRAeYISUnVj>s}eIHb-zPWtQ zakl+@reQTJd?xuE`1ayj^syv-w$AhzTO1cUjIur>p=$71u6+6Z`*jLN{<;lQ ziC8I2)ksQv`+e8>KfiFyHtGdwSGl?6&`D5!k03!IAwMOyhX|1Oe5S;3g9mDAQ~CES zE%yAj7PTw325hW)G8u2(y5+b%>|qKIoIH4aeZXJy{>CT3@pHpGm+RiK0V!+;zEn{{+HY zeUMj`Q2X=eN~aR~IRKWSgJYmup)Pkgw)t&pWtxH9C@_Bk;TiM#3c zGpl$c1eTXgc*v39;iE^!xmu2ccEz84wwKOG`iijnuE-QT?jnBh-~sF4T~$?V!z-q! z4G=5g(7Er+HGPn8aZfq!CY{|t z!H=BkqY$y>imsWPdU29|XVcQsL_C(5(J^f-4Ku;I_a0-gEFL8ie~m$m^LaZ>dAvIx6u*y723qi9g-kY#|HvXmMBB( zpYH5>2&gMm~O?#titmuwA(+oKWl-5I7(&fbeSv6GRL z^Z9JqA`FI>nEk=g_H2DI<@eX|;OI7d5_MTQSZ}+)!*dcYb8~HNZDpx^Imx`~orvq~ zsdUY4#+Nbi>!fq=++@gAPR`DlmINt7q!F%8$&vFVDe0@*LJvFp8r+c_(atq_fu$W5 z6&1yoZU|$1v_!fJb@v3KUFMo*?4_so35i@>w9Sba&NB~1B6rtkKF)W+S~uxi!f_@H zFf%uwYf_09zlxZ_(2{$`e|Hfve{8v|5B+7^iAy8%6q08*DC8IG9(7$vNaL=O(&jXJ zP>{%{Pw}bwijU>we%96!1uTwM8yZ?{&+vc02Z&FjQ_^$op_}*{pITfWq(VJP&r8H2l+44wL z;>Fa|RKT9LhDKnVmD*4*%?9DFb8}SlaqMTQs(2oH4_Gk9U}Nl8_+_%GxbIlL1-^4*Kp-j;MX{n zUAlIy#c{ROBzLTV$>C4&iySvSvexm%IS+w-)khf`SpjSrSGl-kBUmNA8~wmX7`7vd zol4a^EX=QWIpB8OSnDU}JRF)Ugz)Ja-AMx4q>h;hNKXDyuq?D6J_|EjZ&y4V2rvK% z?~2M^ppethpl9{pG4}ftyY9bLpfa;4>a}(eAXACc8DaZRRYNAZfWdB6A0?knFjOm@ zOa6$6v@*ntd(i;q+T+le33X<<=~|PmUmh7BAC6*(4>!0HJrr>)EwNj{R|Ex5zj*N? zC?Z1P`8wF^1b;bCDE2Ww5dvm=#aG&D5dJ(dlS z@Ml*zImup9vE6oe7rJ}*Ze&c%OK7?i&@1>0Ptv^~#{BSJ45Va2XCaJPlm?cucU#qK z=1xXgi_!J2WhVO)ynspkZvy!Y7yK%PIN94Lr&`69>(bRTB0qi%eL>D(0#D(&k1MA1 zbN2JMs1BRvyn2-sU|55up&t=-$F@t3L8Y%`kwK+565xNhHx5;zv8hQ`TbmIol;g^n z%)w?3hB8$(y2Vo32E%V@ZvI*38F8w1zD4P_!=F@Y;*-oL5E0Lna|o7*5!rK;G_2bF z%T&9}By3MOc4jRl&H%bW2bCwHRcIY;=+|kbnNdi6dO*HPY^j6*=IfS~)s^|4tc9&# z_02b*$`AU(5V!YpkCv)KB)1x)0L}phJ4{k~{P-#qfNa~37SZqCc~`b5S38tW(<3zi z0xh+Upx}R8lJ&}YwYE3E$5CO!#_9YC3BSGs08OXJHVq@)(NhWqt=2D2;rwnF77!Uy zV|cX2;0K4~T@~f;N9FEzfG`*P^GPs!T?t0HTG09@)}7Q_X0C>pl!#oscrg)*#2G#l zLUwlc`&wE-xWVEJH*cN=$Wf;%b%>%M*h2W0ZSnPa=1qw7ao6nLLe9&2lOL}yDYfUB zD+yY)BOb$UF}*?wzH5pV$4C2U&-`xQL=T&Z915=tw4et$1`+@pl@lacpe)J1I(uUU zMrL}jvl{5OGv5W!?*7AvPXQK=LwnuXnT)^_xs8w1kn>gqc&+~(?@(Z?uX#d%Ud7DP zauW7jL;Gi0CqNmydJ5mIH0$B>QYFzoMxI4f9clyR8-qCqQO@*WF-L?}V%Ku(a8*8+%p2I$XZ zW$Z`t{=*TQT3y30U%sIKT=}CArO-!8Xpmhm8LaX9m!Q(@KqGXTiStOZX!!upfT1T- z``Pp7gh&lgnQ+*JpEYv~cG@sU&tU7Dtp9D<8GA-G)MK6;flhaY*ZOtazU$&!eWrKr z-K+nq_-05&-uq2tWB?SXBpBi>th#kiW-xRdU%-v;MO~6hSsc?u4f!?%LuBR5;8AGk z=)PetF)~!W%hZ2yPf?L?h@OSz8gzgDOw-?QukPW7%RRO|>mQc$AHk$D_AoFoSnmAZ zg{8fS7K&ku{J9G(7H#o%>8cJDtAXQNP}P4uB_gLdjFs>yI=W$m;}93uJIN+#>&?!ZB{6+uJ?%6NpE}!(28!k z1?z(KRAZD$uGYprwxQuS5SQWGwrcVpv&COcwBN>9T`+Eq6XO7sNrQCe7)nu7qohre zOrA;9EO``aN>A@M7FC!%TIlQ!m7DK+{kP`26m$x##@7qS({jW-mINydfXp2+$EDD( zc$BkAc>np{Vkh*m4vSS%Q3>}?$f?@9JF22H2zlra6 zO-6TmjuKP-_`yw}uAH2&e>@{$Vx^KcF)^vI-%h7zH}U^@1en3h!XgQ#s02zY1;f|h zzkj1;^6U-kMEH=E4YM>PIyEyh2`6TI=f(gaIFlbwiO@3i-~qMDCy^&ohjXfX2|KNF zD1sHU?oNLJ3?l-!JO9SrKvYn$V2CfWzrVk+r9~cq_E7fQkNOJNPt(&UrKX+`9`Q7l zwu>%4etOt9F=6sGCFKijFnfkzHT!&V;C#1X{x(8Y*`ESl`jhqu>qyqDT@6aa{thW|1$GG2iurKNSoeX;M!{!%$rQw*Is?9h-N^x$*c+|4-ld`7^ijg5^p0K=TR?g@LfaMA71wMVyzCI-*`E&$>kfp>37 zWcUhoRMOi$!YoQm?jTNmn2tUDBGm3$6lD+EmuLN~CCG8TOC(55tzY+u`Tti7?#X?< zLM#YB%Xzvzo>Q%y%zblmZNRYH{aTU3X!da{8TLo?n>WvZ0t4&$@~{W9G5f%GwVq)I z=)r@B4*||KUmbspDO*|(SyR^+J0q@cmebrzN|aqt@W*mI@NP@2boJBOv|EK7Pc)usd)+>+Rnfj_u+ZkEdWMEqW~AR$D3C37PcK^@A8l8!=3ovpe&Y15(N!L= zCmh!Ru7EnYS0@Z)-tFUuH?t2(NlAY|aj}2jX9v~v{Y|&|OCKMKj0L!7-PhNSF2@{h zYStD63~c|EP;abyJL0>355Ofuz?TwpUte)J=&wjvJ&~IFT{o_I>$KTQ0Bmv!3QQK4 zm?XSi>9&GS*t>*ST3F0?Tk^cHmJOufd;5tb;F8k2bHJ*BHVJdH3E7@OAqdhy)@{M* zp^8@c4}u+BtKrO|ooWwMvdT)fzNq^;+K;k%d3l}THw!XQ7v<)r09Yfa%$cpua16)+ zl^2Gf;n^>cl9RhEbX_YRO5)AX%!zL+N>+(W*WBs>yiH6cxpU5YdkL4HRLo_lo)y6+ z72eo*CyYtB)$)>KuXQu#aKUi%^}VW{NBZRz^M3&)%eTc#+$)^R%uBcNzzxtvNNl|y zTm#O+&d>jl_m55T*^2&|j(kg1LHi-CoV&*;g6x)o;uZ$i<<`!V!-NHmeSXP$%gpSn zq{q#%)KjM!8Ry){dX}q?S$V5=lB!xh2tGFPw9{|xF6GLLEBL)0V7jM<%h`jUAM)EC z-m5~G_YXn5=-S_iz#M$}8Ev0Pm+2b+Iv23>Ua9oV)34Oj)N6kpsg}lh^jBl5DcB_S z5}aK?RoU~po7MLXNCXs~CTNPAbEjgi>HgguQaV05BnkjL?o~0|yn9D=dk%^!64`6u zb5C-AQNRnj4`G1#{$l=~UxDQ0PuDbO@(8bNi8VX@&&Ob_4jsko+AF=6nazIn+ z)3))|uo6wOG7ynr!$5ip*8^aFfK2j*dOQmc?~or=(O=3~*vh8^TZl&QHqf zQAbG7kuO@CyOQs{5gzq@gz+C~l~H|vT?T2Sk~%Qb8iDE!BAzpdUW)hbbr;V-%`@Lx z?5BbDhfMVvVs0$HTx*p{knqs}paZvW1P09U>({TnDmcy2FrSK#AEyCS@>(jL*}&pP zDs?fetqZ-$Wp42XYA%198R6=kN%G`qALQ04xN{o@$Dta3ntBu~n^%AWHn0 zBVWerMghomaRxh#O({P}8={z_3dvWtKwg{U#57;OeqCo>c-Paje z*p;H%n{NZ_4l`~g>bF<$IiXEr`_sism*%iTaO8*gCaHjwjykTy!w8T&uu_x^<8a1I zN)i@A>vgB?uPuUF^ObiWiUDCj1!#5#V#!1sAaR<3!m}6Q9!Wyej>;rRLgU09gD1a)XH4}{cO(`L12!nExJ>yC$xD?dosP9Xij z)q_O-l6u6GdjV&H&4V)jz))QPh5|!v{h%H3B4+g=bgT~5H0drRWpR2DIe^7U^%^v+ z4w*%rzZ#deSO_pzKS>=ac>L)3%a^9G3p`<+P?U*;uVdiBQD)${T)92pRQQPb=(`qo zApZ@WZj!8c9WOmzk=9S5v>qc~DWiS}&%5T*nRp*Z=oV-v17S>RIx;%?Tg;_AEpAZL zbfs}ST^b*S>`t+fHyCV>>k)!CCM^DOf1+viX`jRCy+Yu?%}PA|;4 zJN87wwCZfS4d`6!?}Z&$QDCbQchmUjVApD-qSXJ}s4_a@mkYKI1Ckx~z1eJjz|BB^ z;3=~0e+m#-&|!o*;LFH(!tsG6$QEmFZh@yyK-xQ zE9qYYtuoNW%c6CxC#~VzqT|w%tzJ zo$lAflxGpgKU?Tg03AM2lV=2!wHgvV`9W{kA&7`ICh zHydCma6%aXacLFVGD54#8nuTWA=Os9N&9U}GuYkthV`El?&U1#egi!j5iw|k`VA~X z3uLRKUI%66u->hkuCoE4^PQKFUvhczu|D;z!>k59?ZcIZLX*OE$(+Ucf%E6j&yH4C8;qTH{KVh6^N4ap zIoh$;LGcLo8WN^nAqEfFouP@!&Yl|`3Y*o0H$9fZh{)+tu`nn+yr(ux92nY9m~T80 zl`kUO)6O}qEMd5>m8>nkp*OD-yy>{6Q_!xUD`H4$*P9*Z|KeeJ(3G#q(C*V$(6GSv zXaah(L5@FCb)JY#Q-pxAz9U_o3yOLSU>rqbgsg2&gloC3PTY;KmW!9aS+8~U`iW7^Yjg$xIRdw z+N^hRqd-c3+soY@bS-+Q@cdqDW*`B)`ug>2BdpJ6$9eW71_c%={|Y!pia=?;MX0T8 z-n)fMV&e(B=(vR|9wkFil@7f#9EuC*O+c}n9p!0hX@P;+Ftoj-WP;whI#K^RK0aG2 zVr9iqyTtLc?QoeoOcjSlhPtiL$4_D&Dh@82^WEMU$)g=pztXK1RnL-uXt)khnL z<7qn?3&I<~tS=!`v6k^sAj|#&<<%U-ReJ_MeWJWLJzg4>4}ywF<8xA4v)Vw)hxhM~ z6(#^$6$}d}d(PTEfY%Afr}56u{!u|HRww3TW2_oR^dw@@0A6as!2l^1Q;^Q zd#g8OB>sJxH7<*$M8mwll%~Yy#$RCJ+Mul5kD_Q#o#KtBKzU_ z%~Dw0GXt>E-+_qFxa4w4ZRVtzSgA(5l-vAUs8f1|s-U3v`V(JBd}i>VUUPcscd|aI zdleZ@XIyK+^7d_F(C^OzRLJR$dUpPdU2NK{KBIb;06)Luuv_o$X7rwtzW!jBBnf9u znY*GQDbN$Ay^Y7f(0qp3rq!SI?vWq>NFZ+01nT;v6ks&y6S;|Kn=%a*@LqA z0b+Tddc`8Pm44E)OmK(vL*qs2ALF$zI{Rm*wO=su4!HN^p#@`;7eLx4@pH2{7 zh5@70qrAhqZ~}8QgINKy^8zS;Z2mjvK?_8XN}ui9%E}b5(@ARh-t-H!i9Z|l-=}N= zZOEDf=vGGC_Piqagd3##*=AR-UOgjdb;{YrMdGB*T0sIbHkXpn075P3R_&!Ome52y z)qE5c6}7$_$<*Y(qt?%sBLplV4|k{n36gU`;YQ8sEnm=$hy0-n5Z3T*8iN+s4h9tG z+!Xv@d`oqI7YO3B&Q|E{p_lJ{m@S;<5su$N4ae``EmD9B4MgQVpuE9JGBUDupe6uX zCSPxEI*;~fJ_9UUM%SF0+GJjBLs!Y>yYhn*_tLnavY2cDYRjLQ&MdObM7!{mL)V=Z zW28MLa`S5*XI{+AKR*mY5<53Hcj`jLr>*^OpRhw*)T57ll2cMb0f^U!GAuMTpSFU! z5e=X}w8f&!vdA0G{I~Dl?^n02jMrYeeEIgfYr2=wZr<~r%RvCUYi$Fnj!E$#Wxo3K z%#0E!%wM66zJC8cwXKJam6gYRb@R^Je`W#7D~GfP#dpDHrpQlmTkPw&|MK2oxG6UL zMzb>mE$xNsgSAj&eQ0iP71;dLxamWoCe96)7fZUC+S_MixAeCk!HNyXzp>FTfmxp$ zYz7n$Prg0kqbkRvQhw8JK+rFcKcky-xoSWAbMoid!^P&VqIfZn>_I*a4UNlS&Y{oR zmB}jRuDs}8F3Dm67ltYS)9w$2ZrH-lr4ZE2N@5nCX6_mBrhafMz5*QD!vWuoh7N_^ z?G=;o{B|guUf_c@cI5*&_hQC_0`0Sf{#(nVh5$;wjaHe|lY!Qd2VWi!mlal3E8V?o z1{H5_(Vae29vTFN8*6B2s5vZbXOW%mq2%gr>Z$=QD<7K7^Z`)3{!nGEPGbLI zio0b^+!TZ>434hgQd7g7;dfFZ@e)XX_t zsrq}?!~BGm16=_D>1;;U(;^UXx_czeSwtMAt5pGB__u$cw2BIqc`%alD@4sqxE!bN zHPG`!&7fys8Gw=T;lRbk))!|!p@Ri5bPl>DZ?S|WGSwnHy5a2ufPeV!lkKmmsdoZG zU0g#wG5R0iFFUZv7aZpNHX)Rqyu z{v$j65X*+~{3I>;Uf%9m{pg`yyfi1T4bF#?!l+OuDn5QGH;XIx^(Me5NbW=wk9d=V zSW6H7DxYO5bAjDpk7-Q7V1J3|7+H-4&yvvwI*}Jq1Gt^*q?0 zf?hodU~4tQIg(?5>B+)sYT z)M;sYcWqOu0}pH1o}DrS)iA(|S-Qedt?C*Y zO!~eXqX-284EM8iZXAHlEdpXkn~TH|jLlyB4cBN!SiFkbm>y! zOxG6$DsH`!Q^0~7Q2P<4!w9%Dj6q?HDUBJ0R^0UDP`sW(vrK}TQL>O-t}M~hT(n&` zSY$s~bPZa;Gbn1lKpgiD2f@yTK7$5f&VbgSyASG~BpMAtpBf<88BjQpF}CBKm)I@L z-W&(&rNI-6_-t{*^@$OyjwB*v3@W_>L=aF5`CvxsSO$P}uGhK+-~$r-g^=*@2C&Pn zLw^L03AOwr6wm8JDbRXRG7W4g5Xo zz6n~QSTl$j$aTLxSCB2w0A-K`!&`E1_5!R)1jb-yYHAEX7&gvhBp3{RliqB7Eg&@G zOsUW~Q7rY{ch~t(jv?DM-J-fTs*=HgzQ*C}57b(Qp&J}${Go8zm(YSX842DKYFdD@ zY&BHE2Wg}Ln9d&-`HUmy#N6fyYn*T`)`i{@bW;(i>j_P|9$3fyN^dua2T7pjDa5nr z`^e;dHQObfr-G0M>vqjYL^x2OAW9bnwnfe%nY%g;Iu}z9Jg8W#klhC)5@e(Zzma1qUmPfGnp~Ii z-bfwa$?LbJVPoS3Pj1f-P=7krjRGxhNSVNfxeGM?NCX--+QHtxKT|xOKVr~pk^ZM# zqpPPUZBup~gsu}v?Yf{YrM@l)5)nP5zi+g)=?EYmg+o<1R= zbSOx^6URr(#~Y%KkOf@meQ`&lYU4a4Ae@|>C@k6ep{v^mhUUYU+YcYU9{nPRatNwS zvwIfc{+|aw6&?=ugHz3yzJJ9#e{lhA;7bhtZ(JfqURu-KZ4S&z;9KWJMYVtZ3K74W zGw3?`8QloQ2DMxv~jKZlFt`cdTagn#fq&f_<}r6SC>yM(YhL zF>2v2&n&OfQ&XP@$!-l)>sdSx$PeWlQf#^KI+T}hIl&{_`)w3fS9XLy27&|TWl?jy zgzoao1=r-**jRM=p$oqL^@d}?!N2uUwLd6pd#967C~T`blT>Aq*GLf*L~&N|&X{v$ zyRw^JROmW4z~<#WzmU+-O;sZuX`2PzO7B8rKX6bw0Sz5`s760;QH!DH)7d zcnO`L-qi3@2HpN6?VIh^_1OjI!b3v%)mq&ttuW~nABS7x!x=Q=IbV?IJkhO_NNizM zjo&hsPcJUlux1l;&qNwZE1D)V*4!Z~0iCGUFc6GySdl3R1JF?)M+k@3o6L@a-#>&u z8Wtky;V?M-h^^48xlr=M@Mv~{srOvxx4`Lvm+#j0sz7_kI|p)?@J2VJ2G+5mB9f6M z#09b3{Iv_V?iLx7OCyy`=#jur0o}}VJ}u?C^`9%no1l^CL+Dr*!e;;v93k#G0ex$&cb$~kDzrL?&_u++Wo*+J4=jy?}NCVz1oMh_H* zeghw|4oY7r*qDh!rR7Z-6 zR(VVAU?e=dp1}9sl%X~}z6Iwn!4upvh>3bGEjI$qb*@0;M;x2?S=rdAY7PsNIt-NJ zE%*v_vb^yH6oyNaW|1=Q<@wB%EO;1|xH|8d{9TiYwTxX@IK9N!YM zsNZ$6Aj6%uF`1PY?<22fhOtS|i+ha4KoAQ2Kx(j4AmV>cO3DyZg+?^QZY)5h77Fw@w$R;8-Q=hh_0TP=;#+9=_JBlVilW8d;Oj9 zNJH{uvJpHkH#{zdr=&r}Ft+reCroe3WLNL1n!)Jr3~rpmw=IZF%iX>`0q+VBBL{Hg zB=^U3(AfpBt6S>_B`4QyL1V}X%YtwoduC+L7(|7P(DjcZE5HZO`1hu#je0L+?HMwI zd=Xg9t(m1`?%7N!JcML)hk&5X7+w!X1XFr7i9jd_VtMNUlcq;#QX7I3ZLV%Z5dCBY zN?Cg$LLAKogMCVn;I|b!=yl!B4_t>@G=fV_{p9D*TehQBEZ~C9_T^qc+TiT-KiVzf zC)Cm2dw9Y-+873?*Y?aO<2386yK@%1!6O)Mwh!LG#j` zg*KYC{0@G6y4q#z-63&a8K;)g@{0R8Yb}cNizn-&Q|^vGQ5&Oo%3iVpC7Z83jhM(20)t?~Ie+jX8xmm`i$4x1nVX z^4!N|n;p$Z?%yhl3dWrVRKcM&_M8QyoFcj((n6B+bzWx-!4-ynGcUUW`J&Geq3)~% z_LoDOuZKn71eX@lXbYyFZlDnm(7f+}_K+6+4y5)#>VT+t{*x( zAEGk@C@R*E)jmR1RW%0PI)RD$WDv-71_i1(_eZ*Rv3=a2qYTDN%-|Cp1A`JI`37U~ z@%vR^9oI#;fA_9V-II}d0$Jy1F`i8*Rp2lFaNhR<>l?lL+-jD*yu33Q1J1>|@bo}( z*qouJB@7nlg%|(!Tw?!QI60hU zMNjl?svDG1M-VgrWN1>sxviV156$)7v<_ntqtB__(vjCxl{uBkSt}Rw=0EaHGI8}Q zK%x|!ocOZKmu{l|Blzrn@Zt_s<3RWuDJvWzlRjSw$hBf;A~et(nl2t?@#o*v=yf#9 zQ93{@13vEs;Cr*<6ohG3wh)s97YEILg7F3-YO@{l1@$Z=b;iqOpR8(!o{G|%!~Mb` z6N0AOA?6;)+egTh;f=m{#OCtfk?B7ww>m+3_J?)AIl0hO12XQ@QtSUp$CC!YYwSO% z1UDu^A=1pZOYCK5S5K2b4(T7PyLPty@#DupmN`(c50ypmaVH^f<;H7+Yc>5%yw&JO z@Wu)xq$uE1n!Nmc%I*Gw$F!_Ii+s-m1KEM8L#np|8~lsmn$B}jlhIGWvGW9?_-sWm zIEY7zb!OrU#F!z%2ub!}kb)q(y*4NgfL9_@}BE5U%R{4$z%tnycjC%)eOzHl=r zC|j)?QHcHv3={G_gkW7Lfb|S&0YpneArEd0+S45s759wUEcH$nApU6P7k!BYp7lHH z&{>=wgs|gK4K7o^oL<6vqXtSU#kOv)3DG+q{Sa_H@X$#CtsXQx8gZi<1ems=)S--= zt32($VvI>9;F+_VWG8vR8FpPB8Foy7bMqT~_n~xI$TW@Izf=N?%nw=t-Z`kM>=Wg8 zS762Al?HgdAlh&BZBXj(IXZPYLhz|mAElf`%_xZ)kd-3i;$ER05`0uvSxIGKz!rmp=@%11GKcUir@>_+ zN7G9MN^(CM0=9Hjk;km>BJfsj2I%4y5Zs@6=>Gc`xufr`Z3}^b9vLf3@qjc$gpT`x zxv@0K#j^nmVcwKH2ZLKbRn@i0#)e)TbKt)xFk(RLmfq+-`=fF>z)wbpf!gb*OFLd} zjnm!Q-ISvjgBtij&sRU?Gd@PVkrM=`oQ+xD=r47#))*nn`X!^Ms1{0xWfN>wnz98t zG#n5rq|r%di#DJfPZ@7TBtVhjoq~bZk^wF*h%+!D@!67KfSTUHX`2RZ#15Il_av!l zL3)r`szmyL8J?kKOA#I(9i)z6_O$eIbHOGV^JlTs;bS~cCjTf3G5;`Nk){v0h8#H= zSKc6)C8v&liN_K-ir4I{y+w!*Uh3%@K9Qf>x#UgWHBQzh^gP4`79;`5(R~`H13bo0 ztfY*4n)aPEVqtbT*+w9OC`eP0eP@%KA}6?cZr{;KE-;x6pUMiSx1{g*$-l95#pS|~ zYP9R^vGGM!-De1Q;Dw3^yjha5C^DG*BNJ(ouh)=X?~q<~BR`DZPH}FNGgkmTPg4bh z+zET#8{;C_!;KfCh8afXa#Os-uF~0f_r-!1%IAJoOA%O|ICnbQL#5YN*Ag#`@Dv`F z180m#W_^{Uj^bdhcl!1Aoy(vd!%@pq(-H%714n_uo;xBu?*3#(_yKK*@0_d8EAr!a z0YRK31ZADwl`G3+B2^dhkypqRffoLl2=l050WwRBy%s zC-s2a_=(?vhymfs6=s<|YUateH*V1&K~Ba4R;w5{_H#|`>aEQ zCxBlhUO|3Tyg4)&Psp~!tc6;g)Zk);S+>Wn1R+E9+>CeiAXl}Rj7=ylFSR;-{KUEc zX3YLy#|?FB|NY_ru&@6YCPXbL=#IL_#M*c28d>#W($0FM?|ygOxpcJy!v9h~|A{th zK7IW<6kbyze#b*}lBKM~cH7=)b6#nbm%Xe)Eoc6uo_*E+$t#@Bh9jAbr&v!m$5U?1 z6WNZQ9P;6fjKZs{#}lvsPhD*3<6A;*Eo5q%s2Vd^ghxa);!wS9052Uq8|9%UVj~z( zmGHrT{CKGe8-{0qVJX?vNYocgREbj>6-GSAiLe~54nhvZI>p(ZVv|^cM=AI|+Zw+5 za#M^1H<6Bx8jgfBF*mHnc8cwEu~0_KPK$W7t{&IP8MQJS_414j*swI&SKbi8nOWfP zZxLCdKiDQiQe)!usnhW&&2ZFd@OQMd+1Gz6>SO5$R%xv>r)Va$uv5Pp_6r=SC|D^f zw&N%LWdlocX0`^0_s#P4_1uTuss`tWnLNkv&T-64L@W$KP zKu8Gwh+i-Dn5q;GYH$^cR&ndhiB*OpF)GAog>=k1x?sYAi|t-ytUG02^>pda$TwZMwm{ zSD#^L?`WrX)nT%sVOoQ$J?Vv$c9zbQ;z?EA8#Xsvoum#6CbxSHGPIyJOA)&w*`>r8 za@?Z`LVAGyUVv16F;A$}m$B7S#}tCLC!PmtjEL&z+r>$chcT1N$%#llreY%Y-FrN} z5ga5wc_m!wYr&s=@Cg3y2y->@>E-bAmc-iGOx?lOx2j4JT~r!j#wO6Lp2r0&c^?uzs}3yZz`Y zoq2NBay7~ASI(b$g2IUnyni|EeNkn@-Z_aAMQX5Y1WdyCFJAn>aZux(;(z=sNSdAf zTyE|u>NctM8Cj&U%8wzYa_fVsw%qf{Z3&-qoa2S?1ssLL!7z0{JH5H*QObI zX{qt%`n!Avv!w_~ZuUGO-cT+ ztm)`)^0de@SgoFP|6WGl^TUCIGrfk(JDPQ?j|)^-u%dP|`{ouf@uRM^_p@k@8%S^K zmX=Cu$4+`d{INP>#ObGh86c*6rt)-!*AwS{V_p8suSWQvW#XUTm+_#livv1yK=@6oXi##G*kFmePoT zbW1L}VZj;m{q{L$pM9PGXJ6;~-alS1vF0gZr%ymUY1x3Y%47Utv_%}&7}KdPOUl7b{-)>-e~y}P)xP)%`ZK5dK;X*tmleg6FUwlv~k zNv~cKYHrrkpFErjg8aB;vqPrbEcq-p<0B zz5MRtV}u^D$k(%KO_Kfm)^q#^0~y!K-)3CSyb0rxBS*eF6dghG4YHT(m`#7g3R_YJ zvB?J-DJsG$D1&yF8-=!44$_=F!xVnjj)ZXWC_i7^F6mj9S&J4l7278*U@bT12TUje z8sA>D8>+%Y?;Se9$oO6xr{l9V5wo<~p`mN9A5+lhQq7(y<@rtR%D_jBcsYy|GW`A7 z@jQcUj^vHCZqu=!yvg4QoY?37^QDuit7{JV_j@i|SzDXe2GjVAN2wr4PfyQg-_Tz0 zY)<+7v5klvFEQO5qi61uEo8{CW5@DbN3PBF79H(2@xO?uq-$y0+g}JIt?4GnVaggB z%Mv{2{xGmhdx_retH#Qwe)v!sEP{+rOq{-Wk;b+qA!A<9#Ix|8BAx8&OSx>@)}*PP zg2_)a$D(rT6TJtYF zHH~aZl;$~ehEgqFye?d*YKlpnijG6E(r=Ca*s(`1U()w`4`K8w{faW=?B8BA3^u9s zn{M=7{7OaBI#l5+!r?b{x^~R4_`Yz{m?LY8+|HbcqN3t3RMh6*co%vGhKIyHyd`X7 zXlQ87N~`?Bdcxu3=gby|zal{#vVL6L+~l*}x!s)d55^De?QK-g_Kmy`;rNy$>rdTy zsbF8qd;U2CkMeLfe(;)|-Ff8*Hv8?;Bmt8Oy@h+Q#I|h1D|q*;m{c`2H#cE)7o2)U z5d|x&GxL=jCP+<%?+UtvSFesGJ4uOjEQr{)nElB!w`)&5aP8VP*H+n$;eu8@gm-A zpv==}y~p-WnmW78+V6Yk`S`Bkod;ZZi1;xgeyE=2N`FwL|He0q{NV`cd4*&-%q5#9 z7MacA$5U7;t1)Ke(3>6JmBu@^UP z+)#ks{!&mN>^Y<5LF{F$-rMG%FPqI#-JwL}_cnQ8$xr1l+h+6qrRVwiYh9{IpM|h+ z{6fRRuEFk~Q+xNMx5&|QeXa++_&TvK1Zrp7C>Ym~reO(}m++ovb1t1=qGpqzx2%8O zzK`uT_AZs31jrx=RqsA}dXzcU5bs<B>;R@jG|!#KO_NEFvNtxa+ez z-9c6EMZ8?Oj|`_J2Y!R=ndj@#Gc$)c50u3P=J1Ng(*Vp|hcZE*U8ptLyV;oooXZ30E$(s?j1Hu6ekfR{xq;!vYqVo}Rwg`(n}R;J|^Yq3Y^ZC>>e<^O)~YUBAPY%_N|O_hSexJ0Akbub5oyvt&WW1xPNIP!aL``5SmT;h{h9 zLxKE=)!Ixahla=G-@iwYI0-k4PrTo$0DBQD=mEjg!otGj3J)GUsF3rX?aC%sU=lL9 zpQ-WsoLZ(w++!*_CgfFOVz!2~@A60FWxgfhok1|a2D9sZ@%Yubo`PSK*(Q|+$Pc*M zntiX*x2Y++3qnHOY4ZEKE944cVPT5j`}dAB96oyVIPze)h79wrXYyUt%a>fp8ChA< ztKw-sLBST{Xy4}2*nyxEOiW>+rQuI(wiBbGP9s(|L1e+ce_IkAXSmc@^e!xi4k$#81Uu+_%1Ci{n6Z9 z_u|T6da_{2ScEhp(PAF+_HF9*5ZZLp>Od~65K=>ASBeode);ZQ*y^eWrj%Dbp#}i% zWqkZmhOf_#^CAlSji<}?U6r@p`6Q(znQ z_H8iKBJ^-J-~!5U9wOMK4ITL4pNTnkaR~|0 z-HH*cgXgf+>zK>BJzf@QQ~i8`Pgt0t+p~3q_F{w8di#yu6C5X!!2IvDC}f0sN!F5Bl*7FdYTn5g@m4cf~i1 z=iIr2OG`^r1LarhBiO?Ma858Yj{)&kfiu!pKu_P+Qj@%w*LhD_2A zTa`oC8ikc!10`^$+{+2&YuYZr*T(4i;N@yLbX+6vCzhj7ddt-D=QzY+WLE{ zDOrA~pSI*rLN1G6S@?BDD-c1)SXnc$XIUq!-)0#fKpP4h3# zg^sjH0{`3I=p~@l-FX!Nm9@Gpy4eP2QKT3Xk5{YiIDO&5PpxE=z%A?AF%T`U&fa>< zvio$ zp?Outy|l6c{ZYsLXA(KC8w*22*~TKRo?_1Z68U-p+q>=U?U%880eER~L8bT{nSr|Z z1Elu@hu!DsT$dR{4FSA$5a3KuWLj z@Y>s);bCDPHIn6m_v-5F8-TaeITXz_{+b_k4NQC%3WVuLC<705D-{<|Kok7VCnj!- zz?wbiYoO8I1Klgx`WsKf{1LESQ&Tg@pvVFJB1bY7ZsWv>6T{oHxf`2ey?_4bp>Vgg zwRQf&1+?+H&Sv9hIF}Ni9SYnbUV#Qd_5S^Pj=q&nUSh=q;EjBhRF`>qsepu+On;8= z&v_GXtb7R$>4%RWDM3zIDmZBu?>{2-7udE!+oYg5CGJsv&oCn>qi==^h=z8acgmOLNZEb8) zQ&Y)dEdZKS^W_V2C`kz}zuUAf4B!it17x3P`H%a`fTqBP?-&?_#k*AXIGSYCV96R-XFA7cr8tPMOE?R`A>sa9r4wFi0iio7`ZdgO|~S~qC8uAzT`s#p2}qU z%@I3p?y{gXiO~OL8oUd=afceds-%4(MOHiAi*as-5M2bM$uD4^w6VRA<;rFRYtbux za26JUv)%$lRELUaI=M~6+S;0)p*Hqd(t6sgTrQt!XVK%Y_mtLW0unWc8yXuiOwQ?D z35QqtLzDQl{EykKjlq%g%eq|Uzu}xq40-zWCR}HA2v_iJ36^=+UTn2CgpR7R`IZwL zGIE8kEdArF*83ouL;{-ed3l-!c;|~=<;KZ`jy;coN$mri6q8hs)57My<{VSk*1dD* zG*h@znHSMuI-oO7gbaJ7r|6nz9=|v5xpV)V^A2gxnJ3WOe6n6*gq!0;2u?xgY@q|3 z+gnNA*D6iKeM;Q`Ff`Q@U}BT;(d69xY)Oc4zV3d^Sm5;O(>}XP1R9ERwYbX%pc1O= zt{PXA>=LE4))Z~6>Bq(Y2X zO!1jHZ^H(uXxS71{HZ^@Qj5Jba%?^Hkh5i&t+n-Zr>$V{NjUbRZxX1282NQj-3+=( z_%^4kpD0J*ra4l7q|=7ayA?(FcVZpO8~=bDQ zBuH4Gr()E6IoizoH`dQIRm*OtZzU?)5T#wherJG#Mf2n{_{shJz+uxQi$0lU9v*ISrj61`VCnbd)M%qIDO5W8^hP@bh1z2$1!srHFr**=jQ%4OP@6qxN|Yd_b)3Z9v)fKVsBXj&Ry49+CF-c zjIfx|o3;(V(C^+VyIqB_?U1T=<~w#)Td{iu4bjdij9DHWukyiT*)CO>eDo1OclrBq zjgmxfN!Q_^=d2>ioDbZ;JvqGXtD61iQ=LT=m=8ZdX|9dX14d7*^*4fu=SM!KrhXe6 zvuRQBdzS_`K&}u>bLt#t;O6^^z3o|Yg`|Mxn;4GlRtfop5$JvVvKoaBoqS|m$=JP8 z^RnFHBS(*(m60);^D0k}bXQhXM1LExB1EwNpXJnK`S0Dx2GEU6# z?dnYDEo@G%dt24^h`_~*YUebP1mH9PcHd5t(MPR32DcLwP92|L$mPaiOzz%AKOD8| zfMkQ_OkJRII&Z+x-pwm6uJgAkjwiQbIjkyhu;r>oyto?j1IorWb}&tY69E6NfdQZY z#(XD!5(v-P3m0zo7TWV5R^ST+lQerIr@(%>;p0;Rr^pwO_srR|N<+HD-lErGVMjm< zGUGb=G-TJ_>{Seud(X~}R0KGs8%~ZWi2*QL6d8Q-ob7t`=FJ&s@2^2!HbCJiecxE3 zw_EB4PxGd_`rEp2=Ej$&uTut@8yAwYCmKK)d@L-~;#3wtcTO2xv@_41J%bNADugDII&<2Py|2T zXo?ddR{$7sC^;09l;j-Sx>h%u{Q%G86@SI0D~$xQiCn?NpD32g-a+m+SmBGdR+5VJ z4~oN6P_wXE&^u|I?8l@W$MzjCdyBPnYEFt8WKPA|z-kX8tXG^9K>O2?D=0@o^(MlE`g*&;8)BEA(P;ixGKwm+lW%u);$+QS3Z$q{eN} z1?(dr;52)yY4Xz@MfceD^7I6PFN+y;Qe`I@SQ`mRv?R-OUNkITEvKt_`bu(R&|e5_ zt}tK$&=;rD*g#FKeNhCV1o%&z&W`P!0l#Q;baWA<#c5zSR^xtgMG6I?RWbRS*_aqk=^=|_TaVEOF0GY-_zuCMqahwnH; z1z0K)K8sxM;6_l>8>NJ#jiE@8mMFRfpK`9*UYlJ4b~s>Z*t@3z9vHZ|QXW%gEE~J3 z=`HNfiQ~7xYo@6l^6WM?(I}7AK8}=m&IV63#!$HaUI!MI*GB~FmKSB_K+@S^%YvJ2 z+;Q86190MLm=BY(qyOG6$Ks(;22bnY7{%k8vqOXADSCB3WrJ7C0}2W?+L#3NhP}^v zU~vXJnUoxMVt;Y(%hpkkXMEf$%MecsMquq|U3fbf10>aIfYH6IkXbvIUb%LT?i@}G z_=Vs+$iWi|+uZa4#Z!X93`eVZCF1NQ`c_k%f= z>%IJ`Qr?$}Ukg>4=#pJL?z-#w6X-i|z^HfcGD8|n_qTQDAO%o~VEc;h73dS)(yQ3c zY{Txwd(oL^Uz`*pA5JH3fQI>yo_-_Or~<$-h^{2Fc&*FOh0OK$<$#suE2OF-fj`>W z6xsCwVp`qKLshasKz3)CTwmUGgO0LB3A8AWHufIWk|qeEH?emq8FgOf5v@KAGwN+N;iNNHcsb zx?jJ*2DRLc%RI-dxIo@=$Iy2CnKb}Gtnjv-H;3|&i&_+aY#J6a~qUZRA!k+;q+g0 z?8=G)IBk4#`1tC?=1#Vti?`dR13xdXO0{2(lRHt6mdO3mtH>7$lA-sGGe8Mp|n9W-*Br2A*6ZuPtO?pgm%zq37Xnmd(QCGoO#7+{V*oQag7 zjN#e%C%sDY3DvJ_df(ld-Vyej+jg-XH5zvWN(4Hnk99W*e8fY+Wi26y_FNSXv$k+FSEvAQ+RuRvhEt$f-VL z%ixdJBO77cTacC8(cCxTVY)86+EUi{w?tgat8QDvWbq)XP0;&?!Lw` z(5bJVa_TLNf^9kVxAa)1JJpv&BmUN8DNj|9ZJ&f}!J0JLVSOG%>!-wM8eNWy_d5G$ zi?7h}sbzL@TwGkq-*=8?MFy2RDzw37h1Q=_y*;&xTAG@^113*)>&^&yc{*wAu%%bc z^nwIILnr9yV13aeX&3^K8zgDD#bMiG+DLfBq2rAH$`F;x(x#{w zW}$GL;X;FlLcyc;Pu}2vXBm}#`Q?UGyih`q92oZ{FTyohb>4l(#l?jUc4nU&PT%p~ z-E_lKW$Wh6W!LD(8d9oV$y(KyU!(wV^&45xWAiLCxVq#-M!Hl4k zabRtqP*v2d4#X%bDUASOEwv)0lS!C_M#?OQ_O*h5CzA-4oBQ?YD0JJvn^KatEo_mb zjzBp`qf!c20DnFJ7W+AsHoizBc|baYUJ30-fq`k zu({~ce*9YJFc1L{%``KYh0;iY!UW+B)$bc^Nvy1_7Wh8vt>uYrQIP`%E6dAP-$NNJ z3VK$fvP}Y(lr9Jgc1^O1*gS{Z{F$kZ`)*TE+IiA_!f9N>)Co?=Euiq`g_&Srp(lfy z<&pw~J*(|y-@bkO?u^GFuP*)1SpZ!rhj?C8A(*cgg4uL(CeOal?bpYf!FT3C>yLwJ zDlXs%Rk930VpyF818{t_4~955H8!sNshSO(0pqAoseL-pP^RYTt&#G3dO!*5V1=Z@K4NeghC9{8(WzW5 zL84vr#Gj7Y-sEmQXnj@?D=xzX0*c|VRS-@*0W0Vy*tO}AasZ&xTO%Qy)UC1}Q!iI% zyER<+L2e~s*7bqT9Ai*1CP4Ie)7(k z7eLqu%}HAQppTRB{Sx@n;nAI_fu=yOzxIX;y+!=se;Dkzxw*YcNDzSEY&ZRFVC|r* zYjJk=`*%e$E|B$6pzwb{b`1)tZfrX|6dG$N>>hl33tEshsH!hcec~4`T!@0y&M->{ z_ar0)ZQfR(&w?k#cEF_&Z>DG$6?yB!z`*ZG^sLYs6p&qfaQZx-{}uh{usIuiCBH_W)Jyb+Tjh4L z%(r^E=31VzBp5%7&MNnq<_04=vryI+8IRUAS=#o&>16lUKioi5%Ali`9^pP%<9N&q zkbgJz^kVmHR25gf*IJTQ3$N(+I@aA5TLs++nVq-cmH1{G7Hq$vf^;S?W_ zyz8s)I8-$10p*SurAH8FRI{v{@O?{A<0I%C0uY9!{k@@moJZE^j~_QsU6I#w9~~2+ zwl3rY@eRWjUc->``1J^e*Ny5FK;5?=}q%vN=Gkp)Ai_2X}AY z4cRhdDxzV+Zn#^MXH;YrEo58@>U4mj4JH)ld{^y45%ua-G84?2HtN$JI(oGB+tZ`% z`Iah?m~8->OCD32q!;=->%@H6C{-8zfhvsj-f~PeG(!bbDkoW)!^+lW;{4^t#?m zeK}B@c3E-f`mmpj191nZiXF^(d!$!MNeK+qknT%#UShM-3l+y9u2NQ4k1+Sk!&y3J z-i?xNBh5J(?ifM=V4>j!Pk`A+4>A8CI=YRX82;>6o=~uDkOi(1`cUWU198ZAG7;&Ie63(wPFAf1gFJmpPzsjdkDx(Ut6CsP1~e;z&;%xk zg#A|SP}Tzc7eM?VI$`na*WKhvzuk5)^OBt={{Brxt+jzNF%zGW1HZvAg@;j##*!}G z_GygJWx2TFz6em!VW7`b;31&u34H8<-flT(-ZN*+{&#u<^6zBZHhR|}vXr1omww$6 zdJiC~SK@lw3x=CEl{3G?YU#r39szcxz;4+6PG^{}+HtS zsjtwA5hU9Xe-M$`+Z_dN0*^d&gy1JnE(#>jsO=m(to z`+xGtpv8(bbwIcV1kQ4Gs@-QUpOELhVA2#TOab!l^B|_4K(GY;0izxAe(Q73V5|h_ zFvU>73eDfvWI7~P+FS6QxKkJ?4KC1<3spOU(C-M5-C%{IQ$THBtZzYzc?w2TzJWVH zJ){7OxPZ=>K+JdoA_7IItaga%&?~|zmYXjbqYVHfW;^Y!NCk1j3{0x4kXZp)7q)IV zA?nn70^s#KP%86+9%?LCx##S`8aHCILy?~><9KBGeA!Weo9|ir1)4A~(hp@P;W0%I zjo|nna-{&>HgydR<{CYkZHLSh*$3k{AoY2$KBu2cI^v~Gym0R`%iC_q#X(?1ATNQ& zL}C^Tliv!N3L^dU1zy@HhOZa!CKY>fMaRe%aWXNT1Ey%wS9OeEUs^Z5*dY5CIDb7w zjxmHaH02rD{L*v(7&9|3Y{s?>+-Qf3SfQQ(H}@BpA-QKz#>uu|EJFVRJ~H98lZEdt zisM_$=`1X=b|AF{fR9dIlb2+Jd+*ptIh_Bh+>x`XibFkF?jp*C@B@(U@$u_q(bu>g z{V1QQb!WmMJ;nIWorj>GM!}n4K6g7-b9&{GadR1PchErB?dwf~)|-RL^PFCuar zqy!X}o5Ie;*qwp+@H;dESuajq;g*s*36$>i-clqsHjv|%$=2ea|AP7Wk0^Kqnr&5g z)nkNozMK;Q)fW!*dkp-B&FcLfc?@*|IO98>r(Z=2VM5_?g9}p|7kYbkpd3FkdLQ5z zh|<)CF?Rr!+Bg;Z2@sh_MMpnfUVZ@eAUa(B=UZ*F)}WG{zk`%XW`jW ze21wVV5B89B0|R|%TPp6`GA)Qq487tiTP(~7#~UPw^grP%c+K4usiW`nb8BzG0vuQpV$@r&PhgG~Fkzd+GG+O%z zT_HI>E%IwLyT;)9`|8d@UMu*G%6}n*wdAU#6Z}{?>i6;5=JcSOl!+e<2ZHtos>*Yhlta4qqJ|8~gO} zJX?XxIm-BXQPextNZWJvfiMy8}FI^|V2ZfIc_y)NM{4 zjR5UI6Nsd%2EfYULiI|m8FZxWfISWuNMT;Vq}M{lN(6^1A(C^Wx792c_G_Rh5(Y9o zWxI9(0;jUzmQzRa1138yF4($kQ~2|7#X*w%R}Q%>B=0 zg`uSlgu%>n{vZcGg4x5dGisFmC%NI*LmhsvPY2wrtUkVfQ0%C%bI<;Pl>HO?fF|!kE=G8edymTW3?iD#yyi$cR1=6;6zn z;;#H4$hr4ekXdd=14J^*<_C+*)8rh+VjVG}<<-Xagw=*p`ZSb}uRY(kygrF5E9_|?oSe-3x%U8ORa~s66*3w9kDoeaIISSCr^C8ll!)Vo8%xq! z^SRup`^k%FF%5@p36jInhg2Nt9&_^zd@hazS4bkvWu~F)rGm8c(-*?xEm1Seve?l& zIiPcldc91yz9`Zl$qth>v**QrvKZI|3Q}NN4bDSt=GnG#z}jX!#mJ^6n_wmYWk=r$ zgj4lWE`t@NCeNrA<-_^L#8R4d2+a)*%X$wDaH4Z%GTFfi^$tZRbp$nS_&FUjrI4zX zqvzWiew`HQ3?upG6qxC<=7eQB^V)V|Wa6@a50<*y-cFEUfZF;FCkA>t48Wo+DY7v; zYq{8q+W+LMFEIKIqbrX>H!InV!>Kk;{-`eNs=RE&H$d@_xBx+jk$baPu)V=~m^vYM zeZIrN%uH27DoSlPxs{|WTxgK(XRM4ZNWW}Soo-Pq-RJ(UUu_LSGAs=VVdKs)n;fSY7G!x_ox!Z9Ddi_5~_UC)N~u2wx8-qqXV3v zsnbo=*6}rz4ld$y)zQ>MaGhabcss}sW;zbwQu%fa{U>K`OYhBg;zCA8Exy;)y@zc^ zC-}R)fpP^;+I^jyZd3>L^J|~8Ng$KVD7|oYmqxUp(+$SBpFs{Y!-*}N0OI|7QO3y^ zX53IJ=g^%)%)g$*cQ}5;Wn#?ih%zkq7%Vqva;&aTrX~6tt}Ic8J-I3(tzlb7-`XC_ zUj%+Q8dJlp2MpkVP+jSv>Tc-k72XA+c-+9U_7z|F7)^3BB=TrN5s zjWP#Ncc?KTZrb+chT=E?e{fh0B(RJOE1hog5bct5xU!`d;jkVE8Zo)9(FmiF!TvO| z+W!gqW%K_vK2$5}f)O^bCgye;^p=$M#W;7$Y{H37U^ z+euAaAiMHf$%bV5IZ zQ%?!%Ww;Aix_8Ql9SFYnE{OkR-(&iP<-(Y*HJgBIYL<3R3T z#l=nQV!`^V94s^J6gZ;#69*&Jqp(m^7NhgFkYb=>Fjmz57+6v|optxYpX>)5FWsRMtRnxf9bYdzsGmVgdL;rkzij~o^MTAw)xmiB0$(f@^?iUr-$ZQcTN z8U}}~YqZm!h2~g^AzZ@3?U$;S>R!Q+G!#Vg*so&@#xH!r9{f&|>M;b~Z$aOXxT>edo!S^l$d^EO4}y*a8Q){@s!*bAR&F^$X6udi{DJj0~ORL@&9$I{g@^ zTWVHT(`3!0qN1(pby)!^hGcR;E|vs&b7;lI7$}~pDDj| z;ZjE;(TBubp94BkT+-S16g1j`9_XL7OEl=e9IO54S*V~l$I#XI?5RX> z`{Ad?Ap8mmo$7R@qGmDkF@a7dtHb5RuDD}?F70k2AxPY?T^gh?vP%9XXw0}sM_AW= zGSS-tYyxL*FUq@T|9G;(^)pWfeNc4=z(V73s26nn6~cx5{CvIQ`>e$-LlJM@oWO?- z8k`*fc{~P9;2X5aC76ep{hHn0BIdm?$d109hQ6`{Q&kqA?}E1=y?lX&`CzluqMIUY zS&x)xp7jCi0^XN;AGrNEbSmr|_ZJ`*q=+yW5D5=04I2a_2vrKTGH`L!+QiSa0lErn z9iIQ4@fuW`3UcG>)rX;*ImdPz_;U{-anjz-o~ANqxy!~^!}z!#L2}gZ(V>QBQLBg$ zyoaN%PJ2?!;fa*zj1Tn|xK==7xZ$kK$^?x?L;@E&4HH-JZdf+K7a9gQSCQy&vW?o9 zSQB6qq<*Ew6=%51j^&RtLL4N)MD>4!LVT3Nl6KLqF7#2s3;*ezK2+-kQWiQ+5Z90d z!i)s1*;!=cYE)?G5A3ASvH8b5X2$wtyJR&Q2shd71N7Lv{Le@#awd77M1hcyUGfyA zm*u6LHTkL>E(eDrxbVLAi`=If>|3xgQVccJV5^en9js|a$`HH2{Ot(~+Mo;3GJEHO z_9@>d=90-A9=1d_D9wEp6(72@=3zO5=+lN~llJk#L7mKLDjb#MT zMY_pZD9reSyhrr#ricJ#8%=~fqDDSnYz2BLT zn{l&ER?JGN?bFxDXXa=By0+;<~yDKLNA zq%HGeWJ>hi8{!b`$VToXIqq>L(F;wR9bK*x^9Eat5#GO(j#AMx7&{1N=ygvJ#xFQT zo=n`oa=2yz2^&tg@+6Pgd6%uN=cV22H@jOcF5%qDPir9`8u}y-7}gvIuR?gM_Ew=3R~G zD<^iBUk&?EanKHTajm}&{ZVl;7#q-R6T9FmQT3;t6+Q=hu#Gmtuy0USUgKC0i^RO= z-t3ld$X8O~qm63?f43YQDT4$g1OptfG4;ry$2=vsMm7z0?ztw!?}@MtrNSu^&6F_1 zUSMNpnw3_QrcoJ|G7XTXB0Q~d9G2iTI@-z0;>1|%M)l)Gdatj~zMB9o_piLvFpH8a zp*L!m{>$BQk`JdGu zrl04M>{;5O;zUT>WYxRmg?0@6>hnW%Y|?*8OE%3x(({L$<6~%rjvb4-u7=#WO=;8Q zip{&nYu0>n_;36}LF1sm%|c!qY=4``KHpy2+l+T1-`~>c?AzLQ9RA7sFjD^Dd}-QD zBM*0G*t?eMxjw2VPmDGzos9|A@Kyv)*R;u*wQ~6g{i)f_{rVRQMr<$NP{Lagmc7ka zsL~QEWVkNUNVuPqK!5*8t>hHb#pm*B@lu~TO9@LzRpDi5s0&fZcyaa#=2O8(|8x&H=vn#K-(u|$F(Ib&W6DTgQE>lh6e zFO|gBKeQraFoif@d0k9Iu!lkcUD-?87g`$M&jlrXAvPZ`MN)0wUpd*Ov3wOzCEXe#eXtVyAK>WI-zEjlA z_RtwGeJwHchEi~lpnz!AV~CX(57^pB?Gf8+B?cT0+!r4%n|KIcPuu*{UX-%`ORJ=REx)ttyheW4zE29Js+F7s_oM4!c4B~(3vRlBHtyM2MM90>*AAe&R*%%aTaW=t{%&0HgOG* zVi9k0d!Og;O*qhQ=vx4SV6a3vU2Cojh?u;oCQ^k&%eKaD^nlnQ!isO~OOD!z6kJNiF-Dy0Jml^iJSi&+N0y+2{SVznyda=Ng@BG(P3N*S*%SMPChd#goU_jxjJWoWv;I z(PUuQXU@Q||M`(a@GD=~M$_Ow`y4bCZ!=^yU7Ujd`OV=r26qJh@i_A29RtH92F#sX zIG5PD0aurECgp33=zyZHj!%NBdD72{O8A~NTS&NC$%1=(;GO!9;nOAy3B&dpy0~y= z#h_a^&$Ax9_1op>dv{KETvc4T{_?Vf|JAG4pFUDN^4HDVN34kJff@=rY3Phk(v#~B z2E_?vD_0U_nnoi{Q<;tZh4wStr{;wWFfiPF@|y_5eZS|27*tNTh|W&7e&l&;-B+NN zYuc!J_wL)Jr6rF>YD=Pgw2XU!*wb!}PPI>Y#x(&M>FJZnf1>5&j5s}4f^OWnu`(7Y zr@Umdtkh?{xilUvY^ENTKSItfZ==S0bF6O8^%YvJ&GytaHwzMEy=vrFmvZHF`WB1K zTQ4v7n8hp5`%0X%Z*@jW5lBH#!vEAAJ+(a%W9!e&Rr}lieYuaTju|cY7TJ9adU6Fz zd|=R+`BzfX<+DeAQ}sE;Zt2mpxw(0ThUQ=qt1puf8*AGf?6z z{^DjrU2kiu+J*K{y1D7iW>225U?zt4cAYbp7ttJ~z#)1uC7`CA(`9%czfrlg<#WON zpKtQ(7kui7xq0i>i)b-x5--u^_WF>wcgh3-RkAVqzO%quzcW+6i+yeMy`XZi6Rkhv z;dK@}ieSZ#ra_5Qr~7i)+Ij%og5RwPTAbJ-KEx3nP`m$&V`ETJ+{w=oLJTYLZ@7 z#Z)C~>M90YY>gJR%5$C{RBoQZIgNb~pRT@OyK+Lm%q?H9(1u+!md9eK+_SE(uI<~a z!{icQ{OD?zksMz=q1>>*va^moJUl!#FE1~ojblgT^?b9at2A+qY(!U9ix#;Taz(8p z;no3+=Iz@r(X;Sin%dgowDN5mQOgb`cpqhD<+j&H&q@n#ZOvq_w#YLUk3DdkO6mRl z=#}jDtgwOmTz+t}S-ht<27_^<6;m&ZnJx@}v+N{=O*F-ko#uM^u3WkM;expK{9u{N zQ-8wDPd+g*ZMd+XuWzep*MpA@4XQ@g_NB{B=;@A(5Oh53);e*D+8m#)ovG(aH2VDT zg;{LQ#OdjefwG#Kn!!+=3EAaa%5XouQkO5X^c5{09-gTnMGiipMp(qv;a7~x!L*{W zQ}D|cwEkk+@&$35j=91invD-!(xS?biRilFO7G3LO53GxTTQkkT6Uxd9Xi1>?B4sV zsq~@`Bj?ZbdmkO!)x?p9|MKMvZRg|S@|M)8ixLUR$=n~<(b~bGv9X_^?)8F2T$i4} zoiaXu{@gMCroO(u{rSEF9m$xpRX={fRqjr=r(u0JRbwUYbs0Fvp_`VQ<>t3{w&Csr z))y{ZAotl;80Z(;@DrlTJ-4R=(6ogUeYs|V!NI{RYkjs6CG^Asn*q{`1Ba(cqo_ik`KWvKlN>kv+bTf{bWPP1Xfx^Q06yn9V2OC(s;-af~5JWNN`&5dF)SXvzP1U7%%ieuzFp3X(@0GbXsRY9!3kjBQB(X|7 zOX!>h z?G-LAuIUvo!yyf*EU}M@flfy8$lc++SdIN%;n4VV=X&pX8T;=!v^2S`&GmJaZcfBZ z2zqbAic*~BrUQiSM(=Rd=GhGNmhLRon{F(O&?KM9Q4;haT6MSqtcDDS5&X+Y*(hFrbfeiSAV!*Ht0StBkVMLKNs5b z`lfoE6e}iGF_6QwEg3UW%Ny^w{tcR8QoprGb)+|a#t3;A(`tX#iDD{I_{GhyKj4+& zZP<+c&E&AEsv?zdPYTC~+pZBj-by*mMjk!%Y69*+@PSU-ALl%|y8xBRilMD0w9t!l zYK&dOSXCOBZdl@! ziEfzB8~J{K;<>$+YSEsWl4Y#>-DpV3ZEaTfviY?$Eiq!&H-a1Bv`a6$jr#IR_lf=S z$G0CfH8Ju&r;5iUzK!rExUbXZSa)G?xD_}n9O$*(uEgBH%5FaSL5($WocY-(yn~!v z+)bYgVoxK3o`~5FKJ}MebmQUWb?=>{Zk%wQFI$g(czsfqL()F^=x_T|P3psm@1HZu zZX}qMF3^i_z%s@e{^Xq%wfdtXslyt2v z+md~`GvMe?_vVwcN@!B+r6t^%_rKg$ixI=$yBDOLZ8(?ZeChXR6{o^P1Ec?fVbiXT zwku4|9enfV4auOSAiJx%^^38hfm4qOZEQ(ZKl;ayJDw}8ifmGj=|~qI+FdJzp2SCp z&M_RC4h;+A!BDoQ$@wPr=f@`|P`tb~GBTgt*KOk9fa&F$et;dw5N?vFj9crf*v*Hb zJ#8iFJV1z`E^_W|C}P^SR%gg%bY=s`d%jb_At9^1R@r=i{BcWs$Rn4!)m|5NjRylq zVpY|lhTS$3ZQh z_^v{!x$ZCW!OF3UCl9?+R#IxwHH#ZJT*xx2@baB%mfQ0%F*A!Oj(vObf*S|gd40Ag zH|_SE(}smNw`~QzU~6S+Y|x@J$3&%UcWZ|7D_Eu9Sa9W3vQi)&g+jFrI1gn)hgOkV zlu|6AqDvc*1#PMtLon3pbZ7hbH^)24I)dvb34^7sTLbf6X`ep*3Hw30J%j(bc%QZz zCi*h>tmhtm9rh{W6uZ+0%S2Lsx9$W!MrrLEVo*CK6dv4*oNfE~S3$taiZKy8}tBu79+ z^Jlo2L7VvrZ89t*l%OD1zu;+W!mU>xjg^PIj4DV(F&u24=%eqyvCDegKYspC z4f@vVYMLCKZ?tIK)stKgjq(~s8H?=-l-o^z`EtK=nNGH0=ELjhM!QQ9!O8UP^@zy9 z`2pqawYgWm z$avYt^hS-`ZX&F!)!%>pA!YyT-L}+r*@3^C7g=d9j|*r#ew-w`x8vZsHI)|ZCx@@8 zsktU3bPsAzujpAKHy;mA3aoBt-3fCl7iBqLU}fe3H}ivMFJLRVrhe$;>*ZTwg5A+Z zy5$?A$D56*I=ak89$&Us0qax^Sa44P5fIRcx!BZcPCexZb%Y5PsMxW$?*4oW)|~|J z>5EkVrm$sCP6`4s$Ej4sojhDnSK(#Yf%B`9)U(|a zjWGnk3ko}yYyggKUeXw4eg$=J&$yKHx}sLH{SDFdnC%Qe{|rv=omS!5nd}My=WUg< z@Zr2X5d&}fD$VC4HS}*CBhPVGXDBD0)b;l2#u#yxV8F_m?O3@1gJwp#ofe%7^$s3y zEr-g>U`zwH+)}dc&9kf6=Q!Ja4I@s zg}nd>TkwjlFuhBtOawr^C0ts1f*&2Y%B)**$RawALekmX zFZxzpZEb6*>$1qR;hP5bwLB(GA4##^yPHrEr&*29adOVP>&ays5mYZs!_Dwx)gSP1 zZx|v|DG`R6P(r-V*^zFS-Bp+r+in8YLmBnEw&EkVa|I5vEc`d8xwppy*+i-sUR2a2)%G_3ixuIzJBqeoWwKYE8{ouITsbd94 z^)!~cY4I^P0Dwz7Q~+sq_Gx3JP^E*uk;Tr%_@I85u@7u8cY*fykKs|W29i(y{#`BV zhJyUt6MTf8FiDPj{uRGR$t4p0LZ+=r3ReKdlzq5UV|#iFP&dHfhG^0HYZmEeG(@bkV7qI6i@r3OJ!(azCZh)rVtare&%3_1r}-Tz!~Y40!*RNUZ(1Y(cxabFQ!*x`pIoh# zroQUG{{6UMXi;wRr>556{3nC~mh1|&H)2Cwkx{+I7%@2iScp3MBrqz%X?dV6oy8})CVJ9o~-FamHS zv6TP-N+Ijys_}8JxSp>fZ~neSJf=W_V)Iw za5Wigd&6ZNL)WUfpV)1A>z*CW~Hq8?bkh?$;tePUA zEz9oDk207?>3MP;Ky3+t4>X%!!G2-^<#cXRTU+FSE|AL>4q+++A0ijPky1Ud?juaz<2{yq`+D{=5_EG-0Y#At;uW_C(n!YMS(Ze8WJ$1cN7eV3U>X?~Z|!AT zLbpqHTbcX=vo)LB+yRg@B`s}+GLsd8%Nbkt%gJ|Jt0e`q+d$*P4+_4`>?KsoBpcmJ%8aKc@KIM)|N=kYsgF-kY_mwNrfStP7&l{#) z)1>DCF^YmRE0J+J2xPy++13G|iE}I>%Oxi#MbePiS(v57lsZ@Xe|`GlaA z?pa=S5oqT`A_)eF+WYhTs!&u70O9&M8iB#8!ra;X6@pc^e$NGHV`ZkRS@;tG(JMki zU+!ev!kiX5aQKu+b?juBEpSzvdFyWvTco#VbBPpF1lTw1!6`Pa3*|#-n+@XwAi<^} zPmD*~76Dw0+;`wmQCSx}@-ooVD40nxz()$#v*2g!$-;cAE;lzGgcbR&I{h4h{U-yH zP4(W}O|e<7de2;vKjK(QEp8dCxh=U(g=#|J=fLe!tQL90bOs7BId zbt$ldoL(E>gyW%(A_3bt8^DdM5Y7%CGzV1n{!BeTh{4E0u1Se<#yLGPHI0U`2Lu=7 zK%TWIXwT8&wuVU>UmT>>qeW_E!fi?y@8&-3xzW5E`qy6?mY#o`XBP`qBRm;4%%z$R zg@d%_fdUyg6{h_~cCh5FnY>d^8bH!~3@YSAd8z@$(a!z-vctuQd^z~HgWG5ydqzuY z{PQ&}-EO{f*O+vU6NQZLW}FN--(tdVwYN*ZA}c$HyyvUKjBLj0j_6{?skZb^H~M6M zac&f?FXTl`sszJuy?mXp)=Eh8t5Z+9wfzjHEaq_x0Rm#L@njW&)3 z8Wk%{ba#eB`}ZL;N!YZ3r5yxb=ZiHYJh}fXtDIM9l$dq&6!K(NO`xP)n7Z?amLjZ) z8VpPTUHo766iG|c55F>Sxss)s&z&p!wgamcjSMn_a_(iI6A|}^+DMV;xo0oEpaD;S zfI*W*ZXyi)MA)K@Tl^OyS~?Flm4k|8hCJ$dGr}4bz*^L;SBHwNPst2cD!4VqnY?y7 zUIO59UDRdd4ReEvVA-#%nB_xf%Wc*N-LqN6ZDQT~*QeTRSk9JBJEga+naOR-t1bPu z@~bwaF_cfMUoOqVRbhXxt;r)ZAD87uaZgbR&70c>KS8Ru;f{i78fja;2?9D&xoEFs z%QCOzv^4jv&jui?20f8-oP4~k4E~W zU}F)%gG({s&eOo5&9@;*Is~!QA|onDh~Wi%{z5_5o+*;pY*c?)8)_@8J!{%!Wh(_~ z!BNhYQ`V!KofsQC>ncpx|IhBbNC zeMObf=MQkzs>X=%TW%+nO^oat_^8OpRr|Czua?~jCg6Izo6Ai`)*C;BD_+0;4ZGLp z6*#7}of}V2yy9w;Te7#iO&IcAFZLc8x31$V%~@C)uV00U>W(^&hGn9GfNg2?6A@To zD!}p(+W-`&KubBvay{XDJ>m8FkDDirtV!e0wgyXA;Gpo7AuGZlTtN@h2xh8^Yn}JZ-A+PPs zR(X=P_v%~1%Wnx;6}xWDar?Ur-80h?X8f40g+@lE z1OIO>jh*g9=rh7WerD=llaU$l9L(5hEq0)4_Tlux%%<+-&n_%?Sy)(@e6pU}rF!qy z&?|DA^Q^j>+QWg3&mgVMNKo<8?+g7rL9wxaVu-iDHGR&paa~@H7BnhbvEjtDw~Z%4UhK?%-bUpe(DmPY+$bOKRQ=ed;#n(cA#`RFp3;O zs<(hTYi)Xu{6uvdlX#=TzI zkofp)&=bjZY6J@)T|ZVc>#T<(hUkf@xamyZw(J+N%H?61e?H*wia>-cHLxv!Ftq`g zat2c9y8o(P{vZe${05Y~j_VausWG8(ahVnLIa?rCLjmR4v!laAm6sPVCo@%ACEA~3cIG`re2KUC263ywE+!xz{y0g z3wSo++=!MIW>~(wDpx91Ff^LsC9&=AaIDF0wp)l_FHb4L$jdd1pL`h+Ofnx7Yl1{$ z=ff`JhJ)6g-mY!fp~Q8H2(g1?>%=+ z8#JP@@bC$*e22GkdppbEPThO@^l3_0AqXLe)<>(@^8z@253o@x6ejtCP0=l#2X+c? zCUmv&W*IJ24}ftiU_6kZnkY2N49egIx=pOPsYxLJvOC_MBtPUzjhpVujt7z*AuE9WV1>T2Hd;<7g%F{#0njymAKXnXvPYNC>|KYd&h}_qV5=B(89O=PvD`QfGz?t46p%f&;1p)G>?E<*jC^0Gg5XmA zU|asf_0$c^&&tJ(Ho$N1E@^*?1ET6a(+Ed}OZyT)fg+$BMDnzWuTzYHZoPfhIcNs` zaaZKzhQK#)&06u%nGvlcRc{ z=3DFYMvo2RtVf;86L)X~Loi+3-NPK125k@0%<+{U_=+3pIA2Dl%q07b0m15XLqWfL zQRS1TTM#d)#Cd+r-x&(V*`RVh^JI26N2VGmdSbzab<4=uFZm5xhmi3D0O@`;W$1^33d?LK+Wg>Q#x*4|C!U1?ZOuG(|w z&xccNKn7aqrW~Z`bxDr)(*k9+ZfJ0(6(3iSj-j01V%Swbhe9k(%IFd%;SF9U3=$ zDvZ~7fiAtB6OUTN^Yij5g7k#D&hlyP*qjNF_1z>G_2y76#F7iB`W`bxl@Wc~y^u_F z7x*A_7Z7m=)PQuFY+WU`a}Gvcy?{z(Qb(CalhM>GWXhUT6T9X$M&?1x_{m zp{pQ5X{D;(Ll}-F^KnrNn9GQF4>g*NsFm;|3pr-cKQds5@s_yqp4?|}C@n(n*yAM1 z4k#pBjkaE6tT1)N;azvkmJ=V2{_u61O!SckLFPg#Sc5ys-Z7Fdsjr=M|G@L9(k)o@G&#r8r> zd2{*TL^cv-O4>RLA_R@H5FHhs7vGUHjdVVK-JImR5w15-(Glakc2C)~at>Hm#KtSn zY+!lh(JR>~(J%gW>5~OyJ^-%5@t$i@RNL{oFy0bf(Z@gDohF_dy7qmuHb205*BkY2 zYeZ6LJNw%_rV=qkLyc#Q;2Z z=rFore~#f`0P-3Q7|6n!QNS0J@ookU;$+@J+9Ski2QBp!r|bj%vMGFZt;8wt=d_X- zXG89_O2-b3jo`F0;wSHrP#(YjKAowXdlwF6=7V}=-D+&g8Ap?iOr=tfP5V{afIiX? z(4VT^e@Q8@OFX*?!)tn=q%chGq9kk0Ehi@@U&9IYy9&$C((b&W`CY%Vr1JvmqNGYA zWwfpUXm5_t6*v3{AjuAX7B&cM0E1(day0JVX1a^6}v6KJ?YPXp$K-2At#1FM<`I(N;%Jc9Q6yv`nu3g;qwN zLY~jkk~9TN;=Xf*n>!LHd6>(GB=)hOey2Wb;+POQqIwDqS6rGVXHyKOp~m}9z>itjf)2ll zaRWuUZJqSCfWsA?b7`tkkGZ2dI&jO7QTZn>04oB^n{?hZi|_P--l;yPrdUa0P)JCK z8l2XFnxL(ZjflBixIf1zh;&O)?`|@wtP`&wjeGPcF-U#InO&*^GCA(mgsoI3*{RJ; z(gW?A=p9_Q(^iwyF0yezuvN;GBtQtnmQ+gO1Le1?2f&&NdUA-711#Jxd#~5u&pjhD zdOLXRZc;yR=RO;`ZLGS2Y61jHL^Y{S(6jv9*#9bFMo(Y zQa!lztd*pL{&s;vL>eiz^7)Xl^aBvl8x{ozv&{DyM4@I<=8S^wNFikw7#JwHsI~=q zjYx2V-0osvdi|%Pr<-q~sgeJuC!7Z%Bc%}B-w>O~hr|^)tQ67TQi3}Z=tr_=<6>gS zv9TAiCbnrt1{L@pj38I0s6+smGqB;yzIkc5Nf_rM)cs$4V+7{`Q2{mW+GG5ClbwAONuxVpK~VxL=xh3jSJI{; zNbrdRIg|owAoSoPLo?bOBb}uB5_PX@e^r2=%w%ZO z>qs{9IBCchbi#Y(JI@=!npndA1pn%3R)^C%CxR;7)rN_hlOsQ#acE8vuXEZ6Nb)Wi za3}yY?d3`)6Jw*Jh-CX+xIt3F-ofFT0lqj1L`O}mIbse$KSivL)n@Nq;m7;>AQxo; zT@Y~>5KkG?2D5Cn+c*$|%94)^3mfPAapA~sj!lx(qV4y&dv{7m^U|D#yY+l$e)rM{ z#aC^@Z1lhHD_MHoPWM0Fa%2a0Nsv*lPn9%i480#UV=`xiuay)7(E`Z^xreiIavF^( zJg!tw3jy~kYZ^5Q(q$2%a)ZoPYi(p`X!v7OV!j|T z)_JnK`#zb38giS-3|22qe1o3LYhM99zzT@d7C_gIVh1zW-FkkG3nvbRK|Tr?9GLft z@Bnz2b6~3{0l39T!Qs#$Io)v|>@jV!nnJYW%jI?xVO2G5Ir7LdoW>Vy0QZ|{z$!|aAHzLp!kmFqQwhn_~N5zBax=A2*~XyvZv*A zCU8N!gtxKk%IXAXYmQHR1=d~*A!Rjt5Xg`Y1f1@Gsak&*o<>^wcRx`k<#Js5)r%-i zg~xKS?xttYoKa>MgbWxGZu=Fa$c=%_M=;LA4UW)BzYibqU@j?pIQ*qPD;LzuzU8rz zJy5YZ)s#$y^zeLbN|~DPEG3W_y0g zNj0w=o@PO)g4xg|5%dJS$yfCVKk}PjR4Rf>5JnIh9ZfPUbDM%xAtE3!adKwE*)p#U z=COo$6Ua5c6W$W0JMLZjy`>>am_T3cnt@5!8Kz@6#Y)Yo7a-$ACzbH-bljRiAYwft zQyVEh#1=%teLzNAG;2789={f#OkU=HP^1C$8V_@Jo2k|oKMqm`nCs~v!&1h>_47;{ z*<=>J9^eUQ&bVw>1metH2(jT|S@2+bfSWlzl@_lJGitSDh%PYd2GZm-jC{)Q%j0a)xcbH=Riwg%<61J-1fw0ZT!Qb_ifu41ay`PlAA+2A5=m=lN$TAwLK>SQ|-- zQQcQ26NQYc(*Y}kx_K<}*r%0fZ;%HI)GxG+?EpBH) zzl@T%7QW8nj5>}yhmHXuEUS1Tnxq>wwlixKv?0lnWEapVU_iMF9wj1NfS8qvL=FM& zW&mGcG4|K}^7xIrNpJD1$Kdnw#BjF9^e6TG3c}H9N;Z*2%RSyNlia zz8>FFa^SMe5D1;v&ebKH9S0c5RN@xAPc)!SRt&<%)+ul7vo^4?60O40uv&66xSnIb zMlQ1-4(eS8@w;AW>0*Ufz~VFawOJwHkAxbdV*8YNzlpGn9FB7DH}4&#H!^KF=ekuf z7+*?h^r$vYsQN0LdPsV%dSqVx->kPS0eJ^-1HtzvL)z>fSuI9c`&EE;ej$qdZXO&i z#MB0wt7B@N_x=0rx&C4;I1Z0GOtvyfrDn|>V91f+;To+tz|o2cbELd>Q?VO@G1 zVW|?kI^Bl*#WDnu3qLShBa|a)5OW4%m;eSh3JB2yLOm{)jFad=9MCA!Qo{7A# zdnWYwB+`t*jYP)4H4!@CUgWUDS-{)X*8#pPoI+HFum;4HP*zhTY_A_&Xm_YP=m_Vn z6GAXpvnhrJEu2i;Y1~J!%;%HWbdEEKj*DQ}q+LG4rlx?L2SF?+#A65i*a?LA-FRBG zh(!`;OpsGWhG!>YJJ6X`ss>IQIy8#XN+Fb0F$Hb?C!}}gSZ%?^KvKPrjg6Vkp8W`d z(7N8|hqpQo0k<=N_82Yckcvjq7(eA1Ws)I~S8R=%mz)5xWHL3z#=b8oG&C8GZ0KZ{ z8xTdB1elPNiD)b3(nlAiT{Hj`5(?Y#ItD2S5BYH;kR6@MrABNn}l9Y+FFIO;WyI~?uO~SgKv{!igM@5|vO$6+vpHv~u9A~p zW@|lq1O_k?1+pm`Ib`Hn^R|!+GC!b>5=X!o>x`9j$g}PftE#C{L6w}@ge2U6g*>7$ zP7juqY6XQr7Xs1~X;9c3uznTh;Wt+3_{+BBk_JrZ1^pncK7^yqH)1rGC19VLK$w zmgQ7M=WDw5r$Y&f5&v<&?=ZP?q1^s&;|JoOS^^l{InN7Ody%4 zcv(+w3;+m0LVrt%IuckyG{b-Np)p(vwAL?hkEVi6;2hGGmEJ64h0)JkQQDlK;N4~- z?(42~I7RtX*Wq=|Kn^l0NRSQ*Swo~2V4%@I{_rA7@0)nX|6dM>r^y)|+b;HBA~fHu zYvP;-Gr;)CfOfbAs6@oNS7ddvRURs4&A?Zhj}KE@TZ@c9XjREbNx-w~fO4dgSB3p& zvkGm8vW^RuYJe{etNxkfDE<7{jnoZ+07y=mnpjqZWx-K01vzD6@Fg@4VPQxoBe876 z{A$M6Eomn*euor8R`@1Iw)>2p#oQ#MKB=e9R)qa6ZqskB z2aH=cUVPA{5}e&g#I73K%)Bz2_82sTk^y}ldGhN65`-!iV^z>5MLf40$H&JvNvZ}! z<*ooExB!R*55KcgcP%^ArM#5MLmokO|CYsAGVtw^tS|&k|ID0(%L4co4}TDZ$J6hW z+YZSBQ@S*&P`eZ(VnXea>8(c2pibKGIG9C=TddGa+E)7n@t6%p%>RZbX8aH`VQ z@}k|u>&7wWN7h^8i%xG&U9-}4t>N60d^)HVAh?%1wkIG{zQc_v)PCr>50@5icQazb z9kXkBnfO_`iu%@p`$KSO*68~W;*&pkN4bKvl_^GT;=yk-9;a#6>1lsT&O*t`$bv@<%Z@>-F|7z$$-7R^O%!7 zjeo5~{ACxy!>_q`uy$JSrzh$Zl>@Rd^z`KC$r0RfJX08qEqUXdV#Tq712+j>zQR{qS4>YQn zS7bF7C5P`b87+`cc}3)_SB=D`k78T2@E#m&Uo)P+e?4yh=j>wZUwcLZ7O(s&bBH9t zSh~PWWU@Q{;)IbhMNQKg6C+RYhd%r_tXzojqI_nYxH!epsJ|kd?&bY*Nt&2Gooh zON*D+WzW0aF5Z82+-}Q7l^?$y%n_7_ttw2Llil^8B&(syySpbgDjY8VnVS<+_Q@nO zq(ev348tCPJn8{OCXH1~%oWR;Ke9d_AZ{J}1?>Ot+zLWo5?)^W(*uOsX`G2qksQ+ku zi%0(}wEZ7k>VJLx@L!wvpF`q*@q~XZ=KrKL|5Z=_{*B&$9jSjEsecVT_-gzA9S4Ez q_K^?%o`5mr?*;gO8Mt)p9hR_*UGbGx^nur9z$mES$-4dU<^Kl;p?rA& literal 16874 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.0.png b/integration_tests/snapshots/css/css-animations/animation-delay-003-manual.html.0.png index 844dc66d8b2041aebe7285bc875b0ef580a3a7da..7b11c12730ae6d3dff08003fc9917c20a9b5d5d1 100644 GIT binary patch literal 14580 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 I7q373UoC#IGCFs~jq-YQjgwR`P(mRYI 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 literal 18782 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-direction-004-manual.html.1.png index d9fadaceba45aabd1347280eb5080aa863573e70..bf703259b629af9fc1cc6729571ecbdd68ca56be 100644 GIT binary patch literal 18688 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 Bv9SOE diff --git a/integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-duration-001-manual.html.1.png index 9224b97f19e2668e8f1b6201ed15e3c5f1d52865..ff151222c2075d45684d8eb2e1479857c172fcb6 100644 GIT binary patch literal 17542 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 z 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 From 43f9b59f87af6265635b97b647645fb6b546876d Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 18 Sep 2022 18:02:26 +0800 Subject: [PATCH 326/498] fix: animation-name case sensitive --- .../animation-duration-001-manual.html | 4 +- webf/example/assets/bundle.html | 58 ++++++++-------- webf/lib/src/css/style_declaration.dart | 8 ++- webf/lib/src/css/style_property.dart | 66 ++++++------------- 4 files changed, 57 insertions(+), 79 deletions(-) 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 c798dfb392..b489be7fcf 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 @@ -10,7 +10,7 @@ - - - -

    A
    -
    B
    -
    C
    - - \ No newline at end of file + + +
    + +
    + + + + diff --git a/webf/lib/src/css/style_declaration.dart b/webf/lib/src/css/style_declaration.dart index 6e61712d47..f4e6c48584 100644 --- a/webf/lib/src/css/style_declaration.dart +++ b/webf/lib/src/css/style_declaration.dart @@ -264,7 +264,11 @@ class CSSStyleDeclaration with IterableMixin { return lowerCase; } - String _toLowerCase(String string) { + String _toLowerCase(String propertyName, String string) { + // ignore animation case sensitive + if (propertyName.startsWith(ANIMATION)) { + return string; + } // Like url("http://path") declared with quotation marks and // custom property names are case sensitive. String lowerCase = string.toLowerCase(); @@ -366,7 +370,7 @@ class CSSStyleDeclaration with IterableMixin { return; } - String normalizedValue = _toLowerCase(value.toString().trim()); + String normalizedValue = _toLowerCase(propertyName, value.toString().trim()); if (!_isValidValue(propertyName, normalizedValue)) return; diff --git a/webf/lib/src/css/style_property.dart b/webf/lib/src/css/style_property.dart index faaeb4dcd8..edbc41b14a 100644 --- a/webf/lib/src/css/style_property.dart +++ b/webf/lib/src/css/style_property.dart @@ -871,31 +871,23 @@ class CSSStyleProperty { String? name; for (String part in parts) { - if (parts.length <= 4 && - name == null && - CSSAnimationMixin.isValidAnimationNameValue(part)) { + if (parts.length <= 4 && name == null && CSSAnimationMixin.isValidAnimationNameValue(part)) { name = part; } else if (duration == null && CSSTime.isTime(part)) { duration = part; - } else if (timingFunction == null && - CSSAnimationMixin.isValidTransitionTimingFunctionValue(part)) { + } else if (timingFunction == null && CSSAnimationMixin.isValidTransitionTimingFunctionValue(part)) { timingFunction = part; } else if (delay == null && CSSTime.isTime(part)) { delay = part; } else if (iterationCount == null && (CSSNumber.isNumber(part) || part == 'infinite')) { iterationCount = part; - } else if (direction == null && - CSSAnimationMixin.isValidAnimationDirectionValue(part)) { + } else if (direction == null && CSSAnimationMixin.isValidAnimationDirectionValue(part)) { direction = part; - } else if (fillMode == null && - CSSAnimationMixin.isValidAnimationFillModeValue(part)) { + } else if (fillMode == null && CSSAnimationMixin.isValidAnimationFillModeValue(part)) { fillMode = part; - } else if (playState == null && - CSSAnimationMixin.isValidAnimationPlayStateValue(part)) { + } else if (playState == null && CSSAnimationMixin.isValidAnimationPlayStateValue(part)) { playState = part; - } else if (parts.length > 4 && - name == null && - CSSAnimationMixin.isValidAnimationNameValue(part)) { + } else if (parts.length > 4 && name == null && CSSAnimationMixin.isValidAnimationNameValue(part)) { name = part; } else { continue; @@ -912,30 +904,14 @@ class CSSStyleProperty { playState = playState ?? RUNNING; name = name ?? NONE; - values[0] == null - ? values[0] = duration - : values[0] = values[0]! + (_comma + duration); - values[1] == null - ? values[1] = timingFunction - : values[1] = values[1]! + (_comma + timingFunction); - values[2] == null - ? values[2] = delay - : values[2] = values[2]! + (_comma + delay); - values[3] == null - ? values[3] = iterationCount - : values[3] = values[3]! + (_comma + iterationCount); - values[4] == null - ? values[4] = direction - : values[4] = values[4]! + (_comma + direction); - values[5] == null - ? values[5] = fillMode - : values[5] = values[5]! + (_comma + fillMode); - values[6] == null - ? values[6] = playState - : values[6] = values[6]! + (_comma + playState); - values[7] == null - ? values[7] = name - : values[7] = values[7]! + (_comma + name); + values[0] == null ? values[0] = duration : values[0] = values[0]! + (_comma + duration); + values[1] == null ? values[1] = timingFunction : values[1] = values[1]! + (_comma + timingFunction); + values[2] == null ? values[2] = delay : values[2] = values[2]! + (_comma + delay); + values[3] == null ? values[3] = iterationCount : values[3] = values[3]! + (_comma + iterationCount); + values[4] == null ? values[4] = direction : values[4] = values[4]! + (_comma + direction); + values[5] == null ? values[5] = fillMode : values[5] = values[5]! + (_comma + fillMode); + values[6] == null ? values[6] = playState : values[6] = values[6]! + (_comma + playState); + values[7] == null ? values[7] = name : values[7] = values[7]! + (_comma + name); } return values; @@ -945,13 +921,13 @@ class CSSStyleProperty { List? values = _getAnimationValues(shorthandValue); if (values == null) return; - properties[ANIMATION_DURATION] = values[0]; - properties[ANIMATION_TIMING_FUNCTION] = values[1]; - properties[ANIMATION_DELAY] = values[2]; - properties[ANIMATION_ITERATION_COUNT] = values[3]; - properties[ANIMATION_DIRECTION] = values[4]; - properties[ANIMATION_FILL_MODE] = values[5]; - properties[ANIMATION_PLAY_STATE] = values[6]; + properties[ANIMATION_DURATION] = values[0]?.toLowerCase(); + properties[ANIMATION_TIMING_FUNCTION] = values[1]?.toLowerCase(); + properties[ANIMATION_DELAY] = values[2]?.toLowerCase(); + properties[ANIMATION_ITERATION_COUNT] = values[3]?.toLowerCase(); + properties[ANIMATION_DIRECTION] = values[4]?.toLowerCase(); + properties[ANIMATION_FILL_MODE] = values[5]?.toLowerCase(); + properties[ANIMATION_PLAY_STATE] = values[6]?.toLowerCase(); properties[ANIMATION_NAME] = values[7]; } } From 16e459776fbccb8043c15da9211f831b3cd9b4cb Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 18 Sep 2022 19:10:09 +0800 Subject: [PATCH 327/498] fix: fill model --- .../css/css-animations/animation-delay-002-manual.html | 2 +- .../css/css-animations/animation-delay-003-manual.html | 2 +- webf/lib/src/css/style_property.dart | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) 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 0754242f8e..77821f8919 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 @@ -33,7 +33,7 @@
    Filler Text
    \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-delay-002-manual.html b/integration_tests/specs/css/css-animations/animation-delay-002-manual.html index 77821f8919..572a78cfdc 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 @@ -33,8 +33,13 @@
    Filler Text
    \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-001-manual.html b/integration_tests/specs/css/css-animations/animation-direction-001-manual.html index 1854712092..d605e56e46 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 @@ -1,5 +1,5 @@ - + CSS Animations Test: animation-direction - alternate @@ -48,10 +48,10 @@
    Filler Text
    \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-002-manual.html b/integration_tests/specs/css/css-animations/animation-direction-002-manual.html index 52d356b987..d1285df150 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 @@ -1,5 +1,5 @@ - + CSS Animations Test: animation-direction - normal @@ -42,10 +42,10 @@
    Filler Text
    \ No newline at end of file From e5ff545f151fd58b47754200d3c7ec5802072e8f Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 25 Sep 2022 14:28:54 +0800 Subject: [PATCH 423/498] fix: fix broken symlinks --- webf/include/dart_methods.h | 1 - webf/include/webf_foundation.h | 1 - 2 files changed, 2 deletions(-) delete mode 120000 webf/include/dart_methods.h delete mode 120000 webf/include/webf_foundation.h diff --git a/webf/include/dart_methods.h b/webf/include/dart_methods.h deleted file mode 120000 index 2f126d89a9..0000000000 --- a/webf/include/dart_methods.h +++ /dev/null @@ -1 +0,0 @@ -../../bridge/include/dart_methods.h \ No newline at end of file diff --git a/webf/include/webf_foundation.h b/webf/include/webf_foundation.h deleted file mode 120000 index 73da78854a..0000000000 --- a/webf/include/webf_foundation.h +++ /dev/null @@ -1 +0,0 @@ -../../bridge/include/webf_foundation.h \ No newline at end of file From a2806ecc1fddd5249b3317fc091da53e2245d302 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 25 Sep 2022 14:58:52 +0800 Subject: [PATCH 424/498] fix(test): remove fit for animation spec --- .../css/css-animations/animation-direction-001-manual.html | 2 +- .../css/css-animations/animation-direction-002-manual.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 d605e56e46..7b007507f8 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 @@ -1,5 +1,5 @@ - + CSS Animations Test: animation-direction - alternate 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 d1285df150..b3e3c34ca0 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 @@ -1,5 +1,5 @@ - + CSS Animations Test: animation-direction - normal From a73d2bd2dfb5064f349193b699cac03c088c138c Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 25 Sep 2022 15:11:35 +0800 Subject: [PATCH 425/498] fix: fix publish linter check. --- webf/CHANGELOG.md | 4 ++++ webf/pubspec.yaml | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/webf/CHANGELOG.md b/webf/CHANGELOG.md index 3743236dfe..e1d0ccb231 100644 --- a/webf/CHANGELOG.md +++ b/webf/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.13.0-beta.2 + +* Test for new bridge and css selector. + ## 0.12.0+2 **Bug Fixed** diff --git a/webf/pubspec.yaml b/webf/pubspec.yaml index c545c8b279..dbbc6f9c8a 100644 --- a/webf/pubspec.yaml +++ b/webf/pubspec.yaml @@ -5,7 +5,7 @@ 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. From 2fda093c530c7b3233219d3c1ea32feb677d7568 Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 25 Sep 2022 15:35:16 +0800 Subject: [PATCH 426/498] fix: fix error assertion. --- webf/lib/src/widget/webf.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/webf/lib/src/widget/webf.dart b/webf/lib/src/widget/webf.dart index 60fe52457f..a6a9828c35 100644 --- a/webf/lib/src/widget/webf.dart +++ b/webf/lib/src/widget/webf.dart @@ -246,9 +246,6 @@ class WebFRenderObjectWidget extends SingleChildRenderObjectWidget { window.onMetricsChanged = _ordinaryOnMetricsChanged; } }; - - 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.'''); } if (kProfileMode) { From 6c0092d4a214e215c9230511fd54d7de3f7b752e Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 25 Sep 2022 20:06:26 +0800 Subject: [PATCH 427/498] fix: handle exception when convert script value to native value. --- bridge/core/binding_object.cc | 27 +++++++++++++------ bridge/core/executing_context.cc | 10 +++++++ bridge/core/executing_context.h | 1 + .../core/frame/module_listener_container.cc | 1 + bridge/core/frame/module_manager.cc | 17 +++++++++--- bridge/core/frame/window.cc | 1 + bridge/core/html/custom/widget_element.cc | 2 +- bridge/core/page.cc | 8 +++++- bridge/foundation/native_value.cc | 3 +-- bridge/foundation/native_value.h | 3 ++- bridge/foundation/native_value_converter.h | 2 +- .../json_templates/event_factory.cc.tpl | 7 +++-- bridge/test/webf_test_env.cc | 8 ++++-- 13 files changed, 67 insertions(+), 23 deletions(-) diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 3ef2220ce5..0c3a0fad0d 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -101,20 +101,24 @@ ScriptValue BindingObject::AnonymousFunctionCallback(JSContext* ctx, std::vector arguments; arguments.reserve(argc + 1); - arguments.emplace_back(NativeValueConverter::ToNativeValue(id)); + + ExceptionState exception_state; + for (int i = 0; i < argc; i++) { - arguments.emplace_back(argv[i].ToNative()); + arguments.emplace_back(argv[i].ToNative(exception_state)); + } + + if (exception_state.HasException()) { + event_target->GetExecutingContext()->HandleException(exception_state); + return ScriptValue::Empty(ctx); } - ExceptionState exception_state; NativeValue result = event_target->InvokeBindingMethod(BindingMethodCallOperations::kAnonymousFunctionCall, arguments.size(), arguments.data(), exception_state); if (exception_state.HasException()) { - JSValue error = JS_GetException(ctx); - event_target->GetExecutingContext()->ReportError(error); - JS_FreeValue(ctx, error); + event_target->GetExecutingContext()->HandleException(exception_state); return ScriptValue::Empty(ctx); } return ScriptValue(ctx, result); @@ -173,13 +177,20 @@ ScriptValue BindingObject::AnonymousAsyncFunctionCallback(JSContext* ctx, arguments.emplace_back(NativeValueConverter>::ToNativeValue( reinterpret_cast(HandleAnonymousAsyncCalledFromDart))); + ExceptionState exception_state; + for (int i = 0; i < argc; i++) { - arguments.emplace_back(argv[i].ToNative()); + arguments.emplace_back(argv[i].ToNative(exception_state)); } - ExceptionState exception_state; event_target->InvokeBindingMethod(BindingMethodCallOperations::kAsyncAnonymousFunction, argc + 4, arguments.data(), exception_state); + + if (exception_state.HasException()) { + event_target->GetExecutingContext()->HandleException(exception_state); + return ScriptValue::Empty(ctx); + } + return promise_resolver->Promise().ToValue(); } diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 8b7364ef74..89eefbb8ac 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -168,6 +168,16 @@ bool ExecutingContext::HandleException(ScriptValue* exc) { return HandleException(&value); } +bool ExecutingContext::HandleException(ExceptionState& exception_state) { + if (exception_state.HasException()) { + JSValue error = JS_GetException(ctx()); + ReportError(error); + JS_FreeValue(ctx(), error); + return false; + } + return true; +} + JSValue ExecutingContext::Global() { return global_object_; } diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 558bb3a797..ae7f24e5f3 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -69,6 +69,7 @@ class ExecutingContext { void* owner(); bool HandleException(JSValue* exc); bool HandleException(ScriptValue* exc); + bool HandleException(ExceptionState& exception_state); void ReportError(JSValueConst error); void DrainPendingPromiseJobs(); void DefineGlobalProperty(const char* prop, JSValueConst value); diff --git a/bridge/core/frame/module_listener_container.cc b/bridge/core/frame/module_listener_container.cc index 35b2e7377e..7d0a25eaf0 100644 --- a/bridge/core/frame/module_listener_container.cc +++ b/bridge/core/frame/module_listener_container.cc @@ -3,6 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "module_listener_container.h" +#include "bindings/qjs/cppgc/gc_visitor.h" namespace webf { diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 0583ab3c05..140caddeea 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -31,6 +31,7 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, } JSContext* ctx = moduleContext->context->ctx(); + ExceptionState exception_state; NativeValue* return_value = nullptr; if (errmsg != nullptr) { @@ -40,7 +41,7 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, if (result.IsException()) { context->HandleException(&result); } - NativeValue native_result = result.ToNative(); + NativeValue native_result = result.ToNative(exception_state); return_value = static_cast(malloc(sizeof(NativeValue))); memcpy(return_value, &native_result, sizeof(NativeValue)); } else { @@ -49,11 +50,16 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, if (result.IsException()) { context->HandleException(&result); } - NativeValue native_result = result.ToNative(); + NativeValue native_result = result.ToNative(exception_state); return_value = static_cast(malloc(sizeof(NativeValue))); memcpy(return_value, &native_result, sizeof(NativeValue)); } + if (exception_state.HasException()) { + context->HandleException(exception_state); + return nullptr; + } + context->ModuleCallbacks()->RemoveModuleCallbacks(moduleContext->callback); delete moduleContext; @@ -89,7 +95,12 @@ ScriptValue ModuleManager::__webf_invoke_module__(ExecutingContext* context, ScriptValue& params_value, std::shared_ptr callback, ExceptionState& exception) { - NativeValue params = params_value.ToNative(); + 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, diff --git a/bridge/core/frame/window.cc b/bridge/core/frame/window.cc index dba592d565..960599f1bc 100644 --- a/bridge/core/frame/window.cc +++ b/bridge/core/frame/window.cc @@ -8,6 +8,7 @@ #include "bindings/qjs/cppgc/garbage_collected.h" #include "core/dom/document.h" #include "core/events/message_event.h" +#include "core/executing_context.h" #include "event_type_names.h" #include "foundation/native_value_converter.h" diff --git a/bridge/core/html/custom/widget_element.cc b/bridge/core/html/custom/widget_element.cc index 6474db3515..e0d7e70a1d 100644 --- a/bridge/core/html/custom/widget_element.cc +++ b/bridge/core/html/custom/widget_element.cc @@ -67,7 +67,7 @@ bool WidgetElement::SetItem(const AtomicString& key, const ScriptValue& value, E return true; } - NativeValue result = SetBindingProperty(key, value.ToNative(), exception_state); + NativeValue result = SetBindingProperty(key, value.ToNative(exception_state), exception_state); return NativeValueConverter::FromNativeValue(result); } diff --git a/bridge/core/page.cc b/bridge/core/page.cc index d05dd27275..6557b771ad 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -85,8 +85,14 @@ NativeValue* WebFPage::invokeModuleEvent(const NativeString* native_module_name, return nullptr; } + ExceptionState exception_state; auto* return_value = static_cast(malloc(sizeof(NativeValue))); - NativeValue tmp = result.ToNative(); + 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; } diff --git a/bridge/foundation/native_value.cc b/bridge/foundation/native_value.cc index 6fef0deb03..51a707b219 100644 --- a/bridge/foundation/native_value.cc +++ b/bridge/foundation/native_value.cc @@ -62,8 +62,7 @@ NativeValue Native_NewList(uint32_t argc, NativeValue* argv) { return (NativeValue){.u = {.ptr = reinterpret_cast(argv)}, .uint32 = argc, .tag = NativeTag::TAG_LIST}; } -NativeValue Native_NewJSON(const ScriptValue& value) { - ExceptionState exception_state; +NativeValue Native_NewJSON(const ScriptValue& value, ExceptionState& exception_state) { ScriptValue json = value.ToJSONStringify(&exception_state); if (exception_state.HasException()) { return Native_NewNull(); diff --git a/bridge/foundation/native_value.h b/bridge/foundation/native_value.h index b49ef2ef94..1c98c9385c 100644 --- a/bridge/foundation/native_value.h +++ b/bridge/foundation/native_value.h @@ -30,6 +30,7 @@ enum NativeTag { enum class JSPointerType { AsyncContextContext = 0, NativeFunctionContext = 1, Others = 2 }; class ExecutingContext; +class ExceptionState; class ScriptValue; // Exchange data struct between dart and C++ @@ -73,7 +74,7 @@ NativeValue Native_NewBool(bool value); NativeValue Native_NewInt64(int64_t value); NativeValue Native_NewList(uint32_t argc, NativeValue* argv); NativeValue Native_NewPtr(JSPointerType pointerType, void* ptr); -NativeValue Native_NewJSON(const ScriptValue& value); +NativeValue Native_NewJSON(const ScriptValue& value, ExceptionState& exception_state); } // namespace webf diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 63b288ad1e..5d0a31cdab 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -78,7 +78,7 @@ struct NativeValueConverter : public NativeValueConverterBase< template <> struct NativeValueConverter : public NativeValueConverterBase { - static NativeValue ToNativeValue(ImplType value) { return Native_NewJSON(value); } + static NativeValue ToNativeValue(ImplType value, ExceptionState& exception_state) { return Native_NewJSON(value, exception_state); } static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { assert(value.tag == NativeTag::TAG_JSON); auto* str = static_cast(value.u.ptr); diff --git a/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl index f16b34063c..ba53c87cbc 100644 --- a/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl +++ b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl @@ -86,15 +86,14 @@ Event* EventFactory::Create(ExecutingContext* context, const AtomicString& type, if (!g_event_constructors) CreateEventFunctionMap(); - if (raw_event->is_custom_event) { - return MakeGarbageCollected(context, type, toNativeEvent(raw_event)); - } - auto it = g_event_constructors->find(type); if (it == g_event_constructors->end()) { if (raw_event == nullptr) { return MakeGarbageCollected(context, type); } + if (raw_event->is_custom_event) { + return MakeGarbageCollected(context, type, toNativeEvent(raw_event)); + } return MakeGarbageCollected(context, type, toNativeEvent(raw_event)); } EventConstructorFunction function = it->second; diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index 5ef27411aa..77a20fac99 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -11,6 +11,7 @@ #include "core/frame/dom_timer.h" #include "core/page.h" #include "foundation/native_string.h" +#include "foundation/native_value_converter.h" #include "webf_bridge_test.h" #include "webf_test_env.h" @@ -61,7 +62,7 @@ static void unlink_callback(JSThreadState* ts, JSFrameCallback* th) { ts->os_frameCallbacks.erase(th->callbackId); } -NativeString* TEST_invokeModule(void* callbackContext, +NativeValue* TEST_invokeModule(void* callbackContext, int32_t contextId, NativeString* moduleName, NativeString* method, @@ -78,7 +79,10 @@ NativeString* TEST_invokeModule(void* callbackContext, callback(callbackContext, contextId, nullptr, &data); } - return stringToNativeString(module).release(); + auto* result = static_cast(malloc(sizeof(NativeValue))); + NativeValue tmp = Native_NewCString(module); + memcpy(result, &tmp, sizeof(NativeValue)); + return result; }; void TEST_requestBatchUpdate(int32_t contextId){}; From 091d230c60ed5f3d4b36391ecb8718d6257b1ea7 Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 25 Sep 2022 20:40:30 +0800 Subject: [PATCH 428/498] fix: fix heap use after free when free QJSFunction at dispose stage. --- bridge/bindings/qjs/qjs_function.cc | 2 +- bridge/bindings/qjs/qjs_function.h | 6 +-- bridge/bindings/qjs/script_value.cc | 57 +++++++++++---------- bridge/bindings/qjs/script_value.h | 11 ++-- bridge/core/dom/events/event_listener_map.h | 2 +- bridge/core/dom/events/event_target_test.cc | 19 +++++++ 6 files changed, 61 insertions(+), 36 deletions(-) diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 26f8b58574..100c98a5e6 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -37,7 +37,7 @@ static JSValue HandleQJSFunctionCallback(JSContext* ctx, QJSFunction::QJSFunction(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, - void* private_data) { + 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); diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 8b9ef402e6..ea133c46d2 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -28,10 +28,9 @@ class QJSFunction { void* private_data) { return std::make_shared(ctx, qjs_function_callback, length, private_data); } - explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), function_(JS_DupValue(ctx, function)){}; + 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); - // This safe to free function_ at GC stage. - ~QJSFunction() { JS_FreeValue(ctx_, function_); } + ~QJSFunction() { JS_FreeValueRT(runtime_, function_); } bool IsFunction(JSContext* ctx); @@ -49,6 +48,7 @@ class QJSFunction { private: JSContext* ctx_{nullptr}; + JSRuntime* runtime_{nullptr}; JSValue function_{JS_NULL}; }; diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index f55408849e..abefc7ccc4 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -78,7 +78,7 @@ static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& nat } ScriptValue::ScriptValue(JSContext* ctx, const NativeValue& native_value) - : ctx_(ctx), value_(FromNativeValue(ExecutingContext::From(ctx), 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); @@ -148,13 +148,15 @@ AtomicString ScriptValue::ToString() const { } NativeValue ScriptValue::ToNative() const { - if (JS_IsNull(value_) || JS_IsUndefined(value_)) { - return Native_NewNull(); - } else if (JS_IsBool(value_)) { - return Native_NewBool(JS_ToBool(ctx_, value_)); - } else if (JS_IsNumber(value_)) { - uint32_t tag = JS_VALUE_GET_TAG(value_); - if (JS_TAG_IS_FLOAT64(tag)) { + 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); @@ -163,26 +165,29 @@ NativeValue ScriptValue::ToNative() const { JS_ToInt32(ctx_, &v, value_); return Native_NewInt64(v); } - } else if (JS_IsString(value_)) { - // NativeString owned by NativeValue will be freed by users. - return NativeValueConverter::ToNativeValue(ToString()); - } else 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(); - } - return Native_NewList(values.size(), result); - } else if (JS_IsObject(value_)) { - // TODO: needs a better way to convert bindingObject to pointers. - if (QJSEventTarget::HasInstance(ExecutingContext::From(ctx_), value_)) { - auto* event_target = toScriptWrappable(value_); - return Native_NewPtr(JSPointerType::Others, event_target->bindingObject()); + 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(); + } + 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); + } } - return NativeValueConverter::ToNativeValue(*this); + default: + return Native_NewNull(); } - - return Native_NewNull(); } bool ScriptValue::IsException() const { diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index b264fe9522..17c8d7c93d 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -36,11 +36,11 @@ class ScriptValue final { // Create an empty ScriptValue; static ScriptValue Empty(JSContext* ctx); // Wrap an Quickjs JSValue to ScriptValue. - explicit ScriptValue(JSContext* ctx, JSValue value) : ctx_(ctx), value_(JS_DupValue(ctx, value)){}; + explicit ScriptValue(JSContext* ctx, 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())) {} - explicit ScriptValue(JSContext* ctx, double v) : ctx_(ctx), value_(JS_NewFloat64(ctx, v)) {} - explicit ScriptValue(JSContext* ctx) : ctx_(ctx){}; + : 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; @@ -58,7 +58,7 @@ class ScriptValue final { // Create a new ScriptValue from call JSON.stringify to current value. ScriptValue ToJSONStringify(ExceptionState* exception) const; AtomicString ToString() const; - NativeValue ToNative() const; + NativeValue ToNative(ExceptionState& exception_state) const; bool IsException() const; bool IsEmpty() const; @@ -72,6 +72,7 @@ class ScriptValue final { private: JSContext* ctx_{nullptr}; + JSRuntime* runtime_{nullptr}; JSValue value_{JS_NULL}; }; diff --git a/bridge/core/dom/events/event_listener_map.h b/bridge/core/dom/events/event_listener_map.h index 7532b974df..f7838b55fd 100644 --- a/bridge/core/dom/events/event_listener_map.h +++ b/bridge/core/dom/events/event_listener_map.h @@ -44,7 +44,7 @@ class EventListenerMap final { size_t* index_of_removed_listener, RegisteredEventListener* registered_event_listener, uint32_t* listener_count); - EventListenerVector* Find(const AtomicString& event_type); + EventListenerVector* Find(const AtomicString& event_type) const; void Trace(GCVisitor* visitor) const; diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index 8e59b9ad0e..b6610cba83 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -91,6 +91,25 @@ TEST(EventTarget, propertyEventHandler) { EXPECT_EQ(logCalled, true); } +TEST(EventTarget, overwritePropertyEventHandler) { + bool static errorCalled = false; + bool static logCalled = false; + webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { + }; + auto bridge = TEST_init([](int32_t contextId, const char* errmsg) { + WEBF_LOG(VERBOSE) << errmsg; + errorCalled = true; + }); + auto context = bridge->GetExecutingContext(); + const char* code = + "let div = document.createElement('div'); " + "div.onclick = function() { return 1234; };" + "div.onclick = null;" + "console.log(div.onclick)"; + bridge->evaluateScript(code, strlen(code), "vm://", 0); + EXPECT_EQ(errorCalled, false); +} + TEST(EventTarget, propertyEventOnWindow) { bool static errorCalled = false; bool static logCalled = false; From 592b900be569e31a541023444ec8c251487a82a8 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Sun, 25 Sep 2022 12:41:54 +0000 Subject: [PATCH 429/498] Committing clang-format changes --- bridge/bindings/qjs/qjs_function.cc | 6 ++---- bridge/bindings/qjs/qjs_function.h | 3 ++- bridge/bindings/qjs/script_value.h | 6 ++++-- bridge/core/dom/events/event_target_test.cc | 3 +-- bridge/foundation/native_value_converter.h | 4 +++- bridge/test/webf_test_env.cc | 10 +++++----- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 100c98a5e6..2c0a1ddba1 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -34,10 +34,8 @@ static JSValue HandleQJSFunctionCallback(JSContext* ctx, 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)) { +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); diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index ea133c46d2..ab5bfaadf7 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -28,7 +28,8 @@ class QJSFunction { 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, 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_); } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 17c8d7c93d..5ebd8397a9 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -36,10 +36,12 @@ class ScriptValue final { // 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, 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, 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; diff --git a/bridge/core/dom/events/event_target_test.cc b/bridge/core/dom/events/event_target_test.cc index b6610cba83..852b3983f0 100644 --- a/bridge/core/dom/events/event_target_test.cc +++ b/bridge/core/dom/events/event_target_test.cc @@ -94,8 +94,7 @@ TEST(EventTarget, propertyEventHandler) { TEST(EventTarget, overwritePropertyEventHandler) { bool static errorCalled = false; bool static logCalled = false; - webf::WebFPage::consoleMessageHandler = [](void* ctx, const std::string& message, int logLevel) { - }; + 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; diff --git a/bridge/foundation/native_value_converter.h b/bridge/foundation/native_value_converter.h index 5d0a31cdab..0f853e57cd 100644 --- a/bridge/foundation/native_value_converter.h +++ b/bridge/foundation/native_value_converter.h @@ -78,7 +78,9 @@ struct NativeValueConverter : public NativeValueConverterBase< template <> struct NativeValueConverter : public NativeValueConverterBase { - static NativeValue ToNativeValue(ImplType value, ExceptionState& exception_state) { return Native_NewJSON(value, exception_state); } + static NativeValue ToNativeValue(ImplType value, ExceptionState& exception_state) { + return Native_NewJSON(value, exception_state); + } static ImplType FromNativeValue(JSContext* ctx, NativeValue value) { assert(value.tag == NativeTag::TAG_JSON); auto* str = static_cast(value.u.ptr); diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index 77a20fac99..0cd91d6919 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -63,11 +63,11 @@ static void unlink_callback(JSThreadState* ts, JSFrameCallback* th) { } NativeValue* TEST_invokeModule(void* callbackContext, - int32_t contextId, - NativeString* moduleName, - NativeString* method, - NativeString* params, - AsyncModuleCallback callback) { + int32_t contextId, + NativeString* moduleName, + NativeString* method, + NativeString* params, + AsyncModuleCallback callback) { std::string module = nativeStringToStdString(moduleName); if (module == "throwError") { From 245a188acc9f1796592c6c6e705bee9d2fc857bd Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 25 Sep 2022 20:46:00 +0800 Subject: [PATCH 430/498] fix: fix compile. --- bridge/bindings/qjs/script_value.cc | 6 +++--- bridge/core/dom/events/event_listener_map.cc | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index 9e974baa30..38873173aa 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -147,7 +147,7 @@ AtomicString ScriptValue::ToString() const { return {ctx_, value_}; } -NativeValue ScriptValue::ToNative() const { +NativeValue ScriptValue::ToNative(ExceptionState& exception_state) const { int8_t tag = JS_VALUE_GET_TAG(value_); switch (tag) { @@ -175,7 +175,7 @@ NativeValue ScriptValue::ToNative() const { Converter>::FromValue(ctx_, value_, ASSERT_NO_EXCEPTION()); auto* result = new NativeValue[values.size()]; for (int i = 0; i < values.size(); i++) { - result[i] = values[i].ToNative(); + result[i] = values[i].ToNative(exception_state); } return Native_NewList(values.size(), result); } else if (JS_IsObject(value_)) { @@ -183,7 +183,7 @@ NativeValue ScriptValue::ToNative() const { auto* event_target = toScriptWrappable(value_); return Native_NewPtr(JSPointerType::Others, event_target->bindingObject()); } - return NativeValueConverter::ToNativeValue(*this); + return NativeValueConverter::ToNativeValue(*this, exception_state); } } default: diff --git a/bridge/core/dom/events/event_listener_map.cc b/bridge/core/dom/events/event_listener_map.cc index 241470cdd9..572679a272 100644 --- a/bridge/core/dom/events/event_listener_map.cc +++ b/bridge/core/dom/events/event_listener_map.cc @@ -111,7 +111,7 @@ bool EventListenerMap::Remove(const AtomicString& event_type, return false; } -EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) { +EventListenerVector* EventListenerMap::Find(const AtomicString& event_type) const { for (const auto& entry : entries_) { if (entry.first == event_type) return entry.second.get(); From bd6be27d8db5a49c92d47c023f961de61d233644 Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 25 Sep 2022 23:43:12 +0800 Subject: [PATCH 431/498] fix: fix scriptState cause string leaks. --- bridge/core/script_state.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 5cdbe9ddbf..f5a7088082 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -44,17 +44,14 @@ ScriptState::~ScriptState() { // Run GC to clean up remaining objects about m_ctx; JS_RunGC(runtime_); -#if DUMP_LEAKS if (--runningContexts == 0) { // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. names_installer::Dispose(); HTMLElementFactory::Dispose(); EventFactory::Dispose(); - JS_FreeRuntime(runtime_); runtime_ = nullptr; } -#endif ctx_ = nullptr; } } // namespace webf From f7dd89a64394b96c7a335cdb1f1b3bf30cb2d429 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 00:19:17 +0800 Subject: [PATCH 432/498] beta: 0.13.0-beta.3 --- webf/CHANGELOG.md | 4 ++++ webf/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/webf/CHANGELOG.md b/webf/CHANGELOG.md index e1d0ccb231..d316669a4b 100644 --- a/webf/CHANGELOG.md +++ b/webf/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.13.0-beta.3 + +* Fix reload crash. + ## 0.13.0-beta.2 * Test for new bridge and css selector. diff --git a/webf/pubspec.yaml b/webf/pubspec.yaml index dbbc6f9c8a..524ca88608 100644 --- a/webf/pubspec.yaml +++ b/webf/pubspec.yaml @@ -1,6 +1,6 @@ name: webf description: A W3C standard compliant Web rendering engine based on Flutter. -version: 0.13.0-beta.2 +version: 0.13.0-beta.3 homepage: https://openwebf.com environment: From 4d2c85a01c2a95ebddebf9ab52e892f6ae165b57 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 11:32:40 +0800 Subject: [PATCH 433/498] fix: fix integration tests. --- .github/workflows/integration_test_flutter.yml | 2 +- bridge/core/dom/events/event.h | 3 ++- .../templates/json_templates/event_factory.cc.tpl | 10 ++++------ webf/lib/src/dom/elements/img.dart | 1 + 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/integration_test_flutter.yml b/.github/workflows/integration_test_flutter.yml index cb749eef5f..9041d93520 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: diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index fcc2acf2f7..246fd1d133 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -59,7 +59,8 @@ struct RawEvent : public DartReadable { }; template -T* toNativeEvent(RawEvent* raw_event) { +inline T* toNativeEvent(RawEvent* raw_event) { + if (raw_event == nullptr) return nullptr; // NativeEvent members are memory aligned corresponding to NativeEvent. // So we can reinterpret_cast raw bytes pointer to NativeEvent type directly. return reinterpret_cast(raw_event->bytes); diff --git a/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl index ba53c87cbc..81bf3c3e83 100644 --- a/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl +++ b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl @@ -86,14 +86,12 @@ Event* EventFactory::Create(ExecutingContext* context, const AtomicString& type, if (!g_event_constructors) CreateEventFunctionMap(); + if (raw_event != nullptr && raw_event->is_custom_event) { + return MakeGarbageCollected(context, type, toNativeEvent(raw_event)); + } + auto it = g_event_constructors->find(type); if (it == g_event_constructors->end()) { - if (raw_event == nullptr) { - return MakeGarbageCollected(context, type); - } - if (raw_event->is_custom_event) { - return MakeGarbageCollected(context, type, toNativeEvent(raw_event)); - } return MakeGarbageCollected(context, type, toNativeEvent(raw_event)); } EventConstructorFunction function = it->second; diff --git a/webf/lib/src/dom/elements/img.dart b/webf/lib/src/dom/elements/img.dart index e8a4d95097..dbe009582b 100644 --- a/webf/lib/src/dom/elements/img.dart +++ b/webf/lib/src/dom/elements/img.dart @@ -494,6 +494,7 @@ class ImageElement extends Element { SchedulerBinding.instance.addPostFrameCallback((timeStamp) { _dispatchLoadEvent(); }); + SchedulerBinding.instance.scheduleFrame(); } } From 8ff9fcebb9e6ba13894fd195c70e611c7fe4aacc Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 12:19:37 +0800 Subject: [PATCH 434/498] fix: fix reload crash cause by free QJSFunction when JSContext freed --- bridge/bindings/qjs/qjs_function.cc | 2 +- bridge/bindings/qjs/qjs_function.h | 6 +-- bridge/bindings/qjs/script_value.cc | 62 ++++++++++++++++------------- bridge/bindings/qjs/script_value.h | 11 ++--- 4 files changed, 44 insertions(+), 37 deletions(-) diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 26f8b58574..100c98a5e6 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -37,7 +37,7 @@ static JSValue HandleQJSFunctionCallback(JSContext* ctx, QJSFunction::QJSFunction(JSContext* ctx, QJSFunctionCallback qjs_function_callback, int32_t length, - void* private_data) { + 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); diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index 8b9ef402e6..ea133c46d2 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -28,10 +28,9 @@ class QJSFunction { void* private_data) { return std::make_shared(ctx, qjs_function_callback, length, private_data); } - explicit QJSFunction(JSContext* ctx, JSValue function) : ctx_(ctx), function_(JS_DupValue(ctx, function)){}; + 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); - // This safe to free function_ at GC stage. - ~QJSFunction() { JS_FreeValue(ctx_, function_); } + ~QJSFunction() { JS_FreeValueRT(runtime_, function_); } bool IsFunction(JSContext* ctx); @@ -49,6 +48,7 @@ class QJSFunction { private: JSContext* ctx_{nullptr}; + JSRuntime* runtime_{nullptr}; JSValue function_{JS_NULL}; }; diff --git a/bridge/bindings/qjs/script_value.cc b/bridge/bindings/qjs/script_value.cc index f55408849e..9e974baa30 100644 --- a/bridge/bindings/qjs/script_value.cc +++ b/bridge/bindings/qjs/script_value.cc @@ -78,7 +78,7 @@ static JSValue FromNativeValue(ExecutingContext* context, const NativeValue& nat } ScriptValue::ScriptValue(JSContext* ctx, const NativeValue& native_value) - : ctx_(ctx), value_(FromNativeValue(ExecutingContext::From(ctx), 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); @@ -144,45 +144,51 @@ ScriptValue ScriptValue::ToJSONStringify(ExceptionState* exception) const { } AtomicString ScriptValue::ToString() const { - return AtomicString(ctx_, value_); + return {ctx_, value_}; } NativeValue ScriptValue::ToNative() const { - if (JS_IsNull(value_) || JS_IsUndefined(value_)) { - return Native_NewNull(); - } else if (JS_IsBool(value_)) { - return Native_NewBool(JS_ToBool(ctx_, value_)); - } else if (JS_IsNumber(value_)) { - uint32_t tag = JS_VALUE_GET_TAG(value_); - if (JS_TAG_IS_FLOAT64(tag)) { + 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); - } else { + } + case JS_TAG_INT: { int32_t v; JS_ToInt32(ctx_, &v, value_); return Native_NewInt64(v); } - } else if (JS_IsString(value_)) { - // NativeString owned by NativeValue will be freed by users. - return NativeValueConverter::ToNativeValue(ToString()); - } else 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(); - } - return Native_NewList(values.size(), result); - } else if (JS_IsObject(value_)) { - // TODO: needs a better way to convert bindingObject to pointers. - if (QJSEventTarget::HasInstance(ExecutingContext::From(ctx_), value_)) { - auto* event_target = toScriptWrappable(value_); - return Native_NewPtr(JSPointerType::Others, event_target->bindingObject()); + 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(); + } + 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); + } } - return NativeValueConverter::ToNativeValue(*this); + default: + return Native_NewNull(); } - - return Native_NewNull(); } bool ScriptValue::IsException() const { diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index b264fe9522..17c8d7c93d 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -36,11 +36,11 @@ class ScriptValue final { // Create an empty ScriptValue; static ScriptValue Empty(JSContext* ctx); // Wrap an Quickjs JSValue to ScriptValue. - explicit ScriptValue(JSContext* ctx, JSValue value) : ctx_(ctx), value_(JS_DupValue(ctx, value)){}; + explicit ScriptValue(JSContext* ctx, 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())) {} - explicit ScriptValue(JSContext* ctx, double v) : ctx_(ctx), value_(JS_NewFloat64(ctx, v)) {} - explicit ScriptValue(JSContext* ctx) : ctx_(ctx){}; + : 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; @@ -58,7 +58,7 @@ class ScriptValue final { // Create a new ScriptValue from call JSON.stringify to current value. ScriptValue ToJSONStringify(ExceptionState* exception) const; AtomicString ToString() const; - NativeValue ToNative() const; + NativeValue ToNative(ExceptionState& exception_state) const; bool IsException() const; bool IsEmpty() const; @@ -72,6 +72,7 @@ class ScriptValue final { private: JSContext* ctx_{nullptr}; + JSRuntime* runtime_{nullptr}; JSValue value_{JS_NULL}; }; From 94dcf360b00b68bed1358b623cc03296473fe8b1 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 12:20:16 +0800 Subject: [PATCH 435/498] fix: fix android platform string bad access. --- bridge/core/script_state.cc | 3 --- 1 file changed, 3 deletions(-) diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 5cdbe9ddbf..f5a7088082 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -44,17 +44,14 @@ ScriptState::~ScriptState() { // Run GC to clean up remaining objects about m_ctx; JS_RunGC(runtime_); -#if DUMP_LEAKS if (--runningContexts == 0) { // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. names_installer::Dispose(); HTMLElementFactory::Dispose(); EventFactory::Dispose(); - JS_FreeRuntime(runtime_); runtime_ = nullptr; } -#endif ctx_ = nullptr; } } // namespace webf From 737b5902216eb099d95c85e911ab13678b29f2f5 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 12:23:10 +0800 Subject: [PATCH 436/498] chore: remove deprecated native apis. --- webf/android/build.gradle | 2 +- .../src/main/java/com/openwebf/webf/WebF.java | 47 ---------- .../java/com/openwebf/webf/WebFPlugin.java | 28 ------ webf/include/dart_methods.h | 1 - webf/include/webf_foundation.h | 1 - webf/ios/Assets/.gitkeep | 0 webf/ios/Classes/WebF.h | 31 ------- webf/ios/Classes/WebF.m | 89 ------------------- webf/ios/Classes/WebFPlugin.h | 2 - webf/ios/Classes/WebFPlugin.m | 14 +-- webf/macos/Classes/WebF.h | 31 ------- webf/macos/Classes/WebF.m | 86 ------------------ webf/macos/Classes/WebFPlugin.h | 2 - webf/macos/Classes/WebFPlugin.m | 14 +-- 14 files changed, 3 insertions(+), 345 deletions(-) delete mode 120000 webf/include/dart_methods.h delete mode 120000 webf/include/webf_foundation.h delete mode 100644 webf/ios/Assets/.gitkeep delete mode 100644 webf/ios/Classes/WebF.h delete mode 100644 webf/ios/Classes/WebF.m delete mode 100644 webf/macos/Classes/WebF.h delete mode 100644 webf/macos/Classes/WebF.m 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/include/dart_methods.h b/webf/include/dart_methods.h deleted file mode 120000 index 2f126d89a9..0000000000 --- a/webf/include/dart_methods.h +++ /dev/null @@ -1 +0,0 @@ -../../bridge/include/dart_methods.h \ No newline at end of file diff --git a/webf/include/webf_foundation.h b/webf/include/webf_foundation.h deleted file mode 120000 index 73da78854a..0000000000 --- a/webf/include/webf_foundation.h +++ /dev/null @@ -1 +0,0 @@ -../../bridge/include/webf_foundation.h \ No newline at end of file diff --git a/webf/ios/Assets/.gitkeep b/webf/ios/Assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/webf/ios/Classes/WebF.h b/webf/ios/Classes/WebF.h deleted file mode 100644 index 1e4a59cd2c..0000000000 --- a/webf/ios/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/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/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); From f34da310d3c03e53e1384540964ed8aeea5db971 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Mon, 26 Sep 2022 04:24:04 +0000 Subject: [PATCH 437/498] Committing clang-format changes --- bridge/bindings/qjs/qjs_function.cc | 6 ++---- bridge/bindings/qjs/qjs_function.h | 3 ++- bridge/bindings/qjs/script_value.h | 6 ++++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/bridge/bindings/qjs/qjs_function.cc b/bridge/bindings/qjs/qjs_function.cc index 100c98a5e6..2c0a1ddba1 100644 --- a/bridge/bindings/qjs/qjs_function.cc +++ b/bridge/bindings/qjs/qjs_function.cc @@ -34,10 +34,8 @@ static JSValue HandleQJSFunctionCallback(JSContext* ctx, 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)) { +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); diff --git a/bridge/bindings/qjs/qjs_function.h b/bridge/bindings/qjs/qjs_function.h index ea133c46d2..ab5bfaadf7 100644 --- a/bridge/bindings/qjs/qjs_function.h +++ b/bridge/bindings/qjs/qjs_function.h @@ -28,7 +28,8 @@ class QJSFunction { 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, 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_); } diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 17c8d7c93d..5ebd8397a9 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -36,10 +36,12 @@ class ScriptValue final { // 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, 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, 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; From 5535e7acbdc669e226d3ddfe7a445d13f073a824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=A4=A9=E6=88=90?= Date: Sat, 24 Sep 2022 11:06:56 +0800 Subject: [PATCH 438/498] Update integration_test_flutter.yml --- .github/workflows/integration_test_flutter.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/integration_test_flutter.yml b/.github/workflows/integration_test_flutter.yml index b95c2d7c73..cb749eef5f 100644 --- a/.github/workflows/integration_test_flutter.yml +++ b/.github/workflows/integration_test_flutter.yml @@ -53,15 +53,13 @@ jobs: - name: Check on failures if: steps.bridge_test.outcome == 'success' run: exit 0 - run: sudo chmod -R +rwx /cores/* # Enable access to core dumps - - name: test enabling cores - ulimit -c # should output 0 if disabled - ulimit -c unlimited - ulimit -c # should output 'unlimited' now - - name: test access to core dump directory - sudo touch /cores/test - ls /cores - sudo rm /cores/test + - run: sudo chmod -R +rwx /cores/* # Enable access to core dumps + - run: ulimit -c # should output 0 if disabled + - run: ulimit -c unlimited + - run: ulimit -c # should output 'unlimited' now + - run: sudo touch /cores/test + - run: ls /cores + - run: sudo rm /cores/test - uses: actions/upload-artifact@master # capture all crashes as build artifacts if: steps.bridge_test.outcome != 'success' with: From e1728a6072497de97a75651227922946bb7f8c20 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 12:37:40 +0800 Subject: [PATCH 439/498] fix: fix build. --- bridge/bindings/qjs/script_value.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 5ebd8397a9..50511419cf 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -60,7 +60,7 @@ class ScriptValue final { // Create a new ScriptValue from call JSON.stringify to current value. ScriptValue ToJSONStringify(ExceptionState* exception) const; AtomicString ToString() const; - NativeValue ToNative(ExceptionState& exception_state) const; + NativeValue ToNative() const; bool IsException() const; bool IsEmpty() const; From d282c344b97fb0749e605ff520026965ee5f9f0d Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 13:01:34 +0800 Subject: [PATCH 440/498] fix: fix document create event crash --- bridge/bindings/qjs/script_value.h | 2 +- bridge/core/dom/events/event.h | 1 - .../templates/json_templates/event_factory.cc.tpl | 3 +++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bridge/bindings/qjs/script_value.h b/bridge/bindings/qjs/script_value.h index 50511419cf..5ebd8397a9 100644 --- a/bridge/bindings/qjs/script_value.h +++ b/bridge/bindings/qjs/script_value.h @@ -60,7 +60,7 @@ class ScriptValue final { // Create a new ScriptValue from call JSON.stringify to current value. ScriptValue ToJSONStringify(ExceptionState* exception) const; AtomicString ToString() const; - NativeValue ToNative() const; + NativeValue ToNative(ExceptionState& exception_state) const; bool IsException() const; bool IsEmpty() const; diff --git a/bridge/core/dom/events/event.h b/bridge/core/dom/events/event.h index 246fd1d133..9b6dd166f2 100644 --- a/bridge/core/dom/events/event.h +++ b/bridge/core/dom/events/event.h @@ -60,7 +60,6 @@ struct RawEvent : public DartReadable { template inline T* toNativeEvent(RawEvent* raw_event) { - if (raw_event == nullptr) return nullptr; // NativeEvent members are memory aligned corresponding to NativeEvent. // So we can reinterpret_cast raw bytes pointer to NativeEvent type directly. return reinterpret_cast(raw_event->bytes); diff --git a/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl index 81bf3c3e83..11c35f0aa6 100644 --- a/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl +++ b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl @@ -92,6 +92,9 @@ Event* EventFactory::Create(ExecutingContext* context, const AtomicString& type, auto it = g_event_constructors->find(type); if (it == g_event_constructors->end()) { + if (raw_event == nullptr) { + return MakeGarbageCollected(context, type); + } return MakeGarbageCollected(context, type, toNativeEvent(raw_event)); } EventConstructorFunction function = it->second; From 71b46b06fa315f1e62553a3529d77efb1c664f7e Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 13:51:40 +0800 Subject: [PATCH 441/498] fix: fix message event. --- bridge/core/events/message_event.cc | 11 ++--------- webf/lib/src/dom/event.dart | 13 +++++++++++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index 4dfbfcdd31..24cd6cf72a 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -5,17 +5,10 @@ #include "message_event.h" #include "core/dom/events/event.h" +#include "qjs_message_event.h" namespace webf { -struct NativeMessageEvent { - NativeEvent native_event; - const char* data; - NativeString* origin; - NativeString* lastEventId; - NativeString* source; -}; - MessageEvent* MessageEvent::Create(ExecutingContext* context, const AtomicString& type, ExceptionState& exception_state) { @@ -45,7 +38,7 @@ MessageEvent::MessageEvent(ExecutingContext* context, const AtomicString& type, NativeMessageEvent* native_message_event) : Event(context, type, &native_message_event->native_event), - data_(ScriptValue::CreateJsonObject(ctx(), native_message_event->data, strlen(native_message_event->data))), + data_(ScriptValue(ctx(), *(static_cast(native_message_event->data)))), origin_(AtomicString(ctx(), native_message_event->origin)), lastEventId_(AtomicString(ctx(), native_message_event->lastEventId)), source_(AtomicString(ctx(), native_message_event->source)) {} diff --git a/webf/lib/src/dom/event.dart b/webf/lib/src/dom/event.dart index 0dc3b07718..16cb336b2d 100644 --- a/webf/lib/src/dom/event.dart +++ b/webf/lib/src/dom/event.dart @@ -491,13 +491,22 @@ 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); @override Pointer toRaw([int methodLength = 0]) { - List methods = [(jsonEncode(data)).toNativeUtf8().address, stringToNativeString(origin).address]; - + 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(); int currentStructSize = rawEvent.ref.length + methods.length; Uint64List bytes = rawEvent.ref.bytes.asTypedList(currentStructSize); From 5c638a66b29b6139102d3fc9876a457e396d9b1b Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 13:51:50 +0800 Subject: [PATCH 442/498] fix: fix plugin test. --- bridge/core/page.cc | 2 + integration_tests/lib/plugin.dart | 79 ++++++++++--------------------- integration_tests/pubspec.yaml | 2 +- 3 files changed, 29 insertions(+), 54 deletions(-) diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 155760a292..651825e1e2 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -61,6 +61,8 @@ void WebFPage::invokeModuleEvent(const NativeString* moduleName, if (!context_->IsValid()) return; + MemberMutationScope scope{context_}; + JSContext* ctx = context_->ctx(); Event* event = nullptr; if (ptr != nullptr) { diff --git a/integration_tests/lib/plugin.dart b/integration_tests/lib/plugin.dart index ad16e7a521..2ee141b133 100644 --- a/integration_tests/lib/plugin.dart +++ b/integration_tests/lib/plugin.dart @@ -53,28 +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> allSpecsPayload = [ - { - 'filename': path.basename(specs.path), - 'filepath': specs.path, - 'code': specs.readAsStringSync() - } - ]; - List 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', @@ -82,41 +71,25 @@ void main() async { home: Scaffold( appBar: AppBar(title: Text('WebF Plugin Tests')), body: Wrap( - children: widgets, + children: [ + webF + ], ), ), )); WidgetsBinding.instance.addPostFrameCallback((_) async { - List> testResults = []; - - for (int i = 0; i < widgets.length; i++) { - int contextId = i; - registerDartTestMethodsToCpp(contextId); - initTestFramework(contextId); - addJSErrorListener(contextId, (String err) { - print(err); - }); - - Map 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 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/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 From 3fb58eb9f2fc26c93fc54995c0354b5ee197f15d Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 14:44:09 +0800 Subject: [PATCH 443/498] fix: upgrade websocket plugin. --- webf/example/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webf/example/pubspec.yaml b/webf/example/pubspec.yaml index b59174cf93..45561d1f2a 100644 --- a/webf/example/pubspec.yaml +++ b/webf/example/pubspec.yaml @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter webf: ^0.10.0 - webf_websocket: ^1.0.0 + webf_websocket: ^1.1.0-beta.2 # When depending on this package from a real application, # you should remove the following overrides. From 4791c38eb950d5255b5fd98ee797d5ac9be1f312 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 14:44:26 +0800 Subject: [PATCH 444/498] fix: fix undefined params of addEventListener. --- bridge/core/dom/events/event_target.cc | 3 +++ integration_tests/specs/dom/events/event.ts | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 1b61e2d305..866dbff7ea 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -74,6 +74,9 @@ bool EventTarget::addEventListener(const AtomicString& event_type, const std::shared_ptr& event_listener, const std::shared_ptr& options, ExceptionState& exception_state) { + if (options == nullptr) { + return AddEventListenerInternal(event_type, event_listener, AddEventListenerOptions::Create()); + } return AddEventListenerInternal(event_type, event_listener, options); } 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'); + }); }); From c353f850be70f01ee19564b01eb4d26513fed341 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 26 Sep 2022 12:13:52 +0800 Subject: [PATCH 445/498] fix: animaition snapshots --- .../animation-delay-001-manual.html.0.png | Bin 19050 -> 18993 bytes .../animation-delay-001-manual.html.1.png | Bin 19089 -> 19094 bytes .../animation-delay-001-manual.html.2.png | Bin 0 -> 19073 bytes .../animation-delay-002-manual.html.1.png | Bin 16722 -> 16726 bytes .../animation-delay-002-manual.html.2.png | Bin 16742 -> 16725 bytes .../animation-delay-002-manual.html.3.png | Bin 16871 -> 16738 bytes .../animation-direction-001-manual.html.1.png | Bin 18791 -> 18887 bytes .../animation-direction-001-manual.html.2.png | Bin 18795 -> 18774 bytes .../animation-direction-002-manual.html.2.png | Bin 19360 -> 19466 bytes 9 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.2.png diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.0.png b/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.0.png index d183054892c8c274379e01c68b65864157dd47ae..0f4586c4d07a95c9f6e53862400379a6f553f51b 100644 GIT binary patch literal 18993 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-delay-001-manual.html.2.png new file mode 100644 index 0000000000000000000000000000000000000000..600625ab5af73e45e93375785ceb28d0e7531113 GIT binary patch literal 19073 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%0AD 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 literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.1.png b/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.1.png index 1a4178eb0e2087789b74739cbf2d56fcbe800d51..d0a49e999e872d4cd532e088ef49e7749b9575c5 100644 GIT binary patch delta 977 zcmV;?11|j1f&tco0k9M_f7|@|QcMhgw*0kebE@|UR0RPI0G1-40l-oOGyqtNfW|2l zT9N#aq}h@}E8(n=H0PwY2(Sr1IYSNkN>WjT=}jfYNy?DaUD8rXzmv2>Qkta6lI}e| z?^sFGCDp{peTyX(lyU#>B%MjnHHpix7*Ny?5eeWawvC0!&be^pZ52=kl__p9U+ zdNd0pwXTNsjFvP}(&}nh7XIB3(5OKzB)t|kt-LuycO0>_A#LI_l!!k1^=!HXw;yMf0Cw#O}kx{KkqzS(iWA3 z?|&Sh)>S2^CZ_5{Ks%O6x~vA*l`QF5l?3>mDtTjAD(O>|ygA$_X`M;}x?d&Rfq!QN zG-}Wqo#>I_Xr;;rv~w=^N|l^`$UFXbj!K^@>8Q$wb4AwGPtv<83GXeE9*}fX71j%! zKtQ7g-5@Eve|F+oRaoBkjh3ApizT< zA<18b8-E%e|Mxpd=ShlH&7ITryRL(hhDeH6$tUfnCH<|2+Jt{M^x}YJj+JqrQ$%>T zO1kUSl&hxi=BUyG)B^!l0FEz-zgh+R6s=$f6%amQ_vwxB|j38q{fo+CC!wy zO(nlH3`n{|CEeKOs^ntc|Elza4~ts9km8m+4nq3+C=qmpyn4OO$BHL7D>`y>^sWIL+*=HXvTiuCutpS0DX znsJd3mZ=kVJ4Ppd=@&lVB{koiYFifrFQMW%0>uHdF*$Sr42m(vm{Z*d06=Mcg0n$7 z5eBnBIx+za@qe320002!Nkl$7xn)E*I%B^t^SRQ00000NkvXXu0mjfmPOo) delta 974 zcmV;<12O#8f&tQk0k9M_f7eGJNii|}+42`Qasz&X2xtJX6aftYmLi}5z)}P>PLa@R zleAotGsek9 z)wG@*No%TUS@?IvIcU_7fJ#njg#Oh(e&0uulH@(->_@hwu96x_iaEZF@nQ3{m(*X< z(BtzBh_IYXBD5(Xe`MNOKGt=wq?VFG3q5=z=_*MRB>hR!E61lN9iP6d0^17k2^y!w zUP-HT;!~_22PIuEX`iG&OFCcDCz2kLbhe~>j?edMMoCk{f2Q58%Aa?hEoqBN!uLOp zPwT3ZQxj8lBA^}1Bwbd6>q?gNtV#m>PL;edES2=BO5PmqleA7H0o|{X?ZCe~0va`B zjZXB)aI{k81KK&4d!L=-4m4x>eNe@W6sS4`_ya;I2 zkQ*e0*G)XDe+tXne*E9?YVScw;pNTFuEKgMvaT~!a*q4XS0gW2+3rYT} z+xXM)_`lytI!{upYVMq_-*p|7G(=LoNrBs!lbuE$foJvk>w2<^aDmnepLnS>qoNbV#agydsDy-&q z;^&Tl#wqZN_lpSY; z-L8!Cf7W$GC0*y@Rnn6~Dmh#Ds!D#G^5pSpcSvd`skfvyD(Tq*_(w$*RGW`Js_G!| z&iEKkrFEl{Pos5pBGjGva#V7TyP<0Kb4GQnYoDZIm25{<-#q;5&_T+|I%%sz4d5an zEK(=zc8pH^(l30zOKQG3)wV7MUP9$?iUR;FvqL&^0Spymj4`LW5deVF_yn_2I{^i= wKsrVO3=pS_6aWAKt<8 diff --git a/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-delay-002-manual.html.2.png index c63d94cb0db4a526429a216ae2fa5479439c1e33..c4fc032318c0983d57f27d49ecc4fa44315ce25e 100644 GIT binary patch literal 16725 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 literal 16742 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 literal 16871 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
  • Gf4DsJMt^{F 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 literal 18791 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 hF%q6?f0Yb%i;c;w?5z1f!zjX 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 diff --git a/integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.2.png b/integration_tests/snapshots/css/css-animations/animation-direction-002-manual.html.2.png index 6b2cbdb920269a5b7699c22b7092be7e1ac7a058..79e7d8cd4b097549cf7a071f7afed21e6954640a 100644 GIT binary patch literal 19466 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
    ~o_?(S}Bpcn1^xTbk*<0E#t-0f|brR`==kzH1{ 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 From c92a1206e803aa2cbcce0f371458cfa2759e3d76 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 18:54:18 +0800 Subject: [PATCH 446/498] fix: add isContextValid and isCtxValid(); --- .../bindings/qjs/js_based_event_listener.cc | 2 +- bridge/bindings/qjs/script_promise_resolver.h | 2 +- bridge/core/binding_object.cc | 2 +- .../core/dom/scripted_animation_controller.cc | 2 +- bridge/core/executing_context.cc | 10 ++++++-- bridge/core/executing_context.h | 25 +++++++++++++------ bridge/core/executing_context_test.cc | 6 +++-- bridge/core/frame/dom_timer_coordinator.cc | 2 +- bridge/core/frame/module_manager.cc | 3 ++- .../frame/window_or_worker_global_scope.cc | 4 +-- bridge/core/page.cc | 14 +++++------ bridge/test/webf_test_context.cc | 4 +-- 12 files changed, 47 insertions(+), 29 deletions(-) diff --git a/bridge/bindings/qjs/js_based_event_listener.cc b/bridge/bindings/qjs/js_based_event_listener.cc index 8d40b5f97b..0f9b82237e 100644 --- a/bridge/bindings/qjs/js_based_event_listener.cc +++ b/bridge/bindings/qjs/js_based_event_listener.cc @@ -14,7 +14,7 @@ void JSBasedEventListener::Invoke(ExecutingContext* context, Event* event, Excep assert(context); assert(event); - if (!context->IsValid()) + if (!context->IsContextValid()) return; // Step 10: Call a listener with event's currentTarget as receiver and event // and handle errors if thrown. diff --git a/bridge/bindings/qjs/script_promise_resolver.h b/bridge/bindings/qjs/script_promise_resolver.h index 2289bcbc17..95b01c2f41 100644 --- a/bridge/bindings/qjs/script_promise_resolver.h +++ b/bridge/bindings/qjs/script_promise_resolver.h @@ -60,7 +60,7 @@ class ScriptPromiseResolver { } void ResolveOrReject(JSValue value, ResolutionState new_state) { - if (state_ != kPending || !context_->IsValid() || !context_) + if (state_ != kPending || !context_->IsContextValid() || !context_) return; assert(new_state == kResolving || new_state == kRejecting); state_ = new_state; diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 0c3a0fad0d..a4d457c1ba 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -129,7 +129,7 @@ void BindingObject::HandleAnonymousAsyncCalledFromDart(void* ptr, int32_t contextId, const char* errmsg) { auto* promise_context = static_cast(ptr); - if (!promise_context->context->IsValid()) + if (!promise_context->context->IsContextValid()) return; if (promise_context->context->contextId() != contextId) return; diff --git a/bridge/core/dom/scripted_animation_controller.cc b/bridge/core/dom/scripted_animation_controller.cc index 6a02710cba..c67ce181b1 100644 --- a/bridge/core/dom/scripted_animation_controller.cc +++ b/bridge/core/dom/scripted_animation_controller.cc @@ -12,7 +12,7 @@ static void handleRAFTransientCallback(void* ptr, int32_t contextId, double high auto* frame_callback = static_cast(ptr); auto* context = frame_callback->context(); - if (!context->IsValid()) + if (!context->IsContextValid()) return; if (errmsg != nullptr) { diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 89eefbb8ac..9b9aef3cc2 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -27,7 +27,7 @@ std::unique_ptr createJSContext(int32_t contextId, const JSExc } ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) - : context_id_(contextId), handler_(handler), owner_(owner), unique_id_(context_unique_id++) { + : context_id_(contextId), handler_(handler), owner_(owner), unique_id_(context_unique_id++), is_context_valid_(true) { // #if ENABLE_PROFILE // auto jsContextStartTime = // std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) @@ -80,6 +80,7 @@ ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& } ExecutingContext::~ExecutingContext() { + is_context_valid_ = false; valid_contexts[context_id_] = false; // Check if current context have unhandled exceptions. @@ -143,7 +144,11 @@ bool ExecutingContext::EvaluateByteCode(uint8_t* bytes, size_t byteLength) { return true; } -bool ExecutingContext::IsValid() const { +bool ExecutingContext::IsContextValid() const { + return is_context_valid_; +} + +bool ExecutingContext::IsCtxValid() const { return script_state_.Invalid(); } @@ -183,6 +188,7 @@ JSValue ExecutingContext::Global() { } JSContext* ExecutingContext::ctx() { + assert(IsCtxValid()); return script_state_.ctx(); } diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index ae7f24e5f3..7934eaa7e1 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -61,7 +61,8 @@ class ExecutingContext { bool EvaluateJavaScript(const char16_t* code, size_t length, const char* sourceURL, int startLine); bool EvaluateJavaScript(const char* code, size_t codeLength, const char* sourceURL, int startLine); bool EvaluateByteCode(uint8_t* bytes, size_t byteLength); - bool IsValid() const; + bool IsContextValid() const; + bool IsCtxValid() const; JSValue Global(); JSContext* ctx(); FORCE_INLINE int32_t contextId() const { return context_id_; }; @@ -106,7 +107,7 @@ class ExecutingContext { FORCE_INLINE Window* window() const { return window_; } FORCE_INLINE Performance* performance() const { return performance_; } FORCE_INLINE UICommandBuffer* uiCommandBuffer() { return &ui_command_buffer_; }; - FORCE_INLINE std::unique_ptr& dartMethodPtr() { return dart_method_ptr_; } + FORCE_INLINE const std::unique_ptr& dartMethodPtr() { return dart_method_ptr_; } FORCE_INLINE std::chrono::time_point timeOrigin() const { return time_origin_; } // Force dart side to execute the pending ui commands. @@ -137,14 +138,23 @@ class ExecutingContext { JSValueConst reason, JS_BOOL is_handled, void* opaque); - - // Dart methods ptr should keep alive when ExecutingContext is disposing. - std::unique_ptr dart_method_ptr_ = std::make_unique(); + // Warning: Don't change the orders of members in ExecutingContext if you really know what are you doing. // From C++ standard, https://isocpp.org/wiki/faq/dtors#order-dtors-for-members // Members first initialized and destructed at the last. - // Always keep ScriptState at the top of all stack allocated members to make sure it destructed in the last. + // Dart methods ptr should keep alive when ExecutingContext is disposing. + const std::unique_ptr dart_method_ptr_ = std::make_unique(); + // 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_; @@ -157,7 +167,6 @@ class ExecutingContext { ModuleCallbackCoordinator module_callbacks_; ExecutionContextData context_data_{this}; bool in_dispatch_error_event_{false}; - UICommandBuffer ui_command_buffer_{this}; RejectedPromises rejected_promises_; MemberMutationScope* active_mutation_scope{nullptr}; std::vector active_wrappers_; diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 8e1f981473..1cc423e520 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -12,11 +12,13 @@ using namespace webf; TEST(Context, isValid) { { auto bridge = TEST_init(); - EXPECT_EQ(bridge->GetExecutingContext()->IsValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsContextValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsCtxValid(), true); } { auto bridge = TEST_init(); - EXPECT_EQ(bridge->GetExecutingContext()->IsValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsContextValid(), true); + EXPECT_EQ(bridge->GetExecutingContext()->IsCtxValid(), true); } } diff --git a/bridge/core/frame/dom_timer_coordinator.cc b/bridge/core/frame/dom_timer_coordinator.cc index 8b3f79e309..9540c54591 100644 --- a/bridge/core/frame/dom_timer_coordinator.cc +++ b/bridge/core/frame/dom_timer_coordinator.cc @@ -33,7 +33,7 @@ static void handleTransientCallback(void* ptr, int32_t context_id, const char* e auto* timer = static_cast(ptr); auto* context = timer->context(); - if (!context->IsValid()) + if (!context->IsContextValid()) return; handleTimerCallback(timer, errmsg); diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 140caddeea..498c4cf535 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -20,7 +20,7 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, auto* moduleContext = static_cast(ptr); ExecutingContext* context = moduleContext->context; - if (!context->IsValid()) + if (!context->IsContextValid()) return nullptr; if (moduleContext->callback == nullptr) { @@ -71,6 +71,7 @@ NativeValue* handleInvokeModuleUnexpectedCallback(void* callbackContext, 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, diff --git a/bridge/core/frame/window_or_worker_global_scope.cc b/bridge/core/frame/window_or_worker_global_scope.cc index 0cd3292d6f..b6a0962ddc 100644 --- a/bridge/core/frame/window_or_worker_global_scope.cc +++ b/bridge/core/frame/window_or_worker_global_scope.cc @@ -28,7 +28,7 @@ static void handleTransientCallback(void* ptr, int32_t contextId, const char* er auto* timer = static_cast(ptr); auto* context = timer->context(); - if (!context->IsValid()) + if (!context->IsContextValid()) return; if (timer->status() == DOMTimer::TimerStatus::kCanceled) { @@ -46,7 +46,7 @@ static void handlePersistentCallback(void* ptr, int32_t contextId, const char* e auto* timer = static_cast(ptr); auto* context = timer->context(); - if (!context->IsValid()) + if (!context->IsContextValid()) return; if (timer->status() == DOMTimer::TimerStatus::kCanceled) { diff --git a/bridge/core/page.cc b/bridge/core/page.cc index df0e329a89..46bb5a72fc 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -37,7 +37,7 @@ WebFPage::WebFPage(int32_t contextId, const JSExceptionHandler& handler) } bool WebFPage::parseHTML(const char* code, size_t length) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return false; MemberMutationScope scope{context_}; @@ -59,7 +59,7 @@ NativeValue* WebFPage::invokeModuleEvent(const NativeString* native_module_name, const char* eventType, void* ptr, NativeValue* extra) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return nullptr; MemberMutationScope scope{context_}; @@ -100,7 +100,7 @@ NativeValue* WebFPage::invokeModuleEvent(const NativeString* native_module_name, } void WebFPage::evaluateScript(const NativeString* script, const char* url, int startLine) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return; //#if ENABLE_PROFILE @@ -115,25 +115,25 @@ void WebFPage::evaluateScript(const NativeString* script, const char* url, int s } void WebFPage::evaluateScript(const uint16_t* script, size_t length, const char* url, int startLine) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return; context_->EvaluateJavaScript(script, length, url, startLine); } void WebFPage::evaluateScript(const char* script, size_t length, const char* url, int startLine) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return; context_->EvaluateJavaScript(script, length, url, startLine); } uint8_t* WebFPage::dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return nullptr; return context_->DumpByteCode(script, length, url, byteLength); } void WebFPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return; context_->EvaluateByteCode(bytes, byteLength); } diff --git a/bridge/test/webf_test_context.cc b/bridge/test/webf_test_context.cc index 076fd6302a..95f3050914 100644 --- a/bridge/test/webf_test_context.cc +++ b/bridge/test/webf_test_context.cc @@ -285,13 +285,13 @@ bool WebFTestContext::evaluateTestScripts(const uint16_t* code, size_t codeLength, const char* sourceURL, int startLine) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return false; return context_->EvaluateJavaScript(code, codeLength, sourceURL, startLine); } bool WebFTestContext::parseTestHTML(const uint16_t* code, size_t codeLength) { - if (!context_->IsValid()) + if (!context_->IsContextValid()) return false; std::string utf8Code = toUTF8(std::u16string(reinterpret_cast(code), codeLength)); return page_->parseHTML(utf8Code.c_str(), utf8Code.length()); From 11730de6258c2c96189339000cc85e7aca7811d2 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 18:54:42 +0800 Subject: [PATCH 447/498] feat: flush all disposeEventTarget commands when dispose page. --- bridge/foundation/ui_command_buffer.cc | 11 ++++++++++- bridge/foundation/ui_command_buffer.h | 3 +++ bridge/webf_bridge.cc | 8 ++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 5fa030ba7b..630dd99ea2 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -13,6 +13,15 @@ namespace webf { UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) {} +UICommandBuffer::~UICommandBuffer() { +#if FLUTTER_BACKEND + // Flush and execute all disposeEventTarget commands when context released. + if (context_->dartMethodPtr()->flushUICommand != nullptr && !isDartHostRestart()) { + context_->dartMethodPtr()->flushUICommand(context_->contextId()); + } +#endif +} + void UICommandBuffer::addCommand(int32_t id, UICommand type, void* nativePtr) { UICommandItem item{id, static_cast(type), nativePtr}; addCommand(item); @@ -42,7 +51,7 @@ void UICommandBuffer::addCommand(const UICommandItem& item) { } #if FLUTTER_BACKEND - if (!update_batched_ && context_->IsValid() && context_->dartMethodPtr()->requestBatchUpdate != nullptr) { + if (!update_batched_ && context_->IsContextValid() && context_->dartMethodPtr()->requestBatchUpdate != nullptr) { context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); update_batched_ = true; } diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index f21008edcb..ef636d9d6b 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -63,10 +63,13 @@ struct UICommandItem { int64_t nativePtr{0}; }; +bool isDartHostRestart(); + class UICommandBuffer { public: UICommandBuffer() = delete; explicit UICommandBuffer(ExecutingContext* context); + ~UICommandBuffer(); void addCommand(int32_t id, UICommand type, void* nativePtr); void addCommand(int32_t id, UICommand type, diff --git a/bridge/webf_bridge.cc b/bridge/webf_bridge.cc index 6f846c8a89..9848dd4078 100644 --- a/bridge/webf_bridge.cc +++ b/bridge/webf_bridge.cc @@ -40,6 +40,7 @@ // this is not thread safe std::atomic inited{false}; +std::atomic is_dart_hot_restart{false}; std::atomic poolIndex{0}; int maxPoolSize = 0; @@ -64,10 +65,17 @@ int32_t searchForAvailableContextId() { } // namespace +namespace webf { + bool isDartHostRestart() { return is_dart_hot_restart; } +} + + void initJSPagePool(int poolSize) { // 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++) { From 462e0f675187d36e638c03a8a65208bdf81179a1 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Mon, 26 Sep 2022 10:55:33 +0000 Subject: [PATCH 448/498] Committing clang-format changes --- bridge/core/executing_context.cc | 6 +++++- bridge/core/executing_context.h | 8 +++++--- bridge/webf_bridge.cc | 5 +++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 9b9aef3cc2..41d155d497 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -27,7 +27,11 @@ std::unique_ptr createJSContext(int32_t contextId, const JSExc } ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) - : context_id_(contextId), handler_(handler), owner_(owner), unique_id_(context_unique_id++), is_context_valid_(true) { + : context_id_(contextId), + handler_(handler), + owner_(owner), + unique_id_(context_unique_id++), + is_context_valid_(true) { // #if ENABLE_PROFILE // auto jsContextStartTime = // std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 7934eaa7e1..c60f630b00 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -143,10 +143,12 @@ class ExecutingContext { // Members first initialized and destructed at the last. // Dart methods ptr should keep alive when ExecutingContext is disposing. const std::unique_ptr dart_method_ptr_ = std::make_unique(); - // Keep uiCommandBuffer below dartMethod ptr to make sure we can flush all disposeEventTarget when UICommandBuffer release. + // 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. + // 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 // ---------------------------------------------------------------------- diff --git a/bridge/webf_bridge.cc b/bridge/webf_bridge.cc index 9848dd4078..42b0e19c0e 100644 --- a/bridge/webf_bridge.cc +++ b/bridge/webf_bridge.cc @@ -66,9 +66,10 @@ int32_t searchForAvailableContextId() { } // namespace namespace webf { - bool isDartHostRestart() { return is_dart_hot_restart; } +bool isDartHostRestart() { + return is_dart_hot_restart; } - +} // namespace webf void initJSPagePool(int poolSize) { // When dart hot restarted, should dispose previous bridge and clear task message queue. From 0023f11c11e165e0cde98b5681e99293c1368158 Mon Sep 17 00:00:00 2001 From: andycall Date: Mon, 26 Sep 2022 20:24:25 +0800 Subject: [PATCH 449/498] feat: add document.cookie. --- bridge/core/binding_call_methods.json5 | 3 +- bridge/core/dom/document.d.ts | 1 + integration_tests/specs/cookie/cookie.ts | 4 +-- webf/lib/src/dom/document.dart | 24 +++++++++++++++ webf/lib/src/foundation/cookie_jar.dart | 38 ++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 webf/lib/src/foundation/cookie_jar.dart diff --git a/bridge/core/binding_call_methods.json5 b/bridge/core/binding_call_methods.json5 index 7adf4bad11..61d6b6ab08 100644 --- a/bridge/core/binding_call_methods.json5 +++ b/bridge/core/binding_call_methods.json5 @@ -150,6 +150,7 @@ "getElementsByName", "getElementsByTagName", "id", - "className" + "className", + "cookie" ] } diff --git a/bridge/core/dom/document.d.ts b/bridge/core/dom/document.d.ts index 133a553f3a..0a562d97db 100644 --- a/bridge/core/dom/document.d.ts +++ b/bridge/core/dom/document.d.ts @@ -12,6 +12,7 @@ import {HTMLAllCollection} from "../html/html_all_collection"; interface Document extends Node { readonly all: HTMLAllCollection; body: HTMLBodyElement | null; + cookie: DartImpl; readonly head: HTMLHeadElement | null; readonly documentElement: HTMLHtmlElement | null; // Legacy impl: get the polyfill implements from global object. diff --git a/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/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 5cee361c0f..c465982463 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -14,6 +14,7 @@ import 'package:webf/module.dart'; import 'package:webf/rendering.dart'; import 'package:webf/src/css/query_selector.dart' as QuerySelector; import 'package:webf/src/dom/element_registry.dart' as element_registry; +import 'package:webf/src/foundation/cookie_jar.dart'; import 'package:webf/widget.dart'; class Document extends Node { @@ -54,6 +55,8 @@ class Document extends Node { Element? focusedElement; + CookieJar cookie_ = CookieJar(); + // Returns the Window object of the active document. // https://html.spec.whatwg.org/multipage/window-object.html#dom-document-defaultview-dev Window get defaultView => controller.view.window; @@ -95,6 +98,27 @@ class Document extends Node { } } + @override + void setBindingProperty(String key, value) { + switch(key) { + case 'cookie': + cookie_.setCookie(value); + break; + } + + super.setBindingProperty(key, value); + } + + @override + getBindingProperty(String key) { + switch(key) { + case 'cookie': + return cookie_.cookie(); + } + + return super.getBindingProperty(key); + } + @override invokeBindingMethod(String method, List args) { switch (method) { diff --git a/webf/lib/src/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('; '); + } +} From 648c17476d0ab1801766ace90d8707e7877d57af Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 27 Sep 2022 00:53:05 +0800 Subject: [PATCH 450/498] fix: fix multiple pages didn't schedule frames. --- .../android/app/src/main/AndroidManifest.xml | 1 + webf/lib/src/bridge/bridge.dart | 14 -------------- webf/lib/src/launcher/controller.dart | 8 ++++++++ 3 files changed, 9 insertions(+), 14 deletions(-) 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. --> + Date: Tue, 27 Sep 2022 20:32:29 +0800 Subject: [PATCH 451/498] fix: fix android 32bit build. --- bridge/CMakeLists.txt | 8 +++++++- bridge/core/events/message_event.cc | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 31ac8cd9d3..e906124b75 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -59,10 +59,15 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") endif() if (ENABLE_ASAN) - add_compile_options(-fsanitize=address -fno-omit-frame-pointer -O1) + add_compile_options(-fsanitize=address -fno-omit-frame-pointer) add_link_options(-fsanitize=address -fno-omit-frame-pointer) endif () +if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") + # Avoid quickjs stackoverflow. + add_compile_options(-O1) +endif() + if (DEFINED PLATFORM) if (${PLATFORM} STREQUAL "OS") add_compile_options(-fno-aligned-allocation) @@ -408,6 +413,7 @@ target_compile_definitions(webf PUBLIC -DFLUTTER_BACKEND=1) add_library(gumbo_parse_static STATIC ${GUMBO_PARSER}) list(APPEND BRIDGE_LINK_LIBS gumbo_parse_static) +add_definitions(-DANDROID_32_BIT=1) if (${IS_ANDROID}) find_library(log-lib log) diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index 24cd6cf72a..0cd572a712 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -38,7 +38,11 @@ MessageEvent::MessageEvent(ExecutingContext* context, const AtomicString& type, NativeMessageEvent* native_message_event) : Event(context, type, &native_message_event->native_event), +#if ANDROID_32_BIT + data_(ScriptValue(ctx(), *(reinterpret_cast(native_message_event->data)))), +#else data_(ScriptValue(ctx(), *(static_cast(native_message_event->data)))), +#endif origin_(AtomicString(ctx(), native_message_event->origin)), lastEventId_(AtomicString(ctx(), native_message_event->lastEventId)), source_(AtomicString(ctx(), native_message_event->source)) {} From 4c37f965c56e464f2acafda4ae57f00160069dfb Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Tue, 27 Sep 2022 12:33:34 +0000 Subject: [PATCH 452/498] Committing clang-format changes --- bridge/core/events/message_event.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index 0cd572a712..f9945cc030 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -45,7 +45,8 @@ MessageEvent::MessageEvent(ExecutingContext* context, #endif origin_(AtomicString(ctx(), native_message_event->origin)), lastEventId_(AtomicString(ctx(), native_message_event->lastEventId)), - source_(AtomicString(ctx(), native_message_event->source)) {} + source_(AtomicString(ctx(), native_message_event->source)) { +} ScriptValue MessageEvent::data() const { return data_; From 9624a34d154f38c69db1f013413c0a66b3331167 Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 27 Sep 2022 20:35:13 +0800 Subject: [PATCH 453/498] chore: remove mistake define. --- bridge/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index e906124b75..579097ea95 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -413,7 +413,6 @@ target_compile_definitions(webf PUBLIC -DFLUTTER_BACKEND=1) add_library(gumbo_parse_static STATIC ${GUMBO_PARSER}) list(APPEND BRIDGE_LINK_LIBS gumbo_parse_static) -add_definitions(-DANDROID_32_BIT=1) if (${IS_ANDROID}) find_library(log-lib log) From c9f0609e8743ede3a0594552b7786728809ae046 Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 27 Sep 2022 21:09:01 +0800 Subject: [PATCH 454/498] fix: fix JSPropertyDescriptor not initialized. --- .github/workflows/example_build.yml | 2 +- bridge/bindings/qjs/script_wrappable.cc | 4 ++++ bridge/test/webf_test_env.cc | 4 +++- integration_tests/scripts/core_integration_starter.js | 7 ++++++- 4 files changed, 14 insertions(+), 3 deletions(-) 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/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index 0312b3642d..c59041e998 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -165,6 +165,8 @@ void ScriptWrappable::InitializeQuickJSObject() { if (!JS_IsNull(return_value)) { desc->flags = JS_PROP_ENUMERABLE; desc->value = return_value; + desc->getter = JS_NULL; + desc->setter = JS_NULL; return true; } } @@ -175,6 +177,8 @@ void ScriptWrappable::InitializeQuickJSObject() { if (!JS_IsNull(return_value)) { desc->flags = JS_PROP_ENUMERABLE; desc->value = return_value; + desc->getter = JS_NULL; + desc->setter = JS_NULL; return true; } } diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index 0cd91d6919..34e0a580e4 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -179,7 +179,9 @@ void TEST_toBlob(void* ptr, blobCallback(ptr, contextId, nullptr, bytes, 5); } -void TEST_flushUICommand() {} +void TEST_flushUICommand(int32_t contextId) { + clearUICommandItems(contextId); +} void TEST_onJsLog(int32_t contextId, int32_t level, const char*) {} diff --git a/integration_tests/scripts/core_integration_starter.js b/integration_tests/scripts/core_integration_starter.js index 64eeb8ff14..2412767e98 100644 --- a/integration_tests/scripts/core_integration_starter.js +++ b/integration_tests/scripts/core_integration_starter.js @@ -38,13 +38,18 @@ function startIntegrationTest() { }); tester.on('close', (code) => { + console.log(code); process.exit(code); }); tester.on('error', (error) => { - console.error(error); + console.error('integration failed', error); process.exit(1); }); tester.on('exit', (code, signal) => { + if (signal) { + console.log('Process exit with ' + signal); + process.exit(1); + } if (code != 0) { process.exit(1); } From 0a60ef6c0643d681297eae9fa69f5e866554bd8b Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 27 Sep 2022 21:32:34 +0800 Subject: [PATCH 455/498] ci: fix integration test ci --- .github/workflows/integration_test_flutter.yml | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/.github/workflows/integration_test_flutter.yml b/.github/workflows/integration_test_flutter.yml index 9041d93520..0cf2649772 100644 --- a/.github/workflows/integration_test_flutter.yml +++ b/.github/workflows/integration_test_flutter.yml @@ -30,7 +30,7 @@ jobs: with: cmake-version: ${{ env.cmakeVersion }} - run: npm i - - run: ENABLE_ASAN=true npm run build:bridge:macos + - run: npm run build:bridge:macos - uses: actions/upload-artifact@v2 with: name: macos_bridge_binary @@ -49,22 +49,6 @@ jobs: - run: npm i - run: ENABLE_ASAN=true npm run build:bridge:linux - run: node scripts/run_bridge_unit_test.js - id: bridge_test - - name: Check on failures - if: steps.bridge_test.outcome == 'success' - run: exit 0 - - run: sudo chmod -R +rwx /cores/* # Enable access to core dumps - - run: ulimit -c # should output 0 if disabled - - run: ulimit -c unlimited - - run: ulimit -c # should output 'unlimited' now - - run: sudo touch /cores/test - - run: ls /cores - - run: sudo rm /cores/test - - uses: actions/upload-artifact@master # capture all crashes as build artifacts - if: steps.bridge_test.outcome != 'success' - with: - name: crashes - path: /cores webf_unit_test: runs-on: ubuntu-latest From 5a3c2364775e7badd918ff9e50f252af39de8fc9 Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 28 Sep 2022 00:38:50 +0800 Subject: [PATCH 456/498] fix: should not bind bindObject when it already disposed. --- bridge/core/binding_object.cc | 4 +++- bridge/core/dom/events/event_target.cc | 2 +- webf/lib/src/bridge/binding.dart | 4 ++-- webf/lib/src/launcher/controller.dart | 7 +++++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index a4d457c1ba..41a374d35c 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -25,7 +25,9 @@ void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_ob BindingObject::BindingObject(ExecutingContext* context) : context_(context) {} BindingObject::~BindingObject() { - delete binding_object_; + // Set below properties to nullptr to avoid dart callback to native. + binding_object_->binding_target_ = nullptr; + binding_object_->invoke_binding_methods_from_dart = nullptr; } BindingObject::BindingObject(ExecutingContext* context, NativeBindingObject* native_binding_object) diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 866dbff7ea..21ce9e81f4 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -55,7 +55,7 @@ EventTarget::~EventTarget() { } #endif - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, bindingObject()); } EventTarget::EventTarget(ExecutingContext* context) diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 247cd31bac..1a86d61dfa 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -158,7 +158,7 @@ void _invokeBindingMethodFromNativeImpl(Pointer nativeBindi void _dispatchEventToNative(Event event) { Pointer? pointer = event.currentTarget?.pointer; int? contextId = event.target?.contextId; - if (contextId != null && pointer != null) { + if (contextId != null && pointer != null && pointer.ref.invokeBindingMethodFromDart != nullptr) { BindingObject bindingObject = BindingBridge.getBindingObject(pointer); // Call methods implements at C++ side. DartInvokeBindingMethodsFromDart f = pointer.ref.invokeBindingMethodFromDart.asFunction(); @@ -208,7 +208,7 @@ abstract class BindingBridge { static void _bindObject(BindingObject object) { Pointer? nativeBindingObject = object.pointer; - if (nativeBindingObject != null) { + if (nativeBindingObject != null && nativeBindingObject.ref.instance != nullptr) { _nativeObjects[nativeBindingObject.address] = object; nativeBindingObject.ref.invokeBindingMethodFromNative = _invokeBindingMethodFromNative; } diff --git a/webf/lib/src/launcher/controller.dart b/webf/lib/src/launcher/controller.dart index b47964b6e1..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'; @@ -689,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() { @@ -976,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, From 5f03c2cafc14fb2f51d433e3235d3516c29f48c2 Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 28 Sep 2022 00:40:00 +0800 Subject: [PATCH 457/498] fix: should init dart methods when allocate new bridge. --- bridge/CMakeLists.txt | 1 + bridge/core/dart_methods.cc | 34 +++++++++++++++++++++++ bridge/core/dart_methods.h | 4 ++- bridge/core/executing_context.cc | 17 +++++++----- bridge/core/executing_context.h | 10 ++++--- bridge/core/executing_context_test.cc | 4 +-- bridge/core/page.cc | 35 ++++-------------------- bridge/core/page.h | 3 +-- bridge/foundation/ui_command_buffer.cc | 2 +- bridge/foundation/ui_command_buffer.h | 11 ++++---- bridge/include/webf_bridge.h | 8 +++--- bridge/test/webf_test_env.cc | 18 ++++++------- bridge/test/webf_test_env.h | 2 +- bridge/webf_bridge.cc | 20 +++++--------- webf/lib/src/bridge/bridge.dart | 9 +++---- webf/lib/src/bridge/from_native.dart | 14 ++-------- webf/lib/src/bridge/to_native.dart | 37 +++++++++++++++++--------- 17 files changed, 117 insertions(+), 112 deletions(-) create mode 100644 bridge/core/dart_methods.cc diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 579097ea95..5952bc2890 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -199,6 +199,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/executing_context.cc core/script_state.cc core/page.cc + core/dart_methods.cc core/executing_context_data.cc core/fileapi/blob.cc core/fileapi/blob_part.cc diff --git a/bridge/core/dart_methods.cc b/bridge/core/dart_methods.cc new file mode 100644 index 0000000000..71642c6954 --- /dev/null +++ b/bridge/core/dart_methods.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 "dart_methods.h" + +namespace webf { + +webf::DartMethodPointer::DartMethodPointer(const uint64_t* dart_methods, int32_t dart_methods_length) { + size_t i = 0; + invokeModule = reinterpret_cast(dart_methods[i++]); + requestBatchUpdate = reinterpret_cast(dart_methods[i++]); + reloadApp = reinterpret_cast(dart_methods[i++]); + setTimeout = reinterpret_cast(dart_methods[i++]); + setInterval = reinterpret_cast(dart_methods[i++]); + clearTimeout = reinterpret_cast(dart_methods[i++]); + requestAnimationFrame = reinterpret_cast(dart_methods[i++]); + cancelAnimationFrame = reinterpret_cast(dart_methods[i++]); + toBlob = reinterpret_cast(dart_methods[i++]); + flushUICommand = reinterpret_cast(dart_methods[i++]); + +#if ENABLE_PROFILE + dartMethodPointer->getPerformanceEntries = reinterpret_cast(dart_methods[i++]); +#else + i++; +#endif + + onJsError = reinterpret_cast(dart_methods[i++]); + onJsLog = reinterpret_cast(dart_methods[i++]); + + assert_m(i == dart_methods_length, "Dart native methods count is not equal with C++ side method registrations."); +} +} // namespace webf \ No newline at end of file diff --git a/bridge/core/dart_methods.h b/bridge/core/dart_methods.h index f37a83c661..8cbe605f2c 100644 --- a/bridge/core/dart_methods.h +++ b/bridge/core/dart_methods.h @@ -77,7 +77,9 @@ using SimulatePointer = void (*)(MousePointer*, int32_t length, int32_t pointer) using SimulateInputText = void (*)(NativeString* nativeString); struct DartMethodPointer { - DartMethodPointer() = default; + DartMethodPointer() = delete; + explicit DartMethodPointer(const uint64_t* dart_methods, int32_t dartMethodsLength); + InvokeModule invokeModule{nullptr}; RequestBatchUpdate requestBatchUpdate{nullptr}; ReloadApp reloadApp{nullptr}; diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 41d155d497..57fe2059e3 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -3,6 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "executing_context.h" + +#include #include "bindings/qjs/converter_impl.h" #include "built_in_string.h" #include "core/dom/document.h" @@ -22,16 +24,17 @@ static std::atomic context_unique_id{0}; bool valid_contexts[MAX_JS_CONTEXT]; std::atomic running_context_list{0}; -std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) { - return std::make_unique(contextId, handler, owner); -} - -ExecutingContext::ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner) +ExecutingContext::ExecutingContext(int32_t contextId, + JSExceptionHandler handler, + void* owner, + const uint64_t* dart_methods, + int32_t dart_methods_length) : context_id_(contextId), - handler_(handler), + handler_(std::move(handler)), owner_(owner), unique_id_(context_unique_id++), - is_context_valid_(true) { + is_context_valid_(true), + dart_method_ptr_(std::make_unique(dart_methods, dart_methods_length)) { // #if ENABLE_PROFILE // auto jsContextStartTime = // std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index c60f630b00..dc2b78b49a 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -52,7 +52,11 @@ bool isContextValid(int32_t contextId); class ExecutingContext { public: ExecutingContext() = delete; - ExecutingContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); + ExecutingContext(int32_t contextId, + JSExceptionHandler handler, + void* owner, + const uint64_t* dart_methods, + int32_t dart_methods_length); ~ExecutingContext(); static ExecutingContext* From(JSContext* ctx); @@ -142,7 +146,7 @@ class ExecutingContext { // 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 dart_method_ptr_ = std::make_unique(); + const std::unique_ptr dart_method_ptr_ = nullptr; // Keep uiCommandBuffer below dartMethod ptr to make sure we can flush all disposeEventTarget when UICommandBuffer // release. UICommandBuffer ui_command_buffer_{this}; @@ -192,8 +196,6 @@ class ObjectProperty { JSValue m_value{JS_NULL}; }; -std::unique_ptr createJSContext(int32_t contextId, const JSExceptionHandler& handler, void* owner); - } // namespace webf #endif // BRIDGE_JS_CONTEXT_H diff --git a/bridge/core/executing_context_test.cc b/bridge/core/executing_context_test.cc index 1cc423e520..e20dd9caf2 100644 --- a/bridge/core/executing_context_test.cc +++ b/bridge/core/executing_context_test.cc @@ -282,8 +282,8 @@ TEST(Context, accessGetUICommandItemsAfterDisposed) { } TEST(Context, disposeContext) { - initJSPagePool(1024 * 1024); - TEST_mockDartMethods(0, nullptr); + auto mockedDartMethods = TEST_getMockDartMethods(nullptr); + initJSPagePool(1024 * 1024, mockedDartMethods.data(), mockedDartMethods.size()); uint32_t contextId = 0; auto bridge = static_cast(getPage(contextId)); static bool disposed = false; diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 46bb5a72fc..0ddbcbdea6 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -23,7 +23,10 @@ ConsoleMessageHandler WebFPage::consoleMessageHandler{nullptr}; webf::WebFPage** WebFPage::pageContextPool{nullptr}; -WebFPage::WebFPage(int32_t contextId, const JSExceptionHandler& handler) +WebFPage::WebFPage(int32_t contextId, + const JSExceptionHandler& handler, + const uint64_t* dart_methods, + int32_t dart_methods_length) : contextId(contextId), ownerThreadId(std::this_thread::get_id()) { context_ = new ExecutingContext( contextId, @@ -33,7 +36,7 @@ WebFPage::WebFPage(int32_t contextId, const JSExceptionHandler& handler) } WEBF_LOG(ERROR) << message << std::endl; }, - this); + this, dart_methods, dart_methods_length); } bool WebFPage::parseHTML(const char* code, size_t length) { @@ -138,34 +141,6 @@ void WebFPage::evaluateByteCode(uint8_t* bytes, size_t byteLength) { context_->EvaluateByteCode(bytes, byteLength); } -void WebFPage::registerDartMethods(uint64_t* methodBytes, int32_t length) { - size_t i = 0; - - auto& dartMethodPointer = context_->dartMethodPtr(); - - dartMethodPointer->invokeModule = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->requestBatchUpdate = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->reloadApp = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->setTimeout = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->setInterval = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->clearTimeout = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->requestAnimationFrame = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->cancelAnimationFrame = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->toBlob = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->flushUICommand = reinterpret_cast(methodBytes[i++]); - -#if ENABLE_PROFILE - dartMethodPointer->getPerformanceEntries = reinterpret_cast(methodBytes[i++]); -#else - i++; -#endif - - dartMethodPointer->onJsError = reinterpret_cast(methodBytes[i++]); - dartMethodPointer->onJsLog = reinterpret_cast(methodBytes[i++]); - - assert_m(i == length, "Dart native methods count is not equal with C++ side method registrations."); -} - std::thread::id WebFPage::currentThread() const { return ownerThreadId; } diff --git a/bridge/core/page.h b/bridge/core/page.h index 94f97b2918..fb76541a3b 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -32,7 +32,7 @@ class WebFPage final { static webf::WebFPage** pageContextPool; static ConsoleMessageHandler consoleMessageHandler; WebFPage() = delete; - WebFPage(int32_t jsContext, const JSExceptionHandler& handler); + WebFPage(int32_t jsContext, const JSExceptionHandler& handler, const uint64_t* dart_methods, int32_t dart_methods_length); ~WebFPage(); // Bytecodes which registered by webf plugins. @@ -46,7 +46,6 @@ class WebFPage final { uint8_t* dumpByteCode(const char* script, size_t length, const char* url, size_t* byteLength); void evaluateByteCode(uint8_t* bytes, size_t byteLength); - void registerDartMethods(uint64_t* methodBytes, int32_t length); std::thread::id currentThread() const; [[nodiscard]] ExecutingContext* GetExecutingContext() const { return context_; } diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 630dd99ea2..e11af74027 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -16,7 +16,7 @@ UICommandBuffer::UICommandBuffer(ExecutingContext* context) : context_(context) UICommandBuffer::~UICommandBuffer() { #if FLUTTER_BACKEND // Flush and execute all disposeEventTarget commands when context released. - if (context_->dartMethodPtr()->flushUICommand != nullptr && !isDartHostRestart()) { + if (context_->dartMethodPtr()->flushUICommand != nullptr && !isDartHotRestart()) { context_->dartMethodPtr()->flushUICommand(context_->contextId()); } #endif diff --git a/bridge/foundation/ui_command_buffer.h b/bridge/foundation/ui_command_buffer.h index ef636d9d6b..8c03c70735 100644 --- a/bridge/foundation/ui_command_buffer.h +++ b/bridge/foundation/ui_command_buffer.h @@ -7,7 +7,6 @@ #define BRIDGE_FOUNDATION_UI_COMMAND_BUFFER_H_ #include -#include #include "bindings/qjs/native_string_utils.h" #include "native_value.h" @@ -34,7 +33,7 @@ enum class UICommand { kCreatePerformance, }; -#define MAXIMUM_UI_COMMAND_SIZE 2056 +#define MAXIMUM_UI_COMMAND_SIZE 2048 struct UICommandItem { UICommandItem() = default; @@ -54,8 +53,8 @@ struct UICommandItem { nativePtr(reinterpret_cast(nativePtr)){}; UICommandItem(int32_t id, int32_t type, void* nativePtr) : type(type), id(id), nativePtr(reinterpret_cast(nativePtr)){}; - int32_t type; - int32_t id; + int32_t type{0}; + int32_t id{0}; int32_t args_01_length{0}; int32_t args_02_length{0}; int64_t string_01{0}; @@ -63,7 +62,7 @@ struct UICommandItem { int64_t nativePtr{0}; }; -bool isDartHostRestart(); +bool isDartHotRestart(); class UICommandBuffer { public: @@ -88,7 +87,7 @@ class UICommandBuffer { ExecutingContext* context_{nullptr}; UICommandItem buffer_[MAXIMUM_UI_COMMAND_SIZE]; bool update_batched_{false}; - size_t size_{0}; + int64_t size_{0}; }; } // namespace webf diff --git a/bridge/include/webf_bridge.h b/bridge/include/webf_bridge.h index 50fa32cf39..dd355c5a7d 100644 --- a/bridge/include/webf_bridge.h +++ b/bridge/include/webf_bridge.h @@ -29,11 +29,11 @@ typedef void (*Task)(void*); typedef void (*ConsoleMessageHandler)(void* ctx, const std::string& message, int logLevel); WEBF_EXPORT_C -void initJSPagePool(int poolSize); +void initJSPagePool(int poolSize, uint64_t* dart_methods, int32_t dart_methods_len); WEBF_EXPORT_C void disposePage(int32_t contextId); WEBF_EXPORT_C -int32_t allocateNewPage(int32_t targetContextId); +int32_t allocateNewPage(int32_t targetContextId, uint64_t* dart_methods, int32_t dart_methods_len); WEBF_EXPORT_C void* getPage(int32_t contextId); bool checkPage(int32_t contextId); @@ -45,7 +45,7 @@ void evaluateQuickjsByteCode(int32_t contextId, uint8_t* bytes, int32_t byteLen) WEBF_EXPORT_C void parseHTML(int32_t contextId, const char* code, int32_t length); WEBF_EXPORT_C -void reloadJsContext(int32_t contextId); +void reloadJsContext(int32_t contextId, uint64_t* dart_methods, int32_t dart_methods_len); WEBF_EXPORT_C NativeValue* invokeModuleEvent(int32_t contextId, NativeString* module, @@ -53,8 +53,6 @@ NativeValue* invokeModuleEvent(int32_t contextId, void* event, NativeValue* extra); WEBF_EXPORT_C -void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length); -WEBF_EXPORT_C WebFInfo* getWebFInfo(); WEBF_EXPORT_C void dispatchUITask(int32_t contextId, void* context, void* callback); diff --git a/bridge/test/webf_test_env.cc b/bridge/test/webf_test_env.cc index 34e0a580e4..987c82d668 100644 --- a/bridge/test/webf_test_env.cc +++ b/bridge/test/webf_test_env.cc @@ -196,18 +196,17 @@ static int32_t inited{false}; std::unique_ptr TEST_init(OnJSError onJsError) { uint32_t contextId; + auto mockedDartMethods = TEST_getMockDartMethods(onJsError); if (inited) { - contextId = allocateNewPage(-1); + contextId = allocateNewPage(-1, mockedDartMethods.data(), mockedDartMethods.size()); } else { contextId = 0; } - std::call_once(testInitOnceFlag, []() { - initJSPagePool(1024 * 1024); + std::call_once(testInitOnceFlag, [&mockedDartMethods]() { + initJSPagePool(1024 * 1024, mockedDartMethods.data(), mockedDartMethods.size()); inited = true; }); - TEST_mockDartMethods(contextId, onJsError); - initTestFramework(contextId); TEST_mockTestEnvDartMethods(contextId, onJsError); auto* page = static_cast(getPage(contextId)); @@ -223,9 +222,8 @@ std::unique_ptr TEST_init() { } std::unique_ptr TEST_allocateNewPage(OnJSError onJsError) { - uint32_t newContextId = allocateNewPage(-1); - - TEST_mockDartMethods(newContextId, onJsError); + auto mockedDartMethods = TEST_getMockDartMethods(onJsError); + uint32_t newContextId = allocateNewPage(-1, mockedDartMethods.data(), mockedDartMethods.size()); initTestFramework(newContextId); return std::unique_ptr(static_cast(getPage(newContextId))); @@ -304,7 +302,7 @@ void TEST_simulatePointer(MousePointer*, int32_t length, int32_t pointer) {} void TEST_simulateInputText(NativeString* nativeString) {} -void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { +std::vector TEST_getMockDartMethods(OnJSError onJSError) { std::vector mockMethods{ reinterpret_cast(TEST_invokeModule), reinterpret_cast(TEST_requestBatchUpdate), @@ -326,7 +324,7 @@ void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError) { mockMethods.emplace_back(reinterpret_cast(onJSError)); mockMethods.emplace_back(reinterpret_cast(TEST_onJsLog)); - registerDartMethods(contextId, mockMethods.data(), mockMethods.size()); + return mockMethods; } void TEST_mockTestEnvDartMethods(int32_t contextId, OnJSError onJSError) { diff --git a/bridge/test/webf_test_env.h b/bridge/test/webf_test_env.h index 15fbe34576..eccd4f9dcd 100644 --- a/bridge/test/webf_test_env.h +++ b/bridge/test/webf_test_env.h @@ -28,7 +28,7 @@ std::unique_ptr TEST_init(OnJSError onJsError); std::unique_ptr TEST_init(); std::unique_ptr TEST_allocateNewPage(OnJSError onJsError); void TEST_runLoop(ExecutingContext* context); -void TEST_mockDartMethods(int32_t contextId, OnJSError onJSError); +std::vector TEST_getMockDartMethods(OnJSError onJSError); void TEST_mockTestEnvDartMethods(int32_t contextId, OnJSError onJSError); void TEST_registerEventTargetDisposedCallback(int32_t context_unique_id, TEST_OnEventTargetDisposed callback); std::shared_ptr TEST_getEnv(int32_t context_unique_id); diff --git a/bridge/webf_bridge.cc b/bridge/webf_bridge.cc index 42b0e19c0e..1736aeae5c 100644 --- a/bridge/webf_bridge.cc +++ b/bridge/webf_bridge.cc @@ -66,12 +66,12 @@ int32_t searchForAvailableContextId() { } // namespace namespace webf { -bool isDartHostRestart() { +bool isDartHotRestart() { return is_dart_hot_restart; } } // namespace webf -void initJSPagePool(int poolSize) { +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; @@ -83,7 +83,7 @@ void initJSPagePool(int poolSize) { webf::WebFPage::pageContextPool[i] = nullptr; } - webf::WebFPage::pageContextPool[0] = new webf::WebFPage(0, nullptr); + webf::WebFPage::pageContextPool[0] = new webf::WebFPage(0, nullptr, dart_methods, dart_methods_len); inited = true; maxPoolSize = poolSize; } @@ -98,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; } @@ -111,7 +111,7 @@ int32_t allocateNewPage(int32_t targetContextId) { (std::string("can not Allocate page at index") + std::to_string(targetContextId) + std::string(": page have already exist.")) .c_str()); - auto* page = new webf::WebFPage(targetContextId, nullptr); + auto* page = new webf::WebFPage(targetContextId, nullptr, dart_methods, dart_methods_len); webf::WebFPage::pageContextPool[targetContextId] = page; return targetContextId; } @@ -151,11 +151,11 @@ void parseHTML(int32_t contextId, const char* code, int32_t length) { context->parseHTML(code, length); } -void reloadJsContext(int32_t contextId) { +void reloadJsContext(int32_t contextId, uint64_t* dart_methods, int32_t dart_methods_len) { assert(checkPage(contextId) && "reloadJSContext: contextId is not valid"); auto bridgePtr = getPage(contextId); auto context = static_cast(bridgePtr); - auto newContext = new webf::WebFPage(contextId, nullptr); + auto newContext = new webf::WebFPage(contextId, nullptr, dart_methods, dart_methods_len); delete context; webf::WebFPage::pageContextPool[contextId] = newContext; } @@ -172,12 +172,6 @@ NativeValue* invokeModuleEvent(int32_t contextId, return reinterpret_cast(result); } -void registerDartMethods(int32_t contextId, uint64_t* methodBytes, int32_t length) { - assert(checkPage(contextId) && "registerDartMethods: contextId is not valid"); - auto context = static_cast(getPage(contextId)); - context->registerDartMethods(methodBytes, length); -} - static WebFInfo* webfInfo{nullptr}; WebFInfo* getWebFInfo() { diff --git a/webf/lib/src/bridge/bridge.dart b/webf/lib/src/bridge/bridge.dart index 245ab1e29c..868d06d777 100644 --- a/webf/lib/src/bridge/bridge.dart +++ b/webf/lib/src/bridge/bridge.dart @@ -32,19 +32,18 @@ int initBridge(WebFViewController view) { int contextId = -1; + List dartMethods = makeDartMethodsData(); + if (_firstView) { - initJSPagePool(kWebFJSPagePoolSize); + initJSPagePool(kWebFJSPagePoolSize, dartMethods); _firstView = false; contextId = 0; } else { - contextId = allocateNewPage(); + contextId = allocateNewPage(dartMethods); if (contextId == -1) { throw Exception('Can\' allocate new webf bridge: bridge count had reach the maximum size.'); } } - // Register methods first to share ptrs for bridge polyfill. - registerDartMethodsToCpp(contextId); - return contextId; } diff --git a/webf/lib/src/bridge/from_native.dart b/webf/lib/src/bridge/from_native.dart index e273372fb0..cd11a32275 100644 --- a/webf/lib/src/bridge/from_native.dart +++ b/webf/lib/src/bridge/from_native.dart @@ -4,7 +4,6 @@ */ import 'dart:async'; -import 'dart:convert'; import 'dart:ffi'; import 'dart:typed_data'; @@ -404,15 +403,6 @@ final List _dartNativeMethods = [ _nativeOnJsLog.address, ]; -typedef NativeRegisterDartMethods = Void Function(Int32 contextId, Pointer methodBytes, Int32 length); -typedef DartRegisterDartMethods = void Function(int contextId, Pointer methodBytes, int length); - -final DartRegisterDartMethods _registerDartMethods = - WebFDynamicLibrary.ref.lookup>('registerDartMethods').asFunction(); - -void registerDartMethodsToCpp(int contextId) { - Pointer bytes = malloc.allocate(sizeOf() * _dartNativeMethods.length); - Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); - nativeMethodList.setAll(0, _dartNativeMethods); - _registerDartMethods(contextId, bytes, _dartNativeMethods.length); +List makeDartMethodsData() { + return _dartNativeMethods; } diff --git a/webf/lib/src/bridge/to_native.dart b/webf/lib/src/bridge/to_native.dart index f6c4afd579..600653d1af 100644 --- a/webf/lib/src/bridge/to_native.dart +++ b/webf/lib/src/bridge/to_native.dart @@ -172,14 +172,18 @@ void parseHTML(int contextId, String code) { } // Register initJsEngine -typedef NativeInitJSPagePool = Void Function(Int32 poolSize); -typedef DartInitJSPagePool = void Function(int poolSize); +typedef NativeInitJSPagePool = Void Function(Int32 poolSize, Pointer dartMethods, Int32 methodsLength); +typedef DartInitJSPagePool = void Function(int poolSize, Pointer dartMethods, int length); final DartInitJSPagePool _initJSPagePool = WebFDynamicLibrary.ref.lookup>('initJSPagePool').asFunction(); -void initJSPagePool(int poolSize) { - _initJSPagePool(poolSize); +void initJSPagePool(int poolSize, List dartMethods) { + Pointer bytes = malloc.allocate(sizeOf() * dartMethods.length); + Uint64List nativeMethodList = bytes.asTypedList(dartMethods.length); + nativeMethodList.setAll(0, dartMethods); + + _initJSPagePool(poolSize, bytes, dartMethods.length); } typedef NativeDisposePage = Void Function(Int32 contextId); @@ -192,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); @@ -227,16 +235,19 @@ 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; @@ -456,7 +467,7 @@ void flushUICommand(WebFViewController view) { view.createComment(id, nativePtr.cast()); break; case UICommandType.disposeEventTarget: - view.disposeEventTarget(id); + view.disposeEventTarget(id, nativePtr.cast()); break; case UICommandType.addEvent: view.addEvent(id, command.args[0]); From ea22a72e8d76775ecbb998452c19911294eedcfb Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 28 Sep 2022 01:05:41 +0800 Subject: [PATCH 458/498] fix: fix linux build. --- bridge/core/dart_methods.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/bridge/core/dart_methods.cc b/bridge/core/dart_methods.cc index 71642c6954..e0ea646070 100644 --- a/bridge/core/dart_methods.cc +++ b/bridge/core/dart_methods.cc @@ -3,6 +3,7 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ +#include #include "dart_methods.h" namespace webf { From 99c59fbbfb2aae296a996d8b162344b98ffa7e35 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Tue, 27 Sep 2022 17:06:41 +0000 Subject: [PATCH 459/498] Committing clang-format changes --- bridge/core/dart_methods.cc | 2 +- bridge/core/dom/events/event_target.cc | 3 ++- bridge/core/page.h | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bridge/core/dart_methods.cc b/bridge/core/dart_methods.cc index e0ea646070..ce348d5dcb 100644 --- a/bridge/core/dart_methods.cc +++ b/bridge/core/dart_methods.cc @@ -3,8 +3,8 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -#include #include "dart_methods.h" +#include namespace webf { diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 21ce9e81f4..3bdbab308c 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -55,7 +55,8 @@ EventTarget::~EventTarget() { } #endif - GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, bindingObject()); + GetExecutingContext()->uiCommandBuffer()->addCommand(eventTargetId(), UICommand::kDisposeEventTarget, + bindingObject()); } EventTarget::EventTarget(ExecutingContext* context) diff --git a/bridge/core/page.h b/bridge/core/page.h index fb76541a3b..671cfd711a 100644 --- a/bridge/core/page.h +++ b/bridge/core/page.h @@ -32,7 +32,10 @@ class WebFPage final { static webf::WebFPage** pageContextPool; static ConsoleMessageHandler consoleMessageHandler; WebFPage() = delete; - WebFPage(int32_t jsContext, const JSExceptionHandler& handler, const uint64_t* dart_methods, int32_t dart_methods_length); + WebFPage(int32_t jsContext, + const JSExceptionHandler& handler, + const uint64_t* dart_methods, + int32_t dart_methods_length); ~WebFPage(); // Bytecodes which registered by webf plugins. From 9d28b6b8084c9bc19408c715e3cd68736f7323be Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 28 Sep 2022 01:18:41 +0800 Subject: [PATCH 460/498] chore: remove unused code. --- webf/lib/src/devtools/server.dart | 5 ---- webf/lib/src/devtools/service.dart | 31 +++++-------------------- webf/lib/src/module/method_channel.dart | 1 - webf/lib/src/module/module_manager.dart | 3 --- 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/webf/lib/src/devtools/server.dart b/webf/lib/src/devtools/server.dart index 5822429cd3..53b6d2fe0c 100644 --- a/webf/lib/src/devtools/server.dart +++ b/webf/lib/src/devtools/server.dart @@ -71,9 +71,6 @@ void attachInspector(int contextId) { } void initInspectorServerNativeBinding(int contextId) { - final DartRegisterDartMethods _registerInspectorServerDartMethods = WebFDynamicLibrary.ref - .lookup>('registerInspectorDartMethods') - .asFunction(); final Pointer> _nativeInspectorMessage = Pointer.fromFunction(_onInspectorMessage); final Pointer> _nativeRegisterInspectorMessageCallback = @@ -90,8 +87,6 @@ void initInspectorServerNativeBinding(int contextId) { Pointer bytes = malloc.allocate(_dartNativeMethods.length * sizeOf()); Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); nativeMethodList.setAll(0, _dartNativeMethods); - - _registerInspectorServerDartMethods(contextId, bytes, _dartNativeMethods.length); } void serverIsolateEntryPoint(SendPort isolateToMainStream) { diff --git a/webf/lib/src/devtools/service.dart b/webf/lib/src/devtools/service.dart index 0f0d71edb0..5a5bd339ce 100644 --- a/webf/lib/src/devtools/service.dart +++ b/webf/lib/src/devtools/service.dart @@ -5,26 +5,18 @@ import 'dart:isolate'; import 'dart:ffi'; -import 'dart:typed_data'; - -import 'package:ffi/ffi.dart'; import 'package:webf/webf.dart'; import 'package:webf/devtools.dart'; typedef NativePostTaskToInspectorThread = Void Function(Int32 contextId, Pointer context, Pointer callback); typedef DartPostTaskToInspectorThread = void Function(int contextId, Pointer context, Pointer callback); -void _postTaskToInspectorThread(int contextId, Pointer context, Pointer callback) { - ChromeDevToolsService? devTool = ChromeDevToolsService.getDevToolOfContextId(contextId); - if (devTool != null) { - devTool.isolateServerPort!.send(InspectorPostTaskMessage(context.address, callback.address)); - } -} - -final Pointer> _nativePostTaskToInspectorThread = - Pointer.fromFunction(_postTaskToInspectorThread); - -final List _dartNativeMethods = [_nativePostTaskToInspectorThread.address]; +// void _postTaskToInspectorThread(int contextId, Pointer context, Pointer callback) { +// ChromeDevToolsService? devTool = ChromeDevToolsService.getDevToolOfContextId(contextId); +// if (devTool != null) { +// devTool.isolateServerPort!.send(InspectorPostTaskMessage(context.address, callback.address)); +// } +// } void spawnIsolateInspectorServer(ChromeDevToolsService devTool, WebFController controller, {int port = INSPECTOR_DEFAULT_PORT, String? address}) { @@ -113,15 +105,4 @@ class ChromeDevToolsService extends DevToolsService { controller!.view.debugDOMTreeChanged = _uiInspector!.onDOMTreeChanged; _isolateServerPort!.send(InspectorReload(_controller!.view.contextId)); } - - // @TODO: Implement and remove. - // ignore: unused_element - static bool _registerUIDartMethodsToCpp(int contextId) { - final DartRegisterDartMethods _registerDartMethods = WebFDynamicLibrary.ref.lookup>('registerUIDartMethods').asFunction(); - Pointer bytes = malloc.allocate(_dartNativeMethods.length * sizeOf()); - Uint64List nativeMethodList = bytes.asTypedList(_dartNativeMethods.length); - nativeMethodList.setAll(0, _dartNativeMethods); - _registerDartMethods(contextId, bytes, _dartNativeMethods.length); - return true; - } } diff --git a/webf/lib/src/module/method_channel.dart b/webf/lib/src/module/method_channel.dart index 2fa06debc4..4af794e326 100644 --- a/webf/lib/src/module/method_channel.dart +++ b/webf/lib/src/module/method_channel.dart @@ -5,7 +5,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:webf/webf.dart'; typedef MethodCallCallback = Future Function(String method, Object? arguments); diff --git a/webf/lib/src/module/module_manager.dart b/webf/lib/src/module/module_manager.dart index 12b8f6fa67..707f9a2c80 100644 --- a/webf/lib/src/module/module_manager.dart +++ b/webf/lib/src/module/module_manager.dart @@ -2,9 +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:meta/meta.dart'; import 'package:webf/bridge.dart' as bridge; import 'package:webf/dom.dart'; import 'package:webf/webf.dart'; From 53513bef1f74b01eaf9541555c410ef8b062ad65 Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 28 Sep 2022 11:40:12 +0800 Subject: [PATCH 461/498] fix: fix dart allocated binding object not initialized. --- bridge/core/binding_object.cc | 2 ++ bridge/core/binding_object.h | 1 + webf/lib/src/bridge/binding.dart | 2 +- webf/lib/src/bridge/native_types.dart | 8 ++++++++ webf/lib/src/bridge/to_native.dart | 2 +- webf/lib/src/dom/bounding_client_rect.dart | 3 +-- webf/lib/src/dom/elements/canvas/canvas_context_2d.dart | 3 +-- webf/lib/src/dom/screen.dart | 4 ++-- webf/lib/src/dom/window.dart | 9 +++++---- 9 files changed, 22 insertions(+), 12 deletions(-) diff --git a/bridge/core/binding_object.cc b/bridge/core/binding_object.cc index 41a374d35c..b5d1da1ae1 100644 --- a/bridge/core/binding_object.cc +++ b/bridge/core/binding_object.cc @@ -26,8 +26,10 @@ void NativeBindingObject::HandleCallFromDartSide(NativeBindingObject* binding_ob 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) diff --git a/bridge/core/binding_object.h b/bridge/core/binding_object.h index 2e7449c253..a32f2f7f02 100644 --- a/bridge/core/binding_object.h +++ b/bridge/core/binding_object.h @@ -43,6 +43,7 @@ struct NativeBindingObject : public DartReadable { int32_t argc, NativeValue* argv); + bool disposed_{false}; BindingObject* binding_target_{nullptr}; InvokeBindingMethodsFromDart invoke_binding_methods_from_dart{nullptr}; InvokeBindingsMethodsFromNative invoke_bindings_methods_from_native{nullptr}; diff --git a/webf/lib/src/bridge/binding.dart b/webf/lib/src/bridge/binding.dart index 1a86d61dfa..98893a9ffc 100644 --- a/webf/lib/src/bridge/binding.dart +++ b/webf/lib/src/bridge/binding.dart @@ -208,7 +208,7 @@ abstract class BindingBridge { static void _bindObject(BindingObject object) { Pointer? nativeBindingObject = object.pointer; - if (nativeBindingObject != null && nativeBindingObject.ref.instance != nullptr) { + if (nativeBindingObject != null && !nativeBindingObject.ref.disposed) { _nativeObjects[nativeBindingObject.address] = object; nativeBindingObject.ref.invokeBindingMethodFromNative = _invokeBindingMethodFromNative; } diff --git a/webf/lib/src/bridge/native_types.dart b/webf/lib/src/bridge/native_types.dart index 5df8d86212..6daeda92b4 100644 --- a/webf/lib/src/bridge/native_types.dart +++ b/webf/lib/src/bridge/native_types.dart @@ -99,12 +99,20 @@ typedef DartInvokeBindingMethodsFromDart = void Function(Pointer return_value, Pointer method, int argc, Pointer argv); class NativeBindingObject extends Struct { + @Bool() + external bool disposed; external Pointer instance; external Pointer> invokeBindingMethodFromDart; // Shared method called by JS side. external Pointer> invokeBindingMethodFromNative; } +Pointer allocateNewBindingObject() { + Pointer pointer = malloc.allocate(sizeOf()); + pointer.ref.disposed = false; + return pointer; +} + class NativePerformanceEntry extends Struct { external Pointer name; external Pointer entryType; diff --git a/webf/lib/src/bridge/to_native.dart b/webf/lib/src/bridge/to_native.dart index 600653d1af..3b4b0340d9 100644 --- a/webf/lib/src/bridge/to_native.dart +++ b/webf/lib/src/bridge/to_native.dart @@ -342,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, diff --git a/webf/lib/src/dom/bounding_client_rect.dart b/webf/lib/src/dom/bounding_client_rect.dart index ac9406dce7..cce811598c 100644 --- a/webf/lib/src/dom/bounding_client_rect.dart +++ b/webf/lib/src/dom/bounding_client_rect.dart @@ -4,7 +4,6 @@ */ import 'dart:ffi'; -import 'package:ffi/ffi.dart'; import 'package:webf/bridge.dart'; import 'package:webf/foundation.dart'; @@ -21,7 +20,7 @@ class BoundingClientRect extends BindingObject { final double left; BoundingClientRect(this.x, this.y, this.width, this.height, this.top, this.right, this.bottom, this.left) - : _pointer = malloc.allocate(sizeOf()), + : _pointer = allocateNewBindingObject(), super(); final Pointer _pointer; diff --git a/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart b/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart index e891a37ed3..ffa0bb2aae 100644 --- a/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart +++ b/webf/lib/src/dom/elements/canvas/canvas_context_2d.dart @@ -7,7 +7,6 @@ import 'dart:typed_data'; import 'dart:ui'; import 'dart:ffi' as ffi; -import 'package:ffi/ffi.dart'; import 'package:flutter/painting.dart'; import 'package:webf/bridge.dart'; import 'package:webf/foundation.dart'; @@ -45,7 +44,7 @@ typedef CanvasAction = void Function(Canvas, Size); class CanvasRenderingContext2D extends BindingObject { CanvasRenderingContext2D(this.canvas) - : _pointer = malloc.allocate(ffi.sizeOf()), + : _pointer = allocateNewBindingObject(), super(); final ffi.Pointer _pointer; diff --git a/webf/lib/src/dom/screen.dart b/webf/lib/src/dom/screen.dart index 80a639fd33..076d5f57d2 100644 --- a/webf/lib/src/dom/screen.dart +++ b/webf/lib/src/dom/screen.dart @@ -3,13 +3,13 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ import 'dart:ui'; - import 'package:webf/foundation.dart'; +import 'package:webf/bridge.dart'; // As its name suggests, the Screen interface represents information about the screen of the output device. // https://drafts.csswg.org/cssom-view/#the-screen-interface class Screen extends BindingObject { - Screen([BindingContext? context]) : super(context); + Screen(int contextId) : super(BindingContext(contextId, allocateNewBindingObject())); @override getBindingProperty(String key) { diff --git a/webf/lib/src/dom/window.dart b/webf/lib/src/dom/window.dart index e19ff8d726..aa3dd2e943 100644 --- a/webf/lib/src/dom/window.dart +++ b/webf/lib/src/dom/window.dart @@ -3,8 +3,6 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ import 'dart:ui'; -import 'dart:ffi' as ffi; -import 'package:ffi/ffi.dart'; import 'package:webf/bridge.dart'; import 'package:webf/dom.dart'; @@ -19,7 +17,8 @@ class Window extends EventTarget { final Screen screen; Window(BindingContext? context, this.document) - : screen = Screen(BindingContext(context!.contextId, malloc.allocate(ffi.sizeOf()))), super(context); + : screen = Screen(context!.contextId), + super(context); @override EventTarget? get parentEventTarget => null; @@ -108,7 +107,9 @@ class Window extends EventTarget { @override void dispatchEvent(Event event) { // Events such as EVENT_DOM_CONTENT_LOADED need to ensure that listeners are flushed and registered. - if (contextId != null && event.type == EVENT_DOM_CONTENT_LOADED || event.type == EVENT_LOAD || event.type == EVENT_ERROR) { + if (contextId != null && event.type == EVENT_DOM_CONTENT_LOADED || + event.type == EVENT_LOAD || + event.type == EVENT_ERROR) { flushUICommandWithContextId(contextId!); } super.dispatchEvent(event); From f7d70b6fa1ed078bf36f267ae315a00a9be25770 Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 28 Sep 2022 11:46:48 +0800 Subject: [PATCH 462/498] fix: fix android 32 bit build. --- bridge/core/events/message_event.cc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index f9945cc030..1a8d102951 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -40,12 +40,18 @@ MessageEvent::MessageEvent(ExecutingContext* context, : Event(context, type, &native_message_event->native_event), #if ANDROID_32_BIT data_(ScriptValue(ctx(), *(reinterpret_cast(native_message_event->data)))), + origin_(AtomicString(ctx(), reinterpret_cast(native_message_event->origin))), + lastEventId_(AtomicString(ctx(), reinterpret_cast(native_message_event->lastEventId))), + source_(AtomicString(ctx(), reinterpret_cast(native_message_event->source))) #else data_(ScriptValue(ctx(), *(static_cast(native_message_event->data)))), -#endif origin_(AtomicString(ctx(), native_message_event->origin)), lastEventId_(AtomicString(ctx(), native_message_event->lastEventId)), - source_(AtomicString(ctx(), native_message_event->source)) { + source_(AtomicString(ctx(), native_message_event->source)) +#endif + + +{ } ScriptValue MessageEvent::data() const { From 410f5146c33e15b9298cb1ca680e8dff9109cd07 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Wed, 28 Sep 2022 03:47:34 +0000 Subject: [PATCH 463/498] Committing clang-format changes --- bridge/core/events/message_event.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/bridge/core/events/message_event.cc b/bridge/core/events/message_event.cc index 1a8d102951..4fc16d6e6f 100644 --- a/bridge/core/events/message_event.cc +++ b/bridge/core/events/message_event.cc @@ -50,7 +50,6 @@ MessageEvent::MessageEvent(ExecutingContext* context, source_(AtomicString(ctx(), native_message_event->source)) #endif - { } From 2da12866ddcab279b8737bc021de9612129d8f67 Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 28 Sep 2022 20:07:22 +0800 Subject: [PATCH 464/498] fix: fix set event handler property to custom element. --- bridge/bindings/qjs/script_wrappable.cc | 35 +++++++++++++++++++ .../core/html/custom/widget_element_test.cc | 31 ++++++++++++++++ bridge/test/test.cmake | 1 + 3 files changed, 67 insertions(+) create mode 100644 bridge/core/html/custom/widget_element_test.cc diff --git a/bridge/bindings/qjs/script_wrappable.cc b/bridge/bindings/qjs/script_wrappable.cc index c59041e998..d624314938 100644 --- a/bridge/bindings/qjs/script_wrappable.cc +++ b/bridge/bindings/qjs/script_wrappable.cc @@ -75,6 +75,41 @@ static int HandleJSPropertySetterCallback(JSContext* ctx, auto* object = static_cast(JS_GetOpaque(obj, JSValueGetClassId(obj))); auto* wrapper_type_info = object->GetWrapperTypeInfo(); + ExecutingContext* context = ExecutingContext::From(ctx); + JSValue prototypeObject = context->contextData()->prototypeForType(wrapper_type_info); + if (JS_HasProperty(ctx, prototypeObject, atom)) { + JSValue target = JS_DupValue(ctx, prototypeObject); + JSValue setterFunc = JS_UNDEFINED; + while (JS_IsUndefined(setterFunc)) { + JSPropertyDescriptor descriptor; + descriptor.setter = JS_UNDEFINED; + descriptor.getter = JS_UNDEFINED; + descriptor.value = JS_UNDEFINED; + JS_GetOwnProperty(ctx, &descriptor, target, atom); + setterFunc = descriptor.setter; + if (JS_IsFunction(ctx, setterFunc)) { + JS_FreeValue(ctx, descriptor.getter); + break; + } + + JSValue new_target = JS_GetPrototype(ctx, target); + JS_FreeValue(ctx, target); + target = new_target; + JS_FreeValue(ctx, descriptor.getter); + JS_FreeValue(ctx, descriptor.setter); + } + + assert_m(JS_IsFunction(ctx, setterFunc), "Setter on prototype should be an function."); + JSValue ret = JS_Call(ctx, setterFunc, obj, 1, &value); + if (JS_IsException(ret)) + return -1; + + JS_FreeValue(ctx, ret); + JS_FreeValue(ctx, setterFunc); + JS_FreeValue(ctx, target); + return 0; + } + if (wrapper_type_info->indexed_property_setter_handler_ != nullptr && JS_AtomIsTaggedInt(atom)) { return wrapper_type_info->indexed_property_setter_handler_(ctx, obj, JS_AtomToUInt32(atom), value); } else if (wrapper_type_info->string_property_setter_handler_ != nullptr) { 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..5625b82a96 --- /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/test/test.cmake b/bridge/test/test.cmake index 6df6967ca6..972acc4c39 100644 --- a/bridge/test/test.cmake +++ b/bridge/test/test.cmake @@ -33,6 +33,7 @@ list(APPEND WEBF_UNIT_TEST_SOURCEURCE ./core/frame/window_test.cc ./core/css/legacy/css_style_declaration_test.cc ./core/html/html_element_test.cc + ./core/html/custom/widget_element_test.cc ./core/timing/performance_test.cc ) From 9920cb9a47f5871548a27e81f3e1a3cff90ad28b Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Wed, 28 Sep 2022 12:08:21 +0000 Subject: [PATCH 465/498] Committing clang-format changes --- .../core/html/custom/widget_element_test.cc | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/bridge/core/html/custom/widget_element_test.cc b/bridge/core/html/custom/widget_element_test.cc index 5625b82a96..5f820e7816 100644 --- a/bridge/core/html/custom/widget_element_test.cc +++ b/bridge/core/html/custom/widget_element_test.cc @@ -1,6 +1,6 @@ /* -* Copyright (C) 2022-present The WebF authors. All rights reserved. -*/ + * Copyright (C) 2022-present The WebF authors. All rights reserved. + */ #include "widget_element.h" #include "gtest/gtest.h" @@ -9,23 +9,23 @@ 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); + 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); + EXPECT_EQ(errorCalled, false); } From e4772f02148dfe712deeb78b809862094f6a0490 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 30 Sep 2022 15:03:48 +0800 Subject: [PATCH 466/498] feat: ignore undefined property when set attribute. --- bridge/CMakeLists.txt | 2 ++ bridge/bindings/qjs/cppgc/mutation_scope.cc | 1 + bridge/bindings/qjs/js_based_event_listener.h | 1 + bridge/bindings/qjs/rejected_promises.cc | 1 + bridge/bindings/qjs/script_wrappable.h | 1 + bridge/core/binding_call_methods.json5 | 3 +- bridge/core/defined_properties.json5 | 13 +++++++ .../core/defined_properties_initializer.json5 | 13 +++++++ bridge/core/dom/character_data.cc | 5 +++ bridge/core/dom/character_data.h | 2 ++ bridge/core/dom/document.cc | 5 +++ bridge/core/dom/document.h | 2 ++ bridge/core/dom/element.cc | 9 +++-- bridge/core/dom/element.d.ts | 1 + bridge/core/dom/element.h | 1 + bridge/core/dom/element_data.h | 3 ++ bridge/core/dom/events/event_target.cc | 6 +++- bridge/core/dom/events/event_target.h | 3 ++ bridge/core/dom/legacy/element_attributes.cc | 35 +++++++++++++------ .../core/dom/legacy/element_attributes.d.ts | 1 + bridge/core/dom/legacy/element_attributes.h | 5 +-- bridge/core/dom/node.cc | 5 +++ bridge/core/dom/node.h | 2 ++ bridge/core/events/pop_state_event.cc | 1 + bridge/core/events/wheel_event.d.ts | 13 ------- bridge/core/events/wheel_event_init.d.ts | 10 ------ bridge/core/executing_context.h | 1 + .../core/html/canvas/html_canvas_element.cc | 5 +++ bridge/core/html/canvas/html_canvas_element.h | 2 ++ bridge/core/html/custom/widget_element.cc | 4 +++ bridge/core/html/custom/widget_element.h | 1 + bridge/core/html/forms/html_button_element.cc | 9 +++++ bridge/core/html/forms/html_button_element.h | 3 ++ bridge/core/html/forms/html_input_element.cc | 5 +++ bridge/core/html/forms/html_input_element.h | 2 ++ .../core/html/forms/html_textarea_element.cc | 5 +++ .../html/forms/html_textarea_element.d.ts | 2 +- .../core/html/forms/html_textarea_element.h | 2 ++ bridge/core/html/html_anchor_element.cc | 5 +++ bridge/core/html/html_anchor_element.d.ts | 2 +- bridge/core/html/html_anchor_element.h | 2 ++ bridge/core/html/html_body_element.cc | 5 +++ bridge/core/html/html_body_element.h | 2 ++ bridge/core/html/html_element.cc | 9 ++++- bridge/core/html/html_element.h | 2 ++ bridge/core/html/html_image_element.cc | 5 +++ bridge/core/html/html_image_element.h | 2 ++ bridge/core/html/html_script_element.cc | 5 +++ bridge/core/html/html_script_element.h | 2 ++ bridge/core/html/html_template_element.cc | 5 +++ bridge/core/html/html_template_element.h | 2 ++ bridge/core/script_state.cc | 7 ++-- bridge/core/script_state.h | 2 +- bridge/foundation/ui_command_buffer.cc | 1 + .../code_generator/bin/code_generator.js | 25 +++++++++++-- .../code_generator/src/idl/analyzer.ts | 22 +++++++++--- .../templates/idl_templates/base.cc.tpl | 1 + .../templates/idl_templates/interface.cc.tpl | 25 +++++++++++++ .../templates/idl_templates/interface.h.tpl | 6 ++++ .../defined_properties_initializer.cc.tpl | 27 ++++++++++++++ .../defined_properties_initializer.h.tpl | 22 ++++++++++++ .../json_templates/element_factory.cc.tpl | 2 +- .../json_templates/event_factory.cc.tpl | 2 +- bridge/test/webf_test_env.h | 1 + 64 files changed, 323 insertions(+), 53 deletions(-) create mode 100644 bridge/core/defined_properties.json5 create mode 100644 bridge/core/defined_properties_initializer.json5 delete mode 100644 bridge/core/events/wheel_event.d.ts delete mode 100644 bridge/core/events/wheel_event_init.d.ts create mode 100644 bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.cc.tpl create mode 100644 bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.h.tpl diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 5952bc2890..0e6c485df0 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -392,6 +392,8 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/html_element_factory.cc out/html_names.cc out/script_type_names.cc + out/defined_properties.cc + out/defined_properties_initializer.cc out/element_attribute_names.cc ) diff --git a/bridge/bindings/qjs/cppgc/mutation_scope.cc b/bridge/bindings/qjs/cppgc/mutation_scope.cc index 0e6678df88..5be04f98ff 100644 --- a/bridge/bindings/qjs/cppgc/mutation_scope.cc +++ b/bridge/bindings/qjs/cppgc/mutation_scope.cc @@ -4,6 +4,7 @@ */ #include "mutation_scope.h" +#include "bindings/qjs/script_wrappable.h" #include "core/executing_context.h" namespace webf { diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index a2d7128172..dda4ff14bc 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -7,6 +7,7 @@ #define BRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ #include +#include "foundation/casting.h" #include "core/dom/events/event_listener.h" #include "core/executing_context.h" diff --git a/bridge/bindings/qjs/rejected_promises.cc b/bridge/bindings/qjs/rejected_promises.cc index 37b438bfa8..5e3b3b5bfe 100644 --- a/bridge/bindings/qjs/rejected_promises.cc +++ b/bridge/bindings/qjs/rejected_promises.cc @@ -4,6 +4,7 @@ */ #include "rejected_promises.h" +#include "bindings/qjs/cppgc/mutation_scope.h" #include "core/executing_context.h" namespace webf { diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index fd7a3e377c..03431c3dad 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -10,6 +10,7 @@ #include "bindings/qjs/cppgc/garbage_collected.h" #include "foundation/macros.h" #include "wrapper_type_info.h" +#include "core/executing_context.h" namespace webf { diff --git a/bridge/core/binding_call_methods.json5 b/bridge/core/binding_call_methods.json5 index 61d6b6ab08..bdf23811ea 100644 --- a/bridge/core/binding_call_methods.json5 +++ b/bridge/core/binding_call_methods.json5 @@ -151,6 +151,7 @@ "getElementsByTagName", "id", "className", - "cookie" + "cookie", + "class" ] } diff --git a/bridge/core/defined_properties.json5 b/bridge/core/defined_properties.json5 new file mode 100644 index 0000000000..de4c0d5e70 --- /dev/null +++ b/bridge/core/defined_properties.json5 @@ -0,0 +1,13 @@ +{ + "metadata": { + "templates": [ + { + "template": "make_names", + "filename": "defined_properties" + } + ] + }, + "data": [ + // The data of defined_properties.json5 will be injected by code_generator scripts. + ] +} diff --git a/bridge/core/defined_properties_initializer.json5 b/bridge/core/defined_properties_initializer.json5 new file mode 100644 index 0000000000..47e37c0d5a --- /dev/null +++ b/bridge/core/defined_properties_initializer.json5 @@ -0,0 +1,13 @@ +{ + "metadata": { + "templates": [ + { + "template": "defined_properties_initializer", + "filename": "defined_properties_initializer" + } + ] + }, + "data": [ + // The data of defined_properties_initializer.json5 will be injected by code_generator scripts. + ] +} diff --git a/bridge/core/dom/character_data.cc b/bridge/core/dom/character_data.cc index 4b134e0f45..88dc9483f7 100644 --- a/bridge/core/dom/character_data.cc +++ b/bridge/core/dom/character_data.cc @@ -6,6 +6,7 @@ #include "character_data.h" #include "built_in_string.h" #include "core/dom/document.h" +#include "qjs_character_data.h" namespace webf { @@ -31,6 +32,10 @@ void CharacterData::setNodeValue(const AtomicString& value, ExceptionState& exce setData(!value.IsEmpty() ? value : built_in_string::kempty_string, exception_state); } +bool CharacterData::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSCharacterData::IsAttributeDefinedInternal(key) || Node::IsAttributeDefinedInternal(key); +} + CharacterData::CharacterData(TreeScope& tree_scope, const AtomicString& text, Node::ConstructionType type) : Node(tree_scope.GetDocument().GetExecutingContext(), &tree_scope, type), data_(text) { assert(type == kCreateOther || type == kCreateText); diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index d6ae0ef3f2..30658bb613 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -24,6 +24,8 @@ class CharacterData : public Node { bool IsCharacterDataNode() const override; void setNodeValue(const AtomicString&, ExceptionState&) override; + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + protected: CharacterData(TreeScope& tree_scope, const AtomicString& text, ConstructionType type); diff --git a/bridge/core/dom/document.cc b/bridge/core/dom/document.cc index cf345b173c..f5d67e1905 100644 --- a/bridge/core/dom/document.cc +++ b/bridge/core/dom/document.cc @@ -23,6 +23,7 @@ #include "foundation/ascii_types.h" #include "foundation/native_value_converter.h" #include "html_element_factory.h" +#include "qjs_document.h" namespace webf { @@ -310,6 +311,10 @@ std::shared_ptr Document::GetWindowAttributeEventListener(const A return window->GetAttributeEventListener(event_type); } +bool Document::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSDocument::IsAttributeDefinedInternal(key) || Node::IsAttributeDefinedInternal(key); +} + void Document::Trace(GCVisitor* visitor) const { script_animation_controller_.Trace(visitor); ContainerNode::Trace(visitor); diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index a2270a59e1..6efa5baf1a 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -99,6 +99,8 @@ class Document : public ContainerNode, public TreeScope { ExceptionState& exception_state); std::shared_ptr GetWindowAttributeEventListener(const AtomicString& event_type); + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + void Trace(GCVisitor* visitor) const override; private: diff --git a/bridge/core/dom/element.cc b/bridge/core/dom/element.cc index 450dd55199..bfba99b8f7 100644 --- a/bridge/core/dom/element.cc +++ b/bridge/core/dom/element.cc @@ -15,6 +15,7 @@ #include "element_attribute_names.h" #include "foundation/native_value_converter.h" #include "html_element_type_helper.h" +#include "qjs_element.h" #include "text.h" namespace webf { @@ -37,7 +38,7 @@ bool Element::hasAttribute(const AtomicString& name, ExceptionState& exception_s } AtomicString Element::getAttribute(const AtomicString& name, ExceptionState& exception_state) { - return EnsureElementAttributes().GetAttribute(name); + return EnsureElementAttributes().getAttribute(name, exception_state); } void Element::setAttribute(const AtomicString& name, const AtomicString& value) { @@ -47,7 +48,7 @@ void Element::setAttribute(const AtomicString& name, const AtomicString& value) void Element::setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state) { if (EnsureElementAttributes().hasAttribute(name, exception_state)) { - AtomicString&& oldAttribute = EnsureElementAttributes().GetAttribute(name); + AtomicString&& oldAttribute = EnsureElementAttributes().getAttribute(name, exception_state); if (!EnsureElementAttributes().setAttribute(name, value, exception_state)) { return; }; @@ -220,6 +221,10 @@ bool Element::IsWidgetElement() const { return false; } +bool Element::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSElement::IsAttributeDefinedInternal(key) || Node::IsAttributeDefinedInternal(key); +} + void Element::Trace(GCVisitor* visitor) const { visitor->Trace(attributes_); visitor->Trace(cssom_wrapper_); diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index 6432383501..e2eb979809 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -8,6 +8,7 @@ import {ParentNode} from "./parent_node"; interface Element extends Node, ParentNode { id: DartImpl; className: DartImpl; + class: DartImpl; readonly attributes: ElementAttributes; readonly style: CSSStyleDeclaration; readonly clientHeight: DartImpl; diff --git a/bridge/core/dom/element.h b/bridge/core/dom/element.h index 38e5e436bb..d34dbb8b6b 100644 --- a/bridge/core/dom/element.h +++ b/bridge/core/dom/element.h @@ -80,6 +80,7 @@ class Element : public ContainerNode { virtual void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) {} virtual bool IsWidgetElement() const; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; void Trace(GCVisitor* visitor) const override; protected: diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/element_data.h index 326fd93f41..90f41b5502 100644 --- a/bridge/core/dom/element_data.h +++ b/bridge/core/dom/element_data.h @@ -14,7 +14,10 @@ class ElementData { public: void CopyWith(ElementData* other); + + private: + AtomicString class_; }; } // namespace webf diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index 3bdbab308c..fc2087e474 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -6,7 +6,7 @@ #include #include "binding_call_methods.h" #include "bindings/qjs/converter_impl.h" -#include "custom_event.h" +#include "qjs_event_target.h" #include "event_factory.h" #include "native_value_converter.h" #include "qjs_add_event_listener_options.h" @@ -196,6 +196,10 @@ bool EventTarget::IsEventTarget() const { return true; } +bool EventTarget::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSEventTarget::IsAttributeDefinedInternal(key); +} + void EventTarget::Trace(GCVisitor* visitor) const { ScriptWrappable::Trace(visitor); BindingObject::Trace(visitor); diff --git a/bridge/core/dom/events/event_target.h b/bridge/core/dom/events/event_target.h index e4dd34875f..4ff238265f 100644 --- a/bridge/core/dom/events/event_target.h +++ b/bridge/core/dom/events/event_target.h @@ -132,6 +132,9 @@ class EventTarget : public ScriptWrappable, public BindingObject { virtual bool IsNode() const { return false; } bool IsEventTarget() const override; + // Check the attribute is defined in native. + virtual bool IsAttributeDefinedInternal(const AtomicString& key) const; + void Trace(GCVisitor* visitor) const override; protected: diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 3a77d92e86..4d2136b627 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -7,6 +7,7 @@ #include "bindings/qjs/exception_state.h" #include "built_in_string.h" #include "core/dom/element.h" +#include "foundation/native_value_converter.h" namespace webf { @@ -17,17 +18,27 @@ static inline bool IsNumberIndex(const StringView& name) { return f >= '0' && f <= '9'; } -ElementAttributes::ElementAttributes(Element* element) - : ScriptWrappable(element->ctx()), owner_event_target_id_(element->eventTargetId()) {} +ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()), element_(element) { +} -AtomicString ElementAttributes::GetAttribute(const AtomicString& name) { +AtomicString ElementAttributes::getAttribute(const AtomicString& name, ExceptionState& exception_state) { bool numberIndex = IsNumberIndex(name.ToStringView()); if (numberIndex) { return AtomicString::Empty(); } - return attributes_[name]; + AtomicString value = attributes_[name]; + + // Fallback to directly FFI access to dart. + if (value.IsEmpty()) { + NativeValue dart_result = element_->GetBindingProperty(name, exception_state); + if (dart_result.tag == NativeTag::TAG_STRING) { + return NativeValueConverter::FromNativeValue(element_->ctx(), dart_result); + } + } + + return value; } bool ElementAttributes::setAttribute(const AtomicString& name, @@ -44,11 +55,13 @@ bool ElementAttributes::setAttribute(const AtomicString& name, attributes_[name] = value; - std::unique_ptr args_01 = name.ToNativeString(); - std::unique_ptr args_02 = value.ToNativeString(); + if (element_->IsAttributeDefinedInternal(name)) { + std::unique_ptr args_01 = name.ToNativeString(); + std::unique_ptr args_02 = value.ToNativeString(); - GetExecutingContext()->uiCommandBuffer()->addCommand(owner_event_target_id_, UICommand::kSetAttribute, - std::move(args_01), std::move(args_02), nullptr); + GetExecutingContext()->uiCommandBuffer()->addCommand(element_->eventTargetId(), UICommand::kSetAttribute, + std::move(args_01), std::move(args_02), nullptr); + } return true; } @@ -67,7 +80,7 @@ void ElementAttributes::removeAttribute(const AtomicString& name, ExceptionState attributes_.erase(name); std::unique_ptr args_01 = name.ToNativeString(); - GetExecutingContext()->uiCommandBuffer()->addCommand(owner_event_target_id_, UICommand::kRemoveAttribute, + GetExecutingContext()->uiCommandBuffer()->addCommand(element_->eventTargetId(), UICommand::kRemoveAttribute, std::move(args_01), nullptr); } @@ -100,6 +113,8 @@ bool ElementAttributes::IsEquivalent(const ElementAttributes& other) const { return true; } -void ElementAttributes::Trace(GCVisitor* visitor) const {} +void ElementAttributes::Trace(GCVisitor* visitor) const { + visitor->Trace(element_); +} } // namespace webf diff --git a/bridge/core/dom/legacy/element_attributes.d.ts b/bridge/core/dom/legacy/element_attributes.d.ts index b5ffd4b28a..ef3738f1a3 100644 --- a/bridge/core/dom/legacy/element_attributes.d.ts +++ b/bridge/core/dom/legacy/element_attributes.d.ts @@ -1,5 +1,6 @@ export interface ElementAttributes { // Legacy methods: these methods are not W3C standard. + getAttribute(name: string): string; setAttribute(name: string, value: string): void; hasAttribute(name: string): boolean; removeAttribute(name: string): void; diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index 1c4d05fbf5..72e42dcf45 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -7,6 +7,7 @@ #define BRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ #include +#include "bindings/qjs/cppgc/member.h" #include "bindings/qjs/atomic_string.h" #include "bindings/qjs/script_wrappable.h" #include "space_split_string.h" @@ -27,7 +28,7 @@ class ElementAttributes : public ScriptWrappable { explicit ElementAttributes(Element* element); - AtomicString GetAttribute(const AtomicString& name); + AtomicString getAttribute(const AtomicString& name, ExceptionState& exception_state); bool setAttribute(const AtomicString& name, const AtomicString& value, ExceptionState& exception_state); bool hasAttribute(const AtomicString& name, ExceptionState& exception_state); void removeAttribute(const AtomicString& name, ExceptionState& exception_state); @@ -39,7 +40,7 @@ class ElementAttributes : public ScriptWrappable { void Trace(GCVisitor* visitor) const override; private: - int32_t owner_event_target_id_; + Member element_; std::unordered_map attributes_; }; diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index bf668b81b4..7cd158f644 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -14,6 +14,7 @@ #include "node_data.h" #include "node_traversal.h" #include "text.h" +#include "qjs_node.h" namespace webf { @@ -89,6 +90,10 @@ NodeData& Node::EnsureNodeData() { return CreateNodeData(); } +bool Node::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSNode::IsAttributeDefinedInternal(key) || EventTarget::IsAttributeDefinedInternal(key); +} + Node& Node::TreeRoot() const { const Node* node = this; while (node->parentNode()) diff --git a/bridge/core/dom/node.h b/bridge/core/dom/node.h index 5ce44af936..d807270c47 100644 --- a/bridge/core/dom/node.h +++ b/bridge/core/dom/node.h @@ -227,6 +227,8 @@ class Node : public EventTarget { [[nodiscard]] NodeData* Data() const { return node_data_.get(); } NodeData& EnsureNodeData(); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + void Trace(GCVisitor*) const override; private: diff --git a/bridge/core/events/pop_state_event.cc b/bridge/core/events/pop_state_event.cc index 332c56b0a8..da902dc9c2 100644 --- a/bridge/core/events/pop_state_event.cc +++ b/bridge/core/events/pop_state_event.cc @@ -52,6 +52,7 @@ bool PopStateEvent::IsPopstateEvent() const { } void PopStateEvent::Trace(GCVisitor* visitor) const { + state_.Trace(visitor); Event::Trace(visitor); } diff --git a/bridge/core/events/wheel_event.d.ts b/bridge/core/events/wheel_event.d.ts deleted file mode 100644 index 5107e172ab..0000000000 --- a/bridge/core/events/wheel_event.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {MouseEvent} from "./mouse_event"; -import {WheelEventInit} from "./wheel_event_init"; -/** Events that occur due to the user moving a mouse wheel or similar input device. */ -interface WheelEvent extends MouseEvent { - readonly deltaMode: number; - readonly deltaX: number; - readonly deltaY: number; - readonly deltaZ: number; - readonly DOM_DELTA_LINE: StaticMember; - readonly DOM_DELTA_PAGE: StaticMember; - readonly DOM_DELTA_PIXEL: StaticMember; - new(type: string, init?: WheelEventInit): WheelEvent; -} diff --git a/bridge/core/events/wheel_event_init.d.ts b/bridge/core/events/wheel_event_init.d.ts deleted file mode 100644 index 37ad6a5000..0000000000 --- a/bridge/core/events/wheel_event_init.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {EventInit} from "../dom/events/event_init"; - -// @ts-ignore -@Dictionary() -export interface WheelEventInit extends EventInit { - deltaMode?: number; - deltaX?: number; - deltaY?: number; - deltaZ?: number; -} \ No newline at end of file diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index dc2b78b49a..5b01de23ee 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -41,6 +41,7 @@ class Window; class Performance; class MemberMutationScope; class ErrorEvent; +class ScriptWrappable; using JSExceptionHandler = std::function; diff --git a/bridge/core/html/canvas/html_canvas_element.cc b/bridge/core/html/canvas/html_canvas_element.cc index be6c8029bf..fda87609fb 100644 --- a/bridge/core/html/canvas/html_canvas_element.cc +++ b/bridge/core/html/canvas/html_canvas_element.cc @@ -8,6 +8,7 @@ #include "canvas_types.h" #include "foundation/native_value_converter.h" #include "html_names.h" +#include "qjs_html_canvas_element.h" namespace webf { @@ -26,4 +27,8 @@ CanvasRenderingContext* HTMLCanvasElement::getContext(const AtomicString& type, return nullptr; } +bool HTMLCanvasElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLCanvasElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/canvas/html_canvas_element.h b/bridge/core/html/canvas/html_canvas_element.h index 3ed579b08d..85891ea99a 100644 --- a/bridge/core/html/canvas/html_canvas_element.h +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -17,6 +17,8 @@ class HTMLCanvasElement : public HTMLElement { explicit HTMLCanvasElement(Document&); CanvasRenderingContext* getContext(const AtomicString& type, ExceptionState& exception_state) const; + + bool IsAttributeDefinedInternal(const AtomicString &key) const override; }; } // namespace webf diff --git a/bridge/core/html/custom/widget_element.cc b/bridge/core/html/custom/widget_element.cc index e0d7e70a1d..a08744c7b4 100644 --- a/bridge/core/html/custom/widget_element.cc +++ b/bridge/core/html/custom/widget_element.cc @@ -89,4 +89,8 @@ void WidgetElement::CloneNonAttributePropertiesFrom(const Element& other, CloneC } } +bool WidgetElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return true; +} + } // namespace webf \ No newline at end of file diff --git a/bridge/core/html/custom/widget_element.h b/bridge/core/html/custom/widget_element.h index d1bac9e1cc..3ba5f39aa5 100644 --- a/bridge/core/html/custom/widget_element.h +++ b/bridge/core/html/custom/widget_element.h @@ -35,6 +35,7 @@ class WidgetElement : public HTMLElement { void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) override; void Trace(GCVisitor* visitor) const override; + bool IsAttributeDefinedInternal(const AtomicString &key) const override; private: std::unordered_map unimplemented_properties_; diff --git a/bridge/core/html/forms/html_button_element.cc b/bridge/core/html/forms/html_button_element.cc index 3d44078f1a..95f07f7c01 100644 --- a/bridge/core/html/forms/html_button_element.cc +++ b/bridge/core/html/forms/html_button_element.cc @@ -4,3 +4,12 @@ */ #include "html_button_element.h" +#include "qjs_html_button_element.h" + +namespace webf { + +bool HTMLButtonElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLButtonElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + +} \ No newline at end of file diff --git a/bridge/core/html/forms/html_button_element.h b/bridge/core/html/forms/html_button_element.h index 1d454a5be7..5904b07a1d 100644 --- a/bridge/core/html/forms/html_button_element.h +++ b/bridge/core/html/forms/html_button_element.h @@ -14,6 +14,9 @@ class HTMLButtonElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: + + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + private: }; diff --git a/bridge/core/html/forms/html_input_element.cc b/bridge/core/html/forms/html_input_element.cc index 86e7798e7e..83406b96f3 100644 --- a/bridge/core/html/forms/html_input_element.cc +++ b/bridge/core/html/forms/html_input_element.cc @@ -4,9 +4,14 @@ */ #include "html_input_element.h" #include "html_names.h" +#include "qjs_html_input_element.h" namespace webf { HTMLInputElement::HTMLInputElement(Document& document) : HTMLElement(html_names::kinput, &document) {} +bool HTMLInputElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLInputElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h index d1937c79fa..6c4c286269 100644 --- a/bridge/core/html/forms/html_input_element.h +++ b/bridge/core/html/forms/html_input_element.h @@ -14,6 +14,8 @@ class HTMLInputElement : public HTMLElement { public: explicit HTMLInputElement(Document&); + + bool IsAttributeDefinedInternal(const AtomicString &key) const override; }; } // namespace webf diff --git a/bridge/core/html/forms/html_textarea_element.cc b/bridge/core/html/forms/html_textarea_element.cc index cbf6ea2cd5..b620d57f5b 100644 --- a/bridge/core/html/forms/html_textarea_element.cc +++ b/bridge/core/html/forms/html_textarea_element.cc @@ -4,9 +4,14 @@ */ #include "html_textarea_element.h" #include "html_names.h" +#include "qjs_html_textarea_element.h" namespace webf { HTMLTextareaElement::HTMLTextareaElement(Document& document) : HTMLElement(html_names::ktextarea, &document) {} +bool HTMLTextareaElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLTextareaElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/forms/html_textarea_element.d.ts b/bridge/core/html/forms/html_textarea_element.d.ts index 386344c7cf..5862f0e3bf 100644 --- a/bridge/core/html/forms/html_textarea_element.d.ts +++ b/bridge/core/html/forms/html_textarea_element.d.ts @@ -1,7 +1,7 @@ // https://html.spec.whatwg.org/multipage/form-elements.html#the-textarea-element import {HTMLElement} from "../html_element"; -interface HTMLTextAreaElement extends HTMLElement { +interface HTMLTextareaElement extends HTMLElement { defaultValue: DartImpl; value: DartImpl; cols: DartImpl; diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h index 41ea84f071..6a1927bf44 100644 --- a/bridge/core/html/forms/html_textarea_element.h +++ b/bridge/core/html/forms/html_textarea_element.h @@ -14,6 +14,8 @@ class HTMLTextareaElement : public HTMLElement { public: explicit HTMLTextareaElement(Document&); + + bool IsAttributeDefinedInternal(const AtomicString &key) const override; }; } // namespace webf diff --git a/bridge/core/html/html_anchor_element.cc b/bridge/core/html/html_anchor_element.cc index 1615384da8..8507159498 100644 --- a/bridge/core/html/html_anchor_element.cc +++ b/bridge/core/html/html_anchor_element.cc @@ -4,9 +4,14 @@ */ #include "html_anchor_element.h" #include "html_names.h" +#include "qjs_html_anchor_element.h" namespace webf { HTMLAnchorElement::HTMLAnchorElement(Document& document) : HTMLElement(html_names::ka, &document) {} +bool HTMLAnchorElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLAnchorElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_anchor_element.d.ts b/bridge/core/html/html_anchor_element.d.ts index 3b8ea28c11..fa3f09e8ba 100644 --- a/bridge/core/html/html_anchor_element.d.ts +++ b/bridge/core/html/html_anchor_element.d.ts @@ -1,6 +1,6 @@ import {Element} from "../dom/element"; -interface AnchorElement extends Element { +interface HTMLAnchorElement extends Element { target: DartImpl; accessKey: DartImpl; download: DartImpl; diff --git a/bridge/core/html/html_anchor_element.h b/bridge/core/html/html_anchor_element.h index 51a96c7858..9401092f08 100644 --- a/bridge/core/html/html_anchor_element.h +++ b/bridge/core/html/html_anchor_element.h @@ -15,6 +15,8 @@ class HTMLAnchorElement : public HTMLElement { public: explicit HTMLAnchorElement(Document& document); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/html/html_body_element.cc b/bridge/core/html/html_body_element.cc index f353b84393..bd5d5dd962 100644 --- a/bridge/core/html/html_body_element.cc +++ b/bridge/core/html/html_body_element.cc @@ -4,9 +4,14 @@ */ #include "html_body_element.h" #include "html_names.h" +#include "qjs_html_body_element.h" namespace webf { HTMLBodyElement::HTMLBodyElement(Document& document) : HTMLElement(html_names::kbody, &document) {} +bool HTMLBodyElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLBodyElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index b9927d0184..8da098e86e 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -18,6 +18,8 @@ class HTMLBodyElement : public HTMLElement { using ImplType = HTMLBodyElement*; explicit HTMLBodyElement(Document&); + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(blur, kblur); DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(error, kerror); DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(focus, kfocus); diff --git a/bridge/core/html/html_element.cc b/bridge/core/html/html_element.cc index 9570063f4c..3f87b9b830 100644 --- a/bridge/core/html/html_element.cc +++ b/bridge/core/html/html_element.cc @@ -4,5 +4,12 @@ */ #include "html_element.h" +#include "qjs_html_element.h" -namespace webf {} // namespace webf +namespace webf { + +bool HTMLElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLElement::IsAttributeDefinedInternal(key) || Element::IsAttributeDefinedInternal(key); +} + +} // namespace webf diff --git a/bridge/core/html/html_element.h b/bridge/core/html/html_element.h index 410243cb9c..6b08186b63 100644 --- a/bridge/core/html/html_element.h +++ b/bridge/core/html/html_element.h @@ -18,6 +18,8 @@ class HTMLElement : public Element { using ImplType = HTMLElement*; HTMLElement(const AtomicString& tag_name, Document* document, ConstructionType); + bool IsAttributeDefinedInternal(const AtomicString& key) const override; + private: }; diff --git a/bridge/core/html/html_image_element.cc b/bridge/core/html/html_image_element.cc index 912b5c0f4d..565c6059b3 100644 --- a/bridge/core/html/html_image_element.cc +++ b/bridge/core/html/html_image_element.cc @@ -4,11 +4,16 @@ */ #include "html_image_element.h" #include "html_names.h" +#include "qjs_html_image_element.h" namespace webf { HTMLImageElement::HTMLImageElement(Document& document) : HTMLElement(html_names::kimg, &document) {} +bool HTMLImageElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLImageElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + ScriptPromise HTMLImageElement::decode(ExceptionState& exception_state) const { exception_state.ThrowException(ctx(), ErrorType::InternalError, "Not implemented."); // @TODO not implemented. diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index d61889478c..83c9a86df2 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -16,6 +16,8 @@ class HTMLImageElement : public HTMLElement { using ImplType = HTMLImageElement*; explicit HTMLImageElement(Document& document); + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool KeepAlive() const override; ScriptPromise decode(ExceptionState& exception_state) const; diff --git a/bridge/core/html/html_script_element.cc b/bridge/core/html/html_script_element.cc index 5ea117a4f7..6bdd5dfc83 100644 --- a/bridge/core/html/html_script_element.cc +++ b/bridge/core/html/html_script_element.cc @@ -5,6 +5,7 @@ #include "html_script_element.h" #include "html_names.h" #include "script_type_names.h" +#include "qjs_html_script_element.h" namespace webf { @@ -18,4 +19,8 @@ bool HTMLScriptElement::supports(const AtomicString& type, ExceptionState& excep return false; } +bool HTMLScriptElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLScriptElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h index 4e28efbb31..62fb7d094d 100644 --- a/bridge/core/html/html_script_element.h +++ b/bridge/core/html/html_script_element.h @@ -17,6 +17,8 @@ class HTMLScriptElement : public HTMLElement { explicit HTMLScriptElement(Document& document); + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + private: }; diff --git a/bridge/core/html/html_template_element.cc b/bridge/core/html/html_template_element.cc index bb030a75fe..e00cea764b 100644 --- a/bridge/core/html/html_template_element.cc +++ b/bridge/core/html/html_template_element.cc @@ -5,6 +5,7 @@ #include "html_template_element.h" #include "core/dom/document_fragment.h" #include "html_names.h" +#include "qjs_html_template_element.h" namespace webf { @@ -21,4 +22,8 @@ DocumentFragment* HTMLTemplateElement::ContentInternal() const { return content_.Get(); } +bool HTMLTemplateElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLTemplateElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index 3aa5dd8cfe..cc43afabf8 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -19,6 +19,8 @@ class HTMLTemplateElement : public HTMLElement { DocumentFragment* content() const; + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + private: DocumentFragment* ContentInternal() const; mutable Member content_; diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index f5a7088082..4f4ae41474 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -6,11 +6,12 @@ #include "event_factory.h" #include "html_element_factory.h" #include "names_installer.h" +#include "defined_properties_initializer.h" namespace webf { -JSRuntime* runtime_ = nullptr; -std::atomic runningContexts{0}; +thread_local JSRuntime* runtime_ = nullptr; +thread_local std::atomic runningContexts{0}; ScriptState::ScriptState() { runningContexts++; @@ -25,6 +26,7 @@ ScriptState::ScriptState() { if (first_loaded) { names_installer::Init(ctx_); + DefinedPropertiesInitializer::Init(); // Bump up the built-in classId. To make sure the created classId are larger than JS_CLASS_CUSTOM_CLASS_INIT_COUNT. for (int i = 0; i < JS_CLASS_CUSTOM_CLASS_INIT_COUNT - JS_CLASS_GC_TRACKER + 2; i++) { JSClassID id{0}; @@ -46,6 +48,7 @@ ScriptState::~ScriptState() { if (--runningContexts == 0) { // Prebuilt strings stored in JSRuntime. Only needs to dispose when runtime disposed. + DefinedPropertiesInitializer::Dispose(); names_installer::Dispose(); HTMLElementFactory::Dispose(); EventFactory::Dispose(); diff --git a/bridge/core/script_state.h b/bridge/core/script_state.h index 1e154e29b6..edcfbcb53a 100644 --- a/bridge/core/script_state.h +++ b/bridge/core/script_state.h @@ -6,7 +6,7 @@ #define BRIDGE_CORE_SCRIPT_STATE_H_ #include -#include "bindings/qjs/script_wrappable.h" +#include namespace webf { diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index e11af74027..4e3b2af719 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -79,6 +79,7 @@ void UICommandBuffer::clear() { delete[] reinterpret_cast(buffer_[i].string_02); } size_ = 0; + memset(buffer_, 0, sizeof(buffer_)); update_batched_ = false; } diff --git a/bridge/scripts/code_generator/bin/code_generator.js b/bridge/scripts/code_generator/bin/code_generator.js index ba6bcb3607..72400e3835 100644 --- a/bridge/scripts/code_generator/bin/code_generator.js +++ b/bridge/scripts/code_generator/bin/code_generator.js @@ -45,7 +45,7 @@ function genCodeFromTypeDefine() { // Analyze all files first. for (let i = 0; i < blobs.length; i ++) { let b = blobs[i]; - analyzer(b); + analyzer(b, definedPropertyCollector); } for (let i = 0; i < blobs.length; i ++) { @@ -82,7 +82,6 @@ function genCodeFromJSONData() { return new JSONTemplate(path.join(path.join(__dirname, '../templates/json_templates'), template), filename); }); - let names_needs_install = new Set(); for (let i = 0; i < blobs.length; i ++) { let blob = blobs[i]; blob.json.metadata.templates.forEach((targetTemplate) => { @@ -97,6 +96,19 @@ function genCodeFromJSONData() { depsBlob[filename] = new JSONBlob(path.join(cwdDir, depPath), filename).json; }); } + + // Inject allDefinedProperties set into the definedProperties source. + if (targetTemplate.filename === 'defined_properties') { + blob.json.data = Array.from(definedPropertyCollector.properties); + } + + if (targetTemplate.filename === 'defined_properties_initializer') { + blob.json.data = { + filenames: Array.from(definedPropertyCollector.files), + interfaces: Array.from(definedPropertyCollector.interfaces) + }; + } + let targetTemplateHeaderData = templates.find(t => t.filename === targetTemplate.template + '.h'); let targetTemplateBodyData = templates.find(t => t.filename === targetTemplate.template + '.cc'); blob.filename = targetTemplate.filename; @@ -117,5 +129,14 @@ function genCodeFromJSONData() { result.source && fs.writeFileSync(genFilePath + '.cc', result.source); } +class DefinedPropertyCollector { + properties = new Set(); + files = new Set(); + interfaces = new Set(); +} + +let definedPropertyCollector = new DefinedPropertyCollector(); +let names_needs_install = new Set(); + genCodeFromTypeDefine(); genCodeFromJSONData(); diff --git a/bridge/scripts/code_generator/src/idl/analyzer.ts b/bridge/scripts/code_generator/src/idl/analyzer.ts index 194a22b3b1..0e599b1436 100644 --- a/bridge/scripts/code_generator/src/idl/analyzer.ts +++ b/bridge/scripts/code_generator/src/idl/analyzer.ts @@ -11,12 +11,17 @@ import { ParameterMode, PropsDeclaration, } from './declaration'; -import {generatorSource} from './generator'; -export function analyzer(blob: IDLBlob) { +interface DefinedPropertyCollector { + properties: Set; + files: Set; + interfaces: Set; +} + +export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropertyCollector) { let code = blob.raw; const sourceFile = ts.createSourceFile(blob.source, blob.raw, ScriptTarget.ES2020); - blob.objects = sourceFile.statements.map(statement => walkProgram(statement)).filter(o => { + blob.objects = sourceFile.statements.map(statement => walkProgram(blob, statement, definedPropertyCollector)).filter(o => { return o instanceof ClassObject || o instanceof FunctionObject; }) as (FunctionObject | ClassObject)[]; } @@ -151,7 +156,7 @@ function isParamsReadOnly(m: ts.PropertySignature): boolean { return m.modifiers.some(k => k.kind === ts.SyntaxKind.ReadonlyKeyword); } -function walkProgram(statement: ts.Statement) { +function walkProgram(blob: IDLBlob, statement: ts.Statement, definedPropertyCollector: DefinedPropertyCollector) { switch(statement.kind) { case ts.SyntaxKind.InterfaceDeclaration: { let interfaceName = getInterfaceName(statement) as string; @@ -179,6 +184,11 @@ function walkProgram(statement: ts.Statement) { } } + if (obj.kind === ClassObjectKind.interface) { + definedPropertyCollector.interfaces.add('QJS' + interfaceName); + definedPropertyCollector.files.add(blob.filename); + } + s.members.forEach(member => { switch(member.kind) { case ts.SyntaxKind.PropertySignature: { @@ -187,6 +197,10 @@ function walkProgram(statement: ts.Statement) { prop.name = getPropName(m.name); prop.readonly = isParamsReadOnly(m); + if (obj.kind === ClassObjectKind.interface) { + definedPropertyCollector.properties.add(prop.name); + } + let propKind = m.type; if (propKind) { let mode = new ParameterMode(); diff --git a/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl index 9c2c2219e4..716b6c8404 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/base.cc.tpl @@ -20,6 +20,7 @@ #include "core/dom/comment.h" #include "core/input/touch_list.h" #include "core/html/html_all_collection.h" +#include "defined_properties.h" namespace webf { diff --git a/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl b/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl index 3c6ddd7631..85d96d0a1c 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/interface.cc.tpl @@ -95,6 +95,31 @@ JSValue QJS<%= className %>::ConstructorCallback(JSContext* ctx, JSValue func_ob <% } %> +static thread_local AttributeMap* internal_properties = nullptr; + +void QJS<%= className %>::InitAttributeMap() { + internal_properties = new AttributeMap(); + + for(int i = 0; i < <%= object.props.length %>; i ++) { + <% object.props.forEach(prop => { %> + internal_properties->emplace(std::make_pair(defined_properties::k<%= prop.name %>, true)); + <% }) %> + } +} + +void QJS<%= className %>::DisposeAttributeMap() { + delete internal_properties; +} + +AttributeMap* QJS<%= className %>::definedAttributeMap() { + assert(internal_properties != nullptr); + return internal_properties; +} + +bool QJS<%= className %>::IsAttributeDefinedInternal(const AtomicString& key) { + return definedAttributeMap()->count(key) > 0; +} + <% _.forEach(filtedMethods, function(method, index) { %> <% if (overloadMethods[method.name] && overloadMethods[method.name].length > 1) { %> diff --git a/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl b/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl index 8106a8363e..349d61a50f 100644 --- a/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl +++ b/bridge/scripts/code_generator/templates/idl_templates/interface.h.tpl @@ -33,9 +33,15 @@ Native<%= parentClassName %> native_event; #endif <% } %> +using AttributeMap = std::unordered_map; + class QJS<%= className %> : public QJSInterfaceBridge, <%= className%>> { public: static void Install(ExecutingContext* context); + static void InitAttributeMap(); + static void DisposeAttributeMap(); + static AttributeMap* definedAttributeMap(); + static bool IsAttributeDefinedInternal(const AtomicString& key); static WrapperTypeInfo* GetWrapperTypeInfo() { return const_cast(&wrapper_type_info_); } diff --git a/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.cc.tpl new file mode 100644 index 0000000000..6d8d1c740a --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.cc.tpl @@ -0,0 +1,27 @@ +// Generated from template: +// code_generator/src/json/templates/defined_properties_initializer.cc.tpl +// and input files: +// <%= template_path %> + +#include "defined_properties_initializer.h" + +<% data.filenames.forEach(filename => { %> +#include "<%= filename %>.h" +<% }) %> + +namespace webf { + +void DefinedPropertiesInitializer::Init() { + <% data.interfaces.forEach(interfaceName => { %> + <%= interfaceName %>::InitAttributeMap(); + <% }) %> +} + +void DefinedPropertiesInitializer::Dispose() { + <% data.interfaces.forEach(interfaceName => { %> + <%= interfaceName %>::DisposeAttributeMap(); + <% }) %> +} + + +} \ No newline at end of file diff --git a/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.h.tpl b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.h.tpl new file mode 100644 index 0000000000..77da466fa5 --- /dev/null +++ b/bridge/scripts/code_generator/templates/json_templates/defined_properties_initializer.h.tpl @@ -0,0 +1,22 @@ +/* +* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. +* Copyright (C) 2022-present The WebF authors. All rights reserved. +*/ + +#ifndef BRIDGE_DEFINED_PROPERTIES_INTIALIZER_H_ +#define BRIDGE_DEFINED_PROPERTIES_INTIALIZER_H_ + +#include "bindings/qjs/atomic_string.h" + +namespace webf { + +class DefinedPropertiesInitializer { + public: + static void Init(); + static void Dispose(); +}; + + +} // namespace webf + +#endif // BRIDGE_DEFINED_PROPERTIES_INTIALIZER_H_ diff --git a/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl index 5f1d4fcf6b..01ffa2f70d 100644 --- a/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl +++ b/bridge/scripts/code_generator/templates/json_templates/element_factory.cc.tpl @@ -31,7 +31,7 @@ using HTMLConstructorFunction = HTMLElement* (*)(Document&); using HTMLFunctionMap = std::unordered_map; -static HTMLFunctionMap* g_html_constructors = nullptr; +static thread_local HTMLFunctionMap* g_html_constructors = nullptr; struct CreateHTMLFunctionMapData { const AtomicString& tag; diff --git a/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl index 11c35f0aa6..ebbce63c03 100644 --- a/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl +++ b/bridge/scripts/code_generator/templates/json_templates/event_factory.cc.tpl @@ -29,7 +29,7 @@ using EventConstructorFunction = Event* (*)(ExecutingContext* context, const Ato using EventMap = std::unordered_map; -static EventMap* g_event_constructors = nullptr; +static thread_local EventMap* g_event_constructors = nullptr; struct CreateEventFunctionMapData { const AtomicString& tag; diff --git a/bridge/test/webf_test_env.h b/bridge/test/webf_test_env.h index eccd4f9dcd..566e1aa970 100644 --- a/bridge/test/webf_test_env.h +++ b/bridge/test/webf_test_env.h @@ -9,6 +9,7 @@ #include #include "core/dart_methods.h" #include "core/executing_context.h" +#include "bindings/qjs/cppgc/mutation_scope.h" #include "core/page.h" #include "foundation/logging.h" From eac50cf3c1bae2fe70dccfeef66eb15f7aeae15f Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 30 Sep 2022 15:24:50 +0800 Subject: [PATCH 467/498] feat: add link element interface. --- bridge/CMakeLists.txt | 2 ++ bridge/bindings/qjs/binding_initializer.cc | 2 ++ bridge/bindings/qjs/wrapper_type_info.h | 1 + bridge/core/html/html_link_element.cc | 18 +++++++++++++++ bridge/core/html/html_link_element.d.ts | 9 ++++++++ bridge/core/html/html_link_element.h | 27 ++++++++++++++++++++++ bridge/core/html/html_tag_names.json5 | 5 ++++ 7 files changed, 64 insertions(+) create mode 100644 bridge/core/html/html_link_element.cc create mode 100644 bridge/core/html/html_link_element.d.ts create mode 100644 bridge/core/html/html_link_element.h diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 0e6c485df0..1f6fd57d37 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -276,6 +276,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/html/html_anchor_element.cc core/html/html_image_element.cc core/html/html_script_element.cc + core/html/html_link_element.cc core/html/html_unknown_element.cc core/html/image.cc core/html/canvas/html_canvas_element.cc @@ -368,6 +369,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") out/qjs_html_html_element.cc out/qjs_html_image_element.cc out/qjs_html_canvas_element.cc + out/qjs_html_link_element.cc out/qjs_image.cc out/qjs_widget_element.cc out/qjs_canvas_rendering_context_2d.cc diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index c820d6d187..b2561c8899 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -38,6 +38,7 @@ #include "qjs_html_image_element.h" #include "qjs_html_input_element.h" #include "qjs_html_script_element.h" +#include "qjs_html_link_element.h" #include "qjs_html_template_element.h" #include "qjs_html_textarea_element.h" #include "qjs_html_unknown_element.h" @@ -119,6 +120,7 @@ void InstallBindings(ExecutingContext* context) { QJSHTMLButtonElement::Install(context); QJSImage::Install(context); QJSHTMLScriptElement::Install(context); + QJSHTMLLinkElement::Install(context); QJSHTMLUnknownElement::Install(context); QJSHTMLTemplateElement::Install(context); QJSHTMLCanvasElement::Install(context); diff --git a/bridge/bindings/qjs/wrapper_type_info.h b/bridge/bindings/qjs/wrapper_type_info.h index 2b5d4c3b22..2b36128206 100644 --- a/bridge/bindings/qjs/wrapper_type_info.h +++ b/bridge/bindings/qjs/wrapper_type_info.h @@ -65,6 +65,7 @@ enum { JS_CLASS_HTML_IMAGE_ELEMENT, JS_CLASS_HTML_SCRIPT_ELEMENT, JS_CLASS_HTML_ANCHOR_ELEMENT, + JS_CLASS_HTML_LINK_ELEMENT, JS_CLASS_HTML_CANVAS_ELEMENT, JS_CLASS_IMAGE, JS_CLASS_CANVAS_RENDERING_CONTEXT, diff --git a/bridge/core/html/html_link_element.cc b/bridge/core/html/html_link_element.cc new file mode 100644 index 0000000000..9f9979d652 --- /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); +} + +} \ No newline at end of file diff --git a/bridge/core/html/html_link_element.d.ts b/bridge/core/html/html_link_element.d.ts new file mode 100644 index 0000000000..3f1f73e739 --- /dev/null +++ b/bridge/core/html/html_link_element.d.ts @@ -0,0 +1,9 @@ +import {HTMLElement} from "./html_element"; + +interface HTMLLinkElement extends HTMLElement { + disabled: DartImpl; + rel: DartImpl; + href: DartImpl; + type: DartImpl; + new(): void; +} \ No newline at end of file diff --git a/bridge/core/html/html_link_element.h b/bridge/core/html/html_link_element.h new file mode 100644 index 0000000000..834355e84a --- /dev/null +++ b/bridge/core/html/html_link_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 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: + +}; + +} + + +#endif // WEBF_CORE_HTML_HTML_LINK_ELEMENT_H_ diff --git a/bridge/core/html/html_tag_names.json5 b/bridge/core/html/html_tag_names.json5 index a6710aa070..9134cad299 100644 --- a/bridge/core/html/html_tag_names.json5 +++ b/bridge/core/html/html_tag_names.json5 @@ -32,6 +32,11 @@ "body", "head", "div", + { + "name": "link", + "interfaceName": "HTMLLinkElement", + "filename": "html_link_element" + }, { "name": "input", "interfaceHeaderDir": "core/html/forms" From c586a10e0f581a78ac37a81738d4bddd546c1285 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 30 Sep 2022 15:49:16 +0800 Subject: [PATCH 468/498] fix: disable attribute querySelector. --- bridge/core/dom/element.d.ts | 1 + integration_tests/specs/dom/nodes/query-selector.ts | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bridge/core/dom/element.d.ts b/bridge/core/dom/element.d.ts index e2eb979809..f694432919 100644 --- a/bridge/core/dom/element.d.ts +++ b/bridge/core/dom/element.d.ts @@ -9,6 +9,7 @@ interface Element extends Node, ParentNode { id: DartImpl; className: DartImpl; class: DartImpl; + name: DartImpl; readonly attributes: ElementAttributes; readonly style: CSSStyleDeclaration; readonly clientHeight: DartImpl; diff --git a/integration_tests/specs/dom/nodes/query-selector.ts b/integration_tests/specs/dom/nodes/query-selector.ts index cbd78c83ac..26a88ef292 100644 --- a/integration_tests/specs/dom/nodes/query-selector.ts +++ b/integration_tests/specs/dom/nodes/query-selector.ts @@ -115,7 +115,7 @@ describe('querySelector api', () => { expect(document.querySelectorAll('*').length).toBe(8); }); - it('querySelectorAll work with query attr', () => { + xit('querySelectorAll work with query attr', () => { ['red', 'black', 'green', 'yellow', 'blue'].forEach((item, index) => { const div = document.createElement('div') div.style.width = '100px'; @@ -468,7 +468,7 @@ describe('querySelector api', () => { expect(document.querySelector('a[href="openkraken.com"]')?.getAttribute('href')).toBe('openkraken.com'); }); - it('querySelector work with attr', () => { + xit('querySelector work with attr', () => { const container = document.createElement('div') container.appendChild(document.createTextNode('你好')) container.setAttribute('data-id', 'one') @@ -477,7 +477,7 @@ describe('querySelector api', () => { expect(document.querySelector('[data-id="one"]')?.getAttribute('data-id')).toBe('one'); }); - it('querySelectorAll work with attr', () => { + xit('querySelectorAll work with attr', () => { const container = document.createElement('div') container.appendChild(document.createTextNode('你好')) container.setAttribute('data-id', 'one') From 66d353ab01fb7889d338494927d76577b194df32 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 30 Sep 2022 15:49:28 +0800 Subject: [PATCH 469/498] chore: add hsl snapshots. --- .../css/css-color/hsl-001.html.65645d7c1.png | Bin 0 -> 8250 bytes .../css/css-color/hsl-002.html.797a2f5a1.png | Bin 0 -> 8250 bytes .../css/css-color/hsl-003.html.ac8782251.png | Bin 0 -> 8250 bytes .../css/css-color/hsl-004.html.972de9c51.png | Bin 0 -> 8250 bytes .../css/css-color/hsl-005.html.b1402b061.png | Bin 0 -> 8250 bytes .../css/css-color/hsl-006.html.cc1c51e11.png | Bin 0 -> 8250 bytes .../css/css-color/hsl-007.html.8d366c2f1.png | Bin 0 -> 8250 bytes .../css/css-color/hsl-008.html.0ef86b9b1.png | Bin 0 -> 8250 bytes .../css/css-color/hsla-001.html.36c76bb61.png | Bin 0 -> 8250 bytes .../css/css-color/hsla-002.html.0718b4031.png | Bin 0 -> 8250 bytes .../css/css-color/hsla-003.html.6088464b1.png | Bin 0 -> 8250 bytes .../css/css-color/hsla-004.html.84d8420e1.png | Bin 0 -> 8250 bytes .../css/css-color/hsla-005.html.d3a946911.png | Bin 0 -> 8250 bytes .../css/css-color/hsla-006.html.b259b3691.png | Bin 0 -> 8250 bytes .../css/css-color/hsla-007.html.f58df36c1.png | Bin 0 -> 8250 bytes .../css/css-color/hsla-008.html.be9b8de71.png | Bin 0 -> 8250 bytes 16 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 integration_tests/snapshots/css/css-color/hsl-001.html.65645d7c1.png create mode 100644 integration_tests/snapshots/css/css-color/hsl-002.html.797a2f5a1.png create mode 100644 integration_tests/snapshots/css/css-color/hsl-003.html.ac8782251.png create mode 100644 integration_tests/snapshots/css/css-color/hsl-004.html.972de9c51.png create mode 100644 integration_tests/snapshots/css/css-color/hsl-005.html.b1402b061.png create mode 100644 integration_tests/snapshots/css/css-color/hsl-006.html.cc1c51e11.png create mode 100644 integration_tests/snapshots/css/css-color/hsl-007.html.8d366c2f1.png create mode 100644 integration_tests/snapshots/css/css-color/hsl-008.html.0ef86b9b1.png create mode 100644 integration_tests/snapshots/css/css-color/hsla-001.html.36c76bb61.png create mode 100644 integration_tests/snapshots/css/css-color/hsla-002.html.0718b4031.png create mode 100644 integration_tests/snapshots/css/css-color/hsla-003.html.6088464b1.png create mode 100644 integration_tests/snapshots/css/css-color/hsla-004.html.84d8420e1.png create mode 100644 integration_tests/snapshots/css/css-color/hsla-005.html.d3a946911.png create mode 100644 integration_tests/snapshots/css/css-color/hsla-006.html.b259b3691.png create mode 100644 integration_tests/snapshots/css/css-color/hsla-007.html.f58df36c1.png create mode 100644 integration_tests/snapshots/css/css-color/hsla-008.html.be9b8de71.png 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 zcmeHM`BzhCx5m=ewhkC+oexe)B1sS6yLPdqDAjlL25dw?c&|DsgXwtEIHz6}25r2RMW zFZ!D+6eT6}$CJ3@_Tf+XoTyhxLek2D%HFiY{H)_qTeY31;?0L1ZtT7^y+wVOji=_h z+~+_1(dA^iBYs!cmUz^T>PvoSzezljmv=VnXWy0|JAX6C&!%17qE?Xgt-JEy+8A-% zP1lc7X)IUA+j#26w!;e#KMK<>-!7HTm9|@?iUotRrzB?bIUA+>ZI|bTUnq6x-TL}h zn`i6(*gB0b(jklrZ6nGwM8XnCZk7dTcgjy%g;mg|yNSqp1R?O`Tw#;I5hnM4?69aci* z^-aUUTq9>_3p8EQjCw22Xb`PfKsWt$OL;smJMo2E=y-yW6|P>5P>nis=FEI=*1B(N z6zj1o$LEGC4KSF!woi71afps);^E+hTPo2%VnkLG8AD}$j@66JTE&6mjbUbV?iJ;) zH&WIqf8FCIT4d=q?eu=~Q`@m9T0&>Gbt+{)n?WE%1zvpZb9SfUb*F2-z6Vifon2g5 ztwvl|qt=IZ78iA@KfN6~+}hgewJ_E&@xWT+fVQ^w9*hm`+=0Tv!ihA$q66xcvo8v4 z6ml7hN?se+zwzO(ttgsz`%|x;ytDk2nO;Y{cCrl(hr3JZ%C*Z}e2135dhYdtsgO%q z_kXhVT{4y}ck=?UVS?eVIB{Q|azC%;nkMg5kg>by`vldpZuk<}ou^>dh~9k6vi- z63+IPv4^APYiN#icQ0rIaY@3ild*%|?@0||4x5I~?SHt>P6z6h=ij8~+1OLWt%(X- zZ40YCA1R!5sEYaY;f>c|iP!oX3se1|kADwE`a-?75KR;T= z!|Srk{R_^V=j0Nq_~l*0!>;Lq*#f!_p-vW>%c_fBUDOYq8yGV2hX<>eeLBXMc(GEF zpIhGPOCkJqbKOL(RG@{_Loi%fHX{-7UG|NH3 zD;}0h1KgI!2B&aIsqT^ahuo_xPCwSC*&j^q>gqa|bNcZ)jmYWep^OO0OfLM*f%7>% zZZT3m0|sfscAZ0n8sUX&0RcJycGuw6BVLm&kG``1NxE$9pi%AKt{WIAL739NP?M z0FF)jQ*&04Th%W2%nYPIkM%sQD#asFthzN;T@q!#Vy#^J)P3HRO#jSuN>C^rWEa zTN;ObewzX6&+RudGU`vE&ECPTU4}z*6*4X@sj<&HyY1+BJPiNvj1Z{Zj#K;Fk?56A zw#pn)?-fnyS{LmUfUnrIsqaki3@sI)3(eN1&9QUY@|;&54fmi~K=t#>dN|zbq*2}A zD6#ji5t5igOeHVG;iIi@jd10>N~Xg7#+$E*-A77fD!uY1;sKiIvz|E0VqWX*67G~_oCKhX>Dq%->wz5|4 z+8IuJ?AM#_0bw_lc(Ht!NFaY6qi_FReEHq&{j|?Ah(aMVX2}X!e80~cS3ljEJzl-k z7H3DAJzpl~XkxbMxbCQl5+|BRiRxLpb*@pv$u@f~6uAy|n2l)TSDPKjK!>T`nIuDt zg}Zk3_kPgw0ICcG(!kZ%`Y*$Yn1ayE)fR6I#-lUqMBvc1T=hj6qrAM4ET1Ab9_e>g z_0Aw(SR2fgT@2y%X=5-z$EdjNx`}UM*EbG(_6N`%2ts3>fA_)@EpX-(?}-RdoO)(Td{Hp; zd#oW&6EoIx-Y#K+mD+TBlbQ#Bx|k#tQ7BGTq5e;9y!{qc92U}sUu(ekOp-zVPSlU6 zw3~JVFRlo}>eN867aq4q=Lkxl&2A~46%|U}Zm->9;N3PC@^pRB&%9MF{HhIKkr1G; zA`)F4Gf1LAVpA|tcAZ_WjS=ycmDg{6S;aA)dZMan6+azh%pAJf%db-yPJ=Sw=Gb>e z@^zHm+}<>y$&`EOTxK9gAsA9xYz9h z=QBJgl=J}R0jtE_J5f=NSFD-NIi+nTT6^uD?uHRfoR1M#p-_SEHW}6T*NVz01Ez)% z;6#Y%5r>1xM;HIr^y6g}L{z)n-net?R3tOZ`)Q(P$SNd)CfX* zM<1+@x<5x4t_Um!+1!DRiH=8OEh0s8I}sbTamUX;_PxBQib9>wIei!5_JH&G_F0vc z#)%e^X^PKvl7PS;DqCWQMh=a($71D;N+>hBcOOG}72bb4BB=?Hv!KGmFq?s6clR}dchkfSZ6cfZ zsHCZcnlsFE)Hw14pN|zqCUdVO*wF!U#s!zo(0Y4otdXx`)saEAw!EL7mbQ5%Yy48k z>Yan+l{@4eC=^yY*zN%|1sst$WWWa2^X=ZW>+t@kQG!aR${?@PPmbydU~nuW4e1t3 zvp%0v%?^n z#eV%o6;eT!=im2#3T)A9LdNg4q99{7vcT5SVe_K2IMHM_uVNy_#bc^1V_{{SYE9KJ z9}CWuy5exl)18_a3}Q^3iakysPZ7-xs-_ze5>Lc!Yrrdncx#*|FPIFTAORp@p+Q>i z+)wK%7Xk>YTt^c7tcB0`Hk#6CVuo#|g}FOfCLCZA{ruBX@iD9F)Mn#}r1N8=chHGv z(hVCxxzHQ79ijl8m|;ZbC`Wvuqxvk`Sba7!(I;9OHgPGDXJw!Hq=24RAi{g!dBK!1 zt`Bwzn0z*)&M&)!xiLgo_1BNLqRjL+tpg3U=+&aqaUC;4{ZO(Nd>qVF5mYLZO{Xg?x~? z_CB-zk3FZdqQruQQ5A9pr$Aygxey@fO0E6grTqF`2oQQtBPJiOc&-Y++ z;<1u!>^&P=*Qi!~?8QunUc4q-{-P43d`RB$c$WPG)m-GnnJD#c7c5$6?qW|B(2G*F zUACcR)7ImbXvuGuOBPNbe^i9;uH|N2h#I5oaFM8C#3#%8$;6{n896o*=3zF*qDQzJ zwTYmhP!P_(KzHYi67K}LqJI_gX)-iVF0Ft5B0vE;)bAz-w~s-N3sGghSr)RotqGiy zCq$5i)poMQ-qVk^z;BBE+2!H0`MFsHZf&4w(7OrZqZ;y3larI-;0MRR<-}9+WF{+8 zAYsUM9zY3*3^X9!Ln~IAOl#VM+l==WRSB3!NB^bjF;X27IF%9B2qs``I-8oPVIGMC zZ-;<9uG*bPOg77y^T-U7OHt z0%J2)nkl;;uVHQyxjc>H_0y?M8c~ND6V$dN0-CxMZX^_NOe4htb?!=|QP#Qqo*Mo9 zfb0!A6+af$$t6eSE92wa>sC&RS>E1wS2OV_2BdVSo!J5oiCC!4 z0m(3T&^uGz;A#s+i(akYX13H4KKk$NV?gu);tTKPa>2Y@2S}UVfL(ist4oNgl+-7$zwe$%xkC13+q z5aP^z5@Z8ogjyI5Zf`&m=Z+)3_BdR$AUHD)#PsLeDrgPJ(F)XO#KFr^Pd?mRmhQ-{ zC3zyCSNY-rzYd}h63I_zn3M_3=UyNJ{Cu2w7A+lU%7Vxk z$RYMOSX(SUI4bNp?+Nxm?`q2<%QUY11sg(ZQJg2$+z@=9YPzBSJ)|6%EWuYjrd=Y{ zcWsTw+rc9nHWo|h&@2n>r%13Of`n@;ZGvW`iDt(Rn8Wm{ei|Sk_^{;z-^&PPObh#R&%}Q`>vnk&2__9&cUNZQf zL-ZPv^SdRV0uz&G{QJ*~tbTh99-9H734|vg%*~oTz1(d_Fxp$nOej#anTv-P2is%H zqenO3r`)kdq`QCr3YIx?^a6iZz<_+R!aHIJoMkCmDt9<M5I3k6@ZVQKxkcd=bEnMsL^K|8Xf zJCrIb>vu2ML@f~b0d)6+J@Q&xS;)|_&St;N=5v9$LdT1_cc&x&AdN&XwU&f0jFtHI zz6gz-_v(XjD0u0@C}I_-!GcK(9HZdIQMYWEWcxcW%G7fhbx0yUGcG^?6$x1`Ioaw+{NNsRxm$Y8>t& zGvwMkhH-}lz6|8QBxI(m1TulGxZe_SJ0q+S}zB(@)TeCuJaKy^!9TsF7MKW0WR9Ftw;cz<-`*cLr_|)lVd?l$V2WJuH zb$WR|ONSdfSrZ0(nEK($V9%9Lqx=!N(>)Ltdd%F$VE!q2om3S4aH%>Y;99%IKA2%X5RJhH2A`-nI-z0^ew2o0@#OW~iBdM`s zb@H=m)AMRha;k|W@~cwU9sQCvXBx+c2|q90Mzd;n9tKHBOHXGds+;!X<=0PvxpS^iGxl3RL=Rb|#P2pP5xhPI_JO<$@|B!uR;RSq_d?3-Ecb5q z|M;>27%HAZ+Lw5p2L#VwUh`B6DeRt)4T$O+GiWz6V1~_)2TtxB-q;k1$dl+D#aGeIRsxL}r`1^F!D#x%6jh=6(wll;)U( zN2emMikN+{B{QE+cf!u(9wNwXrt{T9-)vDbaoiAM?ag}xd#2AW37P{-nuZ@7d*lkb zLPAH(=_5mb=hNwi+6R+uV6B9KMVT71C-P?^s{j-VF{fd&<4S9WzSC7lLj}V-3oaDi z6W7d>zykG*=Eoul4*3YyxwV3KjpZWy^@t1y5+49zW-M`dBPS~^vk8+{~H($ Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 zcmeHM`BzhCx5m=ewhkC+oexe)B1sS6yLPdqDAjlL25dw?c&|DsgXwtEIHz6}25r2RMW zFZ!D+6eT6}$CJ3@_Tf+XoTyhxLek2D%HFiY{H)_qTeY31;?0L1ZtT7^y+wVOji=_h z+~+_1(dA^iBYs!cmUz^T>PvoSzezljmv=VnXWy0|JAX6C&!%17qE?Xgt-JEy+8A-% zP1lc7X)IUA+j#26w!;e#KMK<>-!7HTm9|@?iUotRrzB?bIUA+>ZI|bTUnq6x-TL}h zn`i6(*gB0b(jklrZ6nGwM8XnCZk7dTcgjy%g;mg|yNSqp1R?O`Tw#;I5hnM4?69aci* z^-aUUTq9>_3p8EQjCw22Xb`PfKsWt$OL;smJMo2E=y-yW6|P>5P>nis=FEI=*1B(N z6zj1o$LEGC4KSF!woi71afps);^E+hTPo2%VnkLG8AD}$j@66JTE&6mjbUbV?iJ;) zH&WIqf8FCIT4d=q?eu=~Q`@m9T0&>Gbt+{)n?WE%1zvpZb9SfUb*F2-z6Vifon2g5 ztwvl|qt=IZ78iA@KfN6~+}hgewJ_E&@xWT+fVQ^w9*hm`+=0Tv!ihA$q66xcvo8v4 z6ml7hN?se+zwzO(ttgsz`%|x;ytDk2nO;Y{cCrl(hr3JZ%C*Z}e2135dhYdtsgO%q z_kXhVT{4y}ck=?UVS?eVIB{Q|azC%;nkMg5kg>by`vldpZuk<}ou^>dh~9k6vi- z63+IPv4^APYiN#icQ0rIaY@3ild*%|?@0||4x5I~?SHt>P6z6h=ij8~+1OLWt%(X- zZ40YCA1R!5sEYaY;f>c|iP!oX3se1|kADwE`a-?75KR;T= z!|Srk{R_^V=j0Nq_~l*0!>;Lq*#f!_p-vW>%c_fBUDOYq8yGV2hX<>eeLBXMc(GEF zpIhGPOCkJqbKOL(RG@{_Loi%fHX{-7UG|NH3 zD;}0h1KgI!2B&aIsqT^ahuo_xPCwSC*&j^q>gqa|bNcZ)jmYWep^OO0OfLM*f%7>% zZZT3m0|sfscAZ0n8sUX&0RcJycGuw6BVLm&kG``1NxE$9pi%AKt{WIAL739NP?M z0FF)jQ*&04Th%W2%nYPIkM%sQD#asFthzN;T@q!#Vy#^J)P3HRO#jSuN>C^rWEa zTN;ObewzX6&+RudGU`vE&ECPTU4}z*6*4X@sj<&HyY1+BJPiNvj1Z{Zj#K;Fk?56A zw#pn)?-fnyS{LmUfUnrIsqaki3@sI)3(eN1&9QUY@|;&54fmi~K=t#>dN|zbq*2}A zD6#ji5t5igOeHVG;iIi@jd10>N~Xg7#+$E*-A77fD!uY1;sKiIvz|E0VqWX*67G~_oCKhX>Dq%->wz5|4 z+8IuJ?AM#_0bw_lc(Ht!NFaY6qi_FReEHq&{j|?Ah(aMVX2}X!e80~cS3ljEJzl-k z7H3DAJzpl~XkxbMxbCQl5+|BRiRxLpb*@pv$u@f~6uAy|n2l)TSDPKjK!>T`nIuDt zg}Zk3_kPgw0ICcG(!kZ%`Y*$Yn1ayE)fR6I#-lUqMBvc1T=hj6qrAM4ET1Ab9_e>g z_0Aw(SR2fgT@2y%X=5-z$EdjNx`}UM*EbG(_6N`%2ts3>fA_)@EpX-(?}-RdoO)(Td{Hp; zd#oW&6EoIx-Y#K+mD+TBlbQ#Bx|k#tQ7BGTq5e;9y!{qc92U}sUu(ekOp-zVPSlU6 zw3~JVFRlo}>eN867aq4q=Lkxl&2A~46%|U}Zm->9;N3PC@^pRB&%9MF{HhIKkr1G; zA`)F4Gf1LAVpA|tcAZ_WjS=ycmDg{6S;aA)dZMan6+azh%pAJf%db-yPJ=Sw=Gb>e z@^zHm+}<>y$&`EOTxK9gAsA9xYz9h z=QBJgl=J}R0jtE_J5f=NSFD-NIi+nTT6^uD?uHRfoR1M#p-_SEHW}6T*NVz01Ez)% z;6#Y%5r>1xM;HIr^y6g}L{z)n-net?R3tOZ`)Q(P$SNd)CfX* zM<1+@x<5x4t_Um!+1!DRiH=8OEh0s8I}sbTamUX;_PxBQib9>wIei!5_JH&G_F0vc z#)%e^X^PKvl7PS;DqCWQMh=a($71D;N+>hBcOOG}72bb4BB=?Hv!KGmFq?s6clR}dchkfSZ6cfZ zsHCZcnlsFE)Hw14pN|zqCUdVO*wF!U#s!zo(0Y4otdXx`)saEAw!EL7mbQ5%Yy48k z>Yan+l{@4eC=^yY*zN%|1sst$WWWa2^X=ZW>+t@kQG!aR${?@PPmbydU~nuW4e1t3 zvp%0v%?^n z#eV%o6;eT!=im2#3T)A9LdNg4q99{7vcT5SVe_K2IMHM_uVNy_#bc^1V_{{SYE9KJ z9}CWuy5exl)18_a3}Q^3iakysPZ7-xs-_ze5>Lc!Yrrdncx#*|FPIFTAORp@p+Q>i z+)wK%7Xk>YTt^c7tcB0`Hk#6CVuo#|g}FOfCLCZA{ruBX@iD9F)Mn#}r1N8=chHGv z(hVCxxzHQ79ijl8m|;ZbC`Wvuqxvk`Sba7!(I;9OHgPGDXJw!Hq=24RAi{g!dBK!1 zt`Bwzn0z*)&M&)!xiLgo_1BNLqRjL+tpg3U=+&aqaUC;4{ZO(Nd>qVF5mYLZO{Xg?x~? z_CB-zk3FZdqQruQQ5A9pr$Aygxey@fO0E6grTqF`2oQQtBPJiOc&-Y++ z;<1u!>^&P=*Qi!~?8QunUc4q-{-P43d`RB$c$WPG)m-GnnJD#c7c5$6?qW|B(2G*F zUACcR)7ImbXvuGuOBPNbe^i9;uH|N2h#I5oaFM8C#3#%8$;6{n896o*=3zF*qDQzJ zwTYmhP!P_(KzHYi67K}LqJI_gX)-iVF0Ft5B0vE;)bAz-w~s-N3sGghSr)RotqGiy zCq$5i)poMQ-qVk^z;BBE+2!H0`MFsHZf&4w(7OrZqZ;y3larI-;0MRR<-}9+WF{+8 zAYsUM9zY3*3^X9!Ln~IAOl#VM+l==WRSB3!NB^bjF;X27IF%9B2qs``I-8oPVIGMC zZ-;<9uG*bPOg77y^T-U7OHt z0%J2)nkl;;uVHQyxjc>H_0y?M8c~ND6V$dN0-CxMZX^_NOe4htb?!=|QP#Qqo*Mo9 zfb0!A6+af$$t6eSE92wa>sC&RS>E1wS2OV_2BdVSo!J5oiCC!4 z0m(3T&^uGz;A#s+i(akYX13H4KKk$NV?gu);tTKPa>2Y@2S}UVfL(ist4oNgl+-7$zwe$%xkC13+q z5aP^z5@Z8ogjyI5Zf`&m=Z+)3_BdR$AUHD)#PsLeDrgPJ(F)XO#KFr^Pd?mRmhQ-{ zC3zyCSNY-rzYd}h63I_zn3M_3=UyNJ{Cu2w7A+lU%7Vxk z$RYMOSX(SUI4bNp?+Nxm?`q2<%QUY11sg(ZQJg2$+z@=9YPzBSJ)|6%EWuYjrd=Y{ zcWsTw+rc9nHWo|h&@2n>r%13Of`n@;ZGvW`iDt(Rn8Wm{ei|Sk_^{;z-^&PPObh#R&%}Q`>vnk&2__9&cUNZQf zL-ZPv^SdRV0uz&G{QJ*~tbTh99-9H734|vg%*~oTz1(d_Fxp$nOej#anTv-P2is%H zqenO3r`)kdq`QCr3YIx?^a6iZz<_+R!aHIJoMkCmDt9<M5I3k6@ZVQKxkcd=bEnMsL^K|8Xf zJCrIb>vu2ML@f~b0d)6+J@Q&xS;)|_&St;N=5v9$LdT1_cc&x&AdN&XwU&f0jFtHI zz6gz-_v(XjD0u0@C}I_-!GcK(9HZdIQMYWEWcxcW%G7fhbx0yUGcG^?6$x1`Ioaw+{NNsRxm$Y8>t& zGvwMkhH-}lz6|8QBxI(m1TulGxZe_SJ0q+S}zB(@)TeCuJaKy^!9TsF7MKW0WR9Ftw;cz<-`*cLr_|)lVd?l$V2WJuH zb$WR|ONSdfSrZ0(nEK($V9%9Lqx=!N(>)Ltdd%F$VE!q2om3S4aH%>Y;99%IKA2%X5RJhH2A`-nI-z0^ew2o0@#OW~iBdM`s zb@H=m)AMRha;k|W@~cwU9sQCvXBx+c2|q90Mzd;n9tKHBOHXGds+;!X<=0PvxpS^iGxl3RL=Rb|#P2pP5xhPI_JO<$@|B!uR;RSq_d?3-Ecb5q z|M;>27%HAZ+Lw5p2L#VwUh`B6DeRt)4T$O+GiWz6V1~_)2TtxB-q;k1$dl+D#aGeIRsxL}r`1^F!D#x%6jh=6(wll;)U( zN2emMikN+{B{QE+cf!u(9wNwXrt{T9-)vDbaoiAM?ag}xd#2AW37P{-nuZ@7d*lkb zLPAH(=_5mb=hNwi+6R+uV6B9KMVT71C-P?^s{j-VF{fd&<4S9WzSC7lLj}V-3oaDi z6W7d>zykG*=Eoul4*3YyxwV3KjpZWy^@t1y5+49zW-M`dBPS~^vk8+{~H($ Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..313517a0d7fc91831689f730d4087584b30ed635 GIT binary patch literal 8250 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 Be(eAN literal 0 HcmV?d00001 From 6606fb6e279963254592daecd91d6b9b40ee4d23 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Fri, 30 Sep 2022 07:50:18 +0000 Subject: [PATCH 470/498] Committing clang-format changes --- bridge/bindings/qjs/binding_initializer.cc | 2 +- bridge/bindings/qjs/js_based_event_listener.h | 2 +- bridge/bindings/qjs/script_wrappable.h | 2 +- bridge/core/dom/character_data.h | 2 +- bridge/core/dom/document.h | 2 +- bridge/core/dom/element_data.h | 2 -- bridge/core/dom/events/event_target.cc | 2 +- bridge/core/dom/legacy/element_attributes.cc | 3 +-- bridge/core/dom/legacy/element_attributes.h | 2 +- bridge/core/dom/node.cc | 2 +- bridge/core/html/canvas/html_canvas_element.h | 2 +- bridge/core/html/custom/widget_element.h | 2 +- bridge/core/html/forms/html_button_element.cc | 2 +- bridge/core/html/forms/html_button_element.h | 3 +-- bridge/core/html/forms/html_input_element.h | 2 +- bridge/core/html/forms/html_textarea_element.h | 2 +- bridge/core/html/html_body_element.h | 2 +- bridge/core/html/html_image_element.h | 2 +- bridge/core/html/html_link_element.cc | 10 +++++----- bridge/core/html/html_link_element.h | 13 ++++++------- bridge/core/html/html_script_element.cc | 2 +- bridge/core/html/html_script_element.h | 2 +- bridge/core/html/html_template_element.h | 2 +- bridge/core/script_state.cc | 2 +- bridge/test/webf_test_env.h | 2 +- 25 files changed, 33 insertions(+), 38 deletions(-) diff --git a/bridge/bindings/qjs/binding_initializer.cc b/bridge/bindings/qjs/binding_initializer.cc index b2561c8899..ffa9d6de20 100644 --- a/bridge/bindings/qjs/binding_initializer.cc +++ b/bridge/bindings/qjs/binding_initializer.cc @@ -37,8 +37,8 @@ #include "qjs_html_html_element.h" #include "qjs_html_image_element.h" #include "qjs_html_input_element.h" -#include "qjs_html_script_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" diff --git a/bridge/bindings/qjs/js_based_event_listener.h b/bridge/bindings/qjs/js_based_event_listener.h index dda4ff14bc..a5965e6a9e 100644 --- a/bridge/bindings/qjs/js_based_event_listener.h +++ b/bridge/bindings/qjs/js_based_event_listener.h @@ -7,9 +7,9 @@ #define BRIDGE_BINDINGS_QJS_JS_BASED_EVENT_LISTENER_H_ #include -#include "foundation/casting.h" #include "core/dom/events/event_listener.h" #include "core/executing_context.h" +#include "foundation/casting.h" namespace webf { diff --git a/bridge/bindings/qjs/script_wrappable.h b/bridge/bindings/qjs/script_wrappable.h index 03431c3dad..f4462d8e2e 100644 --- a/bridge/bindings/qjs/script_wrappable.h +++ b/bridge/bindings/qjs/script_wrappable.h @@ -8,9 +8,9 @@ #include #include "bindings/qjs/cppgc/garbage_collected.h" +#include "core/executing_context.h" #include "foundation/macros.h" #include "wrapper_type_info.h" -#include "core/executing_context.h" namespace webf { diff --git a/bridge/core/dom/character_data.h b/bridge/core/dom/character_data.h index 30658bb613..b7e99ebc69 100644 --- a/bridge/core/dom/character_data.h +++ b/bridge/core/dom/character_data.h @@ -24,7 +24,7 @@ class CharacterData : public Node { bool IsCharacterDataNode() const override; void setNodeValue(const AtomicString&, ExceptionState&) override; - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; protected: CharacterData(TreeScope& tree_scope, const AtomicString& text, ConstructionType type); diff --git a/bridge/core/dom/document.h b/bridge/core/dom/document.h index 6efa5baf1a..217a53b302 100644 --- a/bridge/core/dom/document.h +++ b/bridge/core/dom/document.h @@ -99,7 +99,7 @@ class Document : public ContainerNode, public TreeScope { ExceptionState& exception_state); std::shared_ptr GetWindowAttributeEventListener(const AtomicString& event_type); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; void Trace(GCVisitor* visitor) const override; diff --git a/bridge/core/dom/element_data.h b/bridge/core/dom/element_data.h index 90f41b5502..3787289a41 100644 --- a/bridge/core/dom/element_data.h +++ b/bridge/core/dom/element_data.h @@ -14,8 +14,6 @@ class ElementData { public: void CopyWith(ElementData* other); - - private: AtomicString class_; }; diff --git a/bridge/core/dom/events/event_target.cc b/bridge/core/dom/events/event_target.cc index fc2087e474..f98f15ff4c 100644 --- a/bridge/core/dom/events/event_target.cc +++ b/bridge/core/dom/events/event_target.cc @@ -6,10 +6,10 @@ #include #include "binding_call_methods.h" #include "bindings/qjs/converter_impl.h" -#include "qjs_event_target.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 diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index 4d2136b627..e86f35e5ab 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -18,8 +18,7 @@ static inline bool IsNumberIndex(const StringView& name) { return f >= '0' && f <= '9'; } -ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()), element_(element) { -} +ElementAttributes::ElementAttributes(Element* element) : ScriptWrappable(element->ctx()), element_(element) {} AtomicString ElementAttributes::getAttribute(const AtomicString& name, ExceptionState& exception_state) { bool numberIndex = IsNumberIndex(name.ToStringView()); diff --git a/bridge/core/dom/legacy/element_attributes.h b/bridge/core/dom/legacy/element_attributes.h index 72e42dcf45..fcb16f6c9c 100644 --- a/bridge/core/dom/legacy/element_attributes.h +++ b/bridge/core/dom/legacy/element_attributes.h @@ -7,8 +7,8 @@ #define BRIDGE_CORE_DOM_LEGACY_ELEMENT_ATTRIBUTES_H_ #include -#include "bindings/qjs/cppgc/member.h" #include "bindings/qjs/atomic_string.h" +#include "bindings/qjs/cppgc/member.h" #include "bindings/qjs/script_wrappable.h" #include "space_split_string.h" diff --git a/bridge/core/dom/node.cc b/bridge/core/dom/node.cc index 7cd158f644..f1156b15a5 100644 --- a/bridge/core/dom/node.cc +++ b/bridge/core/dom/node.cc @@ -13,8 +13,8 @@ #include "empty_node_list.h" #include "node_data.h" #include "node_traversal.h" -#include "text.h" #include "qjs_node.h" +#include "text.h" namespace webf { diff --git a/bridge/core/html/canvas/html_canvas_element.h b/bridge/core/html/canvas/html_canvas_element.h index 85891ea99a..32210e2ed1 100644 --- a/bridge/core/html/canvas/html_canvas_element.h +++ b/bridge/core/html/canvas/html_canvas_element.h @@ -18,7 +18,7 @@ class HTMLCanvasElement : public HTMLElement { CanvasRenderingContext* getContext(const AtomicString& type, ExceptionState& exception_state) const; - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; }; } // namespace webf diff --git a/bridge/core/html/custom/widget_element.h b/bridge/core/html/custom/widget_element.h index 3ba5f39aa5..f240240703 100644 --- a/bridge/core/html/custom/widget_element.h +++ b/bridge/core/html/custom/widget_element.h @@ -35,7 +35,7 @@ class WidgetElement : public HTMLElement { void CloneNonAttributePropertiesFrom(const Element&, CloneChildrenFlag) override; void Trace(GCVisitor* visitor) const override; - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: std::unordered_map unimplemented_properties_; diff --git a/bridge/core/html/forms/html_button_element.cc b/bridge/core/html/forms/html_button_element.cc index 95f07f7c01..198e6895f4 100644 --- a/bridge/core/html/forms/html_button_element.cc +++ b/bridge/core/html/forms/html_button_element.cc @@ -12,4 +12,4 @@ bool HTMLButtonElement::IsAttributeDefinedInternal(const AtomicString& key) cons return QJSHTMLButtonElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); } -} \ No newline at end of file +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/forms/html_button_element.h b/bridge/core/html/forms/html_button_element.h index 5904b07a1d..8b4bc65083 100644 --- a/bridge/core/html/forms/html_button_element.h +++ b/bridge/core/html/forms/html_button_element.h @@ -14,8 +14,7 @@ class HTMLButtonElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); public: - - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: }; diff --git a/bridge/core/html/forms/html_input_element.h b/bridge/core/html/forms/html_input_element.h index 6c4c286269..835f436f22 100644 --- a/bridge/core/html/forms/html_input_element.h +++ b/bridge/core/html/forms/html_input_element.h @@ -15,7 +15,7 @@ class HTMLInputElement : public HTMLElement { public: explicit HTMLInputElement(Document&); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; }; } // namespace webf diff --git a/bridge/core/html/forms/html_textarea_element.h b/bridge/core/html/forms/html_textarea_element.h index 6a1927bf44..91469be3e1 100644 --- a/bridge/core/html/forms/html_textarea_element.h +++ b/bridge/core/html/forms/html_textarea_element.h @@ -15,7 +15,7 @@ class HTMLTextareaElement : public HTMLElement { public: explicit HTMLTextareaElement(Document&); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; }; } // namespace webf diff --git a/bridge/core/html/html_body_element.h b/bridge/core/html/html_body_element.h index 8da098e86e..23589f4375 100644 --- a/bridge/core/html/html_body_element.h +++ b/bridge/core/html/html_body_element.h @@ -18,7 +18,7 @@ class HTMLBodyElement : public HTMLElement { using ImplType = HTMLBodyElement*; explicit HTMLBodyElement(Document&); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(blur, kblur); DEFINE_WINDOW_ATTRIBUTE_EVENT_LISTENER(error, kerror); diff --git a/bridge/core/html/html_image_element.h b/bridge/core/html/html_image_element.h index 83c9a86df2..5b85deb40f 100644 --- a/bridge/core/html/html_image_element.h +++ b/bridge/core/html/html_image_element.h @@ -16,7 +16,7 @@ class HTMLImageElement : public HTMLElement { using ImplType = HTMLImageElement*; explicit HTMLImageElement(Document& document); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; bool KeepAlive() const override; diff --git a/bridge/core/html/html_link_element.cc b/bridge/core/html/html_link_element.cc index 9f9979d652..6459ca87ed 100644 --- a/bridge/core/html/html_link_element.cc +++ b/bridge/core/html/html_link_element.cc @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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 "html_link_element.h" #include "html_names.h" @@ -9,10 +9,10 @@ namespace webf { -HTMLLinkElement::HTMLLinkElement(Document& document): HTMLElement(html_names::klink, &document) {} +HTMLLinkElement::HTMLLinkElement(Document& document) : HTMLElement(html_names::klink, &document) {} bool HTMLLinkElement::IsAttributeDefinedInternal(const AtomicString& key) const { return QJSHTMLLinkElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); } -} \ No newline at end of file +} // namespace webf \ No newline at end of file diff --git a/bridge/core/html/html_link_element.h b/bridge/core/html/html_link_element.h index 834355e84a..04800fdb82 100644 --- a/bridge/core/html/html_link_element.h +++ b/bridge/core/html/html_link_element.h @@ -1,7 +1,7 @@ /* -* Copyright (C) 2019-2022 The Kraken authors. All rights reserved. -* Copyright (C) 2022-present The WebF 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_CORE_HTML_HTML_LINK_ELEMENT_H_ #define WEBF_CORE_HTML_HTML_LINK_ELEMENT_H_ @@ -12,16 +12,15 @@ namespace webf { class HTMLLinkElement : public HTMLElement { DEFINE_WRAPPERTYPEINFO(); + public: explicit HTMLLinkElement(Document& document); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: - }; -} - +} // namespace webf #endif // WEBF_CORE_HTML_HTML_LINK_ELEMENT_H_ diff --git a/bridge/core/html/html_script_element.cc b/bridge/core/html/html_script_element.cc index 6bdd5dfc83..42a564f412 100644 --- a/bridge/core/html/html_script_element.cc +++ b/bridge/core/html/html_script_element.cc @@ -4,8 +4,8 @@ */ #include "html_script_element.h" #include "html_names.h" -#include "script_type_names.h" #include "qjs_html_script_element.h" +#include "script_type_names.h" namespace webf { diff --git a/bridge/core/html/html_script_element.h b/bridge/core/html/html_script_element.h index 62fb7d094d..c02142cb69 100644 --- a/bridge/core/html/html_script_element.h +++ b/bridge/core/html/html_script_element.h @@ -17,7 +17,7 @@ class HTMLScriptElement : public HTMLElement { explicit HTMLScriptElement(Document& document); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: }; diff --git a/bridge/core/html/html_template_element.h b/bridge/core/html/html_template_element.h index cc43afabf8..da93a6b275 100644 --- a/bridge/core/html/html_template_element.h +++ b/bridge/core/html/html_template_element.h @@ -19,7 +19,7 @@ class HTMLTemplateElement : public HTMLElement { DocumentFragment* content() const; - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: DocumentFragment* ContentInternal() const; diff --git a/bridge/core/script_state.cc b/bridge/core/script_state.cc index 4f4ae41474..6a1b9cb125 100644 --- a/bridge/core/script_state.cc +++ b/bridge/core/script_state.cc @@ -3,10 +3,10 @@ * 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" -#include "defined_properties_initializer.h" namespace webf { diff --git a/bridge/test/webf_test_env.h b/bridge/test/webf_test_env.h index 566e1aa970..96262e5ecf 100644 --- a/bridge/test/webf_test_env.h +++ b/bridge/test/webf_test_env.h @@ -7,9 +7,9 @@ #define BRIDGE_TEST_WEBF_TEST_ENV_H_ #include +#include "bindings/qjs/cppgc/mutation_scope.h" #include "core/dart_methods.h" #include "core/executing_context.h" -#include "bindings/qjs/cppgc/mutation_scope.h" #include "core/page.h" #include "foundation/logging.h" From 68dd213e3b4288e283c96c65b977669d0dc359b5 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 30 Sep 2022 19:01:58 +0800 Subject: [PATCH 471/498] fix: fix ui command buffer flush crash when hot restart. --- bridge/foundation/ui_command_buffer.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index 4e3b2af719..cad00dbea2 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -46,12 +46,16 @@ void UICommandBuffer::addCommand(int32_t id, void UICommandBuffer::addCommand(const UICommandItem& item) { if (size_ >= MAXIMUM_UI_COMMAND_SIZE) { - context_->FlushUICommand(); + if (UNLIKELY(isDartHotRestart())) { + clear(); + } else { + context_->FlushUICommand(); + } assert(size_ == 0); } #if FLUTTER_BACKEND - if (!update_batched_ && context_->IsContextValid() && context_->dartMethodPtr()->requestBatchUpdate != nullptr) { + if (UNLIKELY(!update_batched_ && context_->IsContextValid() && context_->dartMethodPtr()->requestBatchUpdate != nullptr)) { context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); update_batched_ = true; } From 811cc27f2c2bb450c1b9a7f942fd2cfc95ca8d54 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Fri, 30 Sep 2022 11:03:15 +0000 Subject: [PATCH 472/498] Committing clang-format changes --- bridge/foundation/ui_command_buffer.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bridge/foundation/ui_command_buffer.cc b/bridge/foundation/ui_command_buffer.cc index cad00dbea2..6bd6f9cbad 100644 --- a/bridge/foundation/ui_command_buffer.cc +++ b/bridge/foundation/ui_command_buffer.cc @@ -55,7 +55,8 @@ void UICommandBuffer::addCommand(const UICommandItem& item) { } #if FLUTTER_BACKEND - if (UNLIKELY(!update_batched_ && context_->IsContextValid() && context_->dartMethodPtr()->requestBatchUpdate != nullptr)) { + if (UNLIKELY(!update_batched_ && context_->IsContextValid() && + context_->dartMethodPtr()->requestBatchUpdate != nullptr)) { context_->dartMethodPtr()->requestBatchUpdate(context_->contextId()); update_batched_ = true; } From 53f1aed6cbf255bd9c70553677b1f17f7f22dcd7 Mon Sep 17 00:00:00 2001 From: andycall Date: Sat, 1 Oct 2022 14:02:39 +0800 Subject: [PATCH 473/498] fix: fix missing isAttributeDefined on element. --- bridge/core/html/html_div_element.cc | 5 +++++ bridge/core/html/html_div_element.h | 2 ++ bridge/core/html/html_head_element.cc | 5 +++++ bridge/core/html/html_head_element.h | 2 ++ bridge/core/html/html_html_element.cc | 5 +++++ bridge/core/html/html_html_element.h | 2 ++ bridge/core/html/html_unknown_element.cc | 5 +++++ bridge/core/html/html_unknown_element.h | 2 ++ bridge/core/html/image.cc | 5 +++++ bridge/core/html/image.h | 2 ++ 10 files changed, 35 insertions(+) diff --git a/bridge/core/html/html_div_element.cc b/bridge/core/html/html_div_element.cc index f0bd24d58e..0f473324f7 100644 --- a/bridge/core/html/html_div_element.cc +++ b/bridge/core/html/html_div_element.cc @@ -5,9 +5,14 @@ #include "html_div_element.h" #include "html_names.h" +#include "qjs_html_div_element.h" namespace webf { HTMLDivElement::HTMLDivElement(Document& document) : HTMLElement(html_names::kdiv, &document) {} +bool HTMLDivElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLDivElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h index a58fa60e2c..ddb532133b 100644 --- a/bridge/core/html/html_div_element.h +++ b/bridge/core/html/html_div_element.h @@ -17,6 +17,8 @@ class HTMLDivElement : public HTMLElement { using ImplType = HTMLDivElement*; explicit HTMLDivElement(Document&); + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + private: }; diff --git a/bridge/core/html/html_head_element.cc b/bridge/core/html/html_head_element.cc index 2c649bddf9..83bdb095a4 100644 --- a/bridge/core/html/html_head_element.cc +++ b/bridge/core/html/html_head_element.cc @@ -4,9 +4,14 @@ */ #include "html_head_element.h" #include "html_names.h" +#include "qjs_html_head_element.h" namespace webf { HTMLHeadElement::HTMLHeadElement(Document& document) : HTMLElement(html_names::khead, &document) {} +bool HTMLHeadElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLHeadElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_head_element.h b/bridge/core/html/html_head_element.h index ff5edf81ba..c6143d567f 100644 --- a/bridge/core/html/html_head_element.h +++ b/bridge/core/html/html_head_element.h @@ -16,6 +16,8 @@ class HTMLHeadElement : public HTMLElement { using ImplType = HTMLHeadElement*; explicit HTMLHeadElement(Document&); + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + private: }; diff --git a/bridge/core/html/html_html_element.cc b/bridge/core/html/html_html_element.cc index cb3fd5e995..a89fd91076 100644 --- a/bridge/core/html/html_html_element.cc +++ b/bridge/core/html/html_html_element.cc @@ -4,9 +4,14 @@ */ #include "html_html_element.h" #include "html_names.h" +#include "qjs_html_html_element.h" namespace webf { HTMLHtmlElement::HTMLHtmlElement(Document& document) : HTMLElement(html_names::khtml, &document) {} +bool HTMLHtmlElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLHtmlElement::IsAttributeDefinedInternal(key) || HTMLHtmlElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_html_element.h b/bridge/core/html/html_html_element.h index e2ab318340..475eb90f04 100644 --- a/bridge/core/html/html_html_element.h +++ b/bridge/core/html/html_html_element.h @@ -16,6 +16,8 @@ class HTMLHtmlElement : public HTMLElement { using ImplType = HTMLHtmlElement*; explicit HTMLHtmlElement(Document&); + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + private: }; diff --git a/bridge/core/html/html_unknown_element.cc b/bridge/core/html/html_unknown_element.cc index dfe858b782..f378590723 100644 --- a/bridge/core/html/html_unknown_element.cc +++ b/bridge/core/html/html_unknown_element.cc @@ -3,10 +3,15 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ #include "html_unknown_element.h" +#include "qjs_html_unknown_element.h" namespace webf { HTMLUnknownElement::HTMLUnknownElement(const AtomicString& tag_name, Document& document) : HTMLElement(tag_name, &document) {} +bool HTMLUnknownElement::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSHTMLUnknownElement::IsAttributeDefinedInternal(key) || HTMLElement::IsAttributeDefinedInternal(key); +} + } // namespace webf diff --git a/bridge/core/html/html_unknown_element.h b/bridge/core/html/html_unknown_element.h index c98ea6b9fb..102de07857 100644 --- a/bridge/core/html/html_unknown_element.h +++ b/bridge/core/html/html_unknown_element.h @@ -15,6 +15,8 @@ class HTMLUnknownElement : public HTMLElement { public: explicit HTMLUnknownElement(const AtomicString&, Document& document); + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + private: }; diff --git a/bridge/core/html/image.cc b/bridge/core/html/image.cc index 7fae11aa82..385ae3fd3a 100644 --- a/bridge/core/html/image.cc +++ b/bridge/core/html/image.cc @@ -3,6 +3,7 @@ */ #include "image.h" +#include "qjs_image.h" namespace webf { @@ -12,4 +13,8 @@ Image* Image::Create(ExecutingContext* context, ExceptionState& exception_state) Image::Image(ExecutingContext* context, ExceptionState& exception_state) : HTMLImageElement(*context->document()) {} +bool Image::IsAttributeDefinedInternal(const AtomicString& key) const { + return QJSImage::IsAttributeDefinedInternal(key) || HTMLImageElement::IsAttributeDefinedInternal(key); +} + } // namespace webf \ No newline at end of file diff --git a/bridge/core/html/image.h b/bridge/core/html/image.h index d80200f9e0..69ce0729d8 100644 --- a/bridge/core/html/image.h +++ b/bridge/core/html/image.h @@ -17,6 +17,8 @@ class Image : public HTMLImageElement { explicit Image(ExecutingContext* context, ExceptionState& exception_state); + bool IsAttributeDefinedInternal(const AtomicString &key) const override; + private: }; From 150aa245b0fd3066d012c1a1ad072b7de2499dd1 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Sat, 1 Oct 2022 06:03:52 +0000 Subject: [PATCH 474/498] Committing clang-format changes --- bridge/core/html/html_div_element.h | 2 +- bridge/core/html/html_head_element.h | 2 +- bridge/core/html/html_html_element.h | 2 +- bridge/core/html/html_unknown_element.h | 2 +- bridge/core/html/image.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bridge/core/html/html_div_element.h b/bridge/core/html/html_div_element.h index ddb532133b..be766ad08d 100644 --- a/bridge/core/html/html_div_element.h +++ b/bridge/core/html/html_div_element.h @@ -17,7 +17,7 @@ class HTMLDivElement : public HTMLElement { using ImplType = HTMLDivElement*; explicit HTMLDivElement(Document&); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: }; diff --git a/bridge/core/html/html_head_element.h b/bridge/core/html/html_head_element.h index c6143d567f..6213266a8d 100644 --- a/bridge/core/html/html_head_element.h +++ b/bridge/core/html/html_head_element.h @@ -16,7 +16,7 @@ class HTMLHeadElement : public HTMLElement { using ImplType = HTMLHeadElement*; explicit HTMLHeadElement(Document&); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: }; diff --git a/bridge/core/html/html_html_element.h b/bridge/core/html/html_html_element.h index 475eb90f04..475106e2c1 100644 --- a/bridge/core/html/html_html_element.h +++ b/bridge/core/html/html_html_element.h @@ -16,7 +16,7 @@ class HTMLHtmlElement : public HTMLElement { using ImplType = HTMLHtmlElement*; explicit HTMLHtmlElement(Document&); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: }; diff --git a/bridge/core/html/html_unknown_element.h b/bridge/core/html/html_unknown_element.h index 102de07857..3ead0006d1 100644 --- a/bridge/core/html/html_unknown_element.h +++ b/bridge/core/html/html_unknown_element.h @@ -15,7 +15,7 @@ class HTMLUnknownElement : public HTMLElement { public: explicit HTMLUnknownElement(const AtomicString&, Document& document); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: }; diff --git a/bridge/core/html/image.h b/bridge/core/html/image.h index 69ce0729d8..cdc62d02fc 100644 --- a/bridge/core/html/image.h +++ b/bridge/core/html/image.h @@ -17,7 +17,7 @@ class Image : public HTMLImageElement { explicit Image(ExecutingContext* context, ExceptionState& exception_state); - bool IsAttributeDefinedInternal(const AtomicString &key) const override; + bool IsAttributeDefinedInternal(const AtomicString& key) const override; private: }; From 91558aef34d0b60de5287e06f7525d21dc416233 Mon Sep 17 00:00:00 2001 From: andycall Date: Sat, 1 Oct 2022 17:21:47 +0800 Subject: [PATCH 475/498] revert: temporary disable ignore element attributes. --- bridge/core/dom/legacy/element_attributes.cc | 10 ++++------ integration_tests/specs/dom/nodes/query-selector.ts | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/bridge/core/dom/legacy/element_attributes.cc b/bridge/core/dom/legacy/element_attributes.cc index e86f35e5ab..2532b934dc 100644 --- a/bridge/core/dom/legacy/element_attributes.cc +++ b/bridge/core/dom/legacy/element_attributes.cc @@ -54,13 +54,11 @@ bool ElementAttributes::setAttribute(const AtomicString& name, attributes_[name] = value; - if (element_->IsAttributeDefinedInternal(name)) { - std::unique_ptr args_01 = name.ToNativeString(); - std::unique_ptr args_02 = value.ToNativeString(); + std::unique_ptr args_01 = name.ToNativeString(); + std::unique_ptr args_02 = value.ToNativeString(); - GetExecutingContext()->uiCommandBuffer()->addCommand(element_->eventTargetId(), UICommand::kSetAttribute, - std::move(args_01), std::move(args_02), nullptr); - } + GetExecutingContext()->uiCommandBuffer()->addCommand(element_->eventTargetId(), UICommand::kSetAttribute, + std::move(args_01), std::move(args_02), nullptr); return true; } diff --git a/integration_tests/specs/dom/nodes/query-selector.ts b/integration_tests/specs/dom/nodes/query-selector.ts index 26a88ef292..cbd78c83ac 100644 --- a/integration_tests/specs/dom/nodes/query-selector.ts +++ b/integration_tests/specs/dom/nodes/query-selector.ts @@ -115,7 +115,7 @@ describe('querySelector api', () => { expect(document.querySelectorAll('*').length).toBe(8); }); - xit('querySelectorAll work with query attr', () => { + it('querySelectorAll work with query attr', () => { ['red', 'black', 'green', 'yellow', 'blue'].forEach((item, index) => { const div = document.createElement('div') div.style.width = '100px'; @@ -468,7 +468,7 @@ describe('querySelector api', () => { expect(document.querySelector('a[href="openkraken.com"]')?.getAttribute('href')).toBe('openkraken.com'); }); - xit('querySelector work with attr', () => { + it('querySelector work with attr', () => { const container = document.createElement('div') container.appendChild(document.createTextNode('你好')) container.setAttribute('data-id', 'one') @@ -477,7 +477,7 @@ describe('querySelector api', () => { expect(document.querySelector('[data-id="one"]')?.getAttribute('data-id')).toBe('one'); }); - xit('querySelectorAll work with attr', () => { + it('querySelectorAll work with attr', () => { const container = document.createElement('div') container.appendChild(document.createTextNode('你好')) container.setAttribute('data-id', 'one') From 18f9c24fd9cc765bf865215fb4db6735f97234df Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 2 Oct 2022 19:47:16 +0800 Subject: [PATCH 476/498] fix: animation-duration should reset to zero if negative. --- .../animation-duration-003-manual.html | 5 ++-- webf/lib/src/css/css_animation.dart | 2 +- webf/lib/src/css/transition.dart | 2 +- webf/lib/src/css/values/time.dart | 27 ++++++++++++++++--- 4 files changed, 28 insertions(+), 8 deletions(-) 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/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index 0cb0426d63..f28ddc6813 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -197,7 +197,7 @@ mixin CSSAnimationMixin on RenderStyle { final fillMode = camelize(_getSingleString(animationFillMode, i)); EffectTiming? options = EffectTiming( - duration: CSSTime.parseTime(duration)!.toDouble(), + duration: CSSTime.parseNotNegativeTime(duration)!.toDouble(), easing: timingFunction, delay: CSSTime.parseTime(delay)!.toDouble(), fill: FillMode.values.firstWhere((element) { diff --git a/webf/lib/src/css/transition.dart b/webf/lib/src/css/transition.dart index c3858d80d7..3254b6659b 100644 --- a/webf/lib/src/css/transition.dart +++ b/webf/lib/src/css/transition.dart @@ -385,7 +385,7 @@ mixin CSSTransitionMixin on RenderStyle { // [duration, function, delay] if (transitionOptions != null) { return EffectTiming( - duration: CSSTime.parseTime(transitionOptions[0])!.toDouble(), + duration: CSSTime.parseNotNegativeTime(transitionOptions[0])!.toDouble(), easing: transitionOptions[1], delay: CSSTime.parseTime(transitionOptions[2])!.toDouble(), // In order for CSS Transitions to be seeked backwards, they need to have their fill mode set to backwards diff --git a/webf/lib/src/css/values/time.dart b/webf/lib/src/css/values/time.dart index 59f5828167..74dcabfe1f 100644 --- a/webf/lib/src/css/values/time.dart +++ b/webf/lib/src/css/values/time.dart @@ -19,16 +19,35 @@ class CSSTime { return (value == _0s || value == _0ms || _timeRegExp.firstMatch(value) != null); } - static int? parseTime(String input) { - if (_cachedParsedTime.containsKey(input)) { - return _cachedParsedTime[input]; - } + static int? _parseTimeValue(String input) { int? milliseconds; if (input.endsWith(MILLISECONDS)) { milliseconds = double.tryParse(input.split(MILLISECONDS)[0])!.toInt(); } else if (input.endsWith(SECOND)) { milliseconds = (double.tryParse(input.split(SECOND)[0])! * 1000).toInt(); } + return milliseconds; + } + + static int? parseTime(String input) { + if (_cachedParsedTime.containsKey(input)) { + return _cachedParsedTime[input]; + } + + int? milliseconds = _parseTimeValue(input); + + return _cachedParsedTime[input] = milliseconds; + } + + static int? parseNotNegativeTime(String input) { + if (_cachedParsedTime.containsKey(input)) { + return _cachedParsedTime[input]; + } + + int? milliseconds = _parseTimeValue(input); + if (milliseconds != null && milliseconds < 0) { + milliseconds = 0; + } return _cachedParsedTime[input] = milliseconds; } From ab43596cebead352cb43ea1f842212350f8111df Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 2 Oct 2022 20:40:48 +0800 Subject: [PATCH 477/498] fix: element's offsetParent should ended at documentElement. --- webf/lib/src/dom/element.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index d4191cbf82..4edb70946f 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1639,7 +1639,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element while (parent != null) { bool isNonStatic = parent.renderStyle.position != CSSPositionType.static; - if (parent is BodyElement || isNonStatic) { + if (parent == ownerDocument.documentElement || isNonStatic) { break; } parent = parent.parentElement; From 93fdd74dbc9ef7b3f3f7be9529bd44d02a1aa1b2 Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 2 Oct 2022 20:41:23 +0800 Subject: [PATCH 478/498] fix: parseHTML should include header and body. --- bridge/core/page.cc | 3 --- bridge/test/webf_test_context.cc | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bridge/core/page.cc b/bridge/core/page.cc index 0ddbcbdea6..48d234d0a0 100644 --- a/bridge/core/page.cc +++ b/bridge/core/page.cc @@ -50,9 +50,6 @@ bool WebFPage::parseHTML(const char* code, size_t length) { return false; } - // Remove all Nodes including body and head. - context_->document()->documentElement()->RemoveChildren(); - HTMLParser::parseHTML(code, length, context_->document()->documentElement()); return true; diff --git a/bridge/test/webf_test_context.cc b/bridge/test/webf_test_context.cc index 95f3050914..d6accbb227 100644 --- a/bridge/test/webf_test_context.cc +++ b/bridge/test/webf_test_context.cc @@ -6,6 +6,7 @@ #include "webf_test_context.h" #include "bindings/qjs/member_installer.h" #include "core/dom/document.h" +#include "core/html/html_html_element.h" #include "core/fileapi/blob.h" #include "core/html/html_body_element.h" #include "core/html/parser/html_parser.h" @@ -195,8 +196,7 @@ static JSValue parseHTML(JSContext* ctx, JSValueConst this_val, int argc, JSValu if (argc == 1) { std::string strHTML = AtomicString(ctx, argv[0]).ToStdString(); - auto* body = context->document()->body(); - HTMLParser::parseHTML(strHTML, body); + HTMLParser::parseHTML(strHTML, context->document()->documentElement()); } return JS_NULL; From 52c6f6ffa9cec67e7a14c5ce324a5b97ac7fb37e Mon Sep 17 00:00:00 2001 From: andycall Date: Sun, 2 Oct 2022 20:41:33 +0800 Subject: [PATCH 479/498] fix: fix animation test specs. --- integration_tests/scripts/html_loader.js | 6 +++++- .../animation-delay-001-manual.html | 19 +++++++++++++------ .../animation-delay-002-manual.html | 13 +++++++++---- .../animation-delay-003-manual.html | 11 +++++++---- .../animation-direction-001-manual.html | 15 ++++++++------- .../animation-direction-002-manual.html | 17 ++++++++++------- .../animation-direction-003-manual.html | 15 ++++++++++----- .../animation-direction-004-manual.html | 15 +++++++++------ .../animation-duration-001-manual.html | 2 +- .../animation-duration-002-manual.html | 13 ++++++++----- .../animation-duration-004-manual.html | 7 ++++--- .../animation-duration-005-manual.html | 10 ++++++---- .../animation-duration-006-manual.html | 2 +- 13 files changed, 91 insertions(+), 54 deletions(-) diff --git a/integration_tests/scripts/html_loader.js b/integration_tests/scripts/html_loader.js index 0ae1c7e683..3c18187b24 100644 --- a/integration_tests/scripts/html_loader.js +++ b/integration_tests/scripts/html_loader.js @@ -41,10 +41,14 @@ const loader = function(source) { // Set attr of HTML can let the case use fit. For example: xxx . let isFit = false; + let isXit = false; root.childNodes && root.childNodes.forEach(ele => { if (ele.rawAttrs && ele.rawAttrs.indexOf('fit') >= 0) { isFit = true; } + if (ele.rawAttrs && ele.rawAttrs.indexOf('xit') >= 0) { + isXit = true; + } }) const htmlString = root.toString().replace(/['\n]/g, function(c){ @@ -57,7 +61,7 @@ const loader = function(source) { const html_parse = () => __webf_parse_html__('${htmlString}'); var index = 0; const snapshotAction = async () => { await snapshot(null, '${snapshotFilepath}', ${scripts.length === 0 ? 'null' : 'index.toString()'}); index++; }; - ${isFit ? 'fit' : 'it'}("should work", async (done) => {\ + ${isFit ? 'fit' : isXit ? 'xit' : 'it'}("should work", async (done) => {\ html_parse();\ requestAnimationFrame(async () => { ${scripts.length === 0 ? `await snapshotAction();` : scripts.join('\n')} diff --git a/integration_tests/specs/css/css-animations/animation-delay-001-manual.html b/integration_tests/specs/css/css-animations/animation-delay-001-manual.html index 378a6ca421..b96f9b569e 100644 --- a/integration_tests/specs/css/css-animations/animation-delay-001-manual.html +++ b/integration_tests/specs/css/css-animations/animation-delay-001-manual.html @@ -24,15 +24,15 @@ #test-negative-delay { animation-name: test-negative-delay; - animation-duration: 10s; - animation-delay: -5s; + animation-duration: 6s; + animation-delay: -3s; background-color: blue; } #ref-no-delay { animation-name: ref-no-delay; - animation-duration: 5s; + animation-duration: 3s; background-color: yellow; } @@ -65,8 +65,15 @@
    Filler Text
    \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-delay-002-manual.html b/integration_tests/specs/css/css-animations/animation-delay-002-manual.html index 77821f8919..4a3e06cf84 100644 --- a/integration_tests/specs/css/css-animations/animation-delay-002-manual.html +++ b/integration_tests/specs/css/css-animations/animation-delay-002-manual.html @@ -30,11 +30,16 @@ which starts moving from right to left after about 5 seconds from the time the page is loaded.

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

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

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

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

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

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

    -
    Filler Text
    +
    Filler Text
    \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-duration-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 From 6ca0074d8e605c99588fe8a2e543d168665da18a Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Sun, 2 Oct 2022 12:42:21 +0000 Subject: [PATCH 480/498] Committing clang-format changes --- bridge/test/webf_test_context.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridge/test/webf_test_context.cc b/bridge/test/webf_test_context.cc index d6accbb227..f5fb478a6a 100644 --- a/bridge/test/webf_test_context.cc +++ b/bridge/test/webf_test_context.cc @@ -6,9 +6,9 @@ #include "webf_test_context.h" #include "bindings/qjs/member_installer.h" #include "core/dom/document.h" -#include "core/html/html_html_element.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" From 4444b912ea57684b545095c4bfcd1dd8ea68ea74 Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 4 Oct 2022 11:17:59 +0800 Subject: [PATCH 481/498] revert: ab43596cebead352cb43ea1f842212350f8111df --- webf/lib/src/dom/element.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 4edb70946f..d4191cbf82 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1639,7 +1639,7 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element while (parent != null) { bool isNonStatic = parent.renderStyle.position != CSSPositionType.static; - if (parent == ownerDocument.documentElement || isNonStatic) { + if (parent is BodyElement || isNonStatic) { break; } parent = parent.parentElement; From 478b1d579a6462e79a562341b7f626ea4de5d8cd Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 4 Oct 2022 20:08:40 +0800 Subject: [PATCH 482/498] test: fix test specs. --- .../specs/css/css-animations/animation-delay-001-manual.html | 4 ++-- .../css/css-animations/animation-direction-003-manual.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) 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 b96f9b569e..4665a12381 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 @@ -69,10 +69,10 @@ const negativeDelay = document.getElementById('test-negative-delay'); const ref = document.getElementById('ref-no-delay'); expect(negativeDelay.offsetLeft).toBe(ref.offsetLeft); - expect(negativeDelay.offsetLeft < 75 && negativeDelay.offsetLeft > 70).toBe(true); + expect(negativeDelay.offsetLeft < 100 && negativeDelay.offsetLeft > 70).toBe(true); await sleep(1); expect(negativeDelay.offsetLeft).toBe(ref.offsetLeft); - expect(negativeDelay.offsetLeft < 50 && negativeDelay.offsetLeft > 40).toBe(true); + expect(negativeDelay.offsetLeft < 60 && negativeDelay.offsetLeft > 20).toBe(true); await sleep(3); expect(negativeDelay.offsetLeft).toBe(ref.offsetLeft); expect(negativeDelay.offsetLeft === 0).toBe(true); 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 70333ac7bf..76039fc5ff 100644 --- a/integration_tests/specs/css/css-animations/animation-direction-003-manual.html +++ b/integration_tests/specs/css/css-animations/animation-direction-003-manual.html @@ -46,9 +46,9 @@ \ No newline at end of file From af4f7955b90f01b0da801a0d638b86a3895088c8 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Fri, 7 Oct 2022 10:05:37 +0800 Subject: [PATCH 484/498] fix: cache anmation keyframes --- webf/lib/src/css/css_animation.dart | 23 +------------------ webf/lib/src/css/css_rule.dart | 16 +++++++++++-- .../test/src/css/style_animations_parser.dart | 2 +- 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/webf/lib/src/css/css_animation.dart b/webf/lib/src/css/css_animation.dart index 477468a3aa..6fdde2b735 100644 --- a/webf/lib/src/css/css_animation.dart +++ b/webf/lib/src/css/css_animation.dart @@ -115,28 +115,7 @@ mixin CSSAnimationMixin on RenderStyle { List? _getKeyFrames(String animationName) { CSSKeyframesRule? cssKeyframesRule = target.ownerDocument.ruleSet.keyframesRules[animationName]; - if (cssKeyframesRule != null) { - List keyframes = []; - - cssKeyframesRule.blocks.forEach((rule) { - double? offset; - final keyText = rule.blockSelectors[0]; - if (keyText == 'from') { - offset = 0; - } else if (keyText == 'to') { - offset = 1; - } else { - offset = CSSPercentage.parsePercentage(keyText); - } - for (MapEntry entry in rule.declarations) { - final property = camelize(entry.key); - keyframes.add(Keyframe(property, entry.value, offset ?? 0, LINEAR)); - } - return; - }); - return keyframes; - } - return null; + return cssKeyframesRule?.keyframes; } void beforeRunningAnimation() { diff --git a/webf/lib/src/css/css_rule.dart b/webf/lib/src/css/css_rule.dart index fd5a1539ab..b0fd65d73d 100644 --- a/webf/lib/src/css/css_rule.dart +++ b/webf/lib/src/css/css_rule.dart @@ -38,7 +38,7 @@ class KeyFrameBlock { class CSSKeyframesRule extends CSSRule { final int _keyframeName; final String name; - final List blocks = []; + final List keyframes = []; @override int get type => CSSRule.KEYFRAMES_RULE; @@ -46,7 +46,19 @@ class CSSKeyframesRule extends CSSRule { CSSKeyframesRule(this._keyframeName, this.name) : super(); void add(KeyFrameBlock block) { - blocks.add(block); + double? offset; + final keyText = block.blockSelectors[0]; + if (keyText == 'from') { + offset = 0; + } else if (keyText == 'to') { + offset = 1; + } else { + offset = CSSPercentage.parsePercentage(keyText); + } + for (MapEntry entry in block.declarations) { + final property = camelize(entry.key); + keyframes.add(Keyframe(property, entry.value, offset ?? 0, LINEAR)); + } } String? get keyFrameName { diff --git a/webf/test/src/css/style_animations_parser.dart b/webf/test/src/css/style_animations_parser.dart index 6cd4cfe985..be618bee4a 100644 --- a/webf/test/src/css/style_animations_parser.dart +++ b/webf/test/src/css/style_animations_parser.dart @@ -15,7 +15,7 @@ void main() { test('0', () { CSSRule? style = parseSingleRule('@keyframes ping { 75%, 100% { transform: scale(2); opacity: 0; } }'); expect(style is CSSKeyframesRule, true); - expect((style as CSSKeyframesRule).blocks.first.blockSelectors, ['75%', '100%']); + expect((style as CSSKeyframesRule).keyframes[0].offset, 0.75); }); }); } From ed72c5c50eb79135e0823d2b7eaf9a959f0f1a7e Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Fri, 7 Oct 2022 12:49:28 +0800 Subject: [PATCH 485/498] =?UTF-8?q?fix=EF=BC=9Acode=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- integration_tests/specs/css/css-selectors/child-selectors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index 406ec70741..ba1d15c5d3 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -1,4 +1,4 @@ -describe("css child selector", () => { +describe('css child selector', () => { it("001", async () => { const style = ; const h1 =

    001 Text should not be green

    ; From 3ab80ba915266883d8dbb0846b459ee578e583c2 Mon Sep 17 00:00:00 2001 From: andycall Date: Sat, 8 Oct 2022 14:17:24 +0800 Subject: [PATCH 486/498] fix: should flush ui command before evaluate script when parse html. --- bridge/core/html/parser/html_parser.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/bridge/core/html/parser/html_parser.cc b/bridge/core/html/parser/html_parser.cc index ea6911aff3..17120b46d1 100644 --- a/bridge/core/html/parser/html_parser.cc +++ b/bridge/core/html/parser/html_parser.cc @@ -79,6 +79,7 @@ void HTMLParser::traverseHTML(Node* root_node, GumboNode* node) { 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); From 7220125d1a677ca71e7e5a0e28b077b748fb0056 Mon Sep 17 00:00:00 2001 From: andycall Date: Thu, 6 Oct 2022 23:38:20 +0800 Subject: [PATCH 487/498] fix: fix module api callback leaks. --- bridge/CMakeLists.txt | 2 +- bridge/core/executing_context.cc | 4 ++-- bridge/core/executing_context.h | 6 +++--- .../core/frame/module_callback_coordinator.cc | 19 ------------------- .../core/frame/module_context_coordinator.cc | 13 +++++++++++++ ...dinator.h => module_context_coordinator.h} | 10 ++++------ bridge/core/frame/module_listener.h | 4 ++-- bridge/core/frame/module_manager.cc | 19 +++++++++++-------- webf/lib/src/bridge/from_native.dart | 2 ++ webf/lib/src/module/method_channel.dart | 2 +- webf/lib/src/module/module_manager.dart | 9 ++++++++- 11 files changed, 47 insertions(+), 43 deletions(-) create mode 100644 bridge/core/frame/module_context_coordinator.cc rename bridge/core/frame/{module_callback_coordinator.h => module_context_coordinator.h} (67%) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 1f6fd57d37..02902c5420 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -212,7 +212,7 @@ if ($ENV{WEBF_JS_ENGINE} MATCHES "quickjs") core/frame/module_listener_container.cc core/frame/module_manager.cc core/frame/module_callback.cc - core/frame/module_callback_coordinator.cc + core/frame/module_context_coordinator.cc core/frame/window.cc core/frame/screen.cc core/frame/legacy/location.cc diff --git a/bridge/core/executing_context.cc b/bridge/core/executing_context.cc index 57fe2059e3..023a45e3b8 100644 --- a/bridge/core/executing_context.cc +++ b/bridge/core/executing_context.cc @@ -373,8 +373,8 @@ ModuleListenerContainer* ExecutingContext::ModuleListeners() { return &module_listener_container_; } -ModuleCallbackCoordinator* ExecutingContext::ModuleCallbacks() { - return &module_callbacks_; +ModuleContextCoordinator* ExecutingContext::ModuleContexts() { + return &module_contexts_; } void ExecutingContext::SetMutationScope(MemberMutationScope& mutation_scope) { diff --git a/bridge/core/executing_context.h b/bridge/core/executing_context.h index 5b01de23ee..da86d28ba2 100644 --- a/bridge/core/executing_context.h +++ b/bridge/core/executing_context.h @@ -24,7 +24,7 @@ #include "dart_methods.h" #include "executing_context_data.h" #include "frame/dom_timer_coordinator.h" -#include "frame/module_callback_coordinator.h" +#include "frame/module_context_coordinator.h" #include "frame/module_listener_container.h" #include "script_state.h" @@ -98,7 +98,7 @@ class ExecutingContext { ModuleListenerContainer* ModuleListeners(); // Gets the ModuleCallbacks which from the 4th parameter of `webf.invokeModule` function. - ModuleCallbackCoordinator* ModuleCallbacks(); + ModuleContextCoordinator* ModuleContexts(); // Get current script state. ScriptState* GetScriptState() { return &script_state_; } @@ -171,7 +171,7 @@ class ExecutingContext { Performance* performance_{nullptr}; DOMTimerCoordinator timers_; ModuleListenerContainer module_listener_container_; - ModuleCallbackCoordinator module_callbacks_; + ModuleContextCoordinator module_contexts_; ExecutionContextData context_data_{this}; bool in_dispatch_error_event_{false}; RejectedPromises rejected_promises_; diff --git a/bridge/core/frame/module_callback_coordinator.cc b/bridge/core/frame/module_callback_coordinator.cc index 0404a7d3df..e69de29bb2 100644 --- a/bridge/core/frame/module_callback_coordinator.cc +++ b/bridge/core/frame/module_callback_coordinator.cc @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2019-2022 The Kraken authors. All rights reserved. - * Copyright (C) 2022-present The WebF authors. All rights reserved. - */ -#include "module_callback_coordinator.h" - -namespace webf { - -void ModuleCallbackCoordinator::AddModuleCallbacks(std::shared_ptr&& callback) { - listeners_.push_front(callback); -} - -void ModuleCallbackCoordinator::RemoveModuleCallbacks(std::shared_ptr callback) { - listeners_.remove(callback); -} - -ModuleCallbackCoordinator::ModuleCallbackCoordinator() {} - -} // namespace webf 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 module_context) { + module_contexts_.push_front(std::move(module_context)); +} + +} // namespace webf diff --git a/bridge/core/frame/module_callback_coordinator.h b/bridge/core/frame/module_context_coordinator.h similarity index 67% rename from bridge/core/frame/module_callback_coordinator.h rename to bridge/core/frame/module_context_coordinator.h index 47f3d43536..46533cad55 100644 --- a/bridge/core/frame/module_callback_coordinator.h +++ b/bridge/core/frame/module_context_coordinator.h @@ -14,16 +14,14 @@ namespace webf { class ModuleListener; +class ModuleContext; -class ModuleCallbackCoordinator final { +class ModuleContextCoordinator final { public: - ModuleCallbackCoordinator(); - - void AddModuleCallbacks(std::shared_ptr&& callback); - void RemoveModuleCallbacks(std::shared_ptr callback); + void AddModuleContext(std::shared_ptr module_context); private: - std::forward_list> listeners_; + std::forward_list> module_contexts_; friend ModuleListener; }; diff --git a/bridge/core/frame/module_listener.h b/bridge/core/frame/module_listener.h index 762b1e7e58..d4e2afa15b 100644 --- a/bridge/core/frame/module_listener.h +++ b/bridge/core/frame/module_listener.h @@ -9,7 +9,7 @@ namespace webf { -class ModuleCallbackCoordinator; +class ModuleContextCoordinator; class ModuleListenerContainer; // ModuleListener is an persistent callback function. Registered from user with `webf.addModuleListener` method. @@ -26,7 +26,7 @@ class ModuleListener { std::shared_ptr function_{nullptr}; friend ModuleListenerContainer; - friend ModuleCallbackCoordinator; + friend ModuleContextCoordinator; }; } // namespace webf diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 498c4cf535..029b8ddd38 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -4,11 +4,14 @@ */ #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& callback) + : context(context), callback(callback) {} ExecutingContext* context; std::shared_ptr callback; }; @@ -20,7 +23,7 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, auto* moduleContext = static_cast(ptr); ExecutingContext* context = moduleContext->context; - if (!context->IsContextValid()) + if (!context->IsCtxValid() || !context->IsContextValid()) return nullptr; if (moduleContext->callback == nullptr) { @@ -31,6 +34,9 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, } JSContext* ctx = moduleContext->context->ctx(); + + if (ctx == nullptr) return nullptr; + ExceptionState exception_state; NativeValue* return_value = nullptr; @@ -60,9 +66,6 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, return nullptr; } - context->ModuleCallbacks()->RemoveModuleCallbacks(moduleContext->callback); - delete moduleContext; - return return_value; } @@ -111,10 +114,10 @@ ScriptValue ModuleManager::__webf_invoke_module__(ExecutingContext* context, NativeValue* result; if (callback != nullptr) { - auto moduleCallback = ModuleCallback::Create(callback); - context->ModuleCallbacks()->AddModuleCallbacks(std::move(moduleCallback)); - auto* moduleContext = new ModuleContext{context, moduleCallback}; - result = context->dartMethodPtr()->invokeModule(moduleContext, context->contextId(), + auto module_callback = ModuleCallback::Create(callback); + auto module_context = std::make_shared(context, module_callback); + context->ModuleContexts()->AddModuleContext(module_context); + result = context->dartMethodPtr()->invokeModule(module_context.get(), context->contextId(), module_name.ToNativeString().get(), method.ToNativeString().get(), ¶ms, handleInvokeModuleTransientCallback); } else { diff --git a/webf/lib/src/bridge/from_native.dart b/webf/lib/src/bridge/from_native.dart index cd11a32275..5798854a8f 100644 --- a/webf/lib/src/bridge/from_native.dart +++ b/webf/lib/src/bridge/from_native.dart @@ -102,6 +102,8 @@ dynamic invokeModule(Pointer callbackContext, int contextId, String module // 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(); diff --git a/webf/lib/src/module/method_channel.dart b/webf/lib/src/module/method_channel.dart index 4af794e326..e00c85713a 100644 --- a/webf/lib/src/module/method_channel.dart +++ b/webf/lib/src/module/method_channel.dart @@ -7,7 +7,7 @@ import 'dart:async'; import 'package:flutter/foundation.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'; diff --git a/webf/lib/src/module/module_manager.dart b/webf/lib/src/module/module_manager.dart index 707f9a2c80..09e3d40cd8 100644 --- a/webf/lib/src/module/module_manager.dart +++ b/webf/lib/src/module/module_manager.dart @@ -28,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) { @@ -77,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(); }); From 5ef6f1925cddf2272ebb2e7edaabe4d254aa3a22 Mon Sep 17 00:00:00 2001 From: openwebf-bot Date: Sat, 8 Oct 2022 06:24:10 +0000 Subject: [PATCH 488/498] Committing clang-format changes --- bridge/core/frame/module_manager.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bridge/core/frame/module_manager.cc b/bridge/core/frame/module_manager.cc index 029b8ddd38..12eb0cc258 100644 --- a/bridge/core/frame/module_manager.cc +++ b/bridge/core/frame/module_manager.cc @@ -35,7 +35,8 @@ NativeValue* handleInvokeModuleTransientCallback(void* ptr, JSContext* ctx = moduleContext->context->ctx(); - if (ctx == nullptr) return nullptr; + if (ctx == nullptr) + return nullptr; ExceptionState exception_state; From 897ef3f5f85a00cee34fa8f3a19d92d2eb990d5b Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sat, 8 Oct 2022 17:23:44 +0800 Subject: [PATCH 489/498] fix: code review for word space --- .../css/css-selectors/attribute-selector.ts | 25 +-- .../css/css-selectors/child-selectors.ts | 24 +-- .../specs/css/css-selectors/class-selector.ts | 34 ++-- .../specs/css/css-selectors/combinator.ts | 66 ++++--- .../css/css-selectors/descendent-selector.ts | 171 +++++++++--------- .../specs/css/css-selectors/id-selector.ts | 24 +-- .../css/css-selectors/sibling-selector.ts | 10 +- .../specs/css/css-selectors/tag-selector.ts | 30 +-- 8 files changed, 188 insertions(+), 196 deletions(-) diff --git a/integration_tests/specs/css/css-selectors/attribute-selector.ts b/integration_tests/specs/css/css-selectors/attribute-selector.ts index 53a6716866..4c6b1414b7 100644 --- a/integration_tests/specs/css/css-selectors/attribute-selector.ts +++ b/integration_tests/specs/css/css-selectors/attribute-selector.ts @@ -1,7 +1,7 @@ describe('css attribute selector', () => { it('001', async () => { const style = ; - const div =
    001 should be green
    ; + const div =
    001 should be green
    ; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -9,7 +9,7 @@ describe('css attribute selector', () => { it('002', async () => { const style = ; - const div =
    002 should be green < /div>; + const div =
    002 should be green
    ; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -17,31 +17,34 @@ describe('css attribute selector', () => { it('003', async () => { const style = ; - const div1 =
    should not be green
    ; - const div2 =
    003 should be green
    ; + const div1 =
    should not be green
    ; + const div2 =
    003 should be green
    ; document.head.appendChild(style); document.body.appendChild(div1); document.body.appendChild(div2); await snapshot(); }); + it('004', async () => { const style = ; - const div =
    004 should be green
    ; + const div =
    004 should be green
    ; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); + it('005', async () => { const style = ; - const div =
    005 should be green
    ; + const div =
    005 should be green
    ; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); + // error xit('006', async () => { const style = ; - const div =
    006 Filler Text
    + const div =
    006 Filler Text
    document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -61,7 +64,7 @@ describe('css attribute selector', () => { it('008', async () => { const style = ; - const div1 =
    8 should be green
    + const div1 =
    8 should be green
    const div2 =
    should be green
    const div3 =
    should not be green
    document.head.appendChild(style); @@ -87,7 +90,7 @@ describe('css attribute selector', () => { }); it('010', async () => { - const style = ; + const style = ; const div1 =
    10 should be green
    const div2 =
    should be green
    const div3 =
    should be green
    @@ -99,8 +102,8 @@ describe('css attribute selector', () => { }); it('011', async () => { - const style = ; - const div1 =
    11 should be green
    + const style = ; + const div1 =
    11 should be green
    const div2 =
    should be green
    const div3 =
    should not be green
    const div4 =
    should not be green
    diff --git a/integration_tests/specs/css/css-selectors/child-selectors.ts b/integration_tests/specs/css/css-selectors/child-selectors.ts index ba1d15c5d3..35e45b56da 100644 --- a/integration_tests/specs/css/css-selectors/child-selectors.ts +++ b/integration_tests/specs/css/css-selectors/child-selectors.ts @@ -38,9 +38,9 @@ describe('css child selector', () => { it("004", async () => { const style = ; - const div1 =
    004 Text should be green
    ; - const div2 =
    Text should not be green
    ; - const p =

    Text should not be green

    ; + const div1 =
    004 Text should be green
    ; + const div2 =
    Text should not be green
    ; + const p =

    Text should not be green

    ; document.head.appendChild(style); document.body.appendChild(div1); @@ -64,7 +64,7 @@ describe('css child selector', () => { it("006", async () => { const style = ; - const div =
    006 should be green
    ; + const div =
    006 should be green
    ; document.body.appendChild(div); document.head.appendChild(style); await snapshot(); @@ -85,8 +85,8 @@ describe('css child selector', () => { it("008", async () => { const style = ; - const div =
    008 Filler Text should be green
    ; - const p =

    Filler Text

    ; + const div =
    008 Filler Text should be green
    ; + const p =

    Filler Text

    ; document.head.appendChild(style); document.body.appendChild(div); @@ -95,12 +95,12 @@ describe('css child selector', () => { }); it("009", async () => { - const style = ; - const p1 =

    009 Should be green

    ; - const p2 =

    Should be green

    ; - const p3 =

    Should be green

    ; - const p4 =

    Should be green

    ; - const p5 =

    Should be green

    ; + const style = ; + const p1 =

    009 Should be green

    ; + const p2 =

    Should be green

    ; + const p3 =

    Should be green

    ; + const p4 =

    Should be green

    ; + const p5 =

    Should be green

    ; document.head.appendChild(style); document.body.appendChild(p1); diff --git a/integration_tests/specs/css/css-selectors/class-selector.ts b/integration_tests/specs/css/css-selectors/class-selector.ts index 55e6b34e3a..940376d41c 100644 --- a/integration_tests/specs/css/css-selectors/class-selector.ts +++ b/integration_tests/specs/css/css-selectors/class-selector.ts @@ -69,7 +69,7 @@ describe('css class selector', () => { it('001', async () => { const style = ; - const div =
    001 Filler Text
    ; + const div =
    001 Filler Text
    ; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -77,7 +77,7 @@ describe('css class selector', () => { it('002', async () => { const style = ; - const div =
    002 Filler Text < /div>; + const div =
    002 Filler Text
    ; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -85,7 +85,7 @@ describe('css class selector', () => { it('003', async () => { const style = ; - const div =
    003 Filler Text < /div>; + const div =
    003 Filler Text
    ; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -93,7 +93,7 @@ describe('css class selector', () => { it('004', async () => { const style = ; - const div =
    004 Filler Text < /div>; + const div =
    004 Filler Text
    ; document.head.appendChild(style); document.body.appendChild(div); await snapshot(); @@ -101,7 +101,7 @@ describe('css class selector', () => { it('005', async () => { const style = ; - const p =

    005 This text should be green.

    ; + const p =

    005 This text should be green.

    ; document.head.appendChild(style); document.body.appendChild(p); await snapshot(); @@ -109,7 +109,7 @@ describe('css class selector', () => { it('006', async () => { const style = ; - const p =

    006 This should have a green background.

    ; + const p =

    006 This should have a green background.

    ; document.head.appendChild(style); document.body.appendChild(p); await snapshot(); @@ -117,7 +117,7 @@ describe('css class selector', () => { it('007', async () => { const style = ; - const p =

    007 This should have a green background.< /p>; + const p =

    007 This should have a green background.

    ; document.head.appendChild(style); document.body.appendChild(p); await snapshot(); @@ -125,7 +125,7 @@ describe('css class selector', () => { it('008', async () => { const style = ; - const p =

    008 This should have a green background.< /p>; + const p =

    008 This should have a green background.

    ; document.head.appendChild(style); document.body.appendChild(p); await snapshot(); @@ -133,14 +133,14 @@ describe('css class selector', () => { it('009', async () => { const style = ; - const p1 =

    This line should be green.< /p> - const p2 =

    This line should be green.< /p> - const p3 =

    This line should be green.< /p> - const p4 =

    This line should be green.< /p> - const p5 =

    This line should be green.< /p> - const p6 = < p class= "line test " > This line should be green.< /p> - const p7 = < p class= " test line " > This line should be green.< /p> - const p8 = < p class= " line test " > This line should be green.< /p> + const p1 =

    This line should be green.

    + const p2 =

    This line should be green.

    + const p3 =

    This line should be green.

    + const p4 =

    This line should be green.

    + const p5 =

    This line should be green.

    + const p6 = < p class= "line test "> This line should be green.

    + const p7 = < p class= " test line "> This line should be green.

    + const p8 = < p class= " line test "> This line should be green.

    document.head.appendChild(style); document.body.appendChild(p1); document.body.appendChild(p2); @@ -155,7 +155,7 @@ describe('css class selector', () => { it('010', async () => { const style = ; - const p =

    010 This should have a green background.< /p>; + const p =

    010 This should have a green background.

    ; document.head.appendChild(style); document.body.appendChild(p); await snapshot(); diff --git a/integration_tests/specs/css/css-selectors/combinator.ts b/integration_tests/specs/css/css-selectors/combinator.ts index 2bbae9247a..5bf9be7efc 100644 --- a/integration_tests/specs/css/css-selectors/combinator.ts +++ b/integration_tests/specs/css/css-selectors/combinator.ts @@ -1,35 +1,33 @@ describe('css combinator selector', () => { - it('001', async () => { - const style = ; - const div =

    001 Filler Text < /p>

    ; - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it('002', async () => { - const style = ; - const div =
    ; - const p =

    002 Filler Text < /p> - document.head.appendChild(style); - document.body.appendChild(div); - document.body.appendChild(p); - await snapshot(); - }); - - it('003', async () => { - const style = ; - const div =

    ; - const p =

    003 Filler Text < /p> - document.head.appendChild(style); - document.body.appendChild(div); - document.body.appendChild(p); - await snapshot(); - }); -}); + it('001', async () => { + const style = ; + const div =

    001 Filler Text

    ; + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it('002', async () => { + const style = ; + const div =
    ; + const p =

    002 Filler Text

    + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p); + await snapshot(); + }); + it('003', async () => { + const style = ; + const div =
    ; + const p =

    003 Filler Text

    + document.head.appendChild(style); + document.body.appendChild(div); + document.body.appendChild(p); + await snapshot(); + }); +}) diff --git a/integration_tests/specs/css/css-selectors/descendent-selector.ts b/integration_tests/specs/css/css-selectors/descendent-selector.ts index ec8f0afbd3..6cd2fe9ac1 100644 --- a/integration_tests/specs/css/css-selectors/descendent-selector.ts +++ b/integration_tests/specs/css/css-selectors/descendent-selector.ts @@ -1,92 +1,83 @@ describe('css descendent selector', () => { - it('001', async () => { - const style = ; - const div =
    001 Filler Text < /div >; - - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it('002', async () => { - const style = ; - const div =
    002 Filler Text
    ; - - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it('003', async () => { - const style = ; - const div =
    003 Filler Text
    ; - - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it('004', async () => { - const style = ; - const div =
    004 Filler Text
    ; - - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it('005', async () => { - const style = ; - const div =
    005 Filler Text
    ; - - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it('006', async () => { - const style = ; - const div =
    006 Filler Text
    ; - - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it('007', async () => { - const style = ; - const div =
    007 Filler Text
    ; - - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it('008', async () => { - const style = ; - const div =
    008 Filler Text < /em>
    ; - - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it('009', async () => { - const style = ; - const div =
    009 Filler Text
    ; - - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); - - it('010', async () => { - const style = ; - const div =
    010 Filler Text < /em>
    ; - - document.head.appendChild(style); - document.body.appendChild(div); - await snapshot(); - }); + it('001', async () => { + const style = ; + const div =
    001 Filler Text < /div >; + + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it('002', async () => { + const style = ; + const div =
    002 Filler Text
    ; + + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it('003', async () => { + const style = ; + const div =
    003 Filler Text
    ; + + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it('004', async () => { + const style = ; + const div =
    004 Filler Text
    ; + + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it('005', async () => { + const style = ; + const div =
    005 Filler Text
    ; + + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it('006', async () => { + const style = ; + const div =
    006 Filler Text
    ; + + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it('007', async () => { + const style = ; + const div =
    007 Filler Text
    ; + + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it('008', async () => { + const style = ; + const div =
    008 Filler Text
    ; + + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it('009', async () => { + const style = ; + const div =
    009 Filler Text
    ; + + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); + it('010', async () => { + const style = ; + const div =
    010 Filler Text
    ; + + document.head.appendChild(style); + document.body.appendChild(div); + await snapshot(); + }); }); diff --git a/integration_tests/specs/css/css-selectors/id-selector.ts b/integration_tests/specs/css/css-selectors/id-selector.ts index 210db3f8ba..8fc7b40747 100644 --- a/integration_tests/specs/css/css-selectors/id-selector.ts +++ b/integration_tests/specs/css/css-selectors/id-selector.ts @@ -1,54 +1,54 @@ describe("css id selector", () => { it("001", async () => { const style = ; - const div =
    001 green Filler Text
    ; + const div =
    001 green Filler Text
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it("002", async () => { const style = ; - const div =
    002 black Filler Text
    ; + const div =
    002 black Filler Text
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it("003", async () => { const style = ; - const div =
    003 green Filler Text
    ; + const div =
    003 green Filler Text
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); xit("004", async () => { const style = ; - const div =
    004 black Filler Text
    ; + const div =
    004 black Filler Text
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it("005", async () => { const style = ; - const div =
    005 green Filler Text
    ; + const div =
    005 green Filler Text
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it("006", async () => { const style = ; - const div =
    006 green Filler Text
    ; + const div =
    006 green Filler Text
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); diff --git a/integration_tests/specs/css/css-selectors/sibling-selector.ts b/integration_tests/specs/css/css-selectors/sibling-selector.ts index 9567985d8b..dd2b902d9d 100644 --- a/integration_tests/specs/css/css-selectors/sibling-selector.ts +++ b/integration_tests/specs/css/css-selectors/sibling-selector.ts @@ -23,7 +23,7 @@ describe("css sibling selector", () => { const p9 =
    This sentence must be green.
    ; const p10 =
    This sentence must be green.
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(div); document.body.appendChild(p1); document.body.appendChild(p2); @@ -41,10 +41,10 @@ describe("css sibling selector", () => { it("002", async () => { const style = ; const p =

    Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.

    ; - const div1 =
    002 Filler Text
    ; + const div1 =
    002 Filler Text
    ; const div2 =
    Filler Text
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(div1); document.body.appendChild(div2); @@ -54,7 +54,7 @@ describe("css sibling selector", () => { it("003", async () => { const style = ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.innerHTML = `

    Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.

    @@ -66,7 +66,7 @@ describe("css sibling selector", () => { xit("004", async () => { const style = ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.innerHTML = `

    Test passes if the first line of "Filler Text" below is green, but the second line of "Filler Text" below is black.

    Filler Text diff --git a/integration_tests/specs/css/css-selectors/tag-selector.ts b/integration_tests/specs/css/css-selectors/tag-selector.ts index 6f34fde38e..e73bf88be5 100644 --- a/integration_tests/specs/css/css-selectors/tag-selector.ts +++ b/integration_tests/specs/css/css-selectors/tag-selector.ts @@ -7,7 +7,7 @@ describe("css tag selector", () => { const p4 =

    This sentence must be green.

    ; const p5 =

    This sentence must be green.

    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(p1); document.body.appendChild(p2); document.body.appendChild(p3); @@ -22,7 +22,7 @@ describe("css tag selector", () => { const blockquote =
    Filler Text
    ; const div =
    002 Filler Text
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(blockquote); document.body.appendChild(div); @@ -33,22 +33,22 @@ describe("css tag selector", () => { const style = ; const div =
    003 Filler Text
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(div); await snapshot(); }); it("004", async () => { const style = ; - const e1 =

    This text should be green. (element)

    ; - const e2 =
    This text should be green. (class)
    - const e3 =
    This text should be green. (id)
    - const e4 =
    This text should be green. (child)
    - const e5 =
    This text should be green. (descendant)
    - const e6 =
    This text should be green. (sibling)
    - const e7 =
    This text should be green. (attribute)
    + const e1 =

    This text should be green. (element)

    ; + const e2 =
    This text should be green. (class)
    + const e3 =
    This text should be green. (id)
    + const e4 =
    This text should be green. (child)
    + const e5 =
    This text should be green. (descendant)
    + const e6 =
    This text should be green. (sibling)
    + const e7 =
    This text should be green. (attribute)
    - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(e1); document.body.appendChild(e2); document.body.appendChild(e3); @@ -62,9 +62,9 @@ describe("css tag selector", () => { it("005", async () => { const style = ; const p =

    Test passes if all text on this page is green.

    ; - const div =
    005 Filler Text
    ; + const div =
    005 Filler Text
    ; - document.head.appendChild(style); + document.head.appendChild(style); document.body.appendChild(p); document.body.appendChild(div); await snapshot(); @@ -73,7 +73,7 @@ describe("css tag selector", () => { it("006", async () => { const style = ; const p =

    Test passes if all text on this page is green.

    ; - const div =
    006 Filler Text
    ; + const div =
    006 Filler Text
    ; document.head.appendChild(style); document.body.appendChild(p); @@ -84,7 +84,7 @@ describe("css tag selector", () => { it("007", async () => { const style = ; const p =

    Test passes if all text on this page is green.

    ; - const div =
    007 Filler Text
    ; + const div =
    007 Filler Text
    ; document.head.appendChild(style); document.body.appendChild(p); From bb01a0d3dd4adc599968f54e7395476e86f9adc3 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Sun, 9 Oct 2022 16:52:07 +0800 Subject: [PATCH 490/498] fix: the timing of Style load --- webf/lib/src/dom/document.dart | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 3feb33d409..f3bdf1c429 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -225,34 +225,34 @@ class Document extends Node { bool _recalculating = false; void updateStyleIfNeeded() { - if (styleSheets.isEmpty && !styleNodeManager.hasPendingStyleSheet) { - return; - } - if (styleSheets.isEmpty && styleNodeManager.hasPendingStyleSheet) { - flushStyle(rebuild: true); + if (!styleNodeManager.hasPendingStyleSheet) { return; } if (_recalculating) { return; } _recalculating = true; - SchedulerBinding.instance.addPostFrameCallback((_) { - _recalculating = false; - flushStyle(); - }); + if (styleSheets.isEmpty && styleNodeManager.hasPendingStyleSheet) { + flushStyle(rebuild: true); + return; + } + flushStyle(); } void flushStyle({bool rebuild = false}) { if (!needsStyleRecalculate) { + _recalculating = false; return; } if (kProfileMode) { PerformanceTiming.instance().mark(PERF_FLUSH_STYLE_START); } if (!styleNodeManager.updateActiveStyleSheets(rebuild: rebuild)) { + _recalculating = false; return; } - recalculateDocumentStyle(); + // Recalculate style for all nodes sync. + documentElement?.recalculateNestedStyle(); needsStyleRecalculate = false; _recalculating = false; if (kProfileMode) { @@ -260,14 +260,6 @@ class Document extends Node { } } - void recalculateDocumentStyle() { - if (!needsStyleRecalculate) { - return; - } - // Recalculate style for all nodes sync. - documentElement?.recalculateNestedStyle(); - } - @override void dispose() { _viewport = null; From b88c8b572967b800cf344599206a88cc5ab5e887 Mon Sep 17 00:00:00 2001 From: andycall Date: Thu, 13 Oct 2022 18:53:06 +0800 Subject: [PATCH 491/498] chore: fix code style and macos build. --- bridge/CMakeLists.txt | 6 +++--- bridge/polyfill/src/index.ts | 8 ++++---- bridge/polyfill/src/location.ts | 8 ++++---- integration_tests/scripts/core_integration_starter.js | 1 - 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/bridge/CMakeLists.txt b/bridge/CMakeLists.txt index 02902c5420..c2c0e97ee0 100644 --- a/bridge/CMakeLists.txt +++ b/bridge/CMakeLists.txt @@ -7,9 +7,9 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -#if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") -# set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") -#endif() +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64") +endif() if (${ENABLE_PROFILE}) add_definitions(-DENABLE_PROFILE=1) diff --git a/bridge/polyfill/src/index.ts b/bridge/polyfill/src/index.ts index afda8138e8..51b2c362d5 100644 --- a/bridge/polyfill/src/index.ts +++ b/bridge/polyfill/src/index.ts @@ -33,9 +33,9 @@ defineGlobalProperty('webf', webf); function defineGlobalProperty(key: string, value: any, isEnumerable: boolean = true) { Object.defineProperty(globalThis, key, { - value: value, - enumerable: isEnumerable, - writable: true, - configurable: true + value: value, + enumerable: isEnumerable, + writable: true, + configurable: true }); } diff --git a/bridge/polyfill/src/location.ts b/bridge/polyfill/src/location.ts index 51c69708e3..8162c4152e 100644 --- a/bridge/polyfill/src/location.ts +++ b/bridge/polyfill/src/location.ts @@ -3,9 +3,9 @@ * Copyright (C) 2022-present The WebF authors. All rights reserved. */ -import {URL} from './url'; -import {webf} from './webf'; -import {webfLocationReload} from './bridge'; +import { URL } from './url'; +import { webf } from './webf'; +import { webfLocationReload } from './bridge'; // Lazy parse url. let _url: URL; @@ -51,7 +51,7 @@ export const location = { }; }, get reload() { - return webfLocationReload.bind(this); + return webfLocationReload.bind(this); }, get replace() { return (replaceURL: string) => { diff --git a/integration_tests/scripts/core_integration_starter.js b/integration_tests/scripts/core_integration_starter.js index 2412767e98..1ffa645cf7 100644 --- a/integration_tests/scripts/core_integration_starter.js +++ b/integration_tests/scripts/core_integration_starter.js @@ -38,7 +38,6 @@ function startIntegrationTest() { }); tester.on('close', (code) => { - console.log(code); process.exit(code); }); tester.on('error', (error) => { From f68836150a142d51a730bc623dacfb0547d7e82b Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 14 Oct 2022 12:44:33 +0800 Subject: [PATCH 492/498] chore: remove empty quickjs.c --- bridge/third_party/quickjs/quickjs.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 bridge/third_party/quickjs/quickjs.c diff --git a/bridge/third_party/quickjs/quickjs.c b/bridge/third_party/quickjs/quickjs.c deleted file mode 100644 index e69de29bb2..0000000000 From acb24b25b29fdfc6c1d81f342f02f5f7b22933d1 Mon Sep 17 00:00:00 2001 From: jiangzhou Date: Mon, 17 Oct 2022 21:30:54 +0800 Subject: [PATCH 493/498] fix: recalculate matching styles for element when inline styles are removed --- .../dom/elements/remove_style.html.0.png | Bin 0 -> 17086 bytes .../dom/elements/remove_style.html.1.png | Bin 0 -> 16722 bytes .../specs/dom/elements/remove_style.html | 41 ++++++++++++++++++ webf/lib/src/dom/element.dart | 8 +++- 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 integration_tests/snapshots/dom/elements/remove_style.html.0.png create mode 100644 integration_tests/snapshots/dom/elements/remove_style.html.1.png create mode 100644 integration_tests/specs/dom/elements/remove_style.html diff --git a/integration_tests/snapshots/dom/elements/remove_style.html.0.png b/integration_tests/snapshots/dom/elements/remove_style.html.0.png new file mode 100644 index 0000000000000000000000000000000000000000..3b5627609cc9ada1e9daae4574e672c49eba4428 GIT binary patch literal 17086 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 UkNF(kgW{O+T?>Osy~nTq2kmh6#Q*>R literal 0 HcmV?d00001 diff --git a/integration_tests/snapshots/dom/elements/remove_style.html.1.png b/integration_tests/snapshots/dom/elements/remove_style.html.1.png new file mode 100644 index 0000000000000000000000000000000000000000..513e500943b31cd4d0d439bf7aebbf71633c2f43 GIT binary patch literal 16722 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 literal 0 HcmV?d00001 diff --git a/integration_tests/specs/dom/elements/remove_style.html b/integration_tests/specs/dom/elements/remove_style.html new file mode 100644 index 0000000000..027bcf457c --- /dev/null +++ b/integration_tests/specs/dom/elements/remove_style.html @@ -0,0 +1,41 @@ + + + + + +
    (background) Background should change green to red after 1s.
    +
    (background-color) Background should change green to red after 1s.
    + + + + \ No newline at end of file diff --git a/webf/lib/src/dom/element.dart b/webf/lib/src/dom/element.dart index 079e8fdf65..8106b7c976 100644 --- a/webf/lib/src/dom/element.dart +++ b/webf/lib/src/dom/element.dart @@ -1501,7 +1501,13 @@ abstract class Element extends Node with ElementBase, ElementEventMixin, Element void setInlineStyle(String property, String value) { // Current only for mark property is setting by inline style. inlineStyle[property] = value; - style.setProperty(property, value, true); + // recalculate matching styles for element when inline styles are removed. + if (value.isEmpty) { + style.removeProperty(property, true); + recalculateStyle(); + } else { + style.setProperty(property, value, true); + } } void applyStyle(CSSStyleDeclaration style) { From 5c7cf942e0061539fe4674001e0306dc010dc44f Mon Sep 17 00:00:00 2001 From: andycall Date: Tue, 18 Oct 2022 16:33:58 +0800 Subject: [PATCH 494/498] chore: fix linter --- webf/lib/src/dom/document.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index f3bdf1c429..261c6f05f2 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -4,7 +4,6 @@ */ import 'package:flutter/foundation.dart'; 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'; From 693dc0757c96011c1be285a654755fac6d684576 Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 19 Oct 2022 01:15:53 +0800 Subject: [PATCH 495/498] fix: fix style didn't changed when remove link element. --- webf/lib/src/dom/document.dart | 2 +- webf/lib/src/dom/style_node_manager.dart | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 1ce6353fa2..2b4195abda 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -273,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/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; } From 8eb2ee12a3494c94d67fd1485cb9f757f49a036b Mon Sep 17 00:00:00 2001 From: andycall Date: Wed, 19 Oct 2022 01:15:53 +0800 Subject: [PATCH 496/498] fix: fix style didn't changed when remove link element. --- webf/lib/src/dom/document.dart | 2 +- webf/lib/src/dom/style_node_manager.dart | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/webf/lib/src/dom/document.dart b/webf/lib/src/dom/document.dart index 261c6f05f2..0dd7414146 100644 --- a/webf/lib/src/dom/document.dart +++ b/webf/lib/src/dom/document.dart @@ -224,7 +224,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/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; } From 76d30bec37e90035bd58d37166c591889fe3cf3b Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 21 Oct 2022 00:51:05 +0800 Subject: [PATCH 497/498] chore: fix specs. --- .../css-animations/animation-direction-003-manual.html | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) 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 76039fc5ff..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 @@ - + CSS Animations Test: animation-direction - alternate-reverse @@ -51,8 +51,6 @@ expect(main.offsetLeft > 60 && main.offsetLeft < 120).toBe(true); await sleep(1); expect(main.offsetLeft > 0 && main.offsetLeft < 30).toBe(true); - await sleep(0.5); - expect(main.offsetLeft > 50 && main.offsetLeft < 100).toBe(true); - await sleep(0.5); - expect(main.offsetLeft > 120 && main.offsetLeft < 150).toBe(true); + await sleep(1); + expect(main.offsetLeft > 110 && main.offsetLeft < 150).toBe(true); \ No newline at end of file From 67438bdc251a1fcd309a9ec91c4ba3d8d1180d37 Mon Sep 17 00:00:00 2001 From: andycall Date: Fri, 21 Oct 2022 01:06:04 +0800 Subject: [PATCH 498/498] chore: migrate animation test specs. --- .../animation-delay-001-manual.html | 24 ++++++++++++------- .../animation-delay-002-manual.html | 18 +++++++------- .../animation-delay-003-manual.html | 11 +++++---- .../animation-direction-001-manual.html | 15 ++++++------ .../animation-direction-002-manual.html | 17 +++++++------ .../animation-direction-003-manual.html | 19 ++++++++------- .../animation-direction-004-manual.html | 15 +++++++----- .../animation-duration-001-manual.html | 2 +- .../animation-duration-002-manual.html | 13 ++++++---- .../animation-duration-003-manual.html | 5 ++-- .../animation-duration-004-manual.html | 7 +++--- .../animation-duration-005-manual.html | 10 ++++---- .../animation-duration-006-manual.html | 2 +- 13 files changed, 92 insertions(+), 66 deletions(-) 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 @@
    Filler Text
    \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-delay-002-manual.html b/integration_tests/specs/css/css-animations/animation-delay-002-manual.html index 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.

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

    -
    Filler Text
    +
    Filler Text
    diff --git a/integration_tests/specs/css/css-animations/animation-direction-001-manual.html b/integration_tests/specs/css/css-animations/animation-direction-001-manual.html index 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.

    -
    Filler Text
    +
    Filler Text
    \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-002-manual.html b/integration_tests/specs/css/css-animations/animation-direction-002-manual.html index 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.

    -
    Filler Text
    +
    Filler Text
    \ No newline at end of file diff --git a/integration_tests/specs/css/css-animations/animation-direction-003-manual.html b/integration_tests/specs/css/css-animations/animation-direction-003-manual.html index b736221d79..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 @@ - + 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